Refactor and fix functionality dealing with ReadPartitionIdRequestDetails (#5691)

* Throw exception when resourceType of parameter value is invalid when performing FHIR search with non-chained reference

* Fix changelog issue id and spotless warning

* Cleanup unnecessary test

* Validate resourceType only when using relative reference

* Fix test for search using _type parameter

* Refactor and fix functionality dealing with ReadPartitionIdRequestDetails

* Spotless fixes

* Revert some of the refactoring changes to BaseRequestPartitionHelperSvc and fix tests

* Small cleanup

* Missing change

* Small changes

* Fix tests and add a few more

* Spotless fix

* Address code review comments

* Some polishing on the interface annotations and documentation

* Spotless fix

* Fix test

* Fix tests and test method rename

* Ensure SearchParameterMap is not null

---------

Co-authored-by: Martha Mitran <martha.mitran@smilecdr.com>
This commit is contained in:
Martha Mitran 2024-02-17 13:16:14 -08:00 committed by GitHub
parent c8d6e9fb73
commit e5d410d10b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 407 additions and 192 deletions

View File

@ -7,6 +7,7 @@ import ca.uhn.fhir.batch2.model.JobInstance;
import ca.uhn.fhir.batch2.model.JobInstanceStartRequest;
import ca.uhn.fhir.batch2.model.StatusEnum;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.batch.models.Batch2JobStartResponse;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.rest.api.server.RequestDetails;
@ -106,6 +107,7 @@ public class BulkImportCommandTest {
writeNdJsonFileToTempDirectory(fileContents1, "file1.json");
writeNdJsonFileToTempDirectory(fileContents2, "file2.json");
when(myRequestPartitionHelperSvc.determineReadPartitionForRequestForServerOperation(any(), any())).thenReturn(RequestPartitionId.allPartitions());
when(myJobCoordinator.startInstance(any(), any())).thenReturn(createJobStartResponse("THE-JOB-ID"));
// Start the command in a separate thread
@ -149,6 +151,7 @@ public class BulkImportCommandTest {
when(myJobCoordinator.startInstance(any(), any()))
.thenReturn(createJobStartResponse("THE-JOB-ID"));
when(myRequestPartitionHelperSvc.determineReadPartitionForRequestForServerOperation(any(), any())).thenReturn(RequestPartitionId.allPartitions());
// Start the command in a separate thread
new Thread(() -> App.main(new String[]{
@ -189,6 +192,7 @@ public class BulkImportCommandTest {
writeNdJsonFileToTempDirectory(fileContents1, "file1.json");
writeNdJsonFileToTempDirectory(fileContents2, "file2.json");
when(myRequestPartitionHelperSvc.determineReadPartitionForRequestForServerOperation(any(), any())).thenReturn(RequestPartitionId.allPartitions());
when(myJobCoordinator.startInstance(any(), any())).thenReturn(createJobStartResponse("THE-JOB-ID"));
// Start the command in a separate thread

View File

@ -30,7 +30,6 @@ import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
@ -1249,9 +1248,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Override
public IBundleProvider history(Date theSince, Date theUntil, Integer theOffset, RequestDetails theRequestDetails) {
StopWatch w = new StopWatch();
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forHistory(myResourceName, null);
RequestPartitionId requestPartitionId =
myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, details);
myRequestPartitionHelperService.determineReadPartitionForRequestForHistory(
theRequestDetails, myResourceName, null);
IBundleProvider retVal = myTransactionService
.withRequest(theRequestDetails)
.withRequestPartitionId(requestPartitionId)
@ -1270,9 +1269,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
final IIdType theId, final Date theSince, Date theUntil, Integer theOffset, RequestDetails theRequest) {
StopWatch w = new StopWatch();
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forHistory(myResourceName, theId);
RequestPartitionId requestPartitionId =
myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, details);
myRequestPartitionHelperService.determineReadPartitionForRequestForHistory(
theRequest, myResourceName, theId);
IBundleProvider retVal = myTransactionService
.withRequest(theRequest)
.withRequestPartitionId(requestPartitionId)
@ -1300,9 +1299,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
final HistorySearchDateRangeParam theHistorySearchDateRangeParam,
RequestDetails theRequest) {
StopWatch w = new StopWatch();
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forHistory(myResourceName, theId);
RequestPartitionId requestPartitionId =
myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, details);
myRequestPartitionHelperService.determineReadPartitionForRequestForHistory(
theRequest, myResourceName, theId);
IBundleProvider retVal = myTransactionService
.withRequest(theRequest)
.withRequestPartitionId(requestPartitionId)
@ -1349,10 +1348,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
addAllResourcesTypesToReindex(theBase, theRequestDetails, params);
}
ReadPartitionIdRequestDetails details =
ReadPartitionIdRequestDetails.forOperation(null, null, ProviderConstants.OPERATION_REINDEX);
RequestPartitionId requestPartition =
myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, details);
myRequestPartitionHelperService.determineReadPartitionForRequestForServerOperation(
theRequestDetails, ProviderConstants.OPERATION_REINDEX);
params.setRequestPartitionId(requestPartition);
JobInstanceStartRequest request = new JobInstanceStartRequest();
@ -1976,7 +1974,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
RequestPartitionId requestPartitionId =
myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(
theRequest, getResourceName(), theParams, null);
theRequest, getResourceName(), theParams);
IBundleProvider retVal = mySearchCoordinatorSvc.registerSearch(
this, theParams, getResourceName(), cacheControlDirective, theRequest, requestPartitionId);
@ -2145,7 +2143,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
BiFunction<RequestDetails, Stream<JpaPid>, Stream<V>> transform) {
RequestPartitionId requestPartitionId =
myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(
theRequest, myResourceName, theParams, null);
theRequest, myResourceName, theParams);
String uuid = UUID.randomUUID().toString();

View File

@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
@ -158,9 +157,9 @@ public abstract class BaseHapiFhirSystemDao<T extends IBaseBundle, MT> extends B
@Override
public IBundleProvider history(Date theSince, Date theUntil, Integer theOffset, RequestDetails theRequestDetails) {
StopWatch w = new StopWatch();
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forHistory(null, null);
RequestPartitionId requestPartitionId =
myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, details);
myRequestPartitionHelperService.determineReadPartitionForRequestForHistory(
theRequestDetails, null, null);
IBundleProvider retVal = myTransactionService
.withRequest(theRequestDetails)
.withRequestPartitionId(requestPartitionId)

View File

@ -65,7 +65,7 @@ public class JpaResourceDaoObservation<T extends IBaseResource> extends BaseHapi
RequestPartitionId requestPartitionId =
myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(
theRequestDetails, getResourceName(), theSearchParameterMap, null);
theRequestDetails, getResourceName(), theSearchParameterMap);
return mySearchCoordinatorSvc.registerSearch(
this,
theSearchParameterMap,
@ -128,7 +128,7 @@ public class JpaResourceDaoObservation<T extends IBaseResource> extends BaseHapi
RequestPartitionId requestPartitionId =
myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(
theRequestDetails, getResourceName(), theSearchParameterMap, null);
theRequestDetails, getResourceName(), theSearchParameterMap);
List<List<IQueryParameterType>> patientParams = new ArrayList<>();
if (theSearchParameterMap.get(getPatientParamName()) != null) {

View File

@ -106,7 +106,7 @@ public class JpaResourceDaoPatient<T extends IBaseResource> extends BaseHapiFhir
}
RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(
theRequest, getResourceName(), paramMap, null);
theRequest, getResourceName(), paramMap);
adjustCount(theRequest, paramMap);

View File

@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.dao.expunge;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
import ca.uhn.fhir.jpa.entity.Batch2JobInstanceEntity;
@ -135,10 +134,9 @@ public class ExpungeEverythingService implements IExpungeEverythingService {
ourLog.info("BEGINNING GLOBAL $expunge");
Propagation propagation = Propagation.REQUIRES_NEW;
ReadPartitionIdRequestDetails details =
ReadPartitionIdRequestDetails.forOperation(null, null, ProviderConstants.OPERATION_EXPUNGE);
RequestPartitionId requestPartitionId =
myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequest, details);
myRequestPartitionHelperSvc.determineReadPartitionForRequestForServerOperation(
theRequest, ProviderConstants.OPERATION_EXPUNGE);
deleteAll(theRequest, propagation, requestPartitionId, counter);

View File

@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.search.reindex;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
@ -33,6 +32,9 @@ import ca.uhn.fhir.jpa.dao.IJpaStorageResourceParser;
import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.partition.BaseRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
@ -236,8 +238,7 @@ public class InstanceReindexServiceImpl implements IInstanceReindexService {
@Nonnull
private RequestPartitionId determinePartition(RequestDetails theRequestDetails, IIdType theResourceId) {
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forRead(theResourceId);
return myPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, details);
return myPartitionHelperSvc.determineReadPartitionForRequestForRead(theRequestDetails, theResourceId);
}
@Nonnull

View File

@ -25,7 +25,6 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.batch.models.Batch2JobStartResponse;
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
@ -184,7 +183,8 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc {
MdmTransactionContext theMdmTransactionContext,
RequestDetails theRequestDetails) {
RequestPartitionId theReadPartitionId =
myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, null);
myRequestPartitionHelperSvc.determineReadPartitionForRequestForServerOperation(
theRequestDetails, ProviderConstants.MDM_QUERY_LINKS);
Page<MdmLinkJson> resultPage;
if (theReadPartitionId.hasPartitionIds()) {
theMdmQuerySearchParameters.setPartitionIds(theReadPartitionId.getPartitionIds());
@ -242,7 +242,8 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc {
String theRequestResourceType) {
Page<MdmLinkJson> resultPage;
RequestPartitionId readPartitionId =
myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, null);
myRequestPartitionHelperSvc.determineReadPartitionForRequestForServerOperation(
theRequestDetails, ProviderConstants.MDM_DUPLICATE_GOLDEN_RESOURCES);
if (readPartitionId.isAllPartitions()) {
resultPage = myMdmLinkQuerySvc.getDuplicateGoldenResources(
@ -318,10 +319,9 @@ public class MdmControllerSvcImpl implements IMdmControllerSvc {
params.setBatchSize(theBatchSize.getValue().intValue());
}
ReadPartitionIdRequestDetails details =
ReadPartitionIdRequestDetails.forOperation(null, null, ProviderConstants.OPERATION_MDM_CLEAR);
RequestPartitionId requestPartition =
myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, details);
myRequestPartitionHelperSvc.determineReadPartitionForRequestForServerOperation(
theRequestDetails, ProviderConstants.OPERATION_MDM_CLEAR);
params.setRequestPartitionId(requestPartition);
JobInstanceStartRequest request = new JobInstanceStartRequest();

View File

@ -38,6 +38,8 @@ public class MdmResourceDaoSvcTest extends BaseMdmR4Test {
@Autowired
private ISearchParamExtractor mySearchParamExtractor;
private PatientIdPartitionInterceptor myPatientIdPartitionInterceptor;
@Override
@AfterEach
public void after() throws IOException {
@ -104,9 +106,8 @@ public class MdmResourceDaoSvcTest extends BaseMdmR4Test {
myPartitionSettings.setPartitioningEnabled(true);
myPartitionSettings.setUnnamedPartitionMode(true);
myPartitionSettings.setIncludePartitionInSearchHashes(false);
PatientIdPartitionInterceptor interceptor = new PatientIdPartitionInterceptor(myFhirContext, mySearchParamExtractor, myPartitionSettings);
myInterceptorRegistry.registerInterceptor(interceptor);
myPatientIdPartitionInterceptor = new PatientIdPartitionInterceptor(getFhirContext(), mySearchParamExtractor, myPartitionSettings);
myInterceptorRegistry.registerInterceptor(myPatientIdPartitionInterceptor);
try {
StringOrListParam patientIds = new StringOrListParam();
@ -154,7 +155,7 @@ public class MdmResourceDaoSvcTest extends BaseMdmR4Test {
Patient patient = (Patient) result.getAllResources().get(0);
assertTrue(patient.getId().contains(firstId.getValue()));
} finally {
myInterceptorRegistry.unregisterInterceptor(interceptor);
myInterceptorRegistry.unregisterInterceptor(myPatientIdPartitionInterceptor);
}
}

View File

@ -108,18 +108,9 @@ public class ReadPartitionIdRequestDetails extends PartitionIdRequestDetails {
return forRead(theId.getResourceType(), theId, false);
}
public static ReadPartitionIdRequestDetails forOperation(
@Nullable String theResourceType, @Nullable IIdType theId, @Nonnull String theExtendedOperationName) {
RestOperationTypeEnum op;
if (theId != null) {
op = RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE;
} else if (theResourceType != null) {
op = RestOperationTypeEnum.EXTENDED_OPERATION_TYPE;
} else {
op = RestOperationTypeEnum.EXTENDED_OPERATION_SERVER;
}
return new ReadPartitionIdRequestDetails(theResourceType, op, null, null, null, null, theExtendedOperationName);
public static ReadPartitionIdRequestDetails forServerOperation(@Nonnull String theOperationName) {
return new ReadPartitionIdRequestDetails(
null, RestOperationTypeEnum.EXTENDED_OPERATION_SERVER, null, null, null, null, theOperationName);
}
public static ReadPartitionIdRequestDetails forRead(
@ -135,7 +126,7 @@ public class ReadPartitionIdRequestDetails extends PartitionIdRequestDetails {
theResourceType,
RestOperationTypeEnum.SEARCH_TYPE,
null,
theParams,
theParams != null ? theParams : SearchParameterMap.newSynchronous(),
theConditionalOperationTargetOrNull,
null,
null);

View File

@ -34,39 +34,119 @@ public interface IRequestPartitionHelperSvc {
@Nonnull
RequestPartitionId determineReadPartitionForRequest(
@Nullable RequestDetails theRequest, ReadPartitionIdRequestDetails theDetails);
@Nullable RequestDetails theRequest, @Nonnull ReadPartitionIdRequestDetails theDetails);
/**
* Determine partition to use when performing a server operation such as $bulk-import, $bulk-export, $reindex etc.
* @param theRequest the request details from the context of the call
* @param theOperationName the explicit name of the operation
* @return the partition id which should be used for the operation
*/
@Nonnull
default RequestPartitionId determineReadPartitionForRequestForServerOperation(
@Nullable RequestDetails theRequest, @Nonnull String theOperationName) {
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forServerOperation(theOperationName);
return determineReadPartitionForRequest(theRequest, details);
}
/**
* Determine partition to use when performing database reads based on a resource instance.
* @param theRequest the request details from the context of the call
* @param theId the id of the resource instance
* @return the partition id which should be used for the database read
*/
@Nonnull
default RequestPartitionId determineReadPartitionForRequestForRead(
RequestDetails theRequest, String theResourceType, @Nonnull IIdType theId) {
@Nullable RequestDetails theRequest, @Nonnull IIdType theId) {
ReadPartitionIdRequestDetails details =
ReadPartitionIdRequestDetails.forRead(theId.getResourceType(), theId, theId.hasVersionIdPart());
return determineReadPartitionForRequest(theRequest, details);
}
/**
* Determine partition to use when performing database reads against a certain resource type based on a resource instance.
* @param theRequest the request details from the context of the call
* @param theResourceType the resource type
* @param theId the id of the resource instance
* @return the partition id which should be used for the database read
*/
@Nonnull
default RequestPartitionId determineReadPartitionForRequestForRead(
@Nullable RequestDetails theRequest, @Nonnull String theResourceType, @Nonnull IIdType theId) {
ReadPartitionIdRequestDetails details =
ReadPartitionIdRequestDetails.forRead(theResourceType, theId, theId.hasVersionIdPart());
return determineReadPartitionForRequest(theRequest, details);
}
/**
* Determine partition to use when performing a database search against a certain resource type.
* @param theRequest the request details from the context of the call
* @param theResourceType the resource type
* @return the partition id which should be used for the database search
*/
@Nonnull
default RequestPartitionId determineReadPartitionForRequestForSearchType(
@Nullable RequestDetails theRequest, @Nonnull String theResourceType) {
ReadPartitionIdRequestDetails details =
ReadPartitionIdRequestDetails.forSearchType(theResourceType, SearchParameterMap.newSynchronous(), null);
return determineReadPartitionForRequest(theRequest, details);
}
/**
* Determine partition to use when performing a database search based on a resource type and other search parameters.
* @param theRequest the request details from the context of the call
* @param theResourceType the resource type
* @param theParams the search parameters
* @return the partition id which should be used for the database search
*/
@Nonnull
default RequestPartitionId determineReadPartitionForRequestForSearchType(
@Nullable RequestDetails theRequest,
@Nonnull String theResourceType,
@Nonnull SearchParameterMap theParams) {
ReadPartitionIdRequestDetails details =
ReadPartitionIdRequestDetails.forSearchType(theResourceType, theParams, null);
return determineReadPartitionForRequest(theRequest, details);
}
/**
* Determine partition to use when performing a database search based on a resource type, search parameters and a conditional target resource (if available).
* @param theRequest the request details from the context of the call
* @param theResourceType the resource type
* @param theParams the search parameters
* @param theConditionalOperationTargetOrNull the conditional target resource
* @return the partition id which should be used for the database search
*/
@Nonnull
default RequestPartitionId determineReadPartitionForRequestForSearchType(
RequestDetails theRequest,
String theResourceType,
SearchParameterMap theParams,
IBaseResource theConditionalOperationTargetOrNull) {
SearchParameterMap searchParameterMap = theParams != null ? theParams : SearchParameterMap.newSynchronous();
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forSearchType(
theResourceType, theParams, theConditionalOperationTargetOrNull);
theResourceType, searchParameterMap, theConditionalOperationTargetOrNull);
return determineReadPartitionForRequest(theRequest, details);
}
RequestPartitionId determineGenericPartitionForRequest(RequestDetails theRequestDetails);
/**
* Determine partition to use when performing the history operation based on a resource type and resource instance.
* @param theRequest the request details from the context of the call
* @param theResourceType the resource type
* @param theIdType the id of the resource instance
* @return the partition id which should be used for the history operation
*/
@Nonnull
default RequestPartitionId determineReadPartitionForRequestForHistory(
RequestDetails theRequest, String theResourceType, IIdType theIdType) {
@Nullable RequestDetails theRequest, String theResourceType, IIdType theIdType) {
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forHistory(theResourceType, theIdType);
return determineReadPartitionForRequest(theRequest, details);
}
@Nonnull
default void validateHasPartitionPermissions(
RequestDetails theRequest, String theResourceType, RequestPartitionId theRequestPartitionId) {}
@Nonnull RequestDetails theRequest, String theResourceType, RequestPartitionId theRequestPartitionId) {}
@Nonnull
RequestPartitionId determineCreatePartitionForRequest(

View File

@ -136,7 +136,7 @@ public class SubscriptionMatcherInterceptor {
// Even though the resource is being written, the subscription will be interacting with it by effectively
// "reading" it so we set the RequestPartitionId as a read request
RequestPartitionId requestPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequestForRead(
theRequest, theNewResource.getIdElement().getResourceType(), theNewResource.getIdElement());
theRequest, theNewResource.getIdElement());
return new ResourceModifiedMessage(
myFhirContext, theNewResource, theOperationType, theRequest, requestPartitionId);
}

View File

@ -32,6 +32,7 @@ import ca.uhn.fhir.util.JsonUtil;
import ca.uhn.fhir.util.SearchParameterUtil;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import jakarta.annotation.Nonnull;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
@ -62,7 +63,6 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@ -71,7 +71,6 @@ import java.util.Set;
import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
@ -1245,7 +1244,7 @@ public class BulkDataExportProviderTest {
private class MyRequestPartitionHelperSvc extends RequestPartitionHelperSvc {
@Override
public @NotNull RequestPartitionId determineReadPartitionForRequest(RequestDetails theRequest, ReadPartitionIdRequestDetails theDetails) {
public @NotNull RequestPartitionId determineReadPartitionForRequest(@Nonnull RequestDetails theRequest, @Nonnull ReadPartitionIdRequestDetails theDetails) {
assert theRequest != null;
if (myPartitionName.equals(theRequest.getTenantId())) {
return myRequestPartitionId;
@ -1255,7 +1254,7 @@ public class BulkDataExportProviderTest {
}
@Override
public void validateHasPartitionPermissions(RequestDetails theRequest, String theResourceType, RequestPartitionId theRequestPartitionId) {
public void validateHasPartitionPermissions(@Nonnull RequestDetails theRequest, String theResourceType, RequestPartitionId theRequestPartitionId) {
if (!myPartitionName.equals(theRequest.getTenantId()) && theRequest.getTenantId() != null) {
throw new ForbiddenOperationException("User does not have access to resources on the requested partition");
}

View File

@ -0,0 +1,88 @@
package ca.uhn.fhir.jpa.bulk;
import ca.uhn.fhir.jpa.interceptor.PatientIdPartitionInterceptor;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.provider.BulkDataExportProvider;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
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.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class BulkExportWithPatientIdPartitioningTest extends BaseResourceProviderR4Test {
private final Logger ourLog = LoggerFactory.getLogger(BulkExportWithPatientIdPartitioningTest.class);
@Autowired
private ISearchParamExtractor mySearchParamExtractor;
private PatientIdPartitionInterceptor myPatientIdPartitionInterceptor;
@BeforeEach
public void before() {
myPatientIdPartitionInterceptor = new PatientIdPartitionInterceptor(getFhirContext(), mySearchParamExtractor, myPartitionSettings);
myInterceptorRegistry.registerInterceptor(myPatientIdPartitionInterceptor);
myPartitionSettings.setPartitioningEnabled(true);
myPartitionSettings.setUnnamedPartitionMode(true);
}
@AfterEach
public void after() {
myInterceptorRegistry.unregisterInterceptor(myPatientIdPartitionInterceptor);
myPartitionSettings.setPartitioningEnabled(new PartitionSettings().isPartitioningEnabled());
myPartitionSettings.setUnnamedPartitionMode(new PartitionSettings().isUnnamedPartitionMode());
}
@Test
public void testSystemBulkExport_withResourceType_success() 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());
}
}
@Test
public void testSystemBulkExport_withResourceType_pollSuccessful() 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"); // 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)) {
ourLog.info("Response: {}", postResponse);
assertEquals(202, postResponse.getStatusLine().getStatusCode());
}
}
}

View File

@ -0,0 +1,112 @@
package ca.uhn.fhir.jpa.bulk.imprt2;
import ca.uhn.fhir.batch2.api.IJobCoordinator;
import ca.uhn.fhir.batch2.api.IJobMaintenanceService;
import ca.uhn.fhir.batch2.jobs.imprt.BulkImportAppCtx;
import ca.uhn.fhir.batch2.jobs.imprt.BulkImportFileServlet;
import ca.uhn.fhir.batch2.jobs.imprt.BulkImportJobParameters;
import ca.uhn.fhir.batch2.model.JobInstance;
import ca.uhn.fhir.batch2.model.JobInstanceStartRequest;
import ca.uhn.fhir.batch2.model.StatusEnum;
import ca.uhn.fhir.jpa.batch.models.Batch2JobStartResponse;
import ca.uhn.fhir.jpa.interceptor.PatientIdPartitionInterceptor;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
import ca.uhn.fhir.test.utilities.server.HttpServletExtension;
import ca.uhn.fhir.util.JsonUtil;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.concurrent.TimeUnit;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.blankOrNullString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* This test class has just one test. It can potentially be moved
*/
public class BulkImportWithPatientIdPartitioningTest extends BaseJpaR4Test {
private static final String USERNAME = "username";
private static final String PASSWORD = "password";
private static final BulkImportFileServlet ourBulkImportFileServlet = new BulkImportFileServlet(USERNAME, PASSWORD);
@RegisterExtension
public static HttpServletExtension myHttpServletExtension = new HttpServletExtension().withServlet(ourBulkImportFileServlet);
@Autowired
private IJobCoordinator myJobCoordinator;
@Autowired
private IJobMaintenanceService myJobCleanerService;
@Autowired
private ISearchParamExtractor mySearchParamExtractor;
private PatientIdPartitionInterceptor myPatientIdPartitionInterceptor;
@BeforeEach
public void before() {
myPatientIdPartitionInterceptor = new PatientIdPartitionInterceptor(getFhirContext(), mySearchParamExtractor, myPartitionSettings);
myInterceptorRegistry.registerInterceptor(myPatientIdPartitionInterceptor);
myPartitionSettings.setPartitioningEnabled(true);
myPartitionSettings.setUnnamedPartitionMode(true);
}
@AfterEach
public void after() {
myInterceptorRegistry.unregisterInterceptor(myPatientIdPartitionInterceptor);
myPartitionSettings.setPartitioningEnabled(new PartitionSettings().isPartitioningEnabled());
myPartitionSettings.setUnnamedPartitionMode(new PartitionSettings().isUnnamedPartitionMode());
}
@Test
public void testBulkImport_withOneResource_successful() {
// Setup
Patient p = new Patient().setActive(true);
p.setId("P1");
String fileContents = getFhirContext().newJsonParser().encodeResourceToString(p);
String id = ourBulkImportFileServlet.registerFileByContents(fileContents);
BulkImportJobParameters parameters = new BulkImportJobParameters();
parameters.setHttpBasicCredentials(USERNAME + ":" + PASSWORD);
parameters.addNdJsonUrl(myHttpServletExtension.getBaseUrl() + "/download?index=" + id);
JobInstanceStartRequest request = new JobInstanceStartRequest();
request.setJobDefinitionId(BulkImportAppCtx.JOB_BULK_IMPORT_PULL);
request.setParameters(parameters);
// Execute
Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(request);
String instanceId = startResponse.getInstanceId();
assertThat(instanceId, not(blankOrNullString()));
ourLog.info("Execution got ID: {}", instanceId);
// Verify
await().atMost(120, TimeUnit.SECONDS).until(() -> {
myJobCleanerService.runMaintenancePass();
JobInstance instance = myJobCoordinator.getInstance(instanceId);
return instance.getStatus();
}, equalTo(StatusEnum.COMPLETED));
runInTransaction(() -> {
assertEquals(1, myResourceTableDao.count());
});
runInTransaction(() -> {
JobInstance instance = myJobCoordinator.getInstance(instanceId);
ourLog.info("Instance details:\n{}", JsonUtil.serialize(instance, true));
assertEquals(0, instance.getErrorCount());
});
}
}

View File

@ -1,8 +1,8 @@
package ca.uhn.fhir.jpa.interceptor;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hl7.fhir.r4.model.Annotation;
@ -16,28 +16,26 @@ import org.springframework.beans.factory.annotation.Autowired;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class PatientCompartmentEnforcingInterceptorTest extends BaseJpaR4Test {
public class PatientCompartmentEnforcingInterceptorTest extends BaseResourceProviderR4Test {
public static final int ALTERNATE_DEFAULT_ID = -1;
private PatientIdPartitionInterceptor mySvc;
private ForceOffsetSearchModeInterceptor myForceOffsetSearchModeInterceptor;
private PatientCompartmentEnforcingInterceptor myUpdateCrossPartitionInterceptor;
@Autowired
private ISearchParamExtractor mySearchParamExtractor;
private ForceOffsetSearchModeInterceptor myForceOffsetSearchModeInterceptor;
private PatientIdPartitionInterceptor myPatientIdPartitionInterceptor;
private PatientCompartmentEnforcingInterceptor mySvc;
@Override
@BeforeEach
public void before() throws Exception {
super.before();
mySvc = new PatientIdPartitionInterceptor(myFhirContext, mySearchParamExtractor, myPartitionSettings);
myForceOffsetSearchModeInterceptor = new ForceOffsetSearchModeInterceptor();
myUpdateCrossPartitionInterceptor = new PatientCompartmentEnforcingInterceptor(
myFhirContext, mySearchParamExtractor);
myPatientIdPartitionInterceptor = new PatientIdPartitionInterceptor(getFhirContext(), mySearchParamExtractor, myPartitionSettings);
mySvc = new PatientCompartmentEnforcingInterceptor(getFhirContext(), mySearchParamExtractor);
myInterceptorRegistry.registerInterceptor(mySvc);
myInterceptorRegistry.registerInterceptor(myPatientIdPartitionInterceptor);
myInterceptorRegistry.registerInterceptor(myForceOffsetSearchModeInterceptor);
myInterceptorRegistry.registerInterceptor(myUpdateCrossPartitionInterceptor);
myInterceptorRegistry.registerInterceptor(mySvc);
myPartitionSettings.setPartitioningEnabled(true);
myPartitionSettings.setUnnamedPartitionMode(true);
@ -46,9 +44,9 @@ public class PatientCompartmentEnforcingInterceptorTest extends BaseJpaR4Test {
@AfterEach
public void after() {
myInterceptorRegistry.unregisterInterceptor(mySvc);
myInterceptorRegistry.unregisterInterceptor(myPatientIdPartitionInterceptor);
myInterceptorRegistry.unregisterInterceptor(myForceOffsetSearchModeInterceptor);
myInterceptorRegistry.unregisterInterceptor(myUpdateCrossPartitionInterceptor);
myInterceptorRegistry.unregisterInterceptor(mySvc);
myPartitionSettings.setPartitioningEnabled(false);
myPartitionSettings.setUnnamedPartitionMode(new PartitionSettings().isUnnamedPartitionMode());

View File

@ -1,8 +1,6 @@
package ca.uhn.fhir.jpa.interceptor;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
@ -12,10 +10,8 @@ import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.jpa.util.SqlQuery;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.rest.api.server.bulk.BulkExportJobParameters;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
@ -40,7 +36,6 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean;
import java.io.IOException;
import java.util.List;
@ -56,19 +51,13 @@ import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
public class PatientIdPartitionInterceptorTest extends BaseResourceProviderR4Test {
public static final int ALTERNATE_DEFAULT_ID = -1;
private ForceOffsetSearchModeInterceptor myForceOffsetSearchModeInterceptor;
@Autowired
private ISearchParamExtractor mySearchParamExtractor;
@SpyBean
@Autowired
private ForceOffsetSearchModeInterceptor myForceOffsetSearchModeInterceptor;
private PatientIdPartitionInterceptor mySvc;
@Override
@ -76,6 +65,7 @@ public class PatientIdPartitionInterceptorTest extends BaseResourceProviderR4Tes
public void before() throws Exception {
super.before();
myForceOffsetSearchModeInterceptor = new ForceOffsetSearchModeInterceptor();
mySvc = new PatientIdPartitionInterceptor(getFhirContext(), mySearchParamExtractor, myPartitionSettings);
myInterceptorRegistry.registerInterceptor(mySvc);
myInterceptorRegistry.registerInterceptor(myForceOffsetSearchModeInterceptor);
@ -553,19 +543,7 @@ public class PatientIdPartitionInterceptorTest extends BaseResourceProviderR4Tes
}
@Test
public void testIdentifyForRead_serverOperation_returnsAllPartitions() {
ReadPartitionIdRequestDetails readRequestDetails = ReadPartitionIdRequestDetails.forOperation(null, null, ProviderConstants.OPERATION_EXPORT);
RequestPartitionId requestPartitionId = mySvc.identifyForRead(readRequestDetails, mySrd);
assertEquals(requestPartitionId, RequestPartitionId.allPartitions());
assertEquals(RestOperationTypeEnum.EXTENDED_OPERATION_SERVER, readRequestDetails.getRestOperationType());
}
@Test
public void testSystemBulkExport_withPatientIdPartitioningWithNoResourceType_usesNonPatientSpecificPartition() throws IOException {
final BulkExportJobParameters options = new BulkExportJobParameters();
options.setExportStyle(BulkExportJobParameters.ExportStyle.SYSTEM);
options.setOutputFormat(Constants.CT_FHIR_NDJSON);
public void testSystemOperation_withNoResourceType_success() throws IOException {
HttpPost post = new HttpPost(myServer.getBaseUrl() + "/" + ProviderConstants.OPERATION_EXPORT);
post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
@ -574,7 +552,5 @@ public class PatientIdPartitionInterceptorTest extends BaseResourceProviderR4Tes
assertEquals(202, postResponse.getStatusLine().getStatusCode());
assertEquals("Accepted", postResponse.getStatusLine().getReasonPhrase());
}
verify(mySvc).provideNonPatientSpecificQueryResponse(any());
}
}

View File

@ -15,8 +15,6 @@ import ca.uhn.fhir.util.ClasspathUtil;
import org.hl7.fhir.utilities.npm.NpmPackage;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
@ -30,8 +28,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
public class JpaPackageCacheTest extends BaseJpaR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(JpaPackageCacheTest.class);
@Autowired
private IHapiPackageCacheManager myPackageCacheManager;
@Autowired
@ -45,6 +41,8 @@ public class JpaPackageCacheTest extends BaseJpaR4Test {
@Autowired
private ISearchParamExtractor mySearchParamExtractor;
private PatientIdPartitionInterceptor myPatientIdPartitionInterceptor;
@AfterEach
public void disablePartitioning() {
myPartitionSettings.setPartitioningEnabled(false);
@ -79,8 +77,8 @@ public class JpaPackageCacheTest extends BaseJpaR4Test {
public void testSaveAndDeletePackagePartitionsEnabled() throws IOException {
myPartitionSettings.setPartitioningEnabled(true);
myPartitionSettings.setDefaultPartitionId(1);
PatientIdPartitionInterceptor patientIdPartitionInterceptor = new PatientIdPartitionInterceptor(myFhirContext, mySearchParamExtractor, myPartitionSettings);
myInterceptorService.registerInterceptor(patientIdPartitionInterceptor);
myPatientIdPartitionInterceptor = new PatientIdPartitionInterceptor(getFhirContext(), mySearchParamExtractor, myPartitionSettings);
myInterceptorService.registerInterceptor(myPatientIdPartitionInterceptor);
myInterceptorService.registerInterceptor(myRequestTenantPartitionInterceptor);
try {
try (InputStream stream = ClasspathUtil.loadResourceAsStream("/packages/basisprofil.de.tar.gz")) {
@ -108,7 +106,7 @@ public class JpaPackageCacheTest extends BaseJpaR4Test {
List<String> deleteOutcomeMsgs = deleteOutcomeJson.getMessage();
assertEquals("Deleting package basisprofil.de#0.2.40", deleteOutcomeMsgs.get(0));
} finally {
myInterceptorService.unregisterInterceptor(patientIdPartitionInterceptor);
myInterceptorService.unregisterInterceptor(myPatientIdPartitionInterceptor);
myInterceptorService.unregisterInterceptor(myRequestTenantPartitionInterceptor);
}
}
@ -119,8 +117,8 @@ public class JpaPackageCacheTest extends BaseJpaR4Test {
myPartitionSettings.setDefaultPartitionId(0);
boolean isUnnamed = myPartitionSettings.isUnnamedPartitionMode();
myPartitionSettings.setUnnamedPartitionMode(true);
PatientIdPartitionInterceptor patientIdPartitionInterceptor = new PatientIdPartitionInterceptor(myFhirContext, mySearchParamExtractor, myPartitionSettings);
myInterceptorService.registerInterceptor(patientIdPartitionInterceptor);
myPatientIdPartitionInterceptor = new PatientIdPartitionInterceptor(getFhirContext(), mySearchParamExtractor, myPartitionSettings);
myInterceptorService.registerInterceptor(myPatientIdPartitionInterceptor);
myInterceptorService.registerInterceptor(myRequestTenantPartitionInterceptor);
try {
try (InputStream stream = ClasspathUtil.loadResourceAsStream("/packages/hl7.fhir.uv.shorthand-0.12.0.tgz")) {
@ -147,7 +145,7 @@ public class JpaPackageCacheTest extends BaseJpaR4Test {
assertEquals("Deleting package hl7.fhir.uv.shorthand#0.12.0", deleteOutcomeMsgs.get(0));
} finally {
myPartitionSettings.setUnnamedPartitionMode(isUnnamed);
myInterceptorService.unregisterInterceptor(patientIdPartitionInterceptor);
myInterceptorService.unregisterInterceptor(myPatientIdPartitionInterceptor);
myInterceptorService.unregisterInterceptor(myRequestTenantPartitionInterceptor);
}
}

View File

@ -19,6 +19,7 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import jakarta.annotation.Nonnull;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r5.model.Enumerations;
@ -34,11 +35,8 @@ import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import jakarta.annotation.Nonnull;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@ -194,8 +192,7 @@ public class CrossPartitionReferencesTest extends BaseJpaR5Test {
when(myCrossPartitionReferencesDetectedInterceptor.handle(any(),any())).thenAnswer(t->{
CrossPartitionReferenceDetails theDetails = t.getArgument(1, CrossPartitionReferenceDetails.class);
IIdType targetId = theDetails.getPathAndRef().getRef().getReferenceElement();
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forRead(targetId);
RequestPartitionId referenceTargetPartition = myPartitionHelperSvc.determineReadPartitionForRequest(theDetails.getRequestDetails(), details);
RequestPartitionId referenceTargetPartition = myPartitionHelperSvc.determineReadPartitionForRequestForRead(theDetails.getRequestDetails(), targetId.getResourceType(), targetId);
IResourceLookup targetResource = myTransactionService
.withRequest(theDetails.getRequestDetails())

View File

@ -72,7 +72,7 @@ public class MdmSearchExpandingInterceptor {
theRequestDetails == null ? new SystemRequestDetails() : theRequestDetails;
final RequestPartitionId requestPartitionId =
myRequestPartitionHelperSvc.determineReadPartitionForRequestForSearchType(
requestDetailsToUse, requestDetailsToUse.getResourceName(), theSearchParameterMap, null);
requestDetailsToUse, requestDetailsToUse.getResourceName(), theSearchParameterMap);
for (Map.Entry<String, List<List<IQueryParameterType>>> set : theSearchParameterMap.entrySet()) {
String paramName = set.getKey();
List<List<IQueryParameterType>> andList = set.getValue();

View File

@ -21,7 +21,6 @@ package ca.uhn.fhir.mdm.provider;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.mdm.api.IMdmMatchFinderSvc;
@ -143,13 +142,11 @@ public class MdmControllerHelper {
public IBaseBundle getMatchesAndPossibleMatchesForResource(
IAnyResource theResource, String theResourceType, RequestDetails theRequestDetails) {
RequestPartitionId requestPartitionId;
ReadPartitionIdRequestDetails details =
ReadPartitionIdRequestDetails.forSearchType(theResourceType, null, null);
if (myMdmSettings.getSearchAllPartitionForMatch()) {
requestPartitionId = RequestPartitionId.allPartitions();
} else {
requestPartitionId =
myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, details);
requestPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequestForSearchType(
theRequestDetails, theResourceType);
}
List<MatchedTarget> matches =
myMdmMatchFinderSvc.getMatchedTargets(theResourceType, theResource, requestPartitionId);

View File

@ -106,7 +106,7 @@ public class MdmSubmitSvcImpl implements IMdmSubmitSvc {
RequestPartitionId requestPartitionId =
myRequestPartitionHelperSvc.determineReadPartitionForRequestForSearchType(
theRequestDetails, theSourceResourceType, spMap, null);
theRequestDetails, theSourceResourceType, spMap);
return submitAllMatchingResourcesToMdmChannel(spMap, searchBuilder, requestPartitionId);
}

View File

@ -29,7 +29,6 @@ import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
@ -186,12 +185,10 @@ public class BulkDataExportProvider {
theOptions.setResourceTypes(resourceTypes);
}
ReadPartitionIdRequestDetails theDetails =
ReadPartitionIdRequestDetails.forOperation(null, null, ProviderConstants.OPERATION_EXPORT);
// Determine and validate partition permissions (if needed).
RequestPartitionId partitionId =
myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, theDetails);
myRequestPartitionHelperService.determineReadPartitionForRequestForServerOperation(
theRequestDetails, ProviderConstants.OPERATION_EXPORT);
myRequestPartitionHelperService.validateHasPartitionPermissions(theRequestDetails, "Binary", partitionId);
theOptions.setPartitionId(partitionId);
@ -472,7 +469,8 @@ public class BulkDataExportProvider {
if (parameters.getPartitionId() != null) {
// Determine and validate permissions for partition (if needed)
RequestPartitionId partitionId =
myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, null);
myRequestPartitionHelperService.determineReadPartitionForRequestForServerOperation(
theRequestDetails, ProviderConstants.OPERATION_EXPORT_POLL_STATUS);
myRequestPartitionHelperService.validateHasPartitionPermissions(theRequestDetails, "Binary", partitionId);
if (!parameters.getPartitionId().equals(partitionId)) {
throw new InvalidRequestException(

View File

@ -27,7 +27,6 @@ import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.batch.models.Batch2JobStartResponse;
@ -103,11 +102,10 @@ public class DeleteExpungeJobSubmitterImpl implements IDeleteExpungeJobSubmitter
.forEach(deleteExpungeJobParameters::addPartitionedUrl);
deleteExpungeJobParameters.setBatchSize(theBatchSize);
ReadPartitionIdRequestDetails details =
ReadPartitionIdRequestDetails.forOperation(null, null, ProviderConstants.OPERATION_DELETE_EXPUNGE);
// Also set toplevel partition in case there are no urls
// Also set top level partition in case there are no urls
RequestPartitionId requestPartition =
myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, details);
myRequestPartitionHelperSvc.determineReadPartitionForRequestForServerOperation(
theRequestDetails, ProviderConstants.OPERATION_DELETE_EXPUNGE);
deleteExpungeJobParameters.setRequestPartitionId(requestPartition);
deleteExpungeJobParameters.setCascade(theCascade);
deleteExpungeJobParameters.setCascadeMaxRounds(theCascadeMaxRounds);

View File

@ -157,8 +157,9 @@ public class BulkDataImportProvider {
}
RequestPartitionId partitionId =
myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, null);
if (partitionId != null && !partitionId.isAllPartitions()) {
myRequestPartitionHelperService.determineReadPartitionForRequestForServerOperation(
theRequestDetails, JpaConstants.OPERATION_IMPORT);
if (!partitionId.isAllPartitions()) {
myRequestPartitionHelperService.validateHasPartitionPermissions(theRequestDetails, "Binary", partitionId);
jobParameters.setPartitionId(partitionId);
}
@ -234,7 +235,8 @@ public class BulkDataImportProvider {
if (parameters != null && parameters.getPartitionId() != null) {
// Determine and validate permissions for partition (if needed)
RequestPartitionId partitionId =
myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, null);
myRequestPartitionHelperService.determineReadPartitionForRequestForServerOperation(
theRequestDetails, JpaConstants.OPERATION_IMPORT);
myRequestPartitionHelperService.validateHasPartitionPermissions(theRequestDetails, "Binary", partitionId);
if (!partitionId.equals(parameters.getPartitionId())) {
throw new InvalidRequestException(

View File

@ -23,7 +23,6 @@ import ca.uhn.fhir.batch2.api.IJobCoordinator;
import ca.uhn.fhir.batch2.jobs.parameters.UrlPartitioner;
import ca.uhn.fhir.batch2.model.JobInstanceStartRequest;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.dao.ReindexParameters;
import ca.uhn.fhir.jpa.batch.models.Batch2JobStartResponse;
@ -129,10 +128,9 @@ public class ReindexProvider {
.forEach(params::addPartitionedUrl);
}
ReadPartitionIdRequestDetails details =
ReadPartitionIdRequestDetails.forOperation(null, null, ProviderConstants.OPERATION_REINDEX);
RequestPartitionId requestPartition =
myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, details);
myRequestPartitionHelperSvc.determineReadPartitionForRequestForServerOperation(
theRequestDetails, ProviderConstants.OPERATION_REINDEX);
params.setRequestPartitionId(requestPartition);
JobInstanceStartRequest request = new JobInstanceStartRequest();

View File

@ -18,6 +18,7 @@ import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy;
import ca.uhn.fhir.test.utilities.HttpClientExtension;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import com.google.common.base.Charsets;
import jakarta.annotation.Nonnull;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
@ -50,7 +51,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.annotation.Nonnull;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@ -399,7 +399,7 @@ public class BulkDataImportProviderTest {
private class MyRequestPartitionHelperSvc implements IRequestPartitionHelperSvc {
@Nonnull
@Override
public RequestPartitionId determineReadPartitionForRequest(@Nullable RequestDetails theRequest, ReadPartitionIdRequestDetails theDetails) {
public RequestPartitionId determineReadPartitionForRequest(@Nullable RequestDetails theRequest, @Nonnull ReadPartitionIdRequestDetails theDetails) {
assert theRequest != null;
if (myPartitionName.equals(theRequest.getTenantId())) {
return myRequestPartitionId;
@ -409,7 +409,7 @@ public class BulkDataImportProviderTest {
}
@Override
public void validateHasPartitionPermissions(RequestDetails theRequest, String theResourceType, RequestPartitionId theRequestPartitionId) {
public void validateHasPartitionPermissions(@Nonnull RequestDetails theRequest, String theResourceType, RequestPartitionId theRequestPartitionId) {
if (!myPartitionName.equals(theRequest.getTenantId()) && theRequest.getTenantId() != null) {
throw new ForbiddenOperationException("User does not have access to resources on the requested partition");
}

View File

@ -74,7 +74,7 @@ public class ReindexProviderTest {
when(myJobCoordinator.startInstance(isNotNull(), any()))
.thenReturn(createJobStartResponse());
when(myRequestPartitionHelperSvc.determineReadPartitionForRequest(any(), any())).thenReturn(RequestPartitionId.allPartitions());
when(myRequestPartitionHelperSvc.determineReadPartitionForRequestForServerOperation(any(), any())).thenReturn(RequestPartitionId.allPartitions());
}
private Batch2JobStartResponse createJobStartResponse() {

View File

@ -38,10 +38,7 @@ public class UrlPartitioner {
ResourceSearch resourceSearch = myMatchUrlService.getResourceSearch(theUrl);
RequestPartitionId requestPartitionId =
myRequestPartitionHelperSvc.determineReadPartitionForRequestForSearchType(
theRequestDetails,
resourceSearch.getResourceName(),
resourceSearch.getSearchParameterMap(),
null);
theRequestDetails, resourceSearch.getResourceName(), resourceSearch.getSearchParameterMap());
PartitionedUrl retval = new PartitionedUrl();
retval.setUrl(theUrl);
retval.setRequestPartitionId(requestPartitionId);

View File

@ -320,8 +320,8 @@ public class DaoRegistryGraphQLStorageServices implements IGraphQLStorageService
CacheControlDirective cacheControlDirective = new CacheControlDirective();
cacheControlDirective.parse(requestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL));
RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(
requestDetails, theType, params, null);
RequestPartitionId requestPartitionId =
myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(requestDetails, theType, params);
response = mySearchCoordinatorSvc.registerSearch(
getDao(theType), params, theType, cacheControlDirective, requestDetails, requestPartitionId);

View File

@ -36,17 +36,18 @@ import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import jakarta.annotation.Nonnull;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.IdType;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -67,13 +68,6 @@ public class PatientIdPartitionInterceptor {
@Autowired
private PartitionSettings myPartitionSettings;
/**
* Constructor
*/
public PatientIdPartitionInterceptor() {
super();
}
/**
* Constructor
*/
@ -81,7 +75,6 @@ public class PatientIdPartitionInterceptor {
FhirContext theFhirContext,
ISearchParamExtractor theSearchParamExtractor,
PartitionSettings thePartitionSettings) {
this();
myFhirContext = theFhirContext;
mySearchParamExtractor = theSearchParamExtractor;
myPartitionSettings = thePartitionSettings;
@ -116,8 +109,7 @@ public class PatientIdPartitionInterceptor {
@Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_READ)
public RequestPartitionId identifyForRead(
ReadPartitionIdRequestDetails theReadDetails, RequestDetails theRequestDetails) {
@Nonnull ReadPartitionIdRequestDetails theReadDetails, RequestDetails theRequestDetails) {
List<RuntimeSearchParam> compartmentSps = Collections.emptyList();
if (!isEmpty(theReadDetails.getResourceType())) {
RuntimeResourceDefinition resourceDef =
@ -140,10 +132,9 @@ public class PatientIdPartitionInterceptor {
break;
case SEARCH_TYPE:
SearchParameterMap params = theReadDetails.getSearchParams();
assert params != null;
if ("Patient".equals(theReadDetails.getResourceType())) {
List<String> idParts = getResourceIdList(params, "_id", "Patient", false);
if (idParts.size() == 1) {
return provideCompartmentMemberInstanceResponse(theRequestDetails, idParts.get(0));
} else {
@ -160,16 +151,12 @@ public class PatientIdPartitionInterceptor {
break;
case EXTENDED_OPERATION_SERVER:
String extendedOp = theReadDetails.getExtendedOperationName();
if (ProviderConstants.OPERATION_EXPORT.equals(extendedOp)) {
return provideNonPatientSpecificQueryResponse(theReadDetails);
}
break;
return provideNonPatientSpecificQueryResponse(theReadDetails);
default:
// nothing
}
if (isEmpty(theReadDetails.getResourceType())) {
if (isBlank(theReadDetails.getResourceType())) {
return provideNonCompartmentMemberTypeResponse(null);
}
@ -184,31 +171,29 @@ public class PatientIdPartitionInterceptor {
private List<String> getResourceIdList(
SearchParameterMap theParams, String theParamName, String theResourceType, boolean theExpectOnlyOneBool) {
List<String> idParts = new ArrayList<>();
List<List<IQueryParameterType>> idParamAndList = theParams.get(theParamName);
if (idParamAndList != null) {
for (List<IQueryParameterType> idParamOrList : idParamAndList) {
for (IQueryParameterType idParam : idParamOrList) {
if (isNotBlank(idParam.getQueryParameterQualifier())) {
throw new MethodNotAllowedException(
Msg.code(1322) + "The parameter " + theParamName + idParam.getQueryParameterQualifier()
+ " is not supported in patient compartment mode");
}
if (idParam instanceof ReferenceParam) {
String chain = ((ReferenceParam) idParam).getChain();
if (chain != null) {
throw new MethodNotAllowedException(Msg.code(1323) + "The parameter " + theParamName + "."
+ chain + " is not supported in patient compartment mode");
}
}
if (idParamAndList == null) {
return Collections.emptyList();
}
IdType id = new IdType(idParam.getValueAsQueryToken(myFhirContext));
if (!id.hasResourceType() || id.getResourceType().equals(theResourceType)) {
idParts.add(id.getIdPart());
}
List<String> idParts = new ArrayList<>();
idParamAndList.stream().flatMap(Collection::stream).forEach(idParam -> {
if (isNotBlank(idParam.getQueryParameterQualifier())) {
throw new MethodNotAllowedException(Msg.code(1322) + "The parameter " + theParamName
+ idParam.getQueryParameterQualifier() + " is not supported in patient compartment mode");
}
if (idParam instanceof ReferenceParam) {
String chain = ((ReferenceParam) idParam).getChain();
if (chain != null) {
throw new MethodNotAllowedException(Msg.code(1323) + "The parameter " + theParamName + "." + chain
+ " is not supported in patient compartment mode");
}
}
}
IdType id = new IdType(idParam.getValueAsQueryToken(myFhirContext));
if (!id.hasResourceType() || id.getResourceType().equals(theResourceType)) {
idParts.add(id.getIdPart());
}
});
if (theExpectOnlyOneBool && idParts.size() > 1) {
throw new MethodNotAllowedException(Msg.code(1324) + "Multiple values for parameter " + theParamName

View File

@ -94,10 +94,10 @@ public abstract class BaseRequestPartitionHelperSvc implements IRequestPartition
@Nonnull
@Override
public RequestPartitionId determineReadPartitionForRequest(
@Nullable RequestDetails theRequest, ReadPartitionIdRequestDetails theDetails) {
@Nullable RequestDetails theRequest, @Nonnull ReadPartitionIdRequestDetails theDetails) {
RequestPartitionId requestPartitionId;
String resourceType = theDetails != null ? theDetails.getResourceType() : null;
String resourceType = theDetails.getResourceType();
boolean nonPartitionableResource = !isResourcePartitionable(resourceType);
if (myPartitionSettings.isPartitioningEnabled()) {
@ -313,7 +313,7 @@ public abstract class BaseRequestPartitionHelperSvc implements IRequestPartition
@Override
public void validateHasPartitionPermissions(
RequestDetails theRequest, String theResourceType, RequestPartitionId theRequestPartitionId) {
@Nonnull RequestDetails theRequest, String theResourceType, RequestPartitionId theRequestPartitionId) {
if (myInterceptorBroadcaster.hasHooks(Pointcut.STORAGE_PARTITION_SELECTED)) {
RuntimeResourceDefinition runtimeResourceDefinition = null;
if (theResourceType != null) {