diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5680-npe-on-poll-export-status-system-export-with-patientid-partitioning.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5680-npe-on-poll-export-status-system-export-with-patientid-partitioning.yaml new file mode 100644 index 00000000000..8f0c43ce3a2 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5680-npe-on-poll-export-status-system-export-with-patientid-partitioning.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 5680 +title: "Previously, after registering built-in interceptor `PatientIdPartitionInterceptor`, while performing +an async system bulk export, the `$poll-export-status` operation would fail with a `NullPointerException`. This has been fixed." diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptorTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptorTest.java index 2a15939ab66..d8eb4c1dc27 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptorTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptorTest.java @@ -20,12 +20,17 @@ import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; +import ca.uhn.fhir.rest.server.provider.BulkDataExportProvider; import ca.uhn.fhir.rest.server.provider.ProviderConstants; import ca.uhn.fhir.util.BundleBuilder; import ca.uhn.fhir.util.MultimapCollector; +import com.google.common.base.Charsets; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimap; +import org.apache.commons.io.IOUtils; +import org.apache.http.Header; import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Encounter; @@ -54,6 +59,7 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.either; import static org.hamcrest.Matchers.matchesPattern; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; @@ -562,10 +568,6 @@ public class PatientIdPartitionInterceptorTest extends BaseResourceProviderR4Tes @Test public void testSystemBulkExport_withPatientIdPartitioningWithNoResourceType_usesNonPatientSpecificPartition() throws IOException { - final BulkExportJobParameters options = new BulkExportJobParameters(); - options.setExportStyle(BulkExportJobParameters.ExportStyle.SYSTEM); - options.setOutputFormat(Constants.CT_FHIR_NDJSON); - HttpPost post = new HttpPost(myServer.getBaseUrl() + "/" + ProviderConstants.OPERATION_EXPORT); post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC); @@ -577,4 +579,51 @@ public class PatientIdPartitionInterceptorTest extends BaseResourceProviderR4Tes verify(mySvc).provideNonPatientSpecificQueryResponse(any()); } + + @Test + public void testSystemBulkExport_withPatientIdPartitioningWithResourceType_exportUsesNonPatientSpecificPartition() throws IOException { + HttpPost post = new HttpPost(myServer.getBaseUrl() + "/" + ProviderConstants.OPERATION_EXPORT); + post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC); + post.addHeader(BulkDataExportProvider.PARAM_EXPORT_TYPE, "Patient"); + post.addHeader(BulkDataExportProvider.PARAM_EXPORT_TYPE_FILTER, "Patient?"); + + try (CloseableHttpResponse postResponse = myServer.getHttpClient().execute(post)){ + ourLog.info("Response: {}",postResponse); + assertEquals(202, postResponse.getStatusLine().getStatusCode()); + assertEquals("Accepted", postResponse.getStatusLine().getReasonPhrase()); + } + + verify(mySvc).provideNonPatientSpecificQueryResponse(any()); + } + + @Test + public void testSystemBulkExport_withPatientIdPartitioningWithResourceType_pollSuccessful() throws IOException { + final BulkExportJobParameters options = new BulkExportJobParameters(); + options.setExportStyle(BulkExportJobParameters.ExportStyle.SYSTEM); + options.setOutputFormat(Constants.CT_FHIR_NDJSON); + + HttpPost post = new HttpPost(myServer.getBaseUrl() + "/" + ProviderConstants.OPERATION_EXPORT); + post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC); + post.addHeader(BulkDataExportProvider.PARAM_EXPORT_TYPE, "Patient"); // ignored when computing partition + post.addHeader(BulkDataExportProvider.PARAM_EXPORT_TYPE_FILTER, "Patient?"); + + String locationUrl; + + try (CloseableHttpResponse postResponse = myServer.getHttpClient().execute(post)){ + ourLog.info("Response: {}",postResponse); + assertEquals(202, postResponse.getStatusLine().getStatusCode()); + assertEquals("Accepted", postResponse.getStatusLine().getReasonPhrase()); + + Header locationHeader = postResponse.getFirstHeader(Constants.HEADER_CONTENT_LOCATION); + assertNotNull(locationHeader); + locationUrl = locationHeader.getValue(); + } + + HttpGet get = new HttpGet(locationUrl); + try (CloseableHttpResponse postResponse = myServer.getHttpClient().execute(get)) { + String responseContent = IOUtils.toString(postResponse.getEntity().getContent(), Charsets.UTF_8); + ourLog.info("Response: {}", responseContent); + assertEquals(202, postResponse.getStatusLine().getStatusCode()); + } + } } diff --git a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/BulkDataExportProvider.java b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/BulkDataExportProvider.java index b08e4bcba17..35067187a86 100644 --- a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/BulkDataExportProvider.java +++ b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/BulkDataExportProvider.java @@ -471,8 +471,10 @@ public class BulkDataExportProvider { BulkExportJobParameters parameters = info.getParameters(BulkExportJobParameters.class); if (parameters.getPartitionId() != null) { // Determine and validate permissions for partition (if needed) + ReadPartitionIdRequestDetails theDetails = ReadPartitionIdRequestDetails.forOperation( + null, null, ProviderConstants.OPERATION_EXPORT_POLL_STATUS); RequestPartitionId partitionId = - myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, null); + myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, theDetails); myRequestPartitionHelperService.validateHasPartitionPermissions(theRequestDetails, "Binary", partitionId); if (!parameters.getPartitionId().equals(partitionId)) { throw new InvalidRequestException( diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptor.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptor.java index a1399ddb592..64e0e1b10bc 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptor.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptor.java @@ -161,7 +161,8 @@ public class PatientIdPartitionInterceptor { break; case EXTENDED_OPERATION_SERVER: String extendedOp = theReadDetails.getExtendedOperationName(); - if (ProviderConstants.OPERATION_EXPORT.equals(extendedOp)) { + if (ProviderConstants.OPERATION_EXPORT.equals(extendedOp) + || ProviderConstants.OPERATION_EXPORT_POLL_STATUS.equals(extendedOp)) { return provideNonPatientSpecificQueryResponse(theReadDetails); } break;