$poll-export-status operation with PatientIdPartitionInterceptor fails with NullPointerException (#5681)

* Fix NullPointerException when performing a system bulk export in the presence of PatientIdPartitionInterceptor.

* Fix NPE on -export-status operation with PatientIdPartitionInterceptor
This commit is contained in:
Martha Mitran 2024-02-06 16:04:49 -08:00 committed by GitHub
parent ace3fccf31
commit de0ddcce5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 63 additions and 6 deletions

View File

@ -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."

View File

@ -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());
}
}
}

View File

@ -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(

View File

@ -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;