From f957661dda12b119eaf9998c52263fa5121916fa Mon Sep 17 00:00:00 2001 From: James Agnew <jamesagnew@gmail.com> Date: Mon, 26 Jul 2021 14:18:58 -0400 Subject: [PATCH] Patient ID Partition Mode should support conditional create (#2828) * Fixes * Test fix * Add changelog * Test fixes * Test fixes * Docs tweak * Partitioning fixes * Import fix * Fix test data --- .../model/ReadPartitionIdRequestDetails.java | 19 +- ...ment-should-support-conditiona-create.yaml | 5 + .../fhir/jpa/api/dao/IFhirResourceDao.java | 34 +- .../bulk/export/job/ResourceToFileWriter.java | 3 +- .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 13 +- .../BaseHapiFhirResourceDaoObservation.java | 2 +- .../jpa/dao/FhirResourceDaoPatientDstu2.java | 2 +- .../fhir/jpa/dao/MatchResourceUrlService.java | 13 +- .../FhirResourceDaoObservationDstu3.java | 2 +- .../dstu3/FhirResourceDaoPatientDstu3.java | 2 +- .../dao/r4/FhirResourceDaoObservationR4.java | 2 +- .../jpa/dao/r4/FhirResourceDaoPatientR4.java | 2 +- .../dao/r5/FhirResourceDaoObservationR5.java | 2 +- .../jpa/dao/r5/FhirResourceDaoPatientR5.java | 2 +- .../delete/DeleteExpungeJobSubmitterImpl.java | 2 +- .../PatientIdPartitionInterceptor.java | 33 +- .../jpa/packages/PackageInstallerSvcImpl.java | 16 +- .../partition/IRequestPartitionHelperSvc.java | 5 +- .../partition/RequestPartitionHelperSvc.java | 17 +- .../jpa/partition/SystemRequestDetails.java | 3 +- .../jpa/search/SearchCoordinatorSvcImpl.java | 2 +- .../jpa/search/builder/SearchBuilder.java | 4 +- .../jpa/bulk/BulkDataExportSvcImplR4Test.java | 15 +- .../ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java | 2 + .../FhirResourceDaoR4SearchOptimizedTest.java | 11 +- .../PatientIdPartitionInterceptorTest.java | 160 ++- .../jpa/packages/JpaPackageCacheTest.java | 7 +- .../ca/uhn/fhir/jpa/packages/NpmR4Test.java | 4 +- .../uhn/fhir/jpa/util/MultimapCollector.java | 64 ++ .../resources/packages/test-draft-sample.tgz | Bin 3123 -> 3134 bytes .../src/test/resources/r4/load_bundle.json | 986 ++++++++++++++++++ .../transaction-perf-bundle-smallchanges.json | 2 +- .../resources/r4/transaction-perf-bundle.json | 2 +- .../cql/common/helper/PartitionHelper.java | 6 +- .../dstu3/CqlMeasureEvaluationDstu3Test.java | 4 +- .../model/search/SearchRuntimeDetails.java | 1 + 36 files changed, 1358 insertions(+), 91 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_5_0/2727-patient-id-compartment-should-support-conditiona-create.yaml create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/MultimapCollector.java create mode 100644 hapi-fhir-jpaserver-base/src/test/resources/r4/load_bundle.json diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/model/ReadPartitionIdRequestDetails.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/model/ReadPartitionIdRequestDetails.java index 6ff03aaf4c4..5c629ee4288 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/model/ReadPartitionIdRequestDetails.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/model/ReadPartitionIdRequestDetails.java @@ -21,20 +21,29 @@ package ca.uhn.fhir.interceptor.model; */ import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; +import javax.annotation.Nullable; + public class ReadPartitionIdRequestDetails { private final String myResourceType; private final RestOperationTypeEnum myRestOperationType; private final IIdType myReadResourceId; private final Object mySearchParams; + private final IBaseResource myConditionalTargetOrNull; - public ReadPartitionIdRequestDetails(String theResourceType, RestOperationTypeEnum theRestOperationType, IIdType theReadResourceId, Object theSearchParams) { + public ReadPartitionIdRequestDetails(String theResourceType, RestOperationTypeEnum theRestOperationType, IIdType theReadResourceId, Object theSearchParams, @Nullable IBaseResource theConditionalTargetOrNull) { myResourceType = theResourceType; myRestOperationType = theRestOperationType; myReadResourceId = theReadResourceId; mySearchParams = theSearchParams; + myConditionalTargetOrNull = theConditionalTargetOrNull; + } + + public IBaseResource getConditionalTargetOrNull() { + return myConditionalTargetOrNull; } public String getResourceType() { @@ -55,11 +64,11 @@ public class ReadPartitionIdRequestDetails { public static ReadPartitionIdRequestDetails forRead(String theResourceType, IIdType theId, boolean theIsVread) { RestOperationTypeEnum op = theIsVread ? RestOperationTypeEnum.VREAD : RestOperationTypeEnum.READ; - return new ReadPartitionIdRequestDetails(theResourceType, op, theId.withResourceType(theResourceType), null); + return new ReadPartitionIdRequestDetails(theResourceType, op, theId.withResourceType(theResourceType), null, null); } - public static ReadPartitionIdRequestDetails forSearchType(String theResourceType, Object theParams) { - return new ReadPartitionIdRequestDetails(theResourceType, RestOperationTypeEnum.SEARCH_TYPE, null, theParams); + public static ReadPartitionIdRequestDetails forSearchType(String theResourceType, Object theParams, IBaseResource theConditionalOperationTargetOrNull) { + return new ReadPartitionIdRequestDetails(theResourceType, RestOperationTypeEnum.SEARCH_TYPE, null, theParams, theConditionalOperationTargetOrNull); } public static ReadPartitionIdRequestDetails forHistory(String theResourceType, IIdType theIdType) { @@ -71,6 +80,6 @@ public class ReadPartitionIdRequestDetails { } else { restOperationTypeEnum = RestOperationTypeEnum.HISTORY_SYSTEM; } - return new ReadPartitionIdRequestDetails(theResourceType, restOperationTypeEnum, theIdType, null); + return new ReadPartitionIdRequestDetails(theResourceType, restOperationTypeEnum, theIdType, null, null); } } diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_5_0/2727-patient-id-compartment-should-support-conditiona-create.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_5_0/2727-patient-id-compartment-should-support-conditiona-create.yaml new file mode 100644 index 00000000000..7682526ca8c --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_5_0/2727-patient-id-compartment-should-support-conditiona-create.yaml @@ -0,0 +1,5 @@ +--- +type: add +issue: 2828 +title: "PatientIdPartitionInterceptor now supports conditional creates of resources where the resource is in + the patient compartment but the conditional URL does not contain a patietn reference." diff --git a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java index b0e1b0db28c..ff8fa2a2303 100644 --- a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java @@ -47,6 +47,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.servlet.http.HttpServletResponse; import java.util.Collection; import java.util.Date; @@ -80,7 +81,7 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao { * won't be indexed and searches won't work. * @param theRequestDetails TODO */ - DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, @Nonnull TransactionDetails theTransactionDetails, RequestDetails theRequestDetails); + DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, @Nonnull TransactionDetails theTransactionDetails, RequestDetails theRequestDetails); DaoMethodOutcome create(T theResource, String theIfNoneExist, RequestDetails theRequestDetails); @@ -211,7 +212,21 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao { /** * Search for IDs for processing a match URLs, etc. */ - Set<ResourcePersistentId> searchForIds(SearchParameterMap theParams, RequestDetails theRequest); + default Set<ResourcePersistentId> searchForIds(SearchParameterMap theParams, RequestDetails theRequest) { + return searchForIds(theParams, theRequest, null); + } + + /** + * Search for IDs for processing a match URLs, etc. + * + * @param theConditionalOperationTargetOrNull If we're searching for IDs in order to satisfy a conditional + * create/update, this is the resource being searched for + * @since 5.5.0 + */ + default Set<ResourcePersistentId> searchForIds(SearchParameterMap theParams, RequestDetails theRequest, @Nullable IBaseResource theConditionalOperationTargetOrNull) { + return searchForIds(theParams, theRequest); + } + /** * Takes a map of incoming raw search parameters and translates/parses them into @@ -249,7 +264,7 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao { * @param theForceUpdateVersion Create a new version with the same contents as the current version even if the content hasn't changed (this is mostly useful for * resources mapping to external content such as external code systems) */ - DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequestDetails, @Nonnull TransactionDetails theTransactionDetails); + DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequestDetails, @Nonnull TransactionDetails theTransactionDetails); /** * Not supported in DSTU1! @@ -262,10 +277,11 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao { /** * Delete a list of resource Pids - * @param theUrl the original URL that triggered the delete - * @param theResourceIds the ids of the resources to be deleted + * + * @param theUrl the original URL that triggered the delete + * @param theResourceIds the ids of the resources to be deleted * @param theDeleteConflicts out parameter of conflicts preventing deletion - * @param theRequest the request that initiated the request + * @param theRequest the request that initiated the request * @return response back to the client */ DeleteMethodOutcome deletePidList(String theUrl, Collection<ResourcePersistentId> theResourceIds, DeleteConflictList theDeleteConflicts, RequestDetails theRequest); @@ -273,8 +289,8 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao { /** * Returns the current version ID for the given resource */ - default String getCurrentVersionId(IIdType theReferenceElement) { - return read(theReferenceElement.toVersionless()).getIdElement().getVersionIdPart(); - } + default String getCurrentVersionId(IIdType theReferenceElement) { + return read(theReferenceElement.toVersionless()).getIdElement().getVersionIdPart(); + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/export/job/ResourceToFileWriter.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/export/job/ResourceToFileWriter.java index df362a79f19..ac807768926 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/export/job/ResourceToFileWriter.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/export/job/ResourceToFileWriter.java @@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.bulk.export.job; */ import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; @@ -101,7 +102,7 @@ public class ResourceToFileWriter implements ItemWriter<List<IBaseResource>> { IBaseBinary binary = BinaryUtil.newBinary(myFhirContext); binary.setContentType(Constants.CT_FHIR_NDJSON); binary.setContent(myOutputStream.toByteArray()); - DaoMethodOutcome outcome = myBinaryDao.create(binary, new SystemRequestDetails()); + DaoMethodOutcome outcome = myBinaryDao.create(binary, new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.defaultPartition())); return outcome.getResource().getIdElement(); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 5d96742e857..7c53cb0a341 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -126,6 +126,7 @@ import org.springframework.transaction.support.TransactionSynchronizationAdapter import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionTemplate; +import javax.annotation.Nullable; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; import javax.persistence.NoResultException; @@ -569,7 +570,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B SearchParameterMap paramMap = resourceSearch.getSearchParameterMap(); paramMap.setLoadSynchronous(true); - Set<ResourcePersistentId> resourceIds = myMatchResourceUrlService.search(paramMap, myResourceType, theRequest); + Set<ResourcePersistentId> resourceIds = myMatchResourceUrlService.search(paramMap, myResourceType, theRequest, null); if (resourceIds.size() > 1) { if (!getConfig().isAllowMultipleDelete()) { @@ -1415,7 +1416,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B cacheControlDirective.parse(theRequest.getHeaders(Constants.HEADER_CACHE_CONTROL)); } - RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), theParams); + RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), theParams, null); IBundleProvider retVal = mySearchCoordinatorSvc.registerSearch(this, theParams, getResourceName(), cacheControlDirective, theRequest, requestPartitionId); if (retVal instanceof PersistedJpaBundleProvider) { @@ -1490,7 +1491,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B } @Override - public Set<ResourcePersistentId> searchForIds(SearchParameterMap theParams, RequestDetails theRequest) { + public Set<ResourcePersistentId> searchForIds(SearchParameterMap theParams, RequestDetails theRequest, @Nullable IBaseResource theConditionalOperationTargetOrNull) { TransactionDetails transactionDetails = new TransactionDetails(); return myTransactionService.execute(theRequest, transactionDetails, tx -> { @@ -1506,9 +1507,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B HashSet<ResourcePersistentId> retVal = new HashSet<>(); String uuid = UUID.randomUUID().toString(); - SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequest, uuid); + RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), theParams, theConditionalOperationTargetOrNull); - RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), theParams); + SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequest, uuid); try (IResultIterator iter = builder.createQuery(theParams, searchRuntimeDetails, theRequest, requestPartitionId)) { while (iter.hasNext()) { retVal.add(iter.next()); @@ -1616,7 +1617,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B IIdType resourceId; if (isNotBlank(theMatchUrl)) { - Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theMatchUrl, myResourceType, theTransactionDetails, theRequest); + Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theMatchUrl, myResourceType, theTransactionDetails, theRequest, theResource); if (match.size() > 1) { String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "UPDATE", theMatchUrl, match.size()); throw new PreconditionFailedException(msg); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDaoObservation.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDaoObservation.java index b7d19a2b9fa..ae26b45e1e4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDaoObservation.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDaoObservation.java @@ -91,7 +91,7 @@ public abstract class BaseHapiFhirResourceDaoObservation<T extends IBaseResource TreeMap<Long, IQueryParameterType> orderedSubjectReferenceMap = new TreeMap<>(); if(theSearchParameterMap.containsKey(getSubjectParamName())) { - RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap); + RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null); List<List<IQueryParameterType>> patientParams = new ArrayList<>(); if (theSearchParameterMap.get(getPatientParamName()) != null) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java index 57b2f37bed6..31375debf3f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoPatientDstu2.java @@ -79,7 +79,7 @@ public class FhirResourceDaoPatientDstu2 extends BaseHapiFhirResourceDao<Patient paramMap.setLoadSynchronous(true); } - RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), paramMap); + RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), paramMap, null); return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName(), new CacheControlDirective().parse(theRequest.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequest, requestPartitionId); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MatchResourceUrlService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MatchResourceUrlService.java index 44e223a27f2..83c544f0c1c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MatchResourceUrlService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MatchResourceUrlService.java @@ -68,6 +68,13 @@ public class MatchResourceUrlService { * Note that this will only return a maximum of 2 results!! */ public <R extends IBaseResource> Set<ResourcePersistentId> processMatchUrl(String theMatchUrl, Class<R> theResourceType, TransactionDetails theTransactionDetails, RequestDetails theRequest) { + return processMatchUrl(theMatchUrl, theResourceType, theTransactionDetails, theRequest, null); + } + + /** + * Note that this will only return a maximum of 2 results!! + */ + public <R extends IBaseResource> Set<ResourcePersistentId> processMatchUrl(String theMatchUrl, Class<R> theResourceType, TransactionDetails theTransactionDetails, RequestDetails theRequest, IBaseResource theConditionalOperationTargetOrNull) { String resourceType = myContext.getResourceType(theResourceType); String matchUrl = massageForStorage(resourceType, theMatchUrl); @@ -92,7 +99,7 @@ public class MatchResourceUrlService { } paramMap.setLoadSynchronousUpTo(2); - Set<ResourcePersistentId> retVal = search(paramMap, theResourceType, theRequest); + Set<ResourcePersistentId> retVal = search(paramMap, theResourceType, theRequest, theConditionalOperationTargetOrNull); if (myDaoConfig.isMatchUrlCacheEnabled() && retVal.size() == 1) { ResourcePersistentId pid = retVal.iterator().next(); @@ -124,14 +131,14 @@ public class MatchResourceUrlService { return existing; } - public <R extends IBaseResource> Set<ResourcePersistentId> search(SearchParameterMap theParamMap, Class<R> theResourceType, RequestDetails theRequest) { + public <R extends IBaseResource> Set<ResourcePersistentId> search(SearchParameterMap theParamMap, Class<R> theResourceType, RequestDetails theRequest, @Nullable IBaseResource theConditionalOperationTargetOrNull) { StopWatch sw = new StopWatch(); IFhirResourceDao<R> dao = myDaoRegistry.getResourceDao(theResourceType); if (dao == null) { throw new InternalErrorException("No DAO for resource type: " + theResourceType.getName()); } - Set<ResourcePersistentId> retVal = dao.searchForIds(theParamMap, theRequest); + Set<ResourcePersistentId> retVal = dao.searchForIds(theParamMap, theRequest, theConditionalOperationTargetOrNull); // Interceptor broadcast: JPA_PERFTRACE_INFO if (CompositeInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_INFO, myInterceptorBroadcaster, theRequest)) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoObservationDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoObservationDstu3.java index 55d518375c1..490f909293a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoObservationDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoObservationDstu3.java @@ -48,7 +48,7 @@ public class FhirResourceDaoObservationDstu3 extends BaseHapiFhirResourceDaoObse updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails); - RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap); + RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null); return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails, requestPartitionId); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java index 163d01363e6..bedc37a3998 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java @@ -74,7 +74,7 @@ public class FhirResourceDaoPatientDstu3 extends BaseHapiFhirResourceDao<Patient paramMap.setLoadSynchronous(true); } - RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), paramMap); + RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), paramMap, null); return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName(), new CacheControlDirective().parse(theRequest.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequest, requestPartitionId); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoObservationR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoObservationR4.java index 8838ec24393..cd447bbb050 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoObservationR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoObservationR4.java @@ -53,7 +53,7 @@ public class FhirResourceDaoObservationR4 extends BaseHapiFhirResourceDaoObserva public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) { updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails); - RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap); + RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null); return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails, requestPartitionId); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoPatientR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoPatientR4.java index 2ae8a673210..dea94ea7d5a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoPatientR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoPatientR4.java @@ -74,7 +74,7 @@ public class FhirResourceDaoPatientR4 extends BaseHapiFhirResourceDao<Patient>im paramMap.setLoadSynchronous(true); } - RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), paramMap); + RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), paramMap, null); return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName(), new CacheControlDirective().parse(theRequest.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequest, requestPartitionId); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoObservationR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoObservationR5.java index ae017465061..535697e9d87 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoObservationR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoObservationR5.java @@ -48,7 +48,7 @@ public class FhirResourceDaoObservationR5 extends BaseHapiFhirResourceDaoObserva updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails); - RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap); + RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null); return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails, requestPartitionId); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoPatientR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoPatientR5.java index c64e04b695f..af1d8f6d380 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoPatientR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoPatientR5.java @@ -74,7 +74,7 @@ public class FhirResourceDaoPatientR5 extends BaseHapiFhirResourceDao<Patient> i paramMap.setLoadSynchronous(true); } - RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), paramMap); + RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, getResourceName(), paramMap, null); return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName(), new CacheControlDirective().parse(theRequest.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequest, requestPartitionId); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteExpungeJobSubmitterImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteExpungeJobSubmitterImpl.java index 5779cc592f1..5a4fff96a7a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteExpungeJobSubmitterImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteExpungeJobSubmitterImpl.java @@ -92,7 +92,7 @@ public class DeleteExpungeJobSubmitterImpl implements IDeleteExpungeJobSubmitter List<RequestPartitionId> retval = new ArrayList<>(); for (String url : theUrlsToDeleteExpunge) { ResourceSearch resourceSearch = myMatchUrlService.getResourceSearch(url); - RequestPartitionId requestPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, resourceSearch.getResourceName(), null); + RequestPartitionId requestPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequest, resourceSearch.getResourceName(), null, null); retval.add(requestPartitionId); } return retval; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptor.java index 3706993ebb3..c301bd248a2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptor.java @@ -118,17 +118,6 @@ public class PatientIdPartitionInterceptor { return provideCompartmentMemberInstanceResponse(theRequestDetails, compartmentIdentity); } - @Nonnull - private List<RuntimeSearchParam> getCompartmentSearchParams(RuntimeResourceDefinition resourceDef) { - return resourceDef - .getSearchParams() - .stream() - .filter(param -> param.getParamType() == RestSearchParameterTypeEnum.REFERENCE) - .filter(param -> param.getProvidesMembershipInCompartments() != null && param.getProvidesMembershipInCompartments().contains("Patient")) - .collect(Collectors.toList()); - } - - @Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_READ) public RequestPartitionId identifyForRead(ReadPartitionIdRequestDetails theReadDetails, RequestDetails theRequestDetails) { RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theReadDetails.getResourceType()); @@ -169,7 +158,23 @@ public class PatientIdPartitionInterceptor { // nothing } - return provideUnsupportedQueryResponse(theReadDetails); + // If we couldn't identify a patient ID by the URL, let's try using the + // conditional target if we have one + if (theReadDetails.getConditionalTargetOrNull() != null) { + return identifyForCreate(theReadDetails.getConditionalTargetOrNull(), theRequestDetails); + } + + return provideNonPatientSpecificQueryResponse(theReadDetails); + } + + @Nonnull + private List<RuntimeSearchParam> getCompartmentSearchParams(RuntimeResourceDefinition resourceDef) { + return resourceDef + .getSearchParams() + .stream() + .filter(param -> param.getParamType() == RestSearchParameterTypeEnum.REFERENCE) + .filter(param -> param.getProvidesMembershipInCompartments() != null && param.getProvidesMembershipInCompartments().contains("Patient")) + .collect(Collectors.toList()); } private String getSingleResourceIdValueOrNull(SearchParameterMap theParams, String theParamName, String theResourceType) { @@ -206,8 +211,8 @@ public class PatientIdPartitionInterceptor { /** * Return a partition or throw an error for FHIR operations that can not be used with this interceptor */ - protected RequestPartitionId provideUnsupportedQueryResponse(ReadPartitionIdRequestDetails theRequestDetails) { - throw new MethodNotAllowedException("This server is not able to handle this request of type " + theRequestDetails.getRestOperationType()); + protected RequestPartitionId provideNonPatientSpecificQueryResponse(ReadPartitionIdRequestDetails theRequestDetails) { + return RequestPartitionId.allPartitions(); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/packages/PackageInstallerSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/packages/PackageInstallerSvcImpl.java index 5a463fca932..14a3aaaef13 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/packages/PackageInstallerSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/packages/PackageInstallerSvcImpl.java @@ -27,13 +27,13 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.ValidationSupportContext; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionDao; import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionEntity; -import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.partition.SystemRequestDetails; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistryController; @@ -63,6 +63,7 @@ import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.support.TransactionTemplate; import org.hl7.fhir.utilities.npm.NpmPackage; +import javax.annotation.Nonnull; import javax.annotation.PostConstruct; import java.io.IOException; import java.util.ArrayList; @@ -358,16 +359,23 @@ public class PackageInstallerSvcImpl implements IPackageInstallerSvc { private IBundleProvider searchResource(IFhirResourceDao theDao, SearchParameterMap theMap) { if (myPartitionSettings.isPartitioningEnabled()) { - SystemRequestDetails requestDetails = new SystemRequestDetails(); + SystemRequestDetails requestDetails = newSystemRequestDetails(); return theDao.search(theMap, requestDetails); } else { return theDao.search(theMap); } } + @Nonnull + private SystemRequestDetails newSystemRequestDetails() { + return + new SystemRequestDetails() + .setRequestPartitionId(RequestPartitionId.defaultPartition()); + } + private void createResource(IFhirResourceDao theDao, IBaseResource theResource) { if (myPartitionSettings.isPartitioningEnabled()) { - SystemRequestDetails requestDetails = new SystemRequestDetails(); + SystemRequestDetails requestDetails = newSystemRequestDetails(); theDao.create(theResource, requestDetails); } else { theDao.create(theResource); @@ -376,7 +384,7 @@ public class PackageInstallerSvcImpl implements IPackageInstallerSvc { private DaoMethodOutcome updateResource(IFhirResourceDao theDao, IBaseResource theResource) { if (myPartitionSettings.isPartitioningEnabled()) { - SystemRequestDetails requestDetails = new SystemRequestDetails(); + SystemRequestDetails requestDetails = newSystemRequestDetails(); return theDao.update(theResource, requestDetails); } else { return theDao.update(theResource); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/IRequestPartitionHelperSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/IRequestPartitionHelperSvc.java index c9e8af42b90..178fe4be70c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/IRequestPartitionHelperSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/IRequestPartitionHelperSvc.java @@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.partition; import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails; import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId; +import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.server.RequestDetails; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -44,8 +45,8 @@ public interface IRequestPartitionHelperSvc { } @Nonnull - default RequestPartitionId determineReadPartitionForRequestForSearchType(RequestDetails theRequest, String theResourceType, SearchParameterMap theParams) { - ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forSearchType(theResourceType, theParams); + default RequestPartitionId determineReadPartitionForRequestForSearchType(RequestDetails theRequest, String theResourceType, SearchParameterMap theParams, IBaseResource theConditionalOperationTargetOrNull) { + ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forSearchType(theResourceType, theParams, theConditionalOperationTargetOrNull); return determineReadPartitionForRequest(theRequest, theResourceType, details); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/RequestPartitionHelperSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/RequestPartitionHelperSvc.java index 543fe75733d..b63df986ba1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/RequestPartitionHelperSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/RequestPartitionHelperSvc.java @@ -109,14 +109,14 @@ public class RequestPartitionHelperSvc implements IRequestPartitionHelperSvc { if (myPartitionSettings.isPartitioningEnabled()) { // Handle system requests //TODO GGG eventually, theRequest will not be allowed to be null here, and we will pass through SystemRequestDetails instead. - if (theRequest == null && nonPartitionableResource) { + if ((theRequest == null || theRequest instanceof SystemRequestDetails) && nonPartitionableResource) { return RequestPartitionId.defaultPartition(); } - if (theRequest instanceof SystemRequestDetails) { + if (theRequest instanceof SystemRequestDetails && systemRequestHasExplicitPartition((SystemRequestDetails) theRequest)) { requestPartitionId = getSystemRequestPartitionId((SystemRequestDetails) theRequest, nonPartitionableResource); - // Interceptor call: STORAGE_PARTITION_IDENTIFY_READ } else if (hasHooks(Pointcut.STORAGE_PARTITION_IDENTIFY_READ, myInterceptorBroadcaster, theRequest)) { + // Interceptor call: STORAGE_PARTITION_IDENTIFY_READ HookParams params = new HookParams() .add(RequestDetails.class, theRequest) .addIfMatchesType(ServletRequestDetails.class, theRequest) @@ -186,15 +186,16 @@ public class RequestPartitionHelperSvc implements IRequestPartitionHelperSvc { boolean nonPartitionableResource = myNonPartitionableResourceNames.contains(theResourceType); //TODO GGG eventually, theRequest will not be allowed to be null here, and we will pass through SystemRequestDetails instead. - if (theRequest == null && nonPartitionableResource) { + if ((theRequest == null || theRequest instanceof SystemRequestDetails) && nonPartitionableResource) { return RequestPartitionId.defaultPartition(); } - if (theRequest instanceof SystemRequestDetails) { + if (theRequest instanceof SystemRequestDetails && systemRequestHasExplicitPartition((SystemRequestDetails) theRequest)) { requestPartitionId = getSystemRequestPartitionId((SystemRequestDetails) theRequest, nonPartitionableResource); } else { //This is an external Request (e.g. ServletRequestDetails) so we want to figure out the partition via interceptor. - HookParams params = new HookParams()// Interceptor call: STORAGE_PARTITION_IDENTIFY_CREATE + // Interceptor call: STORAGE_PARTITION_IDENTIFY_CREATE + HookParams params = new HookParams() .add(IBaseResource.class, theResource) .add(RequestDetails.class, theRequest) .addIfMatchesType(ServletRequestDetails.class, theRequest); @@ -215,6 +216,10 @@ public class RequestPartitionHelperSvc implements IRequestPartitionHelperSvc { return RequestPartitionId.allPartitions(); } + private boolean systemRequestHasExplicitPartition(@Nonnull SystemRequestDetails theRequest) { + return theRequest.getRequestPartitionId() != null || theRequest.getTenantId() != null; + } + @Nonnull @Override public PartitionablePartitionId toStoragePartition(@Nonnull RequestPartitionId theRequestPartitionId) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/SystemRequestDetails.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/SystemRequestDetails.java index fca52f022ea..caa82cbdd5f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/SystemRequestDetails.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/SystemRequestDetails.java @@ -72,8 +72,9 @@ public class SystemRequestDetails extends RequestDetails { return myRequestPartitionId; } - public void setRequestPartitionId(RequestPartitionId theRequestPartitionId) { + public SystemRequestDetails setRequestPartitionId(RequestPartitionId theRequestPartitionId) { myRequestPartitionId = theRequestPartitionId; + return this; } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java index 0d3da0d479c..dba0366e468 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java @@ -271,7 +271,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { String resourceType = search.getResourceType(); SearchParameterMap params = search.getSearchParameterMap().orElseThrow(() -> new IllegalStateException("No map in PASSCOMPLET search")); IFhirResourceDao<?> resourceDao = myDaoRegistry.getResourceDao(resourceType); - RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, resourceType, params); + RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, resourceType, params, null); SearchContinuationTask task = new SearchContinuationTask(search, resourceDao, params, resourceType, theRequestDetails, requestPartitionId); myIdToSearchTask.put(search.getUuid(), task); myExecutor.submit(task); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java index c116819da7e..ebff3054e7f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java @@ -814,7 +814,9 @@ public class SearchBuilder implements ISearchBuilder { if (findVersionFieldName != null) { sqlBuilder.append(", r." + findVersionFieldName); } - sqlBuilder.append(" FROM ResourceLink r WHERE r."); + sqlBuilder.append(" FROM ResourceLink r WHERE "); + + sqlBuilder.append("r."); sqlBuilder.append(searchPidFieldName); sqlBuilder.append(" IN (:target_pids)"); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImplR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImplR4Test.java index d265473a300..ab74d2c3c0d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImplR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImplR4Test.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.bulk; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.batch.BatchJobsConfig; @@ -944,7 +945,7 @@ public class BulkDataExportSvcImplR4Test extends BaseJpaR4Test { public String getBinaryContents(IBulkDataExportSvc.JobInfo theJobInfo, int theIndex) { // Iterate over the files - Binary nextBinary = myBinaryDao.read(theJobInfo.getFiles().get(theIndex).getResourceId(), new SystemRequestDetails()); + Binary nextBinary = myBinaryDao.read(theJobInfo.getFiles().get(theIndex).getResourceId(), new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.defaultPartition())); assertEquals(Constants.CT_FHIR_NDJSON, nextBinary.getContentType()); String nextContents = new String(nextBinary.getContent(), Constants.CHARSET_UTF8); ourLog.info("Next contents for type {}:\n{}", nextBinary.getResourceType(), nextContents); @@ -1238,12 +1239,12 @@ public class BulkDataExportSvcImplR4Test extends BaseJpaR4Test { createCareTeamWithIndex(i, patId); } - myPatientGroupId = myGroupDao.update(group, new SystemRequestDetails()).getId(); + myPatientGroupId = myGroupDao.update(group, new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.defaultPartition())).getId(); //Manually create another golden record Patient goldenPatient2 = new Patient(); goldenPatient2.setId("PAT888"); - DaoMethodOutcome g2Outcome = myPatientDao.update(goldenPatient2, new SystemRequestDetails()); + DaoMethodOutcome g2Outcome = myPatientDao.update(goldenPatient2, new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.defaultPartition())); Long goldenPid2 = myIdHelperService.getPidOrNull(g2Outcome.getResource()); //Create some nongroup patients MDM linked to a different golden resource. They shouldnt be included in the query. @@ -1272,14 +1273,14 @@ public class BulkDataExportSvcImplR4Test extends BaseJpaR4Test { patient.setGender(i % 2 == 0 ? Enumerations.AdministrativeGender.MALE : Enumerations.AdministrativeGender.FEMALE); patient.addName().setFamily("FAM" + i); patient.addIdentifier().setSystem("http://mrns").setValue("PAT" + i); - return myPatientDao.update(patient, new SystemRequestDetails()); + return myPatientDao.update(patient, new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.defaultPartition())); } private void createCareTeamWithIndex(int i, IIdType patId) { CareTeam careTeam = new CareTeam(); careTeam.setId("CT" + i); careTeam.setSubject(new Reference(patId)); // This maps to the "patient" search parameter on CareTeam - myCareTeamDao.update(careTeam, new SystemRequestDetails()); + myCareTeamDao.update(careTeam, new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.defaultPartition())); } private void createImmunizationWithIndex(int i, IIdType patId) { @@ -1297,7 +1298,7 @@ public class BulkDataExportSvcImplR4Test extends BaseJpaR4Test { cc.addCoding().setSystem("vaccines").setCode("COVID-19"); immunization.setVaccineCode(cc); } - myImmunizationDao.update(immunization, new SystemRequestDetails()); + myImmunizationDao.update(immunization, new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.defaultPartition())); } private void createObservationWithIndex(int i, IIdType patId) { @@ -1308,7 +1309,7 @@ public class BulkDataExportSvcImplR4Test extends BaseJpaR4Test { if (patId != null) { obs.getSubject().setReference(patId.getValue()); } - myObservationDao.update(obs, new SystemRequestDetails()); + myObservationDao.update(obs, new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.defaultPartition())); } public void linkToGoldenResource(Long theGoldenPid, Long theSourcePid) { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java index cf5550fa954..716db77adef 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java @@ -506,6 +506,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil myPagingProvider.setDefaultPageSize(BasePagingProvider.DEFAULT_DEFAULT_PAGE_SIZE); myPagingProvider.setMaximumPageSize(BasePagingProvider.DEFAULT_MAX_PAGE_SIZE); + + myPartitionSettings.setPartitioningEnabled(false); } @AfterEach diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java index b70a8378c3f..4cd403e2b2b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java @@ -49,6 +49,7 @@ import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.matchesPattern; import static org.hamcrest.Matchers.not; @@ -566,12 +567,10 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { /* * 20 should be prefetched since that's the initial page size */ - await().until(() -> { - return runInTransaction(() -> { - Search search = mySearchEntityDao.findByUuidAndFetchIncludes(uuid).orElseThrow(() -> new InternalErrorException("")); - return search.getNumFound() == 20; - }); - }); + await().until(() -> runInTransaction(() -> { + Search search = mySearchEntityDao.findByUuidAndFetchIncludes(uuid).orElseThrow(() -> new InternalErrorException("")); + return search.getNumFound(); + }), equalTo(20)); runInTransaction(() -> { Search search = mySearchEntityDao.findByUuidAndFetchIncludes(uuid).orElseThrow(() -> new InternalErrorException("")); assertEquals(20, search.getNumFound()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptorTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptorTest.java index 99047a7d948..faded1b6479 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptorTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptorTest.java @@ -4,15 +4,24 @@ import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4SystemTest; import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.partition.SystemRequestDetails; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; +import ca.uhn.fhir.jpa.util.MultimapCollector; +import ca.uhn.fhir.jpa.util.SqlQuery; +import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.rest.api.server.IBundleProvider; 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 com.google.common.collect.ListMultimap; +import com.google.common.collect.Multimap; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Encounter; import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.ExplanationOfBenefit; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Observation; import org.hl7.fhir.r4.model.Organization; @@ -22,8 +31,18 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; 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.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -225,11 +244,9 @@ public class PatientIdPartitionInterceptorTest extends BaseJpaR4SystemTest { createObservationB(); myCaptureQueriesListener.clear(); - try { - myObservationDao.search(SearchParameterMap.newSynchronous(), mySrd); - } catch (MethodNotAllowedException e) { - assertEquals("This server is not able to handle this request of type SEARCH_TYPE", e.getMessage()); - } + myObservationDao.search(SearchParameterMap.newSynchronous(), mySrd); + myCaptureQueriesListener.logSelectQueries(); + assertEquals("SELECT t0.RES_ID FROM HFJ_RESOURCE t0 WHERE ((t0.RES_TYPE = 'Observation') AND (t0.RES_DELETED_AT IS NULL)) limit '10'", myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false)); } @Test @@ -314,6 +331,139 @@ public class PatientIdPartitionInterceptorTest extends BaseJpaR4SystemTest { assertThat(myCaptureQueriesListener.getSelectQueries().get(1).getSql(false, false), containsString("PARTITION_ID=")); } + + @Test + public void testTransaction_NoRequestDetails() throws IOException { + Bundle input = loadResourceFromClasspath(Bundle.class, "/r4/load_bundle.json"); + + // Maybe in the future we'll make request details mandatory and if that + // causes this to fail that's ok + Bundle outcome = mySystemDao.transaction(null, input); + + ListMultimap<String, String> resourceIds = outcome + .getEntry() + .stream() + .collect(MultimapCollector.toMultimap(t -> new IdType(t.getResponse().getLocation()).toUnqualifiedVersionless().getResourceType(), t -> new IdType(t.getResponse().getLocation()).toUnqualifiedVersionless().getValue())); + + Multimap<String, Integer> resourcesByType = runInTransaction(() -> { + logAllResources(); + return myResourceTableDao.findAll().stream().collect(MultimapCollector.toMultimap(t->t.getResourceType(), t->t.getPartitionId().getPartitionId())); + }); + + assertThat(resourcesByType.get("Patient"), contains(4267)); + assertThat(resourcesByType.get("ExplanationOfBenefit"), contains(4267)); + assertThat(resourcesByType.get("Coverage"), contains(4267)); + assertThat(resourcesByType.get("Organization"), contains(-1, -1)); + assertThat(resourcesByType.get("Practitioner"), contains(-1, -1, -1)); + } + + @Test + public void testTransaction_SystemRequestDetails() throws IOException { + Bundle input = loadResourceFromClasspath(Bundle.class, "/r4/load_bundle.json"); + myCaptureQueriesListener.clear(); + Bundle outcome = mySystemDao.transaction(new SystemRequestDetails(), input); + myCaptureQueriesListener.logSelectQueries(); + List<String> selectQueryStrings = myCaptureQueriesListener + .getSelectQueries() + .stream() + .map(t -> t.getSql(false, false).toUpperCase(Locale.US)) + .filter(t -> !t.contains("FROM HFJ_TAG_DEF")) + .collect(Collectors.toList()); + for (String next : selectQueryStrings) { + assertThat(next, either(containsString("PARTITION_ID =")).or(containsString("PARTITION_ID IN"))); + } + + ListMultimap<String, String> resourceIds = outcome + .getEntry() + .stream() + .collect(MultimapCollector.toMultimap(t -> new IdType(t.getResponse().getLocation()).toUnqualifiedVersionless().getResourceType(), t -> new IdType(t.getResponse().getLocation()).toUnqualifiedVersionless().getValue())); + + String patientId = resourceIds.get("Patient").get(0); + + Multimap<String, Integer> resourcesByType = runInTransaction(() -> { + logAllResources(); + return myResourceTableDao.findAll().stream().collect(MultimapCollector.toMultimap(t->t.getResourceType(), t->t.getPartitionId().getPartitionId())); + }); + + assertThat(resourcesByType.get("Patient"), contains(4267)); + assertThat(resourcesByType.get("ExplanationOfBenefit"), contains(4267)); + assertThat(resourcesByType.get("Coverage"), contains(4267)); + assertThat(resourcesByType.get("Organization"), contains(-1, -1)); + assertThat(resourcesByType.get("Practitioner"), contains(-1, -1, -1)); + + // Try Searching + SearchParameterMap map = new SearchParameterMap(); + map.add(ExplanationOfBenefit.SP_PATIENT, new ReferenceParam(patientId)); + map.addInclude(new Include("*")); + myCaptureQueriesListener.clear(); + IBundleProvider result = myExplanationOfBenefitDao.search(map); + List<String> resultIds = toUnqualifiedVersionlessIdValues(result); + assertThat(resultIds.toString(), resultIds, containsInAnyOrder( + resourceIds.get("Coverage").get(0), + resourceIds.get("Organization").get(0), + resourceIds.get("ExplanationOfBenefit").get(0), + resourceIds.get("Patient").get(0), + resourceIds.get("Practitioner").get(0), + resourceIds.get("Practitioner").get(1), + resourceIds.get("Practitioner").get(2) + )); + + myCaptureQueriesListener.logSelectQueries(); + + List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueries(); + assertThat(selectQueries.get(0).getSql(true, false).toUpperCase(Locale.US), matchesPattern("SELECT.*FROM HFJ_RES_LINK.*WHERE.*PARTITION_ID = '4267'.*")); + + } + + + @Test + public void testSearch() throws IOException { + Bundle input = loadResourceFromClasspath(Bundle.class, "/r4/load_bundle.json"); + Bundle outcome = mySystemDao.transaction(new SystemRequestDetails(), input); + + ListMultimap<String, String> resourceIds = outcome + .getEntry() + .stream() + .collect(MultimapCollector.toMultimap(t -> new IdType(t.getResponse().getLocation()).toUnqualifiedVersionless().getResourceType(), t -> new IdType(t.getResponse().getLocation()).toUnqualifiedVersionless().getValue())); + + String patientId = resourceIds.get("Patient").get(0); + + Multimap<String, Integer> resourcesByType = runInTransaction(() -> { + logAllResources(); + return myResourceTableDao.findAll().stream().collect(MultimapCollector.toMultimap(t->t.getResourceType(), t->t.getPartitionId().getPartitionId())); + }); + + assertThat(resourcesByType.get("Patient"), contains(4267)); + assertThat(resourcesByType.get("ExplanationOfBenefit"), contains(4267)); + assertThat(resourcesByType.get("Coverage"), contains(4267)); + assertThat(resourcesByType.get("Organization"), contains(-1, -1)); + assertThat(resourcesByType.get("Practitioner"), contains(-1, -1, -1)); + + // Try Searching + SearchParameterMap map = new SearchParameterMap(); + map.add(ExplanationOfBenefit.SP_PATIENT, new ReferenceParam(patientId)); + map.addInclude(new Include("*")); + myCaptureQueriesListener.clear(); + IBundleProvider result = myExplanationOfBenefitDao.search(map); + List<String> resultIds = toUnqualifiedVersionlessIdValues(result); + assertThat(resultIds.toString(), resultIds, containsInAnyOrder( + resourceIds.get("Coverage").get(0), + resourceIds.get("Organization").get(0), + resourceIds.get("ExplanationOfBenefit").get(0), + resourceIds.get("Patient").get(0), + resourceIds.get("Practitioner").get(0), + resourceIds.get("Practitioner").get(1), + resourceIds.get("Practitioner").get(2) + )); + + myCaptureQueriesListener.logSelectQueries(); + + List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueries(); + assertThat(selectQueries.get(0).getSql(true, false).toUpperCase(Locale.US), matchesPattern("SELECT.*FROM HFJ_RES_LINK.*WHERE.*PARTITION_ID = '4267'.*")); + + } + + @Test public void testHistory_Type() { myOrganizationDao.history(null, null, null, mySrd); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/packages/JpaPackageCacheTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/packages/JpaPackageCacheTest.java index 29be4e2aebc..1e69896d972 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/packages/JpaPackageCacheTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/packages/JpaPackageCacheTest.java @@ -6,6 +6,7 @@ import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionDao; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; 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.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor; @@ -40,6 +41,8 @@ public class JpaPackageCacheTest extends BaseJpaR4Test { private IInterceptorService myInterceptorService; @Autowired private RequestTenantPartitionInterceptor myRequestTenantPartitionInterceptor; + @Autowired + private ISearchParamExtractor mySearchParamExtractor; @AfterEach public void disablePartitioning() { @@ -75,7 +78,7 @@ public class JpaPackageCacheTest extends BaseJpaR4Test { public void testSaveAndDeletePackagePartitionsEnabled() throws IOException { myPartitionSettings.setPartitioningEnabled(true); myPartitionSettings.setDefaultPartitionId(1); - myInterceptorService.registerInterceptor(new PatientIdPartitionInterceptor()); + myInterceptorService.registerInterceptor(new PatientIdPartitionInterceptor(myFhirCtx, mySearchParamExtractor)); myInterceptorService.registerInterceptor(myRequestTenantPartitionInterceptor); try (InputStream stream = ClasspathUtil.loadResourceAsStream("/packages/basisprofil.de.tar.gz")) { @@ -109,7 +112,7 @@ public class JpaPackageCacheTest extends BaseJpaR4Test { myPartitionSettings.setPartitioningEnabled(true); myPartitionSettings.setDefaultPartitionId(0); myPartitionSettings.setUnnamedPartitionMode(true); - myInterceptorService.registerInterceptor(new PatientIdPartitionInterceptor()); + myInterceptorService.registerInterceptor(new PatientIdPartitionInterceptor(myFhirCtx, mySearchParamExtractor)); myInterceptorService.registerInterceptor(myRequestTenantPartitionInterceptor); try (InputStream stream = ClasspathUtil.loadResourceAsStream("/packages/hl7.fhir.uv.shorthand-0.12.0.tgz")) { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/packages/NpmR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/packages/NpmR4Test.java index 84ef640e24c..13cfb4cd620 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/packages/NpmR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/packages/NpmR4Test.java @@ -423,9 +423,9 @@ public class NpmR4Test extends BaseJpaR4Test { myDaoConfig.setAllowExternalReferences(true); byte[] bytes = loadClasspathBytes("/packages/test-draft-sample.tgz"); - myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.11.1", bytes); + myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.onlydrafts/0.11.1", bytes); - PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL); + PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.onlydrafts").setVersion("0.11.1").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL); PackageInstallOutcomeJson outcome = myPackageInstallerSvc.install(spec); assertEquals(0, outcome.getResourcesInstalled().size(), outcome.getResourcesInstalled().toString()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/MultimapCollector.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/MultimapCollector.java new file mode 100644 index 00000000000..66707836bde --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/util/MultimapCollector.java @@ -0,0 +1,64 @@ +package ca.uhn.fhir.jpa.util; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Multimap; + +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; + +/** + * Copied from https://stackoverflow.com/questions/23003542/cleanest-way-to-create-a-guava-multimap-from-a-java-8-stream + */ +public class MultimapCollector<T, K, V> implements + Collector<T, ListMultimap<K, V>, ListMultimap<K, V>> { + + private final Function<T, K> keyGetter; + private final Function<T, V> valueGetter; + + public MultimapCollector(Function<T, K> keyGetter, Function<T, V> valueGetter) { + this.keyGetter = keyGetter; + this.valueGetter = valueGetter; + } + + public static <T, K, V> MultimapCollector<T, K, V> toMultimap(Function<T, K> keyGetter, Function<T, V> valueGetter) { + return new MultimapCollector<>(keyGetter, valueGetter); + } + + public static <T, K, V> MultimapCollector<T, K, T> toMultimap(Function<T, K> keyGetter) { + return new MultimapCollector<>(keyGetter, v -> v); + } + + @Override + public Supplier<ListMultimap<K, V>> supplier() { + return ArrayListMultimap::create; + } + + @Override + public BiConsumer<ListMultimap<K, V>, T> accumulator() { + return (map, element) -> map.put(keyGetter.apply(element), valueGetter.apply(element)); + } + + @Override + public BinaryOperator<ListMultimap<K, V>> combiner() { + return (map1, map2) -> { + map1.putAll(map2); + return map1; + }; + } + + @Override + public Function<ListMultimap<K, V>, ListMultimap<K, V>> finisher() { + return map -> map; + } + + @Override + public Set<Characteristics> characteristics() { + return ImmutableSet.of(Characteristics.IDENTITY_FINISH); + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/resources/packages/test-draft-sample.tgz b/hapi-fhir-jpaserver-base/src/test/resources/packages/test-draft-sample.tgz index 56788688ce53ddaf084597967092d15112f2b26b..8509edd4320fa9296f7d5fa44dff6a02c3f2c940 100644 GIT binary patch delta 3052 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 delta 3046 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 diff --git a/hapi-fhir-jpaserver-base/src/test/resources/r4/load_bundle.json b/hapi-fhir-jpaserver-base/src/test/resources/r4/load_bundle.json new file mode 100644 index 00000000000..101b34b853c --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/r4/load_bundle.json @@ -0,0 +1,986 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "resource": { + "resourceType": "ExplanationOfBenefit", + "meta": { + "lastUpdated": "2021-06-30", + "profile": [ + "http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-ExplanationOfBenefit-Professional-NonClinician" + ] + }, + "identifier": [ + { + "type": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType", + "code": "payerid" + } + ] + }, + "system": "https://hl7.org/fhir/sid/payerid", + "value": "5824473976" + }, + { + "type": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType", + "code": "uc" + } + ] + }, + "system": "https://hl7.org/fhir/sid/claimid", + "value": "1234094" + } + ], + "status": "active", + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/claim-type", + "code": "professional" + } + ] + }, + "use": "claim", + "patient": { + "reference": "Patient/d1a47e2c-509b-e326-deab-597e3f598ca5" + }, + "billablePeriod": { + "start": "2017-01-08", + "end": "2017-01-08" + }, + "created": "2017-01-11T00:00:00-08:00", + "insurer": { + "reference": "Organization/5954a17b-0779-334c-4f1c-e894e45d15fb" + }, + "provider": { + "reference": "Organization/68ae4f74-afdc-6242-c50e-02ef776d8e5d" + }, + "payee": { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/payeetype", + "code": "provider" + } + ], + "text": "Claim paid to VENDOR" + }, + "party": { + "reference": "Organization/68ae4f74-afdc-6242-c50e-02ef776d8e5d" + } + }, + "outcome": "complete", + "disposition": "PAID", + "careTeam": [ + { + "sequence": 1, + "provider": { + "reference": "Practitioner/23eccc61-ab67-bf8a-e464-6260f7989556" + }, + "responsible": false, + "role": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/claimcareteamrole", + "code": "primary" + } + ] + } + }, + { + "sequence": 2, + "provider": { + "reference": "Practitioner/dbbc9a06-f685-b481-d739-133755af138e" + }, + "responsible": false, + "role": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBClaimCareTeamRole", + "code": "referring" + } + ] + } + }, + { + "sequence": 3, + "provider": { + "reference": "Practitioner/39b9250c-0d01-cbb0-ea89-0de9c74af511" + }, + "responsible": true, + "role": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBClaimCareTeamRole", + "code": "performing" + } + ] + } + } + ], + "supportingInfo": [ + { + "sequence": 1, + "category": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType", + "code": "clmrecvddate" + } + ] + }, + "timingDate": "2017-01-11" + }, + { + "sequence": 2, + "category": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType", + "code": "billingnetworkcontractingstatus" + } + ] + }, + "code": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBPayerAdjudicationStatus", + "code": "other" + } + ] + } + } + ], + "diagnosis": [ + { + "sequence": 4, + "diagnosisCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/sid/icd-10-cm", + "code": "I27.2", + "display": "Other secondary pulmonary hypertension" + } + ], + "text": "Other secondary pulmonary hypertension" + } + } + ], + "procedure": [ + { + "sequence": 1, + "date": "2017-01-08T00:00:00-08:00", + "procedureCodeableConcept": { + "coding": [ + { + "system": "http://www.ama-assn.org/go/cpt", + "code": "99233", + "display": "Subsequent hospital care for severe problem" + } + ], + "text": "SBSQ HOSPITAL CARE/DAY 35 MINUTES" + } + } + ], + "insurance": [ + { + "focal": true, + "coverage": { + "reference": "urn:uuid:175dbf4a-7ee2-446d-9938-82eea27871a7" + } + } + ], + "item": [ + { + "sequence": 1, + "diagnosisSequence": [ + 4 + ], + "procedureSequence": [ + 1 + ], + "productOrService": { + "coding": [ + { + "system": "http://www.ama-assn.org/go/cpt", + "code": "99233", + "display": "Subsequent hospital care for severe problem" + } + ], + "text": "SBSQ HOSPITAL CARE/DAY 35 MINUTES" + }, + "servicedPeriod": { + "start": "2017-01-08", + "end": "2017-01-08" + }, + "locationCodeableConcept": { + "coding": [ + { + "system": "https://www.cms.gov/Medicare/Coding/place-of-service-codes/Place_of_Service_Code_Set", + "code": "99" + } + ] + }, + "quantity": { + "value": 1, + "unit": "Units", + "system": "http://unitsofmeasure.org", + "code": "[arb'U]" + }, + "net": { + "value": 317.00, + "currency": "USD" + }, + "adjudication": [ + { + "category": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/adjudication", + "code": "submitted" + } + ] + }, + "amount": { + "value": 317.00, + "currency": "USD" + } + }, + { + "category": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/adjudication", + "code": "benefit" + } + ] + }, + "amount": { + "value": 124.69, + "currency": "USD" + } + }, + { + "category": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/adjudication", + "code": "copay" + } + ] + }, + "amount": { + "value": 0.00, + "currency": "USD" + } + }, + { + "category": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/adjudication", + "code": "deductible" + } + ] + }, + "amount": { + "value": 124.69, + "currency": "USD" + } + }, + { + "category": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication", + "code": "coinsurance" + } + ] + }, + "amount": { + "value": 0.00, + "currency": "USD" + } + }, + { + "category": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication", + "code": "memberliability" + } + ] + }, + "amount": { + "value": 124.69, + "currency": "USD" + } + }, + { + "category": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication", + "code": "noncovered" + } + ] + }, + "amount": { + "value": 0.00, + "currency": "USD" + } + }, + { + "category": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication", + "code": "priorpayerpaid" + } + ] + }, + "amount": { + "value": 0.00, + "currency": "USD" + } + }, + { + "category": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication", + "code": "paidtoprovider" + } + ] + }, + "amount": { + "value": 0.00, + "currency": "USD" + } + }, + { + "category": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBPayerAdjudicationStatus", + "code": "outofnetwork" + } + ] + }, + "amount": { + "value": 0.00, + "currency": "USD" + } + } + ] + } + ], + "total": [ + { + "category": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/adjudication", + "code": "submitted" + } + ] + }, + "amount": { + "value": 317.00, + "currency": "USD" + } + }, + { + "category": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/adjudication", + "code": "benefit" + } + ] + }, + "amount": { + "value": 124.69, + "currency": "USD" + } + }, + { + "category": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/adjudication", + "code": "copay" + } + ] + }, + "amount": { + "value": 0.00, + "currency": "USD" + } + }, + { + "category": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/adjudication", + "code": "deductible" + } + ] + }, + "amount": { + "value": 124.69, + "currency": "USD" + } + }, + { + "category": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication", + "code": "coinsurance" + } + ] + }, + "amount": { + "value": 0.00, + "currency": "USD" + } + }, + { + "category": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication", + "code": "memberliability" + } + ] + }, + "amount": { + "value": 124.69, + "currency": "USD" + } + }, + { + "category": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication", + "code": "noncovered" + } + ] + }, + "amount": { + "value": 0.00, + "currency": "USD" + } + }, + { + "category": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication", + "code": "priorpayerpaid" + } + ] + }, + "amount": { + "value": 0.00, + "currency": "USD" + } + }, + { + "category": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication", + "code": "paidtoprovider" + } + ] + }, + "amount": { + "value": 0.00, + "currency": "USD" + } + } + ], + "payment": { + "date": "2017-02-02", + "amount": { + "value": 0.00, + "currency": "USD" + } + } + }, + "request": { + "method": "PUT", + "url": "ExplanationOfBenefit?identifier=5824473976" + } + }, + { + "resource": { + "resourceType": "Patient", + "id": "d1a47e2c-509b-e326-deab-597e3f598ca5", + "meta": { + "lastUpdated": "2021-06-30", + "profile": [ + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient" + ] + }, + "identifier": [ + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "MR" + } + ] + }, + "system": "https://example.org/front-door", + "value": "412563524-CO" + } + ], + "name": [ + { + "use": "usual", + "text": "HYOHWAN MGUIRRE", + "family": "MGUIRRE", + "given": [ + "HYOHWAN" + ] + } + ], + "telecom": [ + { + "system": "phone", + "value": "719-654-0220", + "use": "home" + } + ], + "gender": "unknown", + "birthDate": "1958-05-12", + "address": [ + { + "use": "home", + "type": "postal", + "line": [ + "20360 East 45Th Court", + "PO Box 523" + ], + "city": "COLORADO SPRINGS", + "postalCode": "80922-4166" + } + ] + }, + "request": { + "method": "PUT", + "url": "Patient/d1a47e2c-509b-e326-deab-597e3f598ca5" + } + }, + { + "fullUrl": "urn:uuid:175dbf4a-7ee2-446d-9938-82eea27871a7", + "resource": { + "resourceType": "Coverage", + "meta": { + "lastUpdated": "2021-06-30", + "profile": [ + "http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Coverage" + ] + }, + "identifier": [ + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "FILL" + } + ] + }, + "system": "https://hl7.org/fhir/sid/coverageid", + "value": "412563524-CO-80001" + } + ], + "status": "active", + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", + "code": "HMO", + "display": "health maintenance organization policy" + } + ], + "text": "HMO - HMO COMMERCIAL-HDHP-Signature" + }, + "subscriberId": "412563524", + "beneficiary": { + "reference": "Patient/d1a47e2c-509b-e326-deab-597e3f598ca5" + }, + "relationship": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/subscriber-relationship", + "code": "self", + "display": "Self" + } + ], + "text": "The Beneficiary is the Subscriber" + }, + "period": { + "start": "2016-01-01", + "end": "2017-07-01" + }, + "payor": [ + { + "reference": "Organization/5954a17b-0779-334c-4f1c-e894e45d15fb" + } + ], + "class": [ + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/coverage-class", + "code": "group", + "display": "Group" + } + ], + "text": "An employee group" + }, + "value": "80001", + "name": "CS BRZ HDHP 5500/30%/0 ONX S-NON-MEDICARE" + } + ] + }, + "request": { + "method": "PUT", + "url": "Coverage?identifier=412563524-CO-80001" + } + }, + { + "resource": { + "resourceType": "Organization", + "id": "68ae4f74-afdc-6242-c50e-02ef776d8e5d", + "meta": { + "lastUpdated": "2021-06-30", + "profile": [ + "http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Organization" + ] + }, + "identifier": [ + { + "type": { + "coding": [ + { + "system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType", + "code": "npi" + } + ] + }, + "system": "http://hl7.org/fhir/sid/us-npi", + "value": "1407833767" + }, + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "TAX" + } + ] + }, + "system": "urn:oid:2.16.840.1.113883.4.4" + } + ], + "active": true, + "type": [ + { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/organization-type", + "code": "prov" + } + ] + } + ], + "name": "PIKES PEAK NEPHROLOGY ASSOCIATES PC", + "address": [ + { + "use": "work", + "type": "physical", + "line": [ + "1914 LELARAY STREET" + ], + "city": "COLORADO SPRINGS", + "postalCode": "80909", + "country": "USA" + } + ] + }, + "request": { + "method": "PUT", + "url": "Organization/68ae4f74-afdc-6242-c50e-02ef776d8e5d" + } + }, + { + "resource": { + "resourceType": "Organization", + "id": "5954a17b-0779-334c-4f1c-e894e45d15fb", + "meta": { + "lastUpdated": "2021-06-30", + "profile": [ + "http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Organization" + ] + }, + "identifier": [ + { + "use": "usual", + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "FILL" + } + ] + }, + "system": "https://hl7.org/fhir/sid/organizationid", + "value": "NATLTAP CO-KFHP-PAY-CO" + } + ], + "active": true, + "type": [ + { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/organization-type", + "code": "pay", + "display": "Payer" + } + ] + } + ], + "name": "KAISER FOUNDATION HEALTHPLAN, INC", + "telecom": [ + { + "system": "phone", + "value": "1-800-382-4661", + "use": "work" + } + ], + "address": [ + { + "use": "work", + "type": "postal", + "line": [ + "NATIONAL CLAIMS ADMINISTRATION COLORADO", + "PO Box 629028" + ], + "city": "El Dorado Hills", + "state": "CA", + "postalCode": "95762-9028" + } + ] + }, + "request": { + "method": "PUT", + "url": "Organization/5954a17b-0779-334c-4f1c-e894e45d15fb" + } + }, + { + "resource": { + "resourceType": "Practitioner", + "id": "23eccc61-ab67-bf8a-e464-6260f7989556", + "meta": { + "lastUpdated": "2021-06-30", + "profile": [ + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner" + ] + }, + "identifier": [ + { + "use": "usual", + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "NPI" + } + ] + }, + "system": "http://hl7.org/fhir/sid/us-npi", + "value": "1497983654" + } + ], + "name": [ + { + "use": "usual", + "text": "CASSIDY, HEATHER M (MD)", + "family": "CASSIDY", + "given": [ + "HEATHER" + ], + "suffix": [ + "MD" + ] + } + ], + "address": [ + { + "use": "work", + "line": [ + "Briargate", + "1405 Briargate Pkwy #141" + ], + "city": "Colorado Springs", + "postalCode": "80920" + } + ] + }, + "request": { + "method": "PUT", + "url": "Practitioner/23eccc61-ab67-bf8a-e464-6260f7989556" + } + }, + { + "resource": { + "resourceType": "Practitioner", + "id": "dbbc9a06-f685-b481-d739-133755af138e", + "meta": { + "lastUpdated": "2021-06-30", + "profile": [ + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner" + ] + }, + "identifier": [ + { + "use": "usual", + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "NPI" + } + ] + }, + "system": "http://hl7.org/fhir/sid/us-npi", + "value": "1568467280" + }, + { + "use": "usual", + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "TAX" + } + ] + }, + "system": "urn:oid:2.16.840.1.113883.4.4", + "value": "311669909" + } + ], + "name": [ + { + "use": "usual", + "text": "MOHNSSEN, STEVEN R (MD)", + "family": "MOHNSSEN", + "given": [ + "STEVEN" + ], + "suffix": [ + "MD" + ] + } + ], + "address": [ + { + "use": "work", + "line": [ + "1725 E Boulder St", + "Ste 204" + ], + "city": "Colorado Springs", + "postalCode": "80909" + } + ] + }, + "request": { + "method": "PUT", + "url": "Practitioner/dbbc9a06-f685-b481-d739-133755af138e" + } + }, + { + "resource": { + "resourceType": "Practitioner", + "id": "39b9250c-0d01-cbb0-ea89-0de9c74af511", + "meta": { + "lastUpdated": "2021-06-30", + "profile": [ + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner" + ] + }, + "identifier": [ + { + "use": "usual", + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "NPI" + } + ] + }, + "system": "http://hl7.org/fhir/sid/us-npi", + "value": "1679605265" + }, + { + "use": "usual", + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "TAX" + } + ] + }, + "system": "urn:oid:2.16.840.1.113883.4.4", + "value": "840629252" + } + ], + "name": [ + { + "use": "usual", + "text": "ROSS, MICHAEL D (MD)", + "family": "ROSS", + "given": [ + "MICHAEL" + ], + "suffix": [ + "MD" + ] + } + ], + "address": [ + { + "use": "work", + "line": [ + "1914 Lelaray St" + ], + "city": "Colorado Springs", + "postalCode": "80909" + } + ] + }, + "request": { + "method": "PUT", + "url": "Practitioner/39b9250c-0d01-cbb0-ea89-0de9c74af511" + } + } + ] +} diff --git a/hapi-fhir-jpaserver-base/src/test/resources/r4/transaction-perf-bundle-smallchanges.json b/hapi-fhir-jpaserver-base/src/test/resources/r4/transaction-perf-bundle-smallchanges.json index 8cc029b5a47..3a0fcabe6fd 100644 --- a/hapi-fhir-jpaserver-base/src/test/resources/r4/transaction-perf-bundle-smallchanges.json +++ b/hapi-fhir-jpaserver-base/src/test/resources/r4/transaction-perf-bundle-smallchanges.json @@ -550,7 +550,7 @@ } ] }, - "system": "https://healthy.kaiserpermanente.org/front-door", + "system": "https://example.org/front-door", "value": "1000116-GA" } ], diff --git a/hapi-fhir-jpaserver-base/src/test/resources/r4/transaction-perf-bundle.json b/hapi-fhir-jpaserver-base/src/test/resources/r4/transaction-perf-bundle.json index 8f3a8929fa1..8bbc1f4cc01 100644 --- a/hapi-fhir-jpaserver-base/src/test/resources/r4/transaction-perf-bundle.json +++ b/hapi-fhir-jpaserver-base/src/test/resources/r4/transaction-perf-bundle.json @@ -550,7 +550,7 @@ } ] }, - "system": "https://healthy.kaiserpermanente.org/front-door", + "system": "https://example.org/front-door", "value": "1000116-GA" } ], diff --git a/hapi-fhir-jpaserver-cql/src/test/java/ca/uhn/fhir/cql/common/helper/PartitionHelper.java b/hapi-fhir-jpaserver-cql/src/test/java/ca/uhn/fhir/cql/common/helper/PartitionHelper.java index 7049a45b79d..e6ad651f2e1 100644 --- a/hapi-fhir-jpaserver-cql/src/test/java/ca/uhn/fhir/cql/common/helper/PartitionHelper.java +++ b/hapi-fhir-jpaserver-cql/src/test/java/ca/uhn/fhir/cql/common/helper/PartitionHelper.java @@ -5,7 +5,7 @@ import ca.uhn.fhir.interceptor.api.IInterceptorService; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.model.config.PartitionSettings; -import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.rest.api.server.RequestDetails; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; @@ -49,7 +49,7 @@ public class PartitionHelper implements BeforeEachCallback, AfterEachCallback { private boolean myCalled = false; @Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_READ) - RequestPartitionId partitionIdentifyRead(ServletRequestDetails theRequestDetails) { + RequestPartitionId partitionIdentifyRead(RequestDetails theRequestDetails) { myCalled = true; if (theRequestDetails == null) { ourLog.info("useful breakpoint :-)"); @@ -67,7 +67,7 @@ public class PartitionHelper implements BeforeEachCallback, AfterEachCallback { } @Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE) - RequestPartitionId partitionIdentifyCreate(ServletRequestDetails theRequestDetails) { + RequestPartitionId partitionIdentifyCreate(RequestDetails theRequestDetails) { return RequestPartitionId.defaultPartition(); } } diff --git a/hapi-fhir-jpaserver-cql/src/test/java/ca/uhn/fhir/cql/dstu3/CqlMeasureEvaluationDstu3Test.java b/hapi-fhir-jpaserver-cql/src/test/java/ca/uhn/fhir/cql/dstu3/CqlMeasureEvaluationDstu3Test.java index e7a7d7c50e4..072c3e14f04 100644 --- a/hapi-fhir-jpaserver-cql/src/test/java/ca/uhn/fhir/cql/dstu3/CqlMeasureEvaluationDstu3Test.java +++ b/hapi-fhir-jpaserver-cql/src/test/java/ca/uhn/fhir/cql/dstu3/CqlMeasureEvaluationDstu3Test.java @@ -29,7 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; public class CqlMeasureEvaluationDstu3Test extends BaseCqlDstu3Test { - Logger ourLog = LoggerFactory.getLogger(CqlMeasureEvaluationDstu3Test.class); + private static final Logger ourLog = LoggerFactory.getLogger(CqlMeasureEvaluationDstu3Test.class); @Autowired MeasureOperationsProvider myMeasureOperationsProvider; @@ -60,7 +60,7 @@ public class CqlMeasureEvaluationDstu3Test extends BaseCqlDstu3Test { String periodStart = this.getPeriodStart(expected); String periodEnd = this.getPeriodEnd(expected); - this.ourLog.info("Measure: %s, Patient: %s, Start: %s, End: %s", measureId, patientId, periodStart, periodEnd); + ourLog.info("Measure: {}, Patient: {}, Start: {}, End: {}", measureId, patientId, periodStart, periodEnd); MeasureReport actual = this.myMeasureOperationsProvider.evaluateMeasure(new IdType("Measure", measureId), periodStart, periodEnd, null, diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/SearchRuntimeDetails.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/SearchRuntimeDetails.java index ff7dcaf0413..fe6e2c33b21 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/SearchRuntimeDetails.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/SearchRuntimeDetails.java @@ -38,6 +38,7 @@ public class SearchRuntimeDetails { private String myQueryString; private SearchStatusEnum mySearchStatus; private int myFoundIndexMatchesCount; + public SearchRuntimeDetails(RequestDetails theRequestDetails, String theSearchUuid) { myRequestDetails = theRequestDetails; mySearchUuid = theSearchUuid;