Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import com.datadog.api.client.ApiException;

import gov.healthit.chpl.astpai.AstpAiRequestFailedException;
import gov.healthit.chpl.auth.ChplAccountEmailNotConfirmedException;
import gov.healthit.chpl.domain.error.ErrorResponse;
import gov.healthit.chpl.domain.error.ObjectMissingValidationErrorResponse;
Expand Down Expand Up @@ -77,6 +78,14 @@ public ResponseEntity<ErrorResponse> exception(JiraRequestFailedException e) {
HttpStatus.NO_CONTENT);
}

@ExceptionHandler(AstpAiRequestFailedException.class)
public ResponseEntity<ErrorResponse> exception(AstpAiRequestFailedException e) {
LOGGER.error(e.getMessage());
return new ResponseEntity<ErrorResponse>(
new ErrorResponse("ASTP-AI information is not currently available, please check back later."),
HttpStatus.NO_CONTENT);
}

@ExceptionHandler(InsightRequestFailedException.class)
public ResponseEntity<ErrorResponse> exception(InsightRequestFailedException e) {
LOGGER.error(e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,30 @@

import java.io.IOException;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import gov.healthit.chpl.util.ServerEnvironment;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@Component
public class EnvironmentHeaderFilter extends OncePerRequestFilter {

private String serverEnvironment;
private ServerEnvironment serverEnvironment;

@Autowired
public EnvironmentHeaderFilter(@Value("${server.environment}") String serverEnvironment) {
this.serverEnvironment = serverEnvironment != null ? serverEnvironment : "";
this.serverEnvironment = serverEnvironment != null ? ServerEnvironment.getByName(serverEnvironment) : null;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (serverEnvironment.equalsIgnoreCase("production")) {
if (serverEnvironment.equals(ServerEnvironment.PRODUCTION)) {
response.addHeader("Environment", "PRODUCTION");
} else {
response.addHeader("Environment", "NON-PRODUCTION");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package gov.healthit.chpl.util;

import java.util.stream.Stream;

import lombok.Getter;

public enum ServerEnvironment {
PRODUCTION("production"),
NON_PRODUCTION("non-production");

@Getter
private String name;
ServerEnvironment(String name) {
this.name = name;
}

public static ServerEnvironment getByName(String envName) {
return Stream.of(ServerEnvironment.values())
.filter(val -> val.getName().equalsIgnoreCase(envName))
.findAny()
.orElse(null);
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
package gov.healthit.chpl.web.controller;

import org.apache.commons.lang3.NotImplementedException;
import org.ff4j.FF4j;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import gov.healthit.chpl.FeatureList;
import gov.healthit.chpl.domain.schedule.ChplOneTimeTrigger;
import gov.healthit.chpl.exception.UserRetrievalException;
import gov.healthit.chpl.exception.ValidationException;
import gov.healthit.chpl.realworldtesting.domain.RealWorldTestingResultsUrlValidationRequest;
import gov.healthit.chpl.realworldtesting.domain.RealWorldTestingUploadResponse;
import gov.healthit.chpl.realworldtesting.manager.RealWorldTestingManager;
import gov.healthit.chpl.util.ServerEnvironment;
import gov.healthit.chpl.util.SwaggerSecurityRequirement;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
Expand All @@ -26,10 +34,16 @@
public class RealWorldTestingController {

private RealWorldTestingManager realWorldTestingManager;
private FF4j ff4j;
private ServerEnvironment serverEnvironment;

@Autowired
public RealWorldTestingController(RealWorldTestingManager realWorldTestingManager) {
public RealWorldTestingController(RealWorldTestingManager realWorldTestingManager,
FF4j ff4j,
@Value("${server.environment}") String serverEnvironment) {
this.realWorldTestingManager = realWorldTestingManager;
this.ff4j = ff4j;
this.serverEnvironment = serverEnvironment != null ? ServerEnvironment.getByName(serverEnvironment) : null;
}

@Operation(summary = "Upload a file with real world testing data for certified products.",
Expand All @@ -49,4 +63,22 @@ public RealWorldTestingController(RealWorldTestingManager realWorldTestingManage
RealWorldTestingUploadResponse response = realWorldTestingManager.uploadRealWorldTestingCsv(file);
return new ResponseEntity<RealWorldTestingUploadResponse>(response, HttpStatus.OK);
}

@Operation(summary = "Create and run a background job that fetches Real World Testing validation information "
+ "about any URL. The validation is expecting an RWT Results URL. Validation data will be emailed to the "
+ "logged-in user.",
security = {
@SecurityRequirement(name = SwaggerSecurityRequirement.API_KEY),
@SecurityRequirement(name = SwaggerSecurityRequirement.BEARER)
})
@RequestMapping(value = "/validate-results-url", method = RequestMethod.POST)
public @ResponseBody ChplOneTimeTrigger createAiValidationJob(@RequestBody RealWorldTestingResultsUrlValidationRequest request)
throws UserRetrievalException, SchedulerException, ValidationException {
if (!ff4j.check(FeatureList.RWT_AI_INTEGRATION)
|| this.serverEnvironment == null
|| this.serverEnvironment.equals(ServerEnvironment.PRODUCTION)) {
throw new NotImplementedException("This method has not been implemented");
}
return realWorldTestingManager.validateResultsUrlAsBackgroundJob(request);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@
<KeyValuePair key="dd.span_id" value="%X{dd.span_id}" />
</JsonLayout>
</Console>
<Console name="realWorldTestingUrlValidationJobJson" target="SYSTEM_OUT">
<JsonLayout compact="true" eventEol="true" properties="true" stacktraceAsString="true">
<KeyValuePair key="service" value="realWorldTestingUrlValidationJob" />
<KeyValuePair key="dd.trace_id" value="%X{dd.trace_id}" />
<KeyValuePair key="dd.span_id" value="%X{dd.span_id}" />
</JsonLayout>
</Console>
<Console name="auditDataRetentionJobJson" target="SYSTEM_OUT">
<JsonLayout compact="true" eventEol="true" properties="true" stacktraceAsString="true">
<KeyValuePair key="service" value="auditDataRetentionJob" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,18 @@
interval="1" modulate="true" />
</Policies>
</RollingFile>
<RollingFile name="realWorldTestingUrlValidationJob"
fileName="${logDir}/scheduler/realWorldTestingUrlValidationJob.log"
filePattern="${logDir}/scheduler/history/realWorldTestingUrlValidationJob-%d{yyyy-MM-dd}.log"
filePermissions="rw-rw-r--">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %X{dd.trace_id} %X{dd.span_id} - %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy
interval="1" modulate="true" />
</Policies>
</RollingFile>
<RollingFile name="auditDataRetentionJob"
fileName="${logDir}/scheduler/auditDataRetentionJob.log"
filePattern="${logDir}/scheduler/history/auditDataRetentionJob-%d{yyyy-MM-dd}.log"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,18 @@
interval="1" modulate="true" />
</Policies>
</RollingFile>
<RollingFile name="realWorldTestingUrlValidationJob"
fileName="${logDir}/scheduler/realWorldTestingUrlValidationJob.log"
filePattern="${logDir}/scheduler/history/realWorldTestingUrlValidationJob-%d{yyyy-MM-dd}.log"
filePermissions="rw-rw-r--">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %X{dd.trace_id} %X{dd.span_id} - %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy
interval="1" modulate="true" />
</Policies>
</RollingFile>
<RollingFile name="auditDataRetentionJob"
fileName="${logDir}/scheduler/auditDataRetentionJob.log"
filePattern="${logDir}/scheduler/history/auditDataRetentionJob-%d{yyyy-MM-dd}.log"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@
<Logger name="realWorldTestingUploadJobLogger" level="INFO" additivity="false">
<AppenderRef ref="realWorldTestingUploadJob" />
</Logger>
<Logger name="realWorldTestingUrlValidationJobLogger" level="INFO" additivity="false">
<AppenderRef ref="realWorldTestingUrlValidationJob" />
</Logger>
<Logger name="realWorldTestingReportEmailJobLogger" level="INFO" additivity="false">
<AppenderRef ref="realWorldTestingReportEmailJob" />
</Logger>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@
<AppenderRef ref="realWorldTestingUploadJob" />
<AppenderRef ref="realWorldTestingUploadJobJson" />
</Logger>
<Logger name="realWorldTestingUrlValidationJobLogger" level="INFO" additivity="false">
<AppenderRef ref="realWorldTestingUrlValidationJob" />
<AppenderRef ref="realWorldTestingUrlValidationJobJson" />
</Logger>
<Logger name="realWorldTestingReportEmailJobLogger" level="INFO" additivity="false">
<AppenderRef ref="realWorldTestingReportEmailJob" />
<AppenderRef ref="realWorldTestingReportEmailJobJson" />
Expand Down
10 changes: 10 additions & 0 deletions chpl/chpl-resources/src/main/resources/email.properties
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,16 @@ rwt.report.filename=real-world-testing-report-
rwt.report.subject=CHPL Real World Testing Report
rwt.report.body=Report contains data for the following ONC-ACBs

# Real World Testing Validation Email properties
rwtResults.validation.subject=RWT Results Report Validation
rwtResults.validation.body=<h3>Validation Inputs</h3><ul>\
<li>URL: %s</li><li>Listing: %s</li><li>Year: %s</li></ul>\
<p>%s</p>
rwtResults.validation.failure.subject=RWT Results Report Validation Failed
rwtResults.validation.failure.body=<h3>Validation Inputs</h3><ul>\
<li>URL: %s</li><li>Listing: %s</li><li>Year: %s</li></ul>\
<p>Reason: %s</p>

# Scheduled Job Change
job.change.subject=CHPL Scheduled Job Notification
job.change.body= A CHPL scheduled job has been modified. Please check the information below to confirm these changes are acceptable to you.<br/><br/>%s was %s<br/><br/>%s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@ jira.nonconformityUrl=/search/?maxResults=100&jql=project="Review for Signals/Di
insight.submissionsUrl=https://healthit-gov-develop.go-vip.net/wp-json/data-dashboard/v1/developers/%s/submissions
###################################################

############ ASTP-AI CONNECTION PROPERTIES ###########
astpai.authenticate.url=https://us-east-1zmkmlezba.auth.us-east-1.amazoncognito.com/oauth2/token
astpai.authenticate.clientSecret=SECRET
astpai.authenticate.clientId=SECRET
astpai.domain=https://astp-dev.ainq.ai/api
astpai.rwtResultUrlValidation.endpoint=/rwt-validations/from-url
astpai.requestTimeoutMillis=300000
###################################################

###### CHPL-SERVICE DOWNLOAD JAR PROPERTIES ######
dataSourceName=java:/comp/env/jdbc/openchpl
###################################################
Expand Down
16 changes: 16 additions & 0 deletions chpl/chpl-resources/src/main/resources/jobs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,22 @@
</job-data-map>
</job>

<!-- Job fired to run validation of Real World Testing Results URL -->
<job>
<name>realWorldTestingUrlValidationJob</name>
<group>chplBackgroundJobs</group>
<description>Requests validation of an RWT Results URL from an external source. An email is sent to the user with validation results.</description>
<job-class>gov.healthit.chpl.scheduler.job.realworldtesting.RealWorldTestingUrlValidationJob</job-class>
<durability>true</durability>
<recover>false</recover>
<job-data-map>
<entry>
<key>authorities</key>
<value>chpl-admin;chpl-onc;chpl-onc-acb</value>
</entry>
</job-data-map>
</job>

<!-- Overnight Broken Surveillance Job - by ONC-ACB -->
<job>
<name>Overnight Broken Surveillance Rules Report</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ public RestTemplate jiraAuthenticatedRestTemplate()
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create()
.setDefaultSocketConfig(SocketConfig.custom()
.setSoTimeout(getRequestTimeout(), TimeUnit.MILLISECONDS)
.setSoTimeout(getJiraRequestTimeout(), TimeUnit.MILLISECONDS)
.build())
.setTlsSocketStrategy(new DefaultClientTlsStrategy(
SSLContexts.custom().loadTrustMaterial(TrustAllStrategy.INSTANCE).build(),
Expand All @@ -242,7 +242,7 @@ public RestTemplate jiraAuthenticatedRestTemplate()

HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
requestFactory.setConnectionRequestTimeout(getRequestTimeout());
requestFactory.setConnectionRequestTimeout(getJiraRequestTimeout());

RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.getInterceptors().add(
Expand Down Expand Up @@ -277,7 +277,7 @@ public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttp
return restTemplate;
}

private int getRequestTimeout() {
private int getJiraRequestTimeout() {
int requestTimeout = DEFAULT_REQUEST_TIMEOUT;
String requestTimeoutProperty = env.getProperty("jira.requestTimeoutMillis");
if (!StringUtils.isEmpty(requestTimeoutProperty)) {
Expand All @@ -291,6 +291,41 @@ private int getRequestTimeout() {
return requestTimeout;
}

@Bean
public RestTemplate httpsRestTemplate()
throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create()
.setDefaultSocketConfig(SocketConfig.custom()
.setSoTimeout(getAstpAiRequestTimeout(), TimeUnit.MILLISECONDS)
.build())
.setTlsSocketStrategy(new DefaultClientTlsStrategy(
SSLContexts.custom().loadTrustMaterial(TrustAllStrategy.INSTANCE).build(),
NoopHostnameVerifier.INSTANCE))
.build())
.build();

HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
requestFactory.setConnectionRequestTimeout(getJiraRequestTimeout());

return new RestTemplate(requestFactory);
}

private int getAstpAiRequestTimeout() {
int requestTimeout = DEFAULT_REQUEST_TIMEOUT;
String requestTimeoutProperty = env.getProperty("astpai.requestTimeoutMillis");
if (!StringUtils.isEmpty(requestTimeoutProperty)) {
try {
requestTimeout = Integer.parseInt(requestTimeoutProperty);
} catch (NumberFormatException ex) {
LOGGER.warn("Cannot parse " + requestTimeoutProperty + " as an integer. "
+ "Using the default value " + DEFAULT_REQUEST_TIMEOUT);
}
}
return requestTimeout;
}

@Bean
public JobFactory jobFactory() {
QuartzJobFactory jobFactory = new QuartzJobFactory(applicationContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ private FeatureList() {
public static final String ONC_TO_ASTP_EMAIL = "onc-to-astp-email";
public static final String SERVICE_BASE_URL_LIST_CHANGE_REQUEST = "sbul-change-request";
public static final String RWT_CHANGE_REQUEST = "rwt-change-request";
public static final String RWT_AI_INTEGRATION = "rwt-ai-integration";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package gov.healthit.chpl.astpai;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@JsonIgnoreProperties(ignoreUnknown = true)
public class AmazonTokenResponse {
@JsonProperty("access_token")
private String accessToken;

@JsonProperty("id_token")
private String idToken;

@JsonProperty("refresh_token")
private String refreshToken;

@JsonProperty("token_type")
private String tokenType;

@JsonProperty("expires_in")
private Integer expiresIn;
}
Loading