Skip to content

Commit

Permalink
Ab2d-6106/tibq job (#1350)
Browse files Browse the repository at this point in the history
## 🎫 Ticket

https://jira.cms.gov/browse/AB2D-6106
https://jira.cms.gov/browse/AB2D-6105

## 🛠 Changes

TIBQ changes for ab2d Job, Worker and API

ToDo: I will also update pom file with new bfd version, when ab2d-libs
will be ready to merge

## ℹ️ Context for reviewers

Currently, AB2D API only accepts _since as a date parameter. The goal is
for AB2D to be able to support the use case where a PDP could run jobs
with time interval based queries so that they could download their data
in batches. To achieve that, we will add a _until parameter to define
the upper ceiling of the date range.

Both the since and _until parameter will be applied to the
lastupdated_Date.

## ✅ Acceptance Validation

Tested manually on impl and prod-validation
https://impl.ab2d.cms.gov/swagger-ui/index.html

## 🔒 Security Implications

- [ ] This PR adds a new software dependency or dependencies.
- [ ] This PR modifies or invalidates one or more of our security
controls.
- [ ] This PR stores or transmits data that was not stored or
transmitted before.
- [ ] This PR requires additional review of its security implications
for other reasons.

If any security implications apply, add Jason Ashbaugh (GitHub username:
StewGoin) as a reviewer and do not merge this PR without his approval.
  • Loading branch information
smirnovaae authored Oct 21, 2024
1 parent 9d838c7 commit 44d411d
Show file tree
Hide file tree
Showing 28 changed files with 307 additions and 215 deletions.
16 changes: 7 additions & 9 deletions api/src/main/java/gov/cms/ab2d/api/controller/AdminAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@
import javax.validation.constraints.NotBlank;
import java.time.OffsetDateTime;

import static gov.cms.ab2d.api.controller.common.ApiText.APPLICATION_JSON;
import static gov.cms.ab2d.api.controller.common.ApiText.OUT_FORMAT;
import static gov.cms.ab2d.api.controller.common.ApiText.SINCE;
import static gov.cms.ab2d.api.controller.common.ApiText.TYPE_PARAM;
import static gov.cms.ab2d.api.controller.common.ApiText.*;
import static gov.cms.ab2d.common.util.Constants.API_PREFIX_V1;
import static gov.cms.ab2d.common.util.Constants.ADMIN_PREFIX;
import static gov.cms.ab2d.fhir.BundleUtils.EOB;
Expand Down Expand Up @@ -58,13 +55,14 @@ public ResponseEntity<PdpClientDTO> udpateClient(@RequestBody PdpClientDTO pdpCl

@PostMapping("/job/{contractNumber}")
public ResponseEntity<Void> createJobByContractOnBehalfOfClient(@PathVariable @NotBlank String contractNumber,
HttpServletRequest request,
@RequestParam(required = false, name = TYPE_PARAM, defaultValue = EOB) String resourceTypes,
@RequestParam(required = false, name = OUT_FORMAT) String outputFormat,
@RequestParam(required = false, name = SINCE) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime since) {
HttpServletRequest request,
@RequestParam(required = false, name = TYPE_PARAM, defaultValue = EOB) String resourceTypes,
@RequestParam(required = false, name = OUT_FORMAT) String outputFormat,
@RequestParam(required = false, name = SINCE) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime since,
@RequestParam(required = false, name = UNTIL) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime until) {
pdpClientService.setupClientImpersonation(contractNumber, request);

return bulkDataAccessAPIV1.exportPatientsWithContract(request, contractNumber, resourceTypes, outputFormat, since);
return bulkDataAccessAPIV1.exportPatientsWithContract(request, contractNumber, resourceTypes, outputFormat, since, until);
}

@ResponseStatus(value = HttpStatus.OK)
Expand Down
50 changes: 29 additions & 21 deletions api/src/main/java/gov/cms/ab2d/api/controller/common/ApiCommon.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,18 @@
import gov.cms.ab2d.api.controller.InMaintenanceModeException;
import gov.cms.ab2d.api.controller.TooManyRequestsException;
import gov.cms.ab2d.api.remote.JobClient;
import gov.cms.ab2d.contracts.model.Contract;
import gov.cms.ab2d.common.model.PdpClient;
import gov.cms.ab2d.common.properties.PropertiesService;
import gov.cms.ab2d.common.service.ContractService;
import gov.cms.ab2d.common.service.InvalidClientInputException;
import gov.cms.ab2d.common.service.InvalidContractException;
import gov.cms.ab2d.common.service.PdpClientService;
import gov.cms.ab2d.common.util.PropertyConstants;
import gov.cms.ab2d.contracts.model.Contract;
import gov.cms.ab2d.eventclient.clients.SQSEventClient;
import gov.cms.ab2d.eventclient.events.ApiResponseEvent;
import gov.cms.ab2d.fhir.FhirVersion;
import gov.cms.ab2d.job.dto.StartJobDTO;
import java.time.OffsetDateTime;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.http.HttpHeaders;
Expand All @@ -26,15 +23,12 @@
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import javax.servlet.http.HttpServletRequest;
import java.time.OffsetDateTime;
import java.util.Set;

import static gov.cms.ab2d.common.util.Constants.FHIR_PREFIX;
import static gov.cms.ab2d.common.util.Constants.JOB_LOG;
import static gov.cms.ab2d.common.util.Constants.ORGANIZATION;
import static gov.cms.ab2d.common.util.Constants.SINCE_EARLIEST_DATE;
import static gov.cms.ab2d.common.util.Constants.ZIPFORMAT;
import static gov.cms.ab2d.common.util.Constants.ZIP_SUPPORT_ON;
import static gov.cms.ab2d.common.util.Constants.*;
import static gov.cms.ab2d.fhir.BundleUtils.EOB;
import static java.time.format.DateTimeFormatter.ISO_DATE_TIME;
import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
import static org.springframework.http.HttpHeaders.CONTENT_LOCATION;

Expand Down Expand Up @@ -87,14 +81,27 @@ public void checkSinceTime(OffsetDateTime date) {
if (date.isAfter(OffsetDateTime.now())) {
throw new InvalidClientInputException("You can not use a time after the current time for _since");
}
try {
OffsetDateTime ed = OffsetDateTime.parse(SINCE_EARLIEST_DATE, ISO_DATE_TIME);
if (date.isBefore(ed)) {
log.error("Invalid _since time received {}", date);
throw new InvalidClientInputException("_since must be after " + ed.format(ISO_OFFSET_DATE_TIME));
}
} catch (Exception ex) {
throw new InvalidClientInputException("${api.since.date.earliest} date value '" + SINCE_EARLIEST_DATE + "' is invalid");
if (date.isBefore(SINCE_EARLIEST_DATE_TIME)) {
log.error("Invalid _since time received {}", date);
throw new InvalidClientInputException("_since must be after " + SINCE_EARLIEST_DATE_TIME.format(ISO_OFFSET_DATE_TIME));
}
}

public void checkUntilTime(OffsetDateTime since, OffsetDateTime until, FhirVersion version) {
if (until == null) {
return;
}
if (version.equals(FhirVersion.STU3)) {
log.error("_until is not available for V1");
throw new InvalidClientInputException("The _until parameter is only available with version 2 (FHIR R4) of the API");
}
if (since != null && until.isBefore(since)) {
log.error("Invalid _until time received {}", until);
throw new InvalidClientInputException("_until must be after _since " + since.format(ISO_OFFSET_DATE_TIME));
}
if (until.isBefore(SINCE_EARLIEST_DATE_TIME)) {
log.error("Invalid _until time received {}", until);
throw new InvalidClientInputException("_until must be after " + SINCE_EARLIEST_DATE_TIME.format(ISO_OFFSET_DATE_TIME));
}
}

Expand Down Expand Up @@ -149,15 +156,16 @@ public void logSuccessfulJobCreation(String jobGuid) {
}

public StartJobDTO checkValidCreateJob(HttpServletRequest request, String contractNumber, OffsetDateTime since,
String resourceTypes, String outputFormat, FhirVersion version) {
OffsetDateTime until, String resourceTypes, String outputFormat, FhirVersion version) {
PdpClient pdpClient = pdpClientService.getCurrentClient();
contractNumber = checkIfContractAttested(contractService.getContractByContractId(pdpClient.getContractId()), contractNumber);
checkIfInMaintenanceMode();
checkIfCurrentClientCanAddJob();
checkResourceTypesAndOutputFormat(resourceTypes, outputFormat);
checkSinceTime(since);
checkUntilTime(since, until, version);
return new StartJobDTO(contractNumber, pdpClient.getOrganization(), resourceTypes,
getCurrentUrl(request), outputFormat, since, version);
getCurrentUrl(request), outputFormat, since, until, version);
}

protected String checkIfContractAttested(Contract contract, String contractNumber) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ public class ApiText {
public static final String BULK_RESPONSE = "Absolute URL of an endpoint for subsequent status requests (polling location)";
public static final String RUNNING_JOBIDS = "URLs of currently running jobs. To cancel one of those jobs, invoke the Status DELETE call.";
public static final String BULK_SINCE = "Beginning time of query. Returns all records \"since\" this time. At this time, it must be after " + SINCE_EARLIEST_DATE;
public static final String BULK_UNTIL = "The _until parameter is only available with version 2 (FHIR R4) of the API.";
public static final String BULK_SINCE_DEFAULT = " If no value is provided, it will default to the last time a successful job was requested if it exists. The earliest accepted date is 2020-02-13T00:00:00.000-05:00";
public static final String BULK_UNTIL_DEFAULT = " If no value is provided, it will default to the current date.";
public static final String BULK_RESPONSE_LONG = "Absolute URL of an endpoint for subsequent status requests (polling location)";
public static final String EXPORT_STARTED = "Export request has started";
public static final String MAX_JOBS = "Too many jobs are currently running. Either wait for currently running jobs to finish or cancel some/all of those jobs.";
Expand Down Expand Up @@ -43,6 +45,7 @@ public class ApiText {
public static final String ASYNC = "respond-async";
public static final String OUT_FORMAT = "_outputFormat";
public static final String SINCE = "_since";
public static final String UNTIL = "_until";
public static final String TYPE_PARAM = "_type";
public static final String PREFER = "Prefer";
public static final String X_PROG = "X-Progress";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,14 @@
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import static gov.cms.ab2d.api.controller.common.ApiText.APPLICATION_JSON;
import static gov.cms.ab2d.api.controller.common.ApiText.ASYNC;
import static gov.cms.ab2d.api.controller.common.ApiText.BULK_RESPONSE;
import static gov.cms.ab2d.api.controller.common.ApiText.BULK_SINCE;
import static gov.cms.ab2d.api.controller.common.ApiText.CONTRACT_NO;
import static gov.cms.ab2d.api.controller.common.ApiText.EXPORT_STARTED;
import static gov.cms.ab2d.api.controller.common.ApiText.MAX_JOBS;
import static gov.cms.ab2d.api.controller.common.ApiText.OUT_FORMAT;
import static gov.cms.ab2d.api.controller.common.ApiText.PREFER;
import static gov.cms.ab2d.api.controller.common.ApiText.RUNNING_JOBIDS;
import static gov.cms.ab2d.api.controller.common.ApiText.SINCE;
import static gov.cms.ab2d.api.controller.common.ApiText.TYPE_PARAM;
import static gov.cms.ab2d.api.controller.common.ApiText.*;
import static gov.cms.ab2d.api.util.SwaggerConstants.BULK_CONTRACT_EXPORT;
import static gov.cms.ab2d.api.util.SwaggerConstants.BULK_EXPORT;
import static gov.cms.ab2d.api.util.SwaggerConstants.BULK_EXPORT_TYPE;
import static gov.cms.ab2d.api.util.SwaggerConstants.BULK_MAIN;
import static gov.cms.ab2d.api.util.SwaggerConstants.BULK_OUTPUT_FORMAT;
import static gov.cms.ab2d.api.util.SwaggerConstants.BULK_PREFER;
import static gov.cms.ab2d.common.util.Constants.API_PREFIX_V1;
import static gov.cms.ab2d.common.util.Constants.CONTRACT_LOG;
import static gov.cms.ab2d.common.util.Constants.FHIR_JSON_CONTENT_TYPE;
import static gov.cms.ab2d.common.util.Constants.FHIR_PREFIX;
import static gov.cms.ab2d.common.util.Constants.FHIR_NDJSON_CONTENT_TYPE;
import static gov.cms.ab2d.common.util.Constants.REQUEST_ID;
import static gov.cms.ab2d.common.util.Constants.SINCE_EARLIEST_DATE;
import static gov.cms.ab2d.common.util.Constants.*;
import static gov.cms.ab2d.fhir.BundleUtils.EOB;
import static gov.cms.ab2d.fhir.FhirVersion.STU3;
import static org.springframework.http.HttpHeaders.CONTENT_LOCATION;
Expand Down Expand Up @@ -84,8 +67,8 @@ public BulkDataAccessAPIV1(JobClient jobClient, ApiCommon apiCommon) {
"application/fhir+ndjson", "application/ndjson", "ndjson"
}, defaultValue = FHIR_NDJSON_CONTENT_TYPE)
),
@Parameter(name = SINCE, description = BULK_SINCE, schema = @Schema(type = "date-time", description = SINCE_EARLIEST_DATE))

@Parameter(name = SINCE, description = BULK_SINCE, schema = @Schema(type = "date-time", description = SINCE_EARLIEST_DATE)),
@Parameter(name = UNTIL, description = BULK_UNTIL, schema = @Schema(type = "date-time", description = UNTIL_EXAMPLE_DATE))
}
)
@ApiResponses(value = {
Expand All @@ -107,10 +90,12 @@ public ResponseEntity<Void> exportAllPatients(
@RequestParam(name = OUT_FORMAT, required = false, defaultValue = FHIR_NDJSON_CONTENT_TYPE)
String outputFormat,
@RequestParam(required = false, name = SINCE) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
OffsetDateTime since) {
OffsetDateTime since,
@RequestParam(required = false, name = UNTIL) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
OffsetDateTime until) {

log.info("Received request to export");
StartJobDTO startJobDTO = apiCommon.checkValidCreateJob(request, null, since, resourceTypes, outputFormat, STU3);
StartJobDTO startJobDTO = apiCommon.checkValidCreateJob(request, null, since, until, resourceTypes, outputFormat, STU3);
String jobGuid = jobClient.createJob(startJobDTO);
apiCommon.logSuccessfulJobCreation(jobGuid);
return apiCommon.returnStatusForJobCreation(jobGuid, API_PREFIX_V1, (String) request.getAttribute(REQUEST_ID), request);
Expand All @@ -129,7 +114,7 @@ public ResponseEntity<Void> exportAllPatients(
}, defaultValue = FHIR_NDJSON_CONTENT_TYPE)
),
@Parameter(name = SINCE, description = BULK_SINCE, example = SINCE_EARLIEST_DATE, schema = @Schema(type = "date-time"))
}
}
)
@ApiResponses(value = {
@ApiResponse(responseCode = "202", description = EXPORT_STARTED, headers =
Expand All @@ -153,12 +138,14 @@ public ResponseEntity<Void> exportPatientsWithContract(
@RequestParam(required = false, name = OUT_FORMAT)
String outputFormat,
@RequestParam(required = false, name = SINCE) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
OffsetDateTime since
OffsetDateTime since,
@RequestParam(required = false, name = UNTIL) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
OffsetDateTime until
) {

MDC.put(CONTRACT_LOG, contractNumber);
log.info("Received request to export by contractNumber");
StartJobDTO startJobDTO = apiCommon.checkValidCreateJob(request, contractNumber, since, resourceTypes,
StartJobDTO startJobDTO = apiCommon.checkValidCreateJob(request, contractNumber, since, until, resourceTypes,
outputFormat, STU3);
String jobGuid = jobClient.createJob(startJobDTO);
apiCommon.logSuccessfulJobCreation(jobGuid);
Expand Down
Loading

0 comments on commit 44d411d

Please sign in to comment.