diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_4_0/4427-export-poll-status-post-request-failed-with-null-pointer-exception.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_4_0/4427-export-poll-status-post-request-failed-with-null-pointer-exception.yaml new file mode 100644 index 00000000000..bf0e9e1d050 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_4_0/4427-export-poll-status-post-request-failed-with-null-pointer-exception.yaml @@ -0,0 +1,6 @@ +--- +type: fix +issue: 4427 +jira: SMILE-5922 +title: "Previously, $export-poll-status requests through POST are failing with a Null Pointer exception. +Now, this issue has been fixed and POST $export-poll-status request should be able to proceed." diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportProviderTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportProviderTest.java index 42cd7bfdc89..89f2e090a71 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportProviderTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportProviderTest.java @@ -923,6 +923,73 @@ public class BulkDataExportProviderTest { assertEquals(Constants.CT_FHIR_NDJSON, params.getOutputFormat()); } + @Test + public void testOperationExportPollStatus_POST_NonExistingId_NotFound() throws IOException { + String jobId = "NonExisting-JobId"; + + // Create the initial launch Parameters containing the request + Parameters input = new Parameters(); + input.addParameter(JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT, new StringType(ca.uhn.fhir.rest.api.Constants.CT_FHIR_NDJSON)); + input.addParameter(JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID, new StringType(jobId)); + + // Initiate Export Poll Status + HttpPost post = new HttpPost("http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS); + post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC); + post.setEntity(new ResourceEntity(myCtx, input)); + + + try (CloseableHttpResponse response = myClient.execute(post)) { + ourLog.info("Response: {}", response.toString()); + assertEquals(Constants.STATUS_HTTP_404_NOT_FOUND, response.getStatusLine().getStatusCode()); + } + } + + @Test + public void testOperationExportPollStatus_POST_ExistingId_Accepted() throws IOException { + // setup + Batch2JobInfo info = new Batch2JobInfo(); + info.setJobId(A_JOB_ID); + info.setStatus(BulkExportJobStatusEnum.SUBMITTED); + info.setEndTime(InstantType.now().getValue()); + + // when + when(myJobRunner.getJobInfo(eq(A_JOB_ID))) + .thenReturn(info); + + // Create the initial launch Parameters containing the request + Parameters input = new Parameters(); + input.addParameter(JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT, new StringType(ca.uhn.fhir.rest.api.Constants.CT_FHIR_NDJSON)); + input.addParameter(JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID, new StringType(A_JOB_ID)); + + // Initiate Export Poll Status + HttpPost post = new HttpPost("http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS); + post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC); + post.setEntity(new ResourceEntity(myCtx, input)); + + try (CloseableHttpResponse response = myClient.execute(post)) { + ourLog.info("Response: {}", response.toString()); + assertEquals(Constants.STATUS_HTTP_202_ACCEPTED, response.getStatusLine().getStatusCode()); + } + } + + @Test + public void testOperationExportPollStatus_POST_MissingInputParameterJobId_BadRequest() throws IOException { + + // Create the initial launch Parameters containing the request + Parameters input = new Parameters(); + input.addParameter(JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT, new StringType(ca.uhn.fhir.rest.api.Constants.CT_FHIR_NDJSON)); + + // Initiate Export Poll Status + HttpPost post = new HttpPost("http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS); + post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC); + post.setEntity(new ResourceEntity(myCtx, input)); + + try (CloseableHttpResponse response = myClient.execute(post)) { + ourLog.info("Response: {}", response.toString()); + assertEquals(Constants.STATUS_HTTP_400_BAD_REQUEST, response.getStatusLine().getStatusCode()); + } + } + private void callExportAndAssertJobId(Parameters input, String theExpectedJobId) throws IOException { HttpPost post; post = new HttpPost("http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT); diff --git a/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/coordinator/JobQuerySvc.java b/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/coordinator/JobQuerySvc.java index a5da90605dc..d01fd98d260 100644 --- a/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/coordinator/JobQuerySvc.java +++ b/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/coordinator/JobQuerySvc.java @@ -57,7 +57,7 @@ class JobQuerySvc { JobInstance fetchInstance(String theInstanceId) { return myJobPersistence.fetchInstance(theInstanceId) .map(this::massageInstanceForUserAccess) - .orElseThrow(() -> new ResourceNotFoundException(Msg.code(2040) + "Unknown instance ID: " + UrlUtil.escapeUrlParam(theInstanceId))); + .orElseThrow(() -> new ResourceNotFoundException(Msg.code(2040) + "Unknown instance ID: " + UrlUtil.escapeUrlParam(theInstanceId) + ". Please check if the input job ID is valid.")); } List fetchInstances(int thePageSize, int thePageIndex) { diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/bulk/export/provider/BulkDataExportProvider.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/bulk/export/provider/BulkDataExportProvider.java index b555b67e39f..1dd38cb93d1 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/bulk/export/provider/BulkDataExportProvider.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/bulk/export/provider/BulkDataExportProvider.java @@ -50,6 +50,7 @@ import ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions; import ca.uhn.fhir.rest.server.RestfulServerUtils; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster; import ca.uhn.fhir.util.ArrayUtil; @@ -64,6 +65,7 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.InstantType; +import org.hl7.fhir.r4.model.Parameters; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; @@ -76,6 +78,7 @@ import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Set; import java.util.stream.Collectors; @@ -291,7 +294,22 @@ public class BulkDataExportProvider { ) throws IOException { HttpServletResponse response = theRequestDetails.getServletResponse(); theRequestDetails.getServer().addHeadersToResponse(response); + + // When export-poll-status through POST + // Get theJobId from the request details + if (theJobId == null){ + Parameters parameters = (Parameters) theRequestDetails.getResource(); + Parameters.ParametersParameterComponent parameter = parameters.getParameter().stream() + .filter(param -> param.getName().equals(JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID)) + .findFirst() + .orElseThrow(() -> new InvalidRequestException(Msg.code(2227) + "$export-poll-status requires a job ID, please provide the value of target jobId.")); + theJobId = (IPrimitiveType) parameter.getValue(); + } + Batch2JobInfo info = myJobRunner.getJobInfo(theJobId.getValueAsString()); + if (info == null) { + throw new ResourceNotFoundException(Msg.code(2040) + "Unknown instance ID: " + theJobId + ". Please check if the input job ID is valid."); + } switch (info.getStatus()) { case COMPLETE: