From 74159dcb267b6430dae162c7b4d45fb131f16070 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sun, 17 Nov 2019 14:35:19 -0500 Subject: [PATCH 1/2] Work on refactoring how PIDs work (#1580) * Start work on refactoring how PIDs work * Some test fixes * Test fixes * Test fixes * Restore accidentally deleted line * One more tweak * Rework resource link extraction * Test fix * Split out transaction processor * Updates * Test fix * Test fixes * Test fixes * Clean up LGTM warning * Fix compile failure * One more test fix * Fix LGTM issue * Add spot for metadata * Add test logging * Remove testing code * More test logging * Add some test logging * Attempty to fix intermittent test failure * Improve exception tracking * Work on simplifying search param registry * Fix compile * Fix compile errors * Fix two test bugs --- .../src/main/java/example/ValidateSimple.java | 43 + .../uhn/fhir/context/RuntimeSearchParam.java | 152 ++- .../java/ca/uhn/fhir/util/ReflectionUtil.java | 3 +- .../fhir/jpa/bulk/BulkDataExportSvcImpl.java | 3 +- .../ca/uhn/fhir/jpa/config/BaseConfig.java | 9 + .../uhn/fhir/jpa/config/BaseDstu2Config.java | 10 - .../jpa/config/dstu3/BaseDstu3Config.java | 18 +- .../uhn/fhir/jpa/config/r4/BaseR4Config.java | 18 +- .../uhn/fhir/jpa/config/r5/BaseR5Config.java | 11 +- .../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 163 ++- .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 233 +--- .../fhir/jpa/dao/BaseHapiFhirSystemDao.java | 5 + .../ca/uhn/fhir/jpa/dao/BaseStorageDao.java | 204 +++ .../jpa/dao/BaseTransactionProcessor.java | 1195 +++++++++++++++++ .../ca/uhn/fhir/jpa/dao/DaoMethodOutcome.java | 7 +- .../java/ca/uhn/fhir/jpa/dao/DaoRegistry.java | 12 +- .../fhir/jpa/dao/DaoSearchParamProvider.java | 4 +- .../jpa/dao/FhirResourceDaoBundleDstu2.java | 11 +- .../dao/FhirResourceDaoSubscriptionDstu2.java | 5 +- .../jpa/dao/FhirResourceDaoValueSetDstu2.java | 19 +- .../uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java | 35 +- .../fhir/jpa/dao/FulltextSearchSvcImpl.java | 21 +- .../ca/uhn/fhir/jpa/dao/IFhirResourceDao.java | 7 +- .../uhn/fhir/jpa/dao/IFulltextSearchSvc.java | 5 +- .../java/ca/uhn/fhir/jpa/dao/IJpaDao.java | 7 +- .../ca/uhn/fhir/jpa/dao/IResultIterator.java | 4 +- .../ca/uhn/fhir/jpa/dao/ISearchBuilder.java | 7 +- .../fhir/jpa/dao/MatchResourceUrlService.java | 8 +- .../ca/uhn/fhir/jpa/dao/SearchBuilder.java | 325 +++-- .../fhir/jpa/dao/TransactionProcessor.java | 1170 +--------------- .../IResourceIndexedSearchParamStringDao.java | 5 +- .../dao/dstu3/FhirResourceDaoBundleDstu3.java | 14 +- .../dstu3/FhirResourceDaoCodeSystemDstu3.java | 17 +- .../dstu3/FhirResourceDaoConceptMapDstu3.java | 3 +- .../FhirResourceDaoSubscriptionDstu3.java | 5 +- .../dstu3/FhirResourceDaoValueSetDstu3.java | 3 +- .../jpa/dao/dstu3/FhirSystemDaoDstu3.java | 2 +- .../dao/index/DaoResourceLinkResolver.java | 7 +- .../fhir/jpa/dao/index/IdHelperService.java | 40 +- ...rchParamWithInlineReferencesExtractor.java | 17 +- .../jpa/dao/r4/FhirResourceDaoBundleR4.java | 12 +- .../dao/r4/FhirResourceDaoCodeSystemR4.java | 17 +- .../dao/r4/FhirResourceDaoConceptMapR4.java | 3 +- .../dao/r4/FhirResourceDaoSubscriptionR4.java | 3 +- .../jpa/dao/r4/FhirResourceDaoValueSetR4.java | 3 +- .../uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java | 2 +- .../jpa/dao/r5/FhirResourceDaoBundleR5.java | 12 +- .../dao/r5/FhirResourceDaoCodeSystemR5.java | 20 +- .../dao/r5/FhirResourceDaoConceptMapR5.java | 5 +- .../dao/r5/FhirResourceDaoSubscriptionR5.java | 3 +- .../jpa/dao/r5/FhirResourceDaoValueSetR5.java | 3 +- .../uhn/fhir/jpa/dao/r5/FhirSystemDaoR5.java | 2 +- .../jpa/delete/DeleteConflictService.java | 6 +- .../fhir/jpa/graphql/JpaStorageServices.java | 7 + .../JpaPreResourceAccessDetails.java | 5 +- .../jpa/search/ISearchCoordinatorSvc.java | 3 +- .../search/PersistedJpaBundleProvider.java | 9 +- ...istedJpaSearchFirstPageBundleProvider.java | 3 +- .../jpa/search/SearchCoordinatorSvcImpl.java | 38 +- .../DatabaseSearchResultCacheSvcImpl.java | 24 +- .../search/cache/ISearchResultCacheSvc.java | 7 +- .../SubscriptionTriggeringSvcImpl.java | 7 +- .../fhir/jpa/term/BaseTermReadSvcImpl.java | 21 +- .../term/TermCodeSystemStorageSvcImpl.java | 23 +- .../term/api/ITermCodeSystemStorageSvc.java | 5 +- .../uhn/fhir/jpa/config/TestDstu2Config.java | 2 +- .../FhirResourceDaoDstu2SearchNoFtTest.java | 3 +- .../dao/dstu2/FhirResourceDaoDstu2Test.java | 12 +- .../dstu2/FhirResourceDaoDstu2UpdateTest.java | 5 +- .../jpa/dao/dstu2/FhirSearchDaoDstu2Test.java | 41 +- .../FhirResourceDaoDstu3TerminologyTest.java | 9 +- .../dao/dstu3/FhirResourceDaoDstu3Test.java | 56 +- .../dstu3/FhirResourceDaoDstu3UpdateTest.java | 13 +- .../jpa/dao/dstu3/FhirSearchDaoDstu3Test.java | 41 +- .../ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java | 4 +- ...irResourceDaoCreatePlaceholdersR4Test.java | 5 +- .../dao/r4/FhirResourceDaoR4DeleteTest.java | 2 + .../r4/FhirResourceDaoR4SearchNoFtTest.java | 1 + ...ourceDaoR4SearchWithElasticSearchTest.java | 5 +- .../r4/FhirResourceDaoR4TerminologyTest.java | 11 +- .../jpa/dao/r4/FhirResourceDaoR4Test.java | 15 +- .../dao/r4/FhirResourceDaoR4UpdateTest.java | 5 +- .../fhir/jpa/dao/r4/FhirSearchDaoR4Test.java | 41 +- .../dao/r4/SearchParamExtractorR4Test.java | 55 +- .../ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java | 4 +- .../dstu3/BaseResourceProviderDstu3Test.java | 6 +- .../ResourceProviderDstu3ValueSetTest.java | 5 +- .../r4/BaseResourceProviderR4Test.java | 6 +- ...sourceProviderCustomSearchParamR4Test.java | 44 +- .../r4/ResourceProviderR4ValueSetTest.java | 3 +- .../r5/BaseResourceProviderR5Test.java | 6 +- .../r5/ResourceProviderR5ValueSetTest.java | 3 +- .../search/SearchCoordinatorSvcImplTest.java | 70 +- .../ca/uhn/fhir/jpa/term/BaseTermR4Test.java | 3 +- .../jpa/term/TerminologySvcImplDstu3Test.java | 15 +- .../model/cross/IBasePersistedResource.java | 40 + .../jpa/model/cross/ResourcePersistentId.java | 88 ++ .../jpa/model/entity/BaseHasResource.java | 17 +- .../fhir/jpa/model/entity/ModelConfig.java | 7 + .../model/entity/ResourceHistoryTable.java | 5 + .../ResourceIndexedSearchParamString.java | 6 +- .../fhir/jpa/model/entity/ResourceTable.java | 39 +- .../fhir/jpa/searchparam/MatchUrlService.java | 36 +- .../jpa/searchparam/ResourceMetaParams.java | 29 +- .../config/BaseSearchParamConfig.java | 8 + .../config/SearchParamDstu2Config.java | 7 - .../config/SearchParamDstu3Config.java | 7 - .../config/SearchParamR4Config.java | 9 - .../extractor/BaseSearchParamExtractor.java | 124 +- .../extractor/ISearchParamExtractor.java | 2 +- .../jpa/searchparam/extractor/PathAndRef.java | 28 +- .../extractor/ResourceLinkExtractor.java | 136 +- .../SearchParamExtractorService.java | 16 +- .../registry/ISearchParamProvider.java | 2 +- .../registry/SearchParamRegistryDstu2.java | 116 -- .../registry/SearchParamRegistryDstu3.java | 126 -- ...stry.java => SearchParamRegistryImpl.java} | 407 +++++- .../registry/SearchParamRegistryR4.java | 130 -- .../registry/SearchParamRegistryR5.java | 130 -- .../SearchParamExtractorServiceTest.java | 3 +- ....java => SearchParamRegistryImplTest.java} | 53 +- .../FhirClientSearchParamProvider.java | 4 +- .../standalone/SearchParamLoaderTest.java | 4 +- .../webapp/WEB-INF/templates/tmpl-head.html | 4 +- .../src/main/webapp/js/RestfulTester.js | 6 +- 125 files changed, 3292 insertions(+), 2858 deletions(-) create mode 100644 examples/src/main/java/example/ValidateSimple.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageDao.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java create mode 100644 hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/IBasePersistedResource.java create mode 100644 hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/ResourcePersistentId.java delete mode 100644 hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu2.java delete mode 100644 hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu3.java rename hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/{BaseSearchParamRegistry.java => SearchParamRegistryImpl.java} (51%) delete mode 100644 hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryR4.java delete mode 100644 hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryR5.java rename hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/{BaseSearchParamRegistryTest.java => SearchParamRegistryImplTest.java} (58%) diff --git a/examples/src/main/java/example/ValidateSimple.java b/examples/src/main/java/example/ValidateSimple.java new file mode 100644 index 00000000000..bef1e2b3912 --- /dev/null +++ b/examples/src/main/java/example/ValidateSimple.java @@ -0,0 +1,43 @@ +package example; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.validation.FhirValidator; +import ca.uhn.fhir.validation.ValidationResult; +import org.apache.commons.io.IOUtils; +import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; +import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; +import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain; +import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; + +import java.io.FileReader; + +public class ValidateSimple { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateSimple.class); + + public static void main(String[] args) throws Exception { + FhirContext ctx = FhirContext.forR4(); + + // Create a validator module + FhirInstanceValidator instanceValidator = new FhirInstanceValidator(); + + // We'll create a validation chain with only the DefaultProfileValidationupport registered + ValidationSupportChain validationSupportChain = new ValidationSupportChain(); + validationSupportChain.addValidationSupport(new DefaultProfileValidationSupport()); + instanceValidator.setValidationSupport(validationSupportChain); + + // Create a validator and register the InstanceValidator module + FhirValidator val = ctx.newValidator(); + val.registerValidatorModule(instanceValidator); + + // Read in the file and validate it + String nextFile = args[0]; + try (FileReader fileReader = new FileReader(nextFile)) { + String input = IOUtils.toString(fileReader); + ValidationResult result = val.validateWithResult(input); + IBaseOperationOutcome oo = result.toOperationOutcome(); + ourLog.info("Result:\n{}", ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(oo)); + } + + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java index 9ce0051fbff..5ce2dd6594f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java @@ -1,19 +1,27 @@ package ca.uhn.fhir.context; -import static org.apache.commons.lang3.StringUtils.isNotBlank; -import static org.apache.commons.lang3.StringUtils.trim; - -import java.util.*; - +import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import org.hl7.fhir.instance.model.api.IBaseDatatype; +import org.hl7.fhir.instance.model.api.IBaseExtension; import org.hl7.fhir.instance.model.api.IIdType; -import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; - import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.trim; /* * #%L @@ -44,61 +52,22 @@ public class RuntimeSearchParam { private final RestSearchParameterTypeEnum myParamType; private final String myPath; private final Set myTargets; - - @Override - public String toString() { - return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) - .append("base", myBase) - .append("name", myName) - .append("path", myPath) - .append("id", myId) - .append("uri", myUri) - .toString(); - } - private final Set myProvidesMembershipInCompartments; private final RuntimeSearchParamStatusEnum myStatus; private final String myUri; + private final Map>> myExtensions = new HashMap<>(); - public IIdType getId() { - return myId; - } - - public String getUri() { - return myUri; - } - + /** + * Constructor + */ public RuntimeSearchParam(IIdType theId, String theUri, String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, List theCompositeOf, Set theProvidesMembershipInCompartments, Set theTargets, RuntimeSearchParamStatusEnum theStatus) { this(theId, theUri, theName, theDescription, thePath, theParamType, theCompositeOf, theProvidesMembershipInCompartments, theTargets, theStatus, null); } - @Override - public boolean equals(Object theO) { - if (this == theO) return true; - - if (theO == null || getClass() != theO.getClass()) return false; - - RuntimeSearchParam that = (RuntimeSearchParam) theO; - - return new EqualsBuilder() - .append(getId(), that.getId()) - .append(getName(), that.getName()) - .append(getPath(), that.getPath()) - .append(getUri(), that.getUri()) - .isEquals(); - } - - @Override - public int hashCode() { - return new HashCodeBuilder(17, 37) - .append(getId()) - .append(getName()) - .append(getPath()) - .append(getUri()) - .toHashCode(); - } - + /** + * Constructor + */ public RuntimeSearchParam(IIdType theId, String theUri, String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, List theCompositeOf, Set theProvidesMembershipInCompartments, Set theTargets, RuntimeSearchParamStatusEnum theStatus, Collection theBase) { super(); @@ -136,6 +105,77 @@ public class RuntimeSearchParam { } } + public RuntimeSearchParam(String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, Set theProvidesMembershipInCompartments, Set theTargets, RuntimeSearchParamStatusEnum theStatus) { + this(null, null, theName, theDescription, thePath, theParamType, null, theProvidesMembershipInCompartments, theTargets, theStatus); + } + + /** + * Retrieve user data - This can be used to store any application-specific data + * + * @return + */ + public List> getExtensions(String theKey) { + List> retVal = myExtensions.get(theKey); + if (retVal != null) { + retVal = Collections.unmodifiableList(retVal); + } + return retVal; + } + + /** + * Sets user data - This can be used to store any application-specific data + */ + public RuntimeSearchParam addExtension(String theKey, IBaseExtension theValue) { + List> valuesList = myExtensions.computeIfAbsent(theKey, k -> new ArrayList<>()); + valuesList.add(theValue); + return this; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) + .append("base", myBase) + .append("name", myName) + .append("path", myPath) + .append("id", myId) + .append("uri", myUri) + .toString(); + } + + public IIdType getId() { + return myId; + } + + public String getUri() { + return myUri; + } + + @Override + public boolean equals(Object theO) { + if (this == theO) return true; + + if (theO == null || getClass() != theO.getClass()) return false; + + RuntimeSearchParam that = (RuntimeSearchParam) theO; + + return new EqualsBuilder() + .append(getId(), that.getId()) + .append(getName(), that.getName()) + .append(getPath(), that.getPath()) + .append(getUri(), that.getUri()) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(getId()) + .append(getName()) + .append(getPath()) + .append(getUri()) + .toHashCode(); + } + public Set getBase() { return myBase; } @@ -153,10 +193,6 @@ public class RuntimeSearchParam { return myStatus; } - public RuntimeSearchParam(String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, Set theProvidesMembershipInCompartments, Set theTargets, RuntimeSearchParamStatusEnum theStatus) { - this(null, null, theName, theDescription, thePath, theParamType, null, theProvidesMembershipInCompartments, theTargets, theStatus); - } - public List getCompositeOf() { return myCompositeOf; } @@ -206,5 +242,5 @@ public class RuntimeSearchParam { RETIRED, UNKNOWN } - + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ReflectionUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ReflectionUtil.java index e83b42d05db..20ab85b24b2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ReflectionUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ReflectionUtil.java @@ -118,11 +118,10 @@ public class ReflectionUtil { /** * Instantiate a class by no-arg constructor, throw {@link ConfigurationException} if we fail to do so */ - @CoverageIgnore public static T newInstance(Class theType) { Validate.notNull(theType, "theType must not be null"); try { - return theType.newInstance(); + return theType.getConstructor().newInstance(); } catch (Exception e) { throw new ConfigurationException("Failed to instantiate " + theType.getName(), e); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java index aeed83fd634..987936d8677 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java @@ -25,6 +25,7 @@ import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.IResultIterator; import ca.uhn.fhir.jpa.dao.ISearchBuilder; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.dao.data.IBulkExportCollectionDao; import ca.uhn.fhir.jpa.dao.data.IBulkExportCollectionFileDao; import ca.uhn.fhir.jpa.dao.data.IBulkExportJobDao; @@ -251,7 +252,7 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc { OutputStreamWriter writer = new OutputStreamWriter(outputStream, Constants.CHARSET_UTF8); IParser parser = myContext.newJsonParser().setPrettyPrint(false); - List pidsSpool = new ArrayList<>(); + List pidsSpool = new ArrayList<>(); List resourcesSpool = new ArrayList<>(); while (query.hasNext()) { pidsSpool.add(query.next()); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java index 0374debca57..0fdb7f9de30 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java @@ -10,6 +10,7 @@ import ca.uhn.fhir.jpa.bulk.BulkDataExportProvider; import ca.uhn.fhir.jpa.bulk.BulkDataExportSvcImpl; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.delete.DeleteConflictService; import ca.uhn.fhir.jpa.graphql.JpaStorageServices; import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices; import ca.uhn.fhir.jpa.model.sched.ISchedulerService; @@ -26,6 +27,8 @@ import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc; import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor; import ca.uhn.fhir.jpa.subscription.dbmatcher.CompositeInMemoryDaoSubscriptionMatcher; import ca.uhn.fhir.jpa.subscription.dbmatcher.DaoSubscriptionMatcher; @@ -129,6 +132,12 @@ public abstract class BaseConfig { return b; } + @Bean + public ISearchParamRegistry searchParamRegistry() { + return new SearchParamRegistryImpl(); + } + + @Bean(name = "mySubscriptionTriggeringProvider") @Lazy public SubscriptionTriggeringProvider subscriptionTriggeringProvider() { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java index 4c4713f1a6c..9c00fda3004 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java @@ -5,16 +5,11 @@ import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu2; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryDstu2; import ca.uhn.fhir.jpa.term.TermReadSvcDstu2; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; -import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; -import ca.uhn.fhir.jpa.term.TermVersionAdapterSvcDstu2; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.validation.IInstanceValidatorModule; -import ca.uhn.fhir.validation.IValidatorModule; import org.apache.commons.lang3.time.DateUtils; import org.hl7.fhir.instance.hapi.validation.CachingValidationSupport; import org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport; @@ -117,11 +112,6 @@ public class BaseDstu2Config extends BaseConfig { return new SearchParamExtractorDstu2(); } - @Bean - public ISearchParamRegistry searchParamRegistry() { - return new SearchParamRegistryDstu2(); - } - @Bean(name = "mySystemDaoDstu2", autowire = Autowire.BY_NAME) public IFhirSystemDao systemDaoDstu2() { ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu2 retVal = new ca.uhn.fhir.jpa.dao.FhirSystemDaoDstu2(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java index a3251957593..c247464c25d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java @@ -2,7 +2,6 @@ package ca.uhn.fhir.jpa.config.dstu3; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.ParserOptions; -import ca.uhn.fhir.jpa.config.BaseConfig; import ca.uhn.fhir.jpa.config.BaseConfigDstu3Plus; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; @@ -11,24 +10,20 @@ import ca.uhn.fhir.jpa.dao.TransactionProcessor; import ca.uhn.fhir.jpa.dao.dstu3.TransactionProcessorVersionAdapterDstu3; import ca.uhn.fhir.jpa.provider.GraphQLProvider; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu3; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryDstu3; -import ca.uhn.fhir.jpa.term.TermReadSvcDstu3; import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl; +import ca.uhn.fhir.jpa.term.TermReadSvcDstu3; import ca.uhn.fhir.jpa.term.TermVersionAdapterSvcDstu3; -import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3; import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; +import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3; import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; import ca.uhn.fhir.validation.IInstanceValidatorModule; -import ca.uhn.fhir.validation.IValidatorModule; import org.apache.commons.lang3.time.DateUtils; import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.hapi.validation.CachingValidationSupport; import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.r5.utils.IResourceValidator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -95,8 +90,8 @@ public class BaseDstu3Config extends BaseConfigDstu3Plus { } @Bean - public TransactionProcessor transactionProcessor() { - return new TransactionProcessor<>(); + public TransactionProcessor transactionProcessor() { + return new TransactionProcessor(); } @Bean(name = "myInstanceValidatorDstu3") @@ -140,11 +135,6 @@ public class BaseDstu3Config extends BaseConfigDstu3Plus { return new SearchParamExtractorDstu3(); } - @Bean - public ISearchParamRegistry searchParamRegistry() { - return new SearchParamRegistryDstu3(); - } - @Bean(name = "mySystemDaoDstu3") public IFhirSystemDao systemDaoDstu3() { return new ca.uhn.fhir.jpa.dao.dstu3.FhirSystemDaoDstu3(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java index 3bb855fd142..b97ab840e2d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java @@ -2,7 +2,6 @@ package ca.uhn.fhir.jpa.config.r4; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.ParserOptions; -import ca.uhn.fhir.jpa.config.BaseConfig; import ca.uhn.fhir.jpa.config.BaseConfigDstu3Plus; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; @@ -11,22 +10,20 @@ import ca.uhn.fhir.jpa.dao.TransactionProcessor; import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4; import ca.uhn.fhir.jpa.provider.GraphQLProvider; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR4; -import ca.uhn.fhir.jpa.term.*; +import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl; +import ca.uhn.fhir.jpa.term.TermReadSvcR4; +import ca.uhn.fhir.jpa.term.TermVersionAdapterSvcR4; import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4; import ca.uhn.fhir.validation.IInstanceValidatorModule; -import ca.uhn.fhir.validation.IValidatorModule; import org.apache.commons.lang3.time.DateUtils; import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; import org.hl7.fhir.r4.hapi.validation.CachingValidationSupport; import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; -import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r5.utils.IResourceValidator; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.context.annotation.Bean; @@ -88,8 +85,8 @@ public class BaseR4Config extends BaseConfigDstu3Plus { } @Bean - public TransactionProcessor transactionProcessor() { - return new TransactionProcessor<>(); + public TransactionProcessor transactionProcessor() { + return new TransactionProcessor(); } @Bean(name = GRAPHQL_PROVIDER_NAME) @@ -142,11 +139,6 @@ public class BaseR4Config extends BaseConfigDstu3Plus { return new SearchParamExtractorR4(); } - @Bean - public ISearchParamRegistry searchParamRegistry() { - return new SearchParamRegistryR4(); - } - @Bean(name = "mySystemDaoR4", autowire = Autowire.BY_NAME) public IFhirSystemDao systemDaoR4() { ca.uhn.fhir.jpa.dao.r4.FhirSystemDaoR4 retVal = new ca.uhn.fhir.jpa.dao.r4.FhirSystemDaoR4(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/BaseR5Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/BaseR5Config.java index e012e930173..baa0a23941c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/BaseR5Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/BaseR5Config.java @@ -10,8 +10,6 @@ import ca.uhn.fhir.jpa.dao.TransactionProcessor; import ca.uhn.fhir.jpa.dao.r5.TransactionProcessorVersionAdapterR5; import ca.uhn.fhir.jpa.provider.GraphQLProvider; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR5; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR5; import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl; import ca.uhn.fhir.jpa.term.TermReadSvcR5; import ca.uhn.fhir.jpa.term.TermVersionAdapterSvcR5; @@ -88,8 +86,8 @@ public class BaseR5Config extends BaseConfigDstu3Plus { } @Bean - public TransactionProcessor transactionProcessor() { - return new TransactionProcessor<>(); + public TransactionProcessor transactionProcessor() { + return new TransactionProcessor(); } @Bean(name = GRAPHQL_PROVIDER_NAME) @@ -142,11 +140,6 @@ public class BaseR5Config extends BaseConfigDstu3Plus { return new SearchParamExtractorR5(); } - @Bean - public ISearchParamRegistry searchParamRegistry() { - return new SearchParamRegistryR5(); - } - @Bean(name = "mySystemDaoR5", autowire = Autowire.BY_NAME) public IFhirSystemDao systemDaoR5() { ca.uhn.fhir.jpa.dao.r5.FhirSystemDaoR5 retVal = new ca.uhn.fhir.jpa.dao.r5.FhirSystemDaoR5(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index 01bf0fdb1fc..f9d876d7ced 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -1,10 +1,21 @@ package ca.uhn.fhir.jpa.dao; -import ca.uhn.fhir.context.*; +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.RuntimeChildResourceDefinition; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.RuntimeSearchParam; 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.jpa.dao.data.*; +import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; +import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; +import ca.uhn.fhir.jpa.dao.data.IResourceProvenanceDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTagDao; import ca.uhn.fhir.jpa.dao.expunge.ExpungeService; import ca.uhn.fhir.jpa.dao.index.DaoSearchParamSynchronizer; import ca.uhn.fhir.jpa.dao.index.IdHelperService; @@ -13,6 +24,7 @@ import ca.uhn.fhir.jpa.delete.DeleteConflictService; import ca.uhn.fhir.jpa.entity.ResourceSearchView; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.SearchTypeEnum; +import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; @@ -74,7 +86,11 @@ import org.springframework.transaction.support.TransactionSynchronizationAdapter import org.springframework.transaction.support.TransactionSynchronizationManager; import javax.annotation.PostConstruct; -import javax.persistence.*; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.PersistenceContext; +import javax.persistence.PersistenceContextType; +import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; @@ -83,7 +99,12 @@ import javax.xml.stream.events.XMLEvent; import java.util.*; import java.util.Map.Entry; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.defaultIfBlank; +import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.left; +import static org.apache.commons.lang3.StringUtils.trim; /* * #%L @@ -107,7 +128,7 @@ import static org.apache.commons.lang3.StringUtils.*; @SuppressWarnings("WeakerAccess") @Repository -public abstract class BaseHapiFhirDao implements IDao, IJpaDao, ApplicationContextAware { +public abstract class BaseHapiFhirDao extends BaseStorageDao implements IDao, IJpaDao, ApplicationContextAware { public static final long INDEX_STATUS_INDEXED = 1L; public static final long INDEX_STATUS_INDEXING_FAILED = 2L; @@ -168,6 +189,11 @@ public abstract class BaseHapiFhirDao implements IDao, private FhirContext myContext; private ApplicationContext myApplicationContext; + @Override + protected IInterceptorBroadcaster getInterceptorBroadcaster() { + return myInterceptorBroadcaster; + } + protected ApplicationContext getApplicationContext() { return myApplicationContext; } @@ -290,6 +316,7 @@ public abstract class BaseHapiFhirDao implements IDao, return retVal; } + @Override protected DaoConfig getConfig() { return myConfig; } @@ -925,34 +952,36 @@ public abstract class BaseHapiFhirDao implements IDao, @SuppressWarnings("unchecked") @Override - public ResourceTable updateEntity(RequestDetails theRequest, final IBaseResource theResource, ResourceTable + public ResourceTable updateEntity(RequestDetails theRequest, final IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { Validate.notNull(theEntity); - Validate.isTrue(theDeletedTimestampOrNull != null || theResource != null, "Must have either a resource[%s] or a deleted timestamp[%s] for resource PID[%s]", theDeletedTimestampOrNull != null, theResource != null, theEntity.getId()); + Validate.isTrue(theDeletedTimestampOrNull != null || theResource != null, "Must have either a resource[%s] or a deleted timestamp[%s] for resource PID[%s]", theDeletedTimestampOrNull != null, theResource != null, theEntity.getPersistentId()); ourLog.debug("Starting entity update"); + ResourceTable entity = (ResourceTable) theEntity; + /* * This should be the very first thing.. */ if (theResource != null) { if (thePerformIndexing) { if (!ourValidationDisabledForUnitTest) { - validateResourceForStorage((T) theResource, theEntity); + validateResourceForStorage((T) theResource, entity); } } String resourceType = myContext.getResourceDefinition(theResource).getName(); - if (isNotBlank(theEntity.getResourceType()) && !theEntity.getResourceType().equals(resourceType)) { + if (isNotBlank(entity.getResourceType()) && !entity.getResourceType().equals(resourceType)) { throw new UnprocessableEntityException( - "Existing resource ID[" + theEntity.getIdDt().toUnqualifiedVersionless() + "] is of type[" + theEntity.getResourceType() + "] - Cannot update with [" + resourceType + "]"); + "Existing resource ID[" + entity.getIdDt().toUnqualifiedVersionless() + "] is of type[" + entity.getResourceType() + "] - Cannot update with [" + resourceType + "]"); } } - if (theEntity.getPublished() == null) { + if (entity.getPublished() == null) { ourLog.debug("Entity has published time: {}", new InstantDt(theUpdateTime)); - theEntity.setPublished(theUpdateTime); + entity.setPublished(theUpdateTime); } ResourceIndexedSearchParams existingParams = null; @@ -963,89 +992,89 @@ public abstract class BaseHapiFhirDao implements IDao, if (theDeletedTimestampOrNull != null) { // DELETE - theEntity.setDeleted(theDeletedTimestampOrNull); - theEntity.setUpdated(theDeletedTimestampOrNull); - theEntity.setNarrativeTextParsedIntoWords(null); - theEntity.setContentTextParsedIntoWords(null); - theEntity.setHashSha256(null); - theEntity.setIndexStatus(INDEX_STATUS_INDEXED); - changed = populateResourceIntoEntity(theRequest, theResource, theEntity, true); + entity.setDeleted(theDeletedTimestampOrNull); + entity.setUpdated(theDeletedTimestampOrNull); + entity.setNarrativeTextParsedIntoWords(null); + entity.setContentTextParsedIntoWords(null); + entity.setHashSha256(null); + entity.setIndexStatus(INDEX_STATUS_INDEXED); + changed = populateResourceIntoEntity(theRequest, theResource, entity, true); } else { // CREATE or UPDATE - existingParams = new ResourceIndexedSearchParams(theEntity); - theEntity.setDeleted(null); + existingParams = new ResourceIndexedSearchParams(entity); + entity.setDeleted(null); if (thePerformIndexing) { newParams = new ResourceIndexedSearchParams(); - mySearchParamWithInlineReferencesExtractor.populateFromResource(newParams, theUpdateTime, theEntity, theResource, existingParams, theRequest); + mySearchParamWithInlineReferencesExtractor.populateFromResource(newParams, theUpdateTime, entity, theResource, existingParams, theRequest); - changed = populateResourceIntoEntity(theRequest, theResource, theEntity, true); + changed = populateResourceIntoEntity(theRequest, theResource, entity, true); if (changed.isChanged()) { - theEntity.setUpdated(theUpdateTime); + entity.setUpdated(theUpdateTime); if (theResource instanceof IResource) { - theEntity.setLanguage(((IResource) theResource).getLanguage().getValue()); + entity.setLanguage(((IResource) theResource).getLanguage().getValue()); } else { - theEntity.setLanguage(((IAnyResource) theResource).getLanguageElement().getValue()); + entity.setLanguage(((IAnyResource) theResource).getLanguageElement().getValue()); } - newParams.setParamsOn(theEntity); - theEntity.setIndexStatus(INDEX_STATUS_INDEXED); - populateFullTextFields(myContext, theResource, theEntity); + newParams.setParamsOn(entity); + entity.setIndexStatus(INDEX_STATUS_INDEXED); + populateFullTextFields(myContext, theResource, entity); } } else { - changed = populateResourceIntoEntity(theRequest, theResource, theEntity, false); + changed = populateResourceIntoEntity(theRequest, theResource, entity, false); - theEntity.setUpdated(theUpdateTime); - theEntity.setIndexStatus(null); + entity.setUpdated(theUpdateTime); + entity.setIndexStatus(null); } } if (!changed.isChanged() && !theForceUpdate && myConfig.isSuppressUpdatesWithNoChange()) { - ourLog.debug("Resource {} has not changed", theEntity.getIdDt().toUnqualified().getValue()); + ourLog.debug("Resource {} has not changed", entity.getIdDt().toUnqualified().getValue()); if (theResource != null) { - updateResourceMetadata(theEntity, theResource); + updateResourceMetadata(entity, theResource); } - theEntity.setUnchangedInCurrentOperation(true); - return theEntity; + entity.setUnchangedInCurrentOperation(true); + return entity; } if (theUpdateVersion) { - theEntity.setVersion(theEntity.getVersion() + 1); + entity.setVersion(entity.getVersion() + 1); } /* * Save the resource itself */ - if (theEntity.getId() == null) { - myEntityManager.persist(theEntity); + if (entity.getId() == null) { + myEntityManager.persist(entity); - if (theEntity.getForcedId() != null) { - myEntityManager.persist(theEntity.getForcedId()); + if (entity.getForcedId() != null) { + myEntityManager.persist(entity.getForcedId()); } - postPersist(theEntity, (T) theResource); + postPersist(entity, (T) theResource); - } else if (theEntity.getDeleted() != null) { - theEntity = myEntityManager.merge(theEntity); + } else if (entity.getDeleted() != null) { + entity = myEntityManager.merge(entity); - postDelete(theEntity); + postDelete(entity); } else { - theEntity = myEntityManager.merge(theEntity); + entity = myEntityManager.merge(entity); - postUpdate(theEntity, (T) theResource); + postUpdate(entity, (T) theResource); } /* * Create history entry */ if (theCreateNewHistoryEntry) { - final ResourceHistoryTable historyEntry = theEntity.toHistory(); + final ResourceHistoryTable historyEntry = entity.toHistory(); historyEntry.setEncoding(changed.getEncoding()); historyEntry.setResource(changed.getResource()); @@ -1076,7 +1105,7 @@ public abstract class BaseHapiFhirDao implements IDao, if (haveSource || haveRequestId) { ResourceHistoryProvenanceEntity provenance = new ResourceHistoryProvenanceEntity(); provenance.setResourceHistoryTable(historyEntry); - provenance.setResourceTable(theEntity); + provenance.setResourceTable(entity); if (haveRequestId) { provenance.setRequestId(left(requestId, Constants.REQUEST_ID_LENGTH)); } @@ -1102,7 +1131,7 @@ public abstract class BaseHapiFhirDao implements IDao, for (String nextKey : newParams.getPopulatedResourceLinkParameters()) { presentSearchParams.put(nextKey, Boolean.TRUE); } - Set> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet(); + Set> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(entity.getResourceType()).entrySet(); for (Entry nextSpEntry : activeSearchParams) { if (nextSpEntry.getValue().getParamType() == RestSearchParameterTypeEnum.REFERENCE) { if (!presentSearchParams.containsKey(nextSpEntry.getKey())) { @@ -1110,13 +1139,13 @@ public abstract class BaseHapiFhirDao implements IDao, } } } - AddRemoveCount presenceCount = mySearchParamPresenceSvc.updatePresence(theEntity, presentSearchParams); + AddRemoveCount presenceCount = mySearchParamPresenceSvc.updatePresence(entity, presentSearchParams); // Interceptor broadcast: JPA_PERFTRACE_INFO if (!presenceCount.isEmpty()) { if (JpaInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_INFO, myInterceptorBroadcaster, theRequest)) { StorageProcessingMessage message = new StorageProcessingMessage(); - message.setMessage("For " + theEntity.getIdDt().toUnqualifiedVersionless().getValue() + " added " + presenceCount.getAddCount() + " and removed " + presenceCount.getRemoveCount() + " resource search parameter presence entries"); + message.setMessage("For " + entity.getIdDt().toUnqualifiedVersionless().getValue() + " added " + presenceCount.getAddCount() + " and removed " + presenceCount.getRemoveCount() + " resource search parameter presence entries"); HookParams params = new HookParams() .add(RequestDetails.class, theRequest) .addIfMatchesType(ServletRequestDetails.class, theRequest) @@ -1132,17 +1161,17 @@ public abstract class BaseHapiFhirDao implements IDao, */ if (thePerformIndexing) { if (newParams == null) { - myExpungeService.deleteAllSearchParams(theEntity.getId()); + myExpungeService.deleteAllSearchParams(entity.getId()); } else { // Synchronize search param indexes - AddRemoveCount searchParamAddRemoveCount = myDaoSearchParamSynchronizer.synchronizeSearchParamsToDatabase(newParams, theEntity, existingParams); + AddRemoveCount searchParamAddRemoveCount = myDaoSearchParamSynchronizer.synchronizeSearchParamsToDatabase(newParams, entity, existingParams); // Interceptor broadcast: JPA_PERFTRACE_INFO if (!searchParamAddRemoveCount.isEmpty()) { if (JpaInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_INFO, myInterceptorBroadcaster, theRequest)) { StorageProcessingMessage message = new StorageProcessingMessage(); - message.setMessage("For " + theEntity.getIdDt().toUnqualifiedVersionless().getValue() + " added " + searchParamAddRemoveCount.getAddCount() + " and removed " + searchParamAddRemoveCount.getRemoveCount() + " resource search parameter index entries"); + message.setMessage("For " + entity.getIdDt().toUnqualifiedVersionless().getValue() + " added " + searchParamAddRemoveCount.getAddCount() + " and removed " + searchParamAddRemoveCount.getRemoveCount() + " resource search parameter index entries"); HookParams params = new HookParams() .add(RequestDetails.class, theRequest) .addIfMatchesType(ServletRequestDetails.class, theRequest) @@ -1152,25 +1181,27 @@ public abstract class BaseHapiFhirDao implements IDao, } // Syncrhonize composite params - mySearchParamWithInlineReferencesExtractor.storeCompositeStringUniques(newParams, theEntity, existingParams); + mySearchParamWithInlineReferencesExtractor.storeCompositeStringUniques(newParams, entity, existingParams); } } if (theResource != null) { - updateResourceMetadata(theEntity, theResource); + updateResourceMetadata(entity, theResource); } - return theEntity; + return entity; } @Override public ResourceTable updateInternal(RequestDetails theRequestDetails, T theResource, boolean thePerformIndexing, boolean theForceUpdateVersion, - ResourceTable theEntity, IIdType theResourceId, IBaseResource theOldResource) { + IBasePersistedResource theEntity2, IIdType theResourceId, IBaseResource theOldResource) { + + ResourceTable entity = (ResourceTable) theEntity2; // We'll update the resource ID with the correct version later but for // now at least set it to something useful for the interceptors - theResource.setId(theEntity.getIdDt()); + theResource.setId(entity.getIdDt()); // Notify interceptors ActionRequestDetails requestDetails; @@ -1188,7 +1219,7 @@ public abstract class BaseHapiFhirDao implements IDao, doCallHooks(theRequestDetails, Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, hookParams); // Perform update - ResourceTable savedEntity = updateEntity(theRequestDetails, theResource, theEntity, null, thePerformIndexing, thePerformIndexing, new Date(), theForceUpdateVersion, thePerformIndexing); + ResourceTable savedEntity = updateEntity(theRequestDetails, theResource, entity, null, thePerformIndexing, thePerformIndexing, new Date(), theForceUpdateVersion, thePerformIndexing); /* * If we aren't indexing (meaning we're probably executing a sub-operation within a transaction), @@ -1226,18 +1257,14 @@ public abstract class BaseHapiFhirDao implements IDao, return savedEntity; } - protected void addPidToResource(ResourceTable theEntity, IBaseResource theResource) { + protected void addPidToResource(IBasePersistedResource theEntity, IBaseResource theResource) { if (theResource instanceof IAnyResource) { - IDao.RESOURCE_PID.put((IAnyResource) theResource, theEntity.getId()); + IDao.RESOURCE_PID.put((IAnyResource) theResource, theEntity.getPersistentId().getIdAsLong()); } else if (theResource instanceof IResource) { - IDao.RESOURCE_PID.put((IResource) theResource, theEntity.getId()); + IDao.RESOURCE_PID.put((IResource) theResource, theEntity.getPersistentId().getIdAsLong()); } } - protected void doCallHooks(RequestDetails theRequestDetails, Pointcut thePointcut, HookParams theParams) { - JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, thePointcut, theParams); - } - protected void updateResourceMetadata(IBaseResourceEntity theEntity, IBaseResource theResource) { IIdType id = theEntity.getIdDt(); if (getContext().getVersion().getVersion().isRi()) { 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 d27febf8fe9..7a083953b51 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 @@ -26,7 +26,15 @@ import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.jpa.delete.DeleteConflictList; -import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.jpa.delete.DeleteConflictService; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; +import ca.uhn.fhir.jpa.model.entity.BaseHasResource; +import ca.uhn.fhir.jpa.model.entity.BaseTag; +import ca.uhn.fhir.jpa.model.entity.ForcedId; +import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.entity.TagDefinition; +import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; @@ -40,22 +48,50 @@ import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils; import ca.uhn.fhir.model.api.IQueryParameterAnd; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.api.*; -import ca.uhn.fhir.rest.api.server.*; +import ca.uhn.fhir.rest.api.CacheControlDirective; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.PatchTypeEnum; +import ca.uhn.fhir.rest.api.QualifiedParamList; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.api.ValidationModeEnum; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails; +import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.SimplePreResourceAccessDetails; +import ca.uhn.fhir.rest.api.server.SimplePreResourceShowDetails; import ca.uhn.fhir.rest.param.ParameterUtil; import ca.uhn.fhir.rest.param.QualifierDetails; -import ca.uhn.fhir.rest.server.exceptions.*; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.method.SearchMethodBinding; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; -import ca.uhn.fhir.util.*; -import ca.uhn.fhir.validation.*; +import ca.uhn.fhir.util.ObjectUtil; +import ca.uhn.fhir.util.OperationOutcomeUtil; +import ca.uhn.fhir.util.ReflectionUtil; +import ca.uhn.fhir.util.StopWatch; +import ca.uhn.fhir.validation.FhirValidator; +import ca.uhn.fhir.validation.IInstanceValidatorModule; +import ca.uhn.fhir.validation.IValidationContext; +import ca.uhn.fhir.validation.IValidatorModule; +import ca.uhn.fhir.validation.ValidationOptions; +import ca.uhn.fhir.validation.ValidationResult; import org.apache.commons.lang3.Validate; -import org.hl7.fhir.instance.model.api.*; -import org.hl7.fhir.r4.model.InstantType; +import org.hl7.fhir.instance.model.api.IBaseCoding; +import org.hl7.fhir.instance.model.api.IBaseMetaType; +import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; -import org.springframework.context.ApplicationContext; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.annotation.Propagation; @@ -64,12 +100,10 @@ import org.springframework.transaction.support.TransactionSynchronizationAdapter import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionTemplate; -import javax.annotation.Nonnull; import javax.annotation.PostConstruct; import javax.persistence.NoResultException; import javax.persistence.TypedQuery; import javax.servlet.http.HttpServletResponse; -import javax.validation.constraints.NotNull; import java.io.IOException; import java.util.*; @@ -168,24 +202,10 @@ public abstract class BaseHapiFhirResourceDao extends B return create(theResource, theIfNoneExist, true, new Date(), theRequestDetails); } - public IBaseOperationOutcome createErrorOperationOutcome(String theMessage, String theCode) { - return createOperationOutcome(OO_SEVERITY_ERROR, theMessage, theCode); - } - - public IBaseOperationOutcome createInfoOperationOutcome(String theMessage) { - return createOperationOutcome(OO_SEVERITY_INFO, theMessage, "informational"); - } - private IInstanceValidatorModule getInstanceValidator() { return myInstanceValidator; } - private IBaseOperationOutcome createOperationOutcome(String theSeverity, String theMessage, String theCode) { - IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext()); - OperationOutcomeUtil.addIssue(getContext(), oo, theSeverity, theMessage, null, theCode); - return oo; - } - @Override public DaoMethodOutcome delete(IIdType theId) { return delete(theId, null); @@ -281,7 +301,7 @@ public abstract class BaseHapiFhirResourceDao extends B DaoMethodOutcome retVal = delete(theId, deleteConflicts, theRequestDetails); - myDeleteConflictService.validateDeleteConflictsEmptyOrThrowException(deleteConflicts); + DeleteConflictService.validateDeleteConflictsEmptyOrThrowException(getContext(), deleteConflicts); ourLog.debug("Processed delete on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); return retVal; @@ -295,7 +315,7 @@ public abstract class BaseHapiFhirResourceDao extends B public DeleteMethodOutcome deleteByUrl(String theUrl, DeleteConflictList deleteConflicts, RequestDetails theRequest) { StopWatch w = new StopWatch(); - Set resourceIds = myMatchResourceUrlService.processMatchUrl(theUrl, myResourceType, theRequest); + Set resourceIds = myMatchResourceUrlService.processMatchUrl(theUrl, myResourceType, theRequest); if (resourceIds.size() > 1) { if (myDaoConfig.isAllowMultipleDelete() == false) { throw new PreconditionFailedException(getContext().getLocalizer().getMessageSanitized(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "DELETE", theUrl, resourceIds.size())); @@ -303,8 +323,8 @@ public abstract class BaseHapiFhirResourceDao extends B } List deletedResources = new ArrayList<>(); - for (Long pid : resourceIds) { - ResourceTable entity = myEntityManager.find(ResourceTable.class, pid); + for (ResourcePersistentId pid : resourceIds) { + ResourceTable entity = myEntityManager.find(ResourceTable.class, pid.getId()); deletedResources.add(entity); T resourceToDelete = toResource(myResourceType, entity, null, false); @@ -372,7 +392,7 @@ public abstract class BaseHapiFhirResourceDao extends B DeleteMethodOutcome outcome = deleteByUrl(theUrl, deleteConflicts, theRequestDetails); - myDeleteConflictService.validateDeleteConflictsEmptyOrThrowException(deleteConflicts); + DeleteConflictService.validateDeleteConflictsEmptyOrThrowException(getContext(), deleteConflicts); return outcome; } @@ -399,13 +419,13 @@ public abstract class BaseHapiFhirResourceDao extends B entity.setResourceType(toResourceName(theResource)); if (isNotBlank(theIfNoneExist)) { - Set match = myMatchResourceUrlService.processMatchUrl(theIfNoneExist, myResourceType, theRequest); + Set match = myMatchResourceUrlService.processMatchUrl(theIfNoneExist, myResourceType, theRequest); if (match.size() > 1) { String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "CREATE", theIfNoneExist, match.size()); throw new PreconditionFailedException(msg); } else if (match.size() == 1) { - Long pid = match.iterator().next(); - entity = myEntityManager.find(ResourceTable.class, pid); + ResourcePersistentId pid = match.iterator().next(); + entity = myEntityManager.find(ResourceTable.class, pid.getId()); IBaseResource resource = toResource(entity, false); theResource.setId(resource.getIdElement().getValue()); return toMethodOutcome(theRequest, entity, resource).setCreated(false); @@ -605,6 +625,7 @@ public abstract class BaseHapiFhirResourceDao extends B return myExpungeService.expunge(getResourceName(), null, null, theExpungeOptions, theRequestDetails); } + @Override public String getResourceName() { return myResourceName; } @@ -790,13 +811,13 @@ public abstract class BaseHapiFhirResourceDao extends B ResourceTable entityToUpdate; if (isNotBlank(theConditionalUrl)) { - Set match = myMatchResourceUrlService.processMatchUrl(theConditionalUrl, myResourceType, theRequest); + Set match = myMatchResourceUrlService.processMatchUrl(theConditionalUrl, myResourceType, theRequest); if (match.size() > 1) { String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "PATCH", theConditionalUrl, match.size()); throw new PreconditionFailedException(msg); } else if (match.size() == 1) { - Long pid = match.iterator().next(); - entityToUpdate = myEntityManager.find(ResourceTable.class, pid); + ResourcePersistentId pid = match.iterator().next(); + entityToUpdate = myEntityManager.find(ResourceTable.class, pid.getId()); } else { String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirDao.class, "invalidMatchUrlNoMatches", theConditionalUrl); throw new ResourceNotFoundException(msg); @@ -848,57 +869,16 @@ public abstract class BaseHapiFhirResourceDao extends B // nothing by default } - /** - * May be overridden by subclasses to validate resources prior to storage - * - * @param theResource The resource that is about to be stored - */ - protected void preProcessResourceForStorage(T theResource) { - String type = getContext().getResourceDefinition(theResource).getName(); - if (!getResourceName().equals(type)) { - throw new InvalidRequestException(getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "incorrectResourceType", type, getResourceName())); - } - - if (theResource.getIdElement().hasIdPart()) { - if (!theResource.getIdElement().isIdPartValid()) { - throw new InvalidRequestException(getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "failedToCreateWithInvalidId", theResource.getIdElement().getIdPart())); - } - } - - /* - * Replace absolute references with relative ones if configured to do so - */ - if (getConfig().getTreatBaseUrlsAsLocal().isEmpty() == false) { - FhirTerser t = getContext().newTerser(); - List refs = t.getAllResourceReferences(theResource); - for (ResourceReferenceInfo nextRef : refs) { - IIdType refId = nextRef.getResourceReference().getReferenceElement(); - if (refId != null && refId.hasBaseUrl()) { - if (getConfig().getTreatBaseUrlsAsLocal().contains(refId.getBaseUrl())) { - IIdType newRefId = refId.toUnqualified(); - nextRef.getResourceReference().setReference(newRefId.getValue()); - } - } - } - } - - } - @Override - public Set processMatchUrl(String theMatchUrl, RequestDetails theRequest) { - return myMatchResourceUrlService.processMatchUrl(theMatchUrl, getResourceType(), theRequest); - } - - @Override - public IBaseResource readByPid(Long thePid) { + public IBaseResource readByPid(ResourcePersistentId thePid) { StopWatch w = new StopWatch(); - Optional entity = myResourceTableDao.findById(thePid); + Optional entity = myResourceTableDao.findById(thePid.getIdAsLong()); if (!entity.isPresent()) { throw new ResourceNotFoundException("No resource found with PID " + thePid); } if (entity.get().getDeleted() != null) { - throw newResourceGoneException(entity.get()); + throw createResourceGoneException(entity.get()); } T retVal = toResource(myResourceType, entity.get(), null, false); @@ -907,17 +887,6 @@ public abstract class BaseHapiFhirResourceDao extends B return retVal; } - @NotNull - private ResourceGoneException newResourceGoneException(BaseHasResource theResourceEntity) { - StringBuilder b = new StringBuilder(); - b.append("Resource was deleted at "); - b.append(new InstantType(theResourceEntity.getDeleted()).getValueAsString()); - ResourceGoneException retVal = new ResourceGoneException(b.toString()); - retVal.setResourceId(theResourceEntity.getIdDt()); - return retVal; - } - - @Override public T read(IIdType theId) { return read(theId, null); @@ -947,7 +916,7 @@ public abstract class BaseHapiFhirResourceDao extends B if (theDeletedOk == false) { if (entity.getDeleted() != null) { - throw newResourceGoneException(entity); + throw createResourceGoneException(entity); } } @@ -990,8 +959,8 @@ public abstract class BaseHapiFhirResourceDao extends B public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) { validateResourceTypeAndThrowInvalidRequestException(theId); - Long pid = myIdHelperService.translateForcedIdToPid(getResourceName(), theId.getIdPart(), theRequest); - BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid); + ResourcePersistentId pid = myIdHelperService.translateForcedIdToPid(getResourceName(), theId.getIdPart(), theRequest); + BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid.getIdAsLong()); if (entity == null) { throw new ResourceNotFoundException(theId); @@ -1009,7 +978,7 @@ public abstract class BaseHapiFhirResourceDao extends B if (entity == null) { if (theId.hasVersionIdPart()) { TypedQuery q = myEntityManager.createQuery("SELECT t from ResourceHistoryTable t WHERE t.myResourceId = :RID AND t.myResourceType = :RTYP AND t.myResourceVersion = :RVER", ResourceHistoryTable.class); - q.setParameter("RID", pid); + q.setParameter("RID", pid.getId()); q.setParameter("RTYP", myResourceName); q.setParameter("RVER", theId.getVersionIdPartAsLong()); try { @@ -1029,7 +998,8 @@ public abstract class BaseHapiFhirResourceDao extends B } protected ResourceTable readEntityLatestVersion(IIdType theId, RequestDetails theRequest) { - ResourceTable entity = myEntityManager.find(ResourceTable.class, myIdHelperService.translateForcedIdToPid(getResourceName(), theId.getIdPart(), theRequest)); + ResourcePersistentId persistentId = myIdHelperService.translateForcedIdToPid(getResourceName(), theId.getIdPart(), theRequest); + ResourceTable entity = myEntityManager.find(ResourceTable.class, persistentId.getId()); if (entity == null) { throw new ResourceNotFoundException(theId); } @@ -1154,14 +1124,14 @@ public abstract class BaseHapiFhirResourceDao extends B } @Override - public Set searchForIds(SearchParameterMap theParams, RequestDetails theRequest) { + public Set searchForIds(SearchParameterMap theParams, RequestDetails theRequest) { SearchBuilder builder = newSearchBuilder(); builder.setType(getResourceType(), getResourceName()); // FIXME: fail if too many results - HashSet retVal = new HashSet<>(); + HashSet retVal = new HashSet<>(); String uuid = UUID.randomUUID().toString(); SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequest, uuid); @@ -1178,12 +1148,7 @@ public abstract class BaseHapiFhirResourceDao extends B } protected MT toMetaDt(Class theType, Collection tagDefinitions) { - MT retVal; - try { - retVal = theType.newInstance(); - } catch (Exception e) { - throw new InternalErrorException("Failed to instantiate " + theType.getName(), e); - } + MT retVal = ReflectionUtil.newInstance(theType); for (TagDefinition next : tagDefinitions) { switch (next.getTagType()) { case PROFILE: @@ -1200,58 +1165,6 @@ public abstract class BaseHapiFhirResourceDao extends B return retVal; } - private DaoMethodOutcome toMethodOutcome(RequestDetails theRequest, @Nonnull final ResourceTable theEntity, @Nonnull IBaseResource theResource) { - DaoMethodOutcome outcome = new DaoMethodOutcome(); - - IIdType id = null; - if (theResource.getIdElement().getValue() != null) { - id = theResource.getIdElement(); - } - if (id == null) { - id = theEntity.getIdDt(); - if (getContext().getVersion().getVersion().isRi()) { - id = getContext().getVersion().newIdType().setValue(id.getValue()); - } - } - - outcome.setId(id); - if (theEntity.getDeleted() == null) { - outcome.setResource(theResource); - } - outcome.setEntity(theEntity); - - // Interceptor broadcast: STORAGE_PREACCESS_RESOURCES - if (outcome.getResource() != null) { - SimplePreResourceAccessDetails accessDetails = new SimplePreResourceAccessDetails(outcome.getResource()); - HookParams params = new HookParams() - .add(IPreResourceAccessDetails.class, accessDetails) - .add(RequestDetails.class, theRequest) - .addIfMatchesType(ServletRequestDetails.class, theRequest); - JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PREACCESS_RESOURCES, params); - if (accessDetails.isDontReturnResourceAtIndex(0)) { - outcome.setResource(null); - } - } - - // Interceptor broadcast: STORAGE_PRESHOW_RESOURCES - // Note that this will only fire if someone actually goes to use the - // resource in a response (it's their responsibility to call - // outcome.fireResourceViewCallback()) - outcome.registerResourceViewCallback(() -> { - if (outcome.getResource() != null) { - SimplePreResourceShowDetails showDetails = new SimplePreResourceShowDetails(outcome.getResource()); - HookParams params = new HookParams() - .add(IPreResourceShowDetails.class, showDetails) - .add(RequestDetails.class, theRequest) - .addIfMatchesType(ServletRequestDetails.class, theRequest); - JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PRESHOW_RESOURCES, params); - outcome.setResource(showDetails.getResource(0)); - } - }); - - return outcome; - } - private ArrayList toTagList(IBaseMetaType theMeta) { ArrayList retVal = new ArrayList<>(); @@ -1340,13 +1253,13 @@ public abstract class BaseHapiFhirResourceDao extends B IIdType resourceId; if (isNotBlank(theMatchUrl)) { - Set match = myMatchResourceUrlService.processMatchUrl(theMatchUrl, myResourceType, theRequest); + Set match = myMatchResourceUrlService.processMatchUrl(theMatchUrl, myResourceType, theRequest); if (match.size() > 1) { String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "UPDATE", theMatchUrl, match.size()); throw new PreconditionFailedException(msg); } else if (match.size() == 1) { - Long pid = match.iterator().next(); - entity = myEntityManager.find(ResourceTable.class, pid); + ResourcePersistentId pid = match.iterator().next(); + entity = myEntityManager.find(ResourceTable.class, pid.getId()); resourceId = entity.getIdDt(); } else { return create(theResource, null, thePerformIndexing, new Date(), theRequest); @@ -1440,7 +1353,7 @@ public abstract class BaseHapiFhirResourceDao extends B if (myDaoConfig.isEnforceReferentialIntegrityOnDelete()) { myDeleteConflictService.validateOkToDelete(deleteConflicts, entity, true, theRequest); } - myDeleteConflictService.validateDeleteConflictsEmptyOrThrowException(deleteConflicts); + DeleteConflictService.validateDeleteConflictsEmptyOrThrowException(getContext(), deleteConflicts); IBaseOperationOutcome oo = createInfoOperationOutcome("Ok to delete"); return new MethodOutcome(new IdDt(theId.getValue()), oo); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java index e5842df1d8c..0d4cf96a5d9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java @@ -88,4 +88,9 @@ public abstract class BaseHapiFhirSystemDao extends BaseHapiFhirDao refs = t.getAllResourceReferences(theResource); + for (ResourceReferenceInfo nextRef : refs) { + IIdType refId = nextRef.getResourceReference().getReferenceElement(); + if (refId != null && refId.hasBaseUrl()) { + if (getConfig().getTreatBaseUrlsAsLocal().contains(refId.getBaseUrl())) { + IIdType newRefId = refId.toUnqualified(); + nextRef.getResourceReference().setReference(newRefId.getValue()); + } + } + } + } + + if ("Bundle".equals(type)) { + Set allowedBundleTypes = getConfig().getBundleTypesAllowedForStorage(); + String bundleType = BundleUtil.getBundleType(getContext(), (IBaseBundle) theResource); + if (isBlank(bundleType) || !allowedBundleTypes.contains(bundleType)) { + String message = "Unable to store a Bundle resource on this server with a Bundle.type value of: " + (isNotBlank(bundleType) ? bundleType : "(missing)"); + throw new UnprocessableEntityException(message); + } + } + + } + + protected DaoMethodOutcome toMethodOutcome(RequestDetails theRequest, @Nonnull final IBasePersistedResource theEntity, @Nonnull IBaseResource theResource) { + DaoMethodOutcome outcome = new DaoMethodOutcome(); + + IIdType id = null; + if (theResource.getIdElement().getValue() != null) { + id = theResource.getIdElement(); + } + if (id == null) { + id = theEntity.getIdDt(); + if (getContext().getVersion().getVersion().isRi()) { + id = getContext().getVersion().newIdType().setValue(id.getValue()); + } + } + + outcome.setId(id); + if (theEntity.isDeleted() == false) { + outcome.setResource(theResource); + } + outcome.setEntity(theEntity); + + // Interceptor broadcast: STORAGE_PREACCESS_RESOURCES + if (outcome.getResource() != null) { + SimplePreResourceAccessDetails accessDetails = new SimplePreResourceAccessDetails(outcome.getResource()); + HookParams params = new HookParams() + .add(IPreResourceAccessDetails.class, accessDetails) + .add(RequestDetails.class, theRequest) + .addIfMatchesType(ServletRequestDetails.class, theRequest); + JpaInterceptorBroadcaster.doCallHooks(getInterceptorBroadcaster(), theRequest, Pointcut.STORAGE_PREACCESS_RESOURCES, params); + if (accessDetails.isDontReturnResourceAtIndex(0)) { + outcome.setResource(null); + } + } + + // Interceptor broadcast: STORAGE_PRESHOW_RESOURCES + // Note that this will only fire if someone actually goes to use the + // resource in a response (it's their responsibility to call + // outcome.fireResourceViewCallback()) + outcome.registerResourceViewCallback(() -> { + if (outcome.getResource() != null) { + SimplePreResourceShowDetails showDetails = new SimplePreResourceShowDetails(outcome.getResource()); + HookParams params = new HookParams() + .add(IPreResourceShowDetails.class, showDetails) + .add(RequestDetails.class, theRequest) + .addIfMatchesType(ServletRequestDetails.class, theRequest); + JpaInterceptorBroadcaster.doCallHooks(getInterceptorBroadcaster(), theRequest, Pointcut.STORAGE_PRESHOW_RESOURCES, params); + outcome.setResource(showDetails.getResource(0)); + } + }); + + return outcome; + } + + protected void doCallHooks(RequestDetails theRequestDetails, Pointcut thePointcut, HookParams theParams) { + JpaInterceptorBroadcaster.doCallHooks(getInterceptorBroadcaster(), theRequestDetails, thePointcut, theParams); + } + + protected abstract IInterceptorBroadcaster getInterceptorBroadcaster(); + + public IBaseOperationOutcome createErrorOperationOutcome(String theMessage, String theCode) { + return createOperationOutcome(OO_SEVERITY_ERROR, theMessage, theCode); + } + + public IBaseOperationOutcome createInfoOperationOutcome(String theMessage) { + return createOperationOutcome(OO_SEVERITY_INFO, theMessage, "informational"); + } + + private IBaseOperationOutcome createOperationOutcome(String theSeverity, String theMessage, String theCode) { + IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext()); + OperationOutcomeUtil.addIssue(getContext(), oo, theSeverity, theMessage, null, theCode); + return oo; + } + + @NotNull + protected ResourceGoneException createResourceGoneException(IBasePersistedResource theResourceEntity) { + StringBuilder b = new StringBuilder(); + b.append("Resource was deleted at "); + b.append(new InstantType(theResourceEntity.getDeleted()).getValueAsString()); + ResourceGoneException retVal = new ResourceGoneException(b.toString()); + retVal.setResourceId(theResourceEntity.getIdDt()); + return retVal; + } + + /** + * Provide the DaoConfig + */ + protected abstract DaoConfig getConfig(); + + /** + * Returns the resource type for this DAO, or null if this is a system-level DAO + */ + @Nullable + protected abstract String getResourceName(); + + /** + * Provides the FHIR context + */ + protected abstract FhirContext getContext(); + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java new file mode 100644 index 00000000000..90c0c229603 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java @@ -0,0 +1,1195 @@ +package ca.uhn.fhir.jpa.dao; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2019 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +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.jpa.delete.DeleteConflictList; +import ca.uhn.fhir.jpa.delete.DeleteConflictOutcome; +import ca.uhn.fhir.jpa.delete.DeleteConflictService; +import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; +import ca.uhn.fhir.jpa.util.DeleteConflict; +import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.PatchTypeEnum; +import ca.uhn.fhir.rest.api.PreferReturnEnum; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.param.ParameterUtil; +import ca.uhn.fhir.rest.server.RestfulServerUtils; +import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.NotModifiedException; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.rest.server.method.BaseMethodBinding; +import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.rest.server.servlet.ServletSubRequestDetails; +import ca.uhn.fhir.rest.server.util.ServletRequestUtil; +import ca.uhn.fhir.util.ElementUtil; +import ca.uhn.fhir.util.FhirTerser; +import ca.uhn.fhir.util.ResourceReferenceInfo; +import ca.uhn.fhir.util.StopWatch; +import ca.uhn.fhir.util.UrlUtil; +import com.google.common.base.Charsets; +import com.google.common.collect.ArrayListMultimap; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseBinary; +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.annotation.PostConstruct; +import java.util.*; + +import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +public abstract class BaseTransactionProcessor { + + public static final String URN_PREFIX = "urn:"; + private static final Logger ourLog = LoggerFactory.getLogger(TransactionProcessor.class); + private BaseHapiFhirDao myDao; + @Autowired + private PlatformTransactionManager myTxManager; + @Autowired + private FhirContext myContext; + @Autowired + private ITransactionProcessorVersionAdapter myVersionAdapter; + @Autowired + private DaoRegistry myDaoRegistry; + @Autowired + private IInterceptorBroadcaster myInterceptorBroadcaster; + @Autowired + private MatchResourceUrlService myMatchResourceUrlService; + + @PostConstruct + public void start() { + + } + + public BUNDLE transaction(RequestDetails theRequestDetails, BUNDLE theRequest) { + if (theRequestDetails != null && myDao != null) { + IServerInterceptor.ActionRequestDetails requestDetails = new IServerInterceptor.ActionRequestDetails(theRequestDetails, theRequest, "Bundle", null); + myDao.notifyInterceptors(RestOperationTypeEnum.TRANSACTION, requestDetails); + } + + String actionName = "Transaction"; + IBaseBundle response = processTransactionAsSubRequest((ServletRequestDetails) theRequestDetails, theRequest, actionName); + + List entries = myVersionAdapter.getEntries(response); + for (int i = 0; i < entries.size(); i++) { + if (ElementUtil.isEmpty(entries.get(i))) { + entries.remove(i); + i--; + } + } + + return (BUNDLE) response; + } + + public IBaseBundle collection(final RequestDetails theRequestDetails, IBaseBundle theRequest) { + String transactionType = myVersionAdapter.getBundleType(theRequest); + + if (!org.hl7.fhir.r4.model.Bundle.BundleType.COLLECTION.toCode().equals(transactionType)) { + throw new InvalidRequestException("Can not process collection Bundle of type: " + transactionType); + } + + ourLog.info("Beginning storing collection with {} resources", myVersionAdapter.getEntries(theRequest).size()); + + TransactionTemplate txTemplate = new TransactionTemplate(myTxManager); + txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + + IBaseBundle resp = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.BATCHRESPONSE.toCode()); + + List resources = new ArrayList<>(); + for (final Object nextRequestEntry : myVersionAdapter.getEntries(theRequest)) { + IBaseResource resource = myVersionAdapter.getResource((IBase) nextRequestEntry); + resources.add(resource); + } + + IBaseBundle transactionBundle = myVersionAdapter.createBundle("transaction"); + for (IBaseResource next : resources) { + IBase entry = myVersionAdapter.addEntry(transactionBundle); + myVersionAdapter.setResource(entry, next); + myVersionAdapter.setRequestVerb(entry, "PUT"); + myVersionAdapter.setRequestUrl(entry, next.getIdElement().toUnqualifiedVersionless().getValue()); + } + + transaction(theRequestDetails, transactionBundle); + + return resp; + } + + private void populateEntryWithOperationOutcome(BaseServerResponseException caughtEx, IBase nextEntry) { + myVersionAdapter.populateEntryWithOperationOutcome(caughtEx, nextEntry); + } + + private void handleTransactionCreateOrUpdateOutcome(Map idSubstitutions, Map idToPersistedOutcome, IIdType nextResourceId, DaoMethodOutcome outcome, + IBase newEntry, String theResourceType, IBaseResource theRes, ServletRequestDetails theRequestDetails) { + IIdType newId = outcome.getId().toUnqualifiedVersionless(); + IIdType resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless(); + if (newId.equals(resourceId) == false) { + idSubstitutions.put(resourceId, newId); + if (isPlaceholder(resourceId)) { + /* + * The correct way for substitution IDs to be is to be with no resource type, but we'll accept the qualified kind too just to be lenient. + */ + IIdType id = myContext.getVersion().newIdType(); + id.setValue(theResourceType + '/' + resourceId.getValue()); + idSubstitutions.put(id, newId); + } + } + idToPersistedOutcome.put(newId, outcome); + if (outcome.getCreated()) { + myVersionAdapter.setResponseStatus(newEntry, toStatusString(Constants.STATUS_HTTP_201_CREATED)); + } else { + myVersionAdapter.setResponseStatus(newEntry, toStatusString(Constants.STATUS_HTTP_200_OK)); + } + Date lastModifier = getLastModified(theRes); + myVersionAdapter.setResponseLastModified(newEntry, lastModifier); + + if (theRequestDetails != null) { + if (outcome.getResource() != null) { + String prefer = theRequestDetails.getHeader(Constants.HEADER_PREFER); + PreferReturnEnum preferReturn = RestfulServerUtils.parsePreferHeader(null, prefer).getReturn(); + if (preferReturn != null) { + if (preferReturn == PreferReturnEnum.REPRESENTATION) { + outcome.fireResourceViewCallbacks(); + myVersionAdapter.setResource(newEntry, outcome.getResource()); + } + } + } + } + + } + + private Date getLastModified(IBaseResource theRes) { + return theRes.getMeta().getLastUpdated(); + } + + private String performIdSubstitutionsInMatchUrl(Map theIdSubstitutions, String theMatchUrl) { + String matchUrl = theMatchUrl; + if (isNotBlank(matchUrl)) { + for (Map.Entry nextSubstitutionEntry : theIdSubstitutions.entrySet()) { + IIdType nextTemporaryId = nextSubstitutionEntry.getKey(); + IIdType nextReplacementId = nextSubstitutionEntry.getValue(); + String nextTemporaryIdPart = nextTemporaryId.getIdPart(); + String nextReplacementIdPart = nextReplacementId.getValueAsString(); + if (isUrn(nextTemporaryId) && nextTemporaryIdPart.length() > URN_PREFIX.length()) { + matchUrl = matchUrl.replace(nextTemporaryIdPart, nextReplacementIdPart); + String escapedUrlParam = UrlUtil.escapeUrlParam(nextTemporaryIdPart); + if (isNotBlank(escapedUrlParam)) { + matchUrl = matchUrl.replace(escapedUrlParam, nextReplacementIdPart); + } + } + } + } + return matchUrl; + } + + private boolean isUrn(IIdType theId) { + return defaultString(theId.getValue()).startsWith(URN_PREFIX); + } + + public void setDao(BaseHapiFhirDao theDao) { + myDao = theDao; + } + + private IBaseBundle processTransactionAsSubRequest(ServletRequestDetails theRequestDetails, IBaseBundle theRequest, String theActionName) { + BaseHapiFhirDao.markRequestAsProcessingSubRequest(theRequestDetails); + try { + return processTransaction(theRequestDetails, theRequest, theActionName); + } finally { + BaseHapiFhirDao.clearRequestAsProcessingSubRequest(theRequestDetails); + } + } + + private IBaseBundle batch(final RequestDetails theRequestDetails, IBaseBundle theRequest) { + ourLog.info("Beginning batch with {} resources", myVersionAdapter.getEntries(theRequest).size()); + long start = System.currentTimeMillis(); + + TransactionTemplate txTemplate = new TransactionTemplate(myTxManager); + txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + + IBaseBundle resp = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.BATCHRESPONSE.toCode()); + + /* + * For batch, we handle each entry as a mini-transaction in its own database transaction so that if one fails, it doesn't prevent others + */ + + for (final Object nextRequestEntry : myVersionAdapter.getEntries(theRequest)) { + + BaseServerResponseExceptionHolder caughtEx = new BaseServerResponseExceptionHolder(); + + try { + IBaseBundle subRequestBundle = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION.toCode()); + myVersionAdapter.addEntry(subRequestBundle, (IBase) nextRequestEntry); + + IBaseBundle nextResponseBundle = processTransactionAsSubRequest((ServletRequestDetails) theRequestDetails, subRequestBundle, "Batch sub-request"); + + IBase subResponseEntry = (IBase) myVersionAdapter.getEntries(nextResponseBundle).get(0); + myVersionAdapter.addEntry(resp, subResponseEntry); + + /* + * If the individual entry didn't have a resource in its response, bring the sub-transaction's OperationOutcome across so the client can see it + */ + if (myVersionAdapter.getResource(subResponseEntry) == null) { + IBase nextResponseBundleFirstEntry = (IBase) myVersionAdapter.getEntries(nextResponseBundle).get(0); + myVersionAdapter.setResource(subResponseEntry, myVersionAdapter.getResource(nextResponseBundleFirstEntry)); + } + + } catch (BaseServerResponseException e) { + caughtEx.setException(e); + } catch (Throwable t) { + ourLog.error("Failure during BATCH sub transaction processing", t); + caughtEx.setException(new InternalErrorException(t)); + } + + if (caughtEx.getException() != null) { + IBase nextEntry = myVersionAdapter.addEntry(resp); + + populateEntryWithOperationOutcome(caughtEx.getException(), nextEntry); + + myVersionAdapter.setResponseStatus(nextEntry, toStatusString(caughtEx.getException().getStatusCode())); + } + + } + + long delay = System.currentTimeMillis() - start; + ourLog.info("Batch completed in {}ms", new Object[]{delay}); + + return resp; + } + + private IBaseBundle processTransaction(final ServletRequestDetails theRequestDetails, final IBaseBundle theRequest, final String theActionName) { + validateDependencies(); + + String transactionType = myVersionAdapter.getBundleType(theRequest); + + if (org.hl7.fhir.r4.model.Bundle.BundleType.BATCH.toCode().equals(transactionType)) { + return batch(theRequestDetails, theRequest); + } + + if (transactionType == null) { + String message = "Transaction Bundle did not specify valid Bundle.type, assuming " + Bundle.BundleType.TRANSACTION.toCode(); + ourLog.warn(message); + transactionType = org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION.toCode(); + } + if (!org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION.toCode().equals(transactionType)) { + throw new InvalidRequestException("Unable to process transaction where incoming Bundle.type = " + transactionType); + } + + ourLog.debug("Beginning {} with {} resources", theActionName, myVersionAdapter.getEntries(theRequest).size()); + + final Date updateTime = new Date(); + final StopWatch transactionStopWatch = new StopWatch(); + + final Set allIds = new LinkedHashSet<>(); + final Map idSubstitutions = new HashMap<>(); + final Map idToPersistedOutcome = new HashMap<>(); + List requestEntries = myVersionAdapter.getEntries(theRequest); + + // Do all entries have a verb? + for (int i = 0; i < myVersionAdapter.getEntries(theRequest).size(); i++) { + IBase nextReqEntry = requestEntries.get(i); + String verb = myVersionAdapter.getEntryRequestVerb(nextReqEntry); + if (verb == null || !isValidVerb(verb)) { + throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionEntryHasInvalidVerb", verb, i)); + } + } + + /* + * We want to execute the transaction request bundle elements in the order + * specified by the FHIR specification (see TransactionSorter) so we save the + * original order in the request, then sort it. + * + * Entries with a type of GET are removed from the bundle so that they + * can be processed at the very end. We do this because the incoming resources + * are saved in a two-phase way in order to deal with interdependencies, and + * we want the GET processing to use the final indexing state + */ + final IBaseBundle response = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTIONRESPONSE.toCode()); + List getEntries = new ArrayList<>(); + final IdentityHashMap originalRequestOrder = new IdentityHashMap<>(); + for (int i = 0; i < requestEntries.size(); i++) { + originalRequestOrder.put(requestEntries.get(i), i); + myVersionAdapter.addEntry(response); + if (myVersionAdapter.getEntryRequestVerb(requestEntries.get(i)).equals("GET")) { + getEntries.add(requestEntries.get(i)); + } + } + + /* + * See FhirSystemDaoDstu3Test#testTransactionWithPlaceholderIdInMatchUrl + * Basically if the resource has a match URL that references a placeholder, + * we try to handle the resource with the placeholder first. + */ + Set placeholderIds = new HashSet<>(); + final List entries = requestEntries; + for (IBase nextEntry : entries) { + String fullUrl = myVersionAdapter.getFullUrl(nextEntry); + if (isNotBlank(fullUrl) && fullUrl.startsWith(URN_PREFIX)) { + placeholderIds.add(fullUrl); + } + } + entries.sort(new TransactionSorter(placeholderIds)); + + /* + * All of the write operations in the transaction (PUT, POST, etc.. basically anything + * except GET) are performed in their own database transaction before we do the reads. + * We do this because the reads (specifically the searches) often spawn their own + * secondary database transaction and if we allow that within the primary + * database transaction we can end up with deadlocks if the server is under + * heavy load with lots of concurrent transactions using all available + * database connections. + */ + TransactionTemplate txManager = new TransactionTemplate(myTxManager); + Map entriesToProcess = txManager.execute(status -> { + Map retVal = doTransactionWriteOperations(theRequestDetails, theActionName, updateTime, allIds, idSubstitutions, idToPersistedOutcome, response, originalRequestOrder, entries, transactionStopWatch); + + transactionStopWatch.startTask("Commit writes to database"); + return retVal; + }); + transactionStopWatch.endCurrentTask(); + + for (Map.Entry nextEntry : entriesToProcess.entrySet()) { + String responseLocation = nextEntry.getValue().getIdDt().toUnqualified().getValue(); + String responseEtag = nextEntry.getValue().getIdDt().getVersionIdPart(); + myVersionAdapter.setResponseLocation(nextEntry.getKey(), responseLocation); + myVersionAdapter.setResponseETag(nextEntry.getKey(), responseEtag); + } + + /* + * Loop through the request and process any entries of type GET + */ + if (getEntries.size() > 0) { + transactionStopWatch.startTask("Process " + getEntries.size() + " GET entries"); + } + for (IBase nextReqEntry : getEntries) { + Integer originalOrder = originalRequestOrder.get(nextReqEntry); + IBase nextRespEntry = (IBase) myVersionAdapter.getEntries(response).get(originalOrder); + + ArrayListMultimap paramValues = ArrayListMultimap.create(); + + String transactionUrl = extractTransactionUrlOrThrowException(nextReqEntry, "GET"); + + ServletSubRequestDetails requestDetails = ServletRequestUtil.getServletSubRequestDetails(theRequestDetails, transactionUrl, paramValues); + + String url = requestDetails.getRequestPath(); + + BaseMethodBinding method = theRequestDetails.getServer().determineResourceMethod(requestDetails, url); + if (method == null) { + throw new IllegalArgumentException("Unable to handle GET " + url); + } + + if (isNotBlank(myVersionAdapter.getEntryRequestIfMatch(nextReqEntry))) { + requestDetails.addHeader(Constants.HEADER_IF_MATCH, myVersionAdapter.getEntryRequestIfMatch(nextReqEntry)); + } + if (isNotBlank(myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry))) { + requestDetails.addHeader(Constants.HEADER_IF_NONE_EXIST, myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry)); + } + if (isNotBlank(myVersionAdapter.getEntryRequestIfNoneMatch(nextReqEntry))) { + requestDetails.addHeader(Constants.HEADER_IF_NONE_MATCH, myVersionAdapter.getEntryRequestIfNoneMatch(nextReqEntry)); + } + + Validate.isTrue(method instanceof BaseResourceReturningMethodBinding, "Unable to handle GET {}", url); + try { + + BaseResourceReturningMethodBinding methodBinding = (BaseResourceReturningMethodBinding) method; + requestDetails.setRestOperationType(methodBinding.getRestOperationType()); + + IBaseResource resource = methodBinding.doInvokeServer(theRequestDetails.getServer(), requestDetails); + if (paramValues.containsKey(Constants.PARAM_SUMMARY) || paramValues.containsKey(Constants.PARAM_CONTENT)) { + resource = filterNestedBundle(requestDetails, resource); + } + myVersionAdapter.setResource(nextRespEntry, resource); + myVersionAdapter.setResponseStatus(nextRespEntry, toStatusString(Constants.STATUS_HTTP_200_OK)); + } catch (NotModifiedException e) { + myVersionAdapter.setResponseStatus(nextRespEntry, toStatusString(Constants.STATUS_HTTP_304_NOT_MODIFIED)); + } catch (BaseServerResponseException e) { + ourLog.info("Failure processing transaction GET {}: {}", url, e.toString()); + myVersionAdapter.setResponseStatus(nextRespEntry, toStatusString(e.getStatusCode())); + populateEntryWithOperationOutcome(e, nextRespEntry); + } + + } + transactionStopWatch.endCurrentTask(); + + // Interceptor broadcast: JPA_PERFTRACE_INFO + if (JpaInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_INFO, myInterceptorBroadcaster, theRequestDetails)) { + String taskDurations = transactionStopWatch.formatTaskDurations(); + StorageProcessingMessage message = new StorageProcessingMessage(); + message.setMessage("Transaction timing:\n" + taskDurations); + HookParams params = new HookParams() + .add(RequestDetails.class, theRequestDetails) + .addIfMatchesType(ServletRequestDetails.class, theRequestDetails) + .add(StorageProcessingMessage.class, message); + JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.JPA_PERFTRACE_INFO, params); + } + + return response; + } + + private boolean isValidVerb(String theVerb) { + try { + return org.hl7.fhir.r4.model.Bundle.HTTPVerb.fromCode(theVerb) != null; + } catch (FHIRException theE) { + return false; + } + } + + /** + * This method is called for nested bundles (e.g. if we received a transaction with an entry that + * was a GET search, this method is called on the bundle for the search result, that will be placed in the + * outer bundle). This method applies the _summary and _content parameters to the output of + * that bundle. + *

+ * TODO: This isn't the most efficient way of doing this.. hopefully we can come up with something better in the future. + */ + private IBaseResource filterNestedBundle(RequestDetails theRequestDetails, IBaseResource theResource) { + IParser p = myContext.newJsonParser(); + RestfulServerUtils.configureResponseParser(theRequestDetails, p); + return p.parseResource(theResource.getClass(), p.encodeResourceToString(theResource)); + } + + protected void validateDependencies() { + Validate.notNull(myContext); + Validate.notNull(myTxManager); + } + + private IIdType newIdType(String theValue) { + return myContext.getVersion().newIdType().setValue(theValue); + } + + + private Map doTransactionWriteOperations(final ServletRequestDetails theRequest, String theActionName, Date theUpdateTime, Set theAllIds, + Map theIdSubstitutions, Map theIdToPersistedOutcome, IBaseBundle theResponse, IdentityHashMap theOriginalRequestOrder, List theEntries, StopWatch theTransactionStopWatch) { + + if (theRequest != null) { + theRequest.startDeferredOperationCallback(); + } + try { + + Set deletedResources = new HashSet<>(); + DeleteConflictList deleteConflicts = new DeleteConflictList(); + Map entriesToProcess = new IdentityHashMap<>(); + Set nonUpdatedEntities = new HashSet<>(); + Set updatedEntities = new HashSet<>(); + List updatedResources = new ArrayList<>(); + Map> conditionalRequestUrls = new HashMap<>(); + + /* + * Look for duplicate conditional creates and consolidate them + */ + final HashMap keyToUuid = new HashMap<>(); + for (int index = 0, originalIndex = 0; index < theEntries.size(); index++, originalIndex++) { + IBase nextReqEntry = theEntries.get(index); + IBaseResource resource = myVersionAdapter.getResource(nextReqEntry); + if (resource != null) { + String verb = myVersionAdapter.getEntryRequestVerb(nextReqEntry); + String entryUrl = myVersionAdapter.getFullUrl(nextReqEntry); + String requestUrl = myVersionAdapter.getEntryRequestUrl(nextReqEntry); + String ifNoneExist = myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry); + String key = verb + "|" + requestUrl + "|" + ifNoneExist; + + // Conditional UPDATE + boolean consolidateEntry = false; + if ("PUT".equals(verb)) { + if (isNotBlank(entryUrl) && isNotBlank(requestUrl)) { + int questionMarkIndex = requestUrl.indexOf('?'); + if (questionMarkIndex >= 0 && requestUrl.length() > (questionMarkIndex + 1)) { + consolidateEntry = true; + } + } + } + + // Conditional CREATE + if ("POST".equals(verb)) { + if (isNotBlank(entryUrl) && isNotBlank(requestUrl) && isNotBlank(ifNoneExist)) { + if (!entryUrl.equals(requestUrl)) { + consolidateEntry = true; + } + } + } + + if (consolidateEntry) { + if (!keyToUuid.containsKey(key)) { + keyToUuid.put(key, entryUrl); + } else { + ourLog.info("Discarding transaction bundle entry {} as it contained a duplicate conditional {}", originalIndex, verb); + theEntries.remove(index); + index--; + String existingUuid = keyToUuid.get(key); + for (IBase nextEntry : theEntries) { + IBaseResource nextResource = myVersionAdapter.getResource(nextEntry); + for (ResourceReferenceInfo nextReference : myContext.newTerser().getAllResourceReferences(nextResource)) { + // We're interested in any references directly to the placeholder ID, but also + // references that have a resource target that has the placeholder ID. + String nextReferenceId = nextReference.getResourceReference().getReferenceElement().getValue(); + if (isBlank(nextReferenceId) && nextReference.getResourceReference().getResource() != null) { + nextReferenceId = nextReference.getResourceReference().getResource().getIdElement().getValue(); + } + if (entryUrl.equals(nextReferenceId)) { + nextReference.getResourceReference().setReference(existingUuid); + nextReference.getResourceReference().setResource(null); + } + } + } + } + } + } + } + + + /* + * Loop through the request and process any entries of type + * PUT, POST or DELETE + */ + for (int i = 0; i < theEntries.size(); i++) { + + if (i % 250 == 0) { + ourLog.info("Processed {} non-GET entries out of {} in transaction", i, theEntries.size()); + } + + IBase nextReqEntry = theEntries.get(i); + IBaseResource res = myVersionAdapter.getResource(nextReqEntry); + IIdType nextResourceId = null; + if (res != null) { + + nextResourceId = res.getIdElement(); + + if (!nextResourceId.hasIdPart()) { + if (isNotBlank(myVersionAdapter.getFullUrl(nextReqEntry))) { + nextResourceId = newIdType(myVersionAdapter.getFullUrl(nextReqEntry)); + } + } + + if (nextResourceId.hasIdPart() && nextResourceId.getIdPart().matches("[a-zA-Z]+:.*") && !isPlaceholder(nextResourceId)) { + throw new InvalidRequestException("Invalid placeholder ID found: " + nextResourceId.getIdPart() + " - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'"); + } + + if (nextResourceId.hasIdPart() && !nextResourceId.hasResourceType() && !isPlaceholder(nextResourceId)) { + nextResourceId = newIdType(toResourceName(res.getClass()), nextResourceId.getIdPart()); + res.setId(nextResourceId); + } + + /* + * Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness + */ + if (isPlaceholder(nextResourceId)) { + if (!theAllIds.add(nextResourceId)) { + throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextResourceId)); + } + } else if (nextResourceId.hasResourceType() && nextResourceId.hasIdPart()) { + IIdType nextId = nextResourceId.toUnqualifiedVersionless(); + if (!theAllIds.add(nextId)) { + throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextId)); + } + } + + } + + String verb = myVersionAdapter.getEntryRequestVerb(nextReqEntry); + String resourceType = res != null ? myContext.getResourceDefinition(res).getName() : null; + Integer order = theOriginalRequestOrder.get(nextReqEntry); + IBase nextRespEntry = (IBase) myVersionAdapter.getEntries(theResponse).get(order); + + theTransactionStopWatch.startTask("Bundle.entry[" + i + "]: " + verb + " " + defaultString(resourceType)); + + switch (verb) { + case "POST": { + // CREATE + validateResourcePresent(res, order, verb); + @SuppressWarnings("rawtypes") + IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass()); + res.setId((String) null); + DaoMethodOutcome outcome; + String matchUrl = myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry); + matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl); + outcome = resourceDao.create(res, matchUrl, false, theUpdateTime, theRequest); + if (nextResourceId != null) { + handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequest); + } + entriesToProcess.put(nextRespEntry, outcome.getEntity()); + if (outcome.getCreated() == false) { + nonUpdatedEntities.add(outcome.getEntity()); + } else { + if (isNotBlank(matchUrl)) { + conditionalRequestUrls.put(matchUrl, res.getClass()); + } + } + + break; + } + case "DELETE": { + // DELETE + String url = extractTransactionUrlOrThrowException(nextReqEntry, verb); + UrlUtil.UrlParts parts = UrlUtil.parseUrl(url); + ca.uhn.fhir.jpa.dao.IFhirResourceDao dao = toDao(parts, verb, url); + int status = Constants.STATUS_HTTP_204_NO_CONTENT; + if (parts.getResourceId() != null) { + IIdType deleteId = newIdType(parts.getResourceType(), parts.getResourceId()); + if (!deletedResources.contains(deleteId.getValueAsString())) { + DaoMethodOutcome outcome = dao.delete(deleteId, deleteConflicts, theRequest); + if (outcome.getEntity() != null) { + deletedResources.add(deleteId.getValueAsString()); + entriesToProcess.put(nextRespEntry, outcome.getEntity()); + } + } + } else { + String matchUrl = parts.getResourceType() + '?' + parts.getParams(); + matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl); + DeleteMethodOutcome deleteOutcome = dao.deleteByUrl(matchUrl, deleteConflicts, theRequest); + List allDeleted = deleteOutcome.getDeletedEntities(); + for (ResourceTable deleted : allDeleted) { + deletedResources.add(deleted.getIdDt().toUnqualifiedVersionless().getValueAsString()); + } + if (allDeleted.isEmpty()) { + status = Constants.STATUS_HTTP_204_NO_CONTENT; + } + + myVersionAdapter.setResponseOutcome(nextRespEntry, deleteOutcome.getOperationOutcome()); + } + + myVersionAdapter.setResponseStatus(nextRespEntry, toStatusString(status)); + + break; + } + case "PUT": { + // UPDATE + validateResourcePresent(res, order, verb); + @SuppressWarnings("rawtypes") + IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass()); + + String url = extractTransactionUrlOrThrowException(nextReqEntry, verb); + + DaoMethodOutcome outcome; + UrlUtil.UrlParts parts = UrlUtil.parseUrl(url); + if (isNotBlank(parts.getResourceId())) { + String version = null; + if (isNotBlank(myVersionAdapter.getEntryRequestIfMatch(nextReqEntry))) { + version = ParameterUtil.parseETagValue(myVersionAdapter.getEntryRequestIfMatch(nextReqEntry)); + } + res.setId(newIdType(parts.getResourceType(), parts.getResourceId(), version)); + outcome = resourceDao.update(res, null, false, false, theRequest); + } else { + res.setId((String) null); + String matchUrl; + if (isNotBlank(parts.getParams())) { + matchUrl = parts.getResourceType() + '?' + parts.getParams(); + } else { + matchUrl = parts.getResourceType(); + } + matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl); + outcome = resourceDao.update(res, matchUrl, false, false, theRequest); + if (Boolean.TRUE.equals(outcome.getCreated())) { + conditionalRequestUrls.put(matchUrl, res.getClass()); + } + } + + if (outcome.getCreated() == Boolean.FALSE + || (outcome.getCreated() == Boolean.TRUE && outcome.getId().getVersionIdPartAsLong() > 1)) { + updatedEntities.add(outcome.getEntity()); + if (outcome.getResource() != null) { + updatedResources.add(outcome.getResource()); + } + } + + handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequest); + entriesToProcess.put(nextRespEntry, outcome.getEntity()); + break; + } + case "PATCH": { + // PATCH + validateResourcePresent(res, order, verb); + + String url = extractTransactionUrlOrThrowException(nextReqEntry, verb); + UrlUtil.UrlParts parts = UrlUtil.parseUrl(url); + + String matchUrl = toMatchUrl(nextReqEntry); + matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl); + String patchBody = null; + String contentType = null; + + if (res instanceof IBaseBinary) { + IBaseBinary binary = (IBaseBinary) res; + if (binary.getContent() != null && binary.getContent().length > 0) { + patchBody = new String(binary.getContent(), Charsets.UTF_8); + } + contentType = binary.getContentType(); + } + + if (isBlank(patchBody)) { + String msg = myContext.getLocalizer().getMessage(TransactionProcessor.class, "missingPatchBody"); + throw new InvalidRequestException(msg); + } + if (isBlank(contentType)) { + String msg = myContext.getLocalizer().getMessage(TransactionProcessor.class, "missingPatchContentType"); + throw new InvalidRequestException(msg); + } + + ca.uhn.fhir.jpa.dao.IFhirResourceDao dao = toDao(parts, verb, url); + PatchTypeEnum patchType = PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(contentType); + IIdType patchId = myContext.getVersion().newIdType().setValue(parts.getResourceId()); + DaoMethodOutcome outcome = dao.patch(patchId, matchUrl, patchType, patchBody, theRequest); + updatedEntities.add(outcome.getEntity()); + if (outcome.getResource() != null) { + updatedResources.add(outcome.getResource()); + } + + break; + } + case "GET": + break; + default: + throw new InvalidRequestException("Unable to handle verb in transaction: " + verb); + + } + + theTransactionStopWatch.endCurrentTask(); + } + + + /* + * Make sure that there are no conflicts from deletions. E.g. we can't delete something + * if something else has a reference to it.. Unless the thing that has a reference to it + * was also deleted as a part of this transaction, which is why we check this now at the + * end. + */ + for (Iterator iter = deleteConflicts.iterator(); iter.hasNext(); ) { + DeleteConflict nextDeleteConflict = iter.next(); + + /* + * If we have a conflict, it means we can't delete Resource/A because + * Resource/B has a reference to it. We'll ignore that conflict though + * if it turns out we're also deleting Resource/B in this transaction. + */ + if (deletedResources.contains(nextDeleteConflict.getSourceId().toUnqualifiedVersionless().getValue())) { + iter.remove(); + continue; + } + + /* + * And then, this is kind of a last ditch check. It's also ok to delete + * Resource/A if Resource/B isn't being deleted, but it is being UPDATED + * in this transaction, and the updated version of it has no references + * to Resource/A any more. + */ + String sourceId = nextDeleteConflict.getSourceId().toUnqualifiedVersionless().getValue(); + String targetId = nextDeleteConflict.getTargetId().toUnqualifiedVersionless().getValue(); + Optional updatedSource = updatedResources + .stream() + .filter(t -> sourceId.equals(t.getIdElement().toUnqualifiedVersionless().getValue())) + .findFirst(); + if (updatedSource.isPresent()) { + List referencesInSource = myContext.newTerser().getAllResourceReferences(updatedSource.get()); + boolean sourceStillReferencesTarget = referencesInSource + .stream() + .anyMatch(t -> targetId.equals(t.getResourceReference().getReferenceElement().toUnqualifiedVersionless().getValue())); + if (!sourceStillReferencesTarget) { + iter.remove(); + } + } + } + DeleteConflictService.validateDeleteConflictsEmptyOrThrowException(myContext, deleteConflicts); + + /* + * Perform ID substitutions and then index each resource we have saved + */ + + FhirTerser terser = myContext.newTerser(); + theTransactionStopWatch.startTask("Index " + theIdToPersistedOutcome.size() + " resources"); + int i = 0; + for (DaoMethodOutcome nextOutcome : theIdToPersistedOutcome.values()) { + + if (i++ % 250 == 0) { + ourLog.info("Have indexed {} entities out of {} in transaction", i, theIdToPersistedOutcome.values().size()); + } + + IBaseResource nextResource = nextOutcome.getResource(); + if (nextResource == null) { + continue; + } + + // References + List allRefs = terser.getAllResourceReferences(nextResource); + for (ResourceReferenceInfo nextRef : allRefs) { + IIdType nextId = nextRef.getResourceReference().getReferenceElement(); + if (!nextId.hasIdPart()) { + continue; + } + if (theIdSubstitutions.containsKey(nextId)) { + IIdType newId = theIdSubstitutions.get(nextId); + ourLog.debug(" * Replacing resource ref {} with {}", nextId, newId); + nextRef.getResourceReference().setReference(newId.getValue()); + } else if (nextId.getValue().startsWith("urn:")) { + throw new InvalidRequestException("Unable to satisfy placeholder ID " + nextId.getValue() + " found in element named '" + nextRef.getName() + "' within resource of type: " + nextResource.getIdElement().getResourceType()); + } else { + ourLog.debug(" * Reference [{}] does not exist in bundle", nextId); + } + } + + // URIs + Class> uriType = (Class>) myContext.getElementDefinition("uri").getImplementingClass(); + List> allUris = terser.getAllPopulatedChildElementsOfType(nextResource, uriType); + for (IPrimitiveType nextRef : allUris) { + if (nextRef instanceof IIdType) { + continue; // No substitution on the resource ID itself! + } + IIdType nextUriString = newIdType(nextRef.getValueAsString()); + if (theIdSubstitutions.containsKey(nextUriString)) { + IIdType newId = theIdSubstitutions.get(nextUriString); + ourLog.debug(" * Replacing resource ref {} with {}", nextUriString, newId); + nextRef.setValueAsString(newId.getValue()); + } else { + ourLog.debug(" * Reference [{}] does not exist in bundle", nextUriString); + } + } + + IPrimitiveType deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) nextResource); + Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null; + + IFhirResourceDao dao = myDaoRegistry.getResourceDao(nextResource.getClass()); + IJpaDao jpaDao = (IJpaDao) dao; + + if (updatedEntities.contains(nextOutcome.getEntity())) { + jpaDao.updateInternal(theRequest, nextResource, true, false, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource()); + } else if (!nonUpdatedEntities.contains(nextOutcome.getEntity())) { + jpaDao.updateEntity(theRequest, nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, theUpdateTime, false, true); + } + } + + theTransactionStopWatch.endCurrentTask(); + theTransactionStopWatch.startTask("Flush writes to database"); + + flushSession(theIdToPersistedOutcome); + + theTransactionStopWatch.endCurrentTask(); + if (conditionalRequestUrls.size() > 0) { + theTransactionStopWatch.startTask("Check for conflicts in conditional resources"); + } + + /* + * Double check we didn't allow any duplicates we shouldn't have + */ + for (Map.Entry> nextEntry : conditionalRequestUrls.entrySet()) { + String matchUrl = nextEntry.getKey(); + Class resType = nextEntry.getValue(); + if (isNotBlank(matchUrl)) { + Set val = myMatchResourceUrlService.processMatchUrl(matchUrl, resType, theRequest); + if (val.size() > 1) { + throw new InvalidRequestException( + "Unable to process " + theActionName + " - Request would cause multiple resources to match URL: \"" + matchUrl + "\". Does transaction request contain duplicates?"); + } + } + } + + theTransactionStopWatch.endCurrentTask(); + + for (IIdType next : theAllIds) { + IIdType replacement = theIdSubstitutions.get(next); + if (replacement == null) { + continue; + } + if (replacement.equals(next)) { + continue; + } + ourLog.debug("Placeholder resource ID \"{}\" was replaced with permanent ID \"{}\"", next, replacement); + } + return entriesToProcess; + + } finally { + if (theRequest != null) { + theRequest.stopDeferredRequestOperationCallbackAndRunDeferredItems(); + } + } + } + + protected abstract void flushSession(Map theIdToPersistedOutcome); + + private void validateResourcePresent(IBaseResource theResource, Integer theOrder, String theVerb) { + if (theResource == null) { + String msg = myContext.getLocalizer().getMessage(TransactionProcessor.class, "missingMandatoryResource", theVerb, theOrder); + throw new InvalidRequestException(msg); + } + } + + private IIdType newIdType(String theResourceType, String theResourceId, String theVersion) { + org.hl7.fhir.r4.model.IdType id = new org.hl7.fhir.r4.model.IdType(theResourceType, theResourceId, theVersion); + return myContext.getVersion().newIdType().setValue(id.getValue()); + } + + private IIdType newIdType(String theToResourceName, String theIdPart) { + return newIdType(theToResourceName, theIdPart, null); + } + + private IFhirResourceDao getDaoOrThrowException(Class theClass) { + return myDaoRegistry.getResourceDao(theClass); + } + + + private String toResourceName(Class theResourceType) { + return myContext.getResourceDefinition(theResourceType).getName(); + } + + public void setContext(FhirContext theContext) { + myContext = theContext; + } + + private String extractTransactionUrlOrThrowException(IBase nextEntry, String verb) { + String url = myVersionAdapter.getEntryRequestUrl(nextEntry); + if (isBlank(url)) { + throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionMissingUrl", verb)); + } + return url; + } + + private ca.uhn.fhir.jpa.dao.IFhirResourceDao toDao(UrlUtil.UrlParts theParts, String theVerb, String theUrl) { + RuntimeResourceDefinition resType; + try { + resType = myContext.getResourceDefinition(theParts.getResourceType()); + } catch (DataFormatException e) { + String msg = myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl); + throw new InvalidRequestException(msg); + } + IFhirResourceDao dao = null; + if (resType != null) { + dao = myDaoRegistry.getResourceDao(resType.getImplementingClass()); + } + if (dao == null) { + String msg = myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl); + throw new InvalidRequestException(msg); + } + + // if (theParts.getResourceId() == null && theParts.getParams() == null) { + // String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl); + // throw new InvalidRequestException(msg); + // } + + return dao; + } + + private String toMatchUrl(IBase theEntry) { + String verb = myVersionAdapter.getEntryRequestVerb(theEntry); + if (verb.equals("POST")) { + return myVersionAdapter.getEntryIfNoneExist(theEntry); + } + if (verb.equals("PATCH")) { + return myVersionAdapter.getEntryRequestIfMatch(theEntry); + } + if (verb.equals("PUT") || verb.equals("DELETE")) { + String url = extractTransactionUrlOrThrowException(theEntry, verb); + UrlUtil.UrlParts parts = UrlUtil.parseUrl(url); + if (isBlank(parts.getResourceId())) { + return parts.getResourceType() + '?' + parts.getParams(); + } + } + return null; + } + + public interface ITransactionProcessorVersionAdapter { + + void setResponseStatus(BUNDLEENTRY theBundleEntry, String theStatus); + + void setResponseLastModified(BUNDLEENTRY theBundleEntry, Date theLastModified); + + void setResource(BUNDLEENTRY theBundleEntry, IBaseResource theResource); + + IBaseResource getResource(BUNDLEENTRY theBundleEntry); + + String getBundleType(BUNDLE theRequest); + + void populateEntryWithOperationOutcome(BaseServerResponseException theCaughtEx, BUNDLEENTRY theEntry); + + BUNDLE createBundle(String theBundleType); + + List getEntries(BUNDLE theRequest); + + void addEntry(BUNDLE theBundle, BUNDLEENTRY theEntry); + + BUNDLEENTRY addEntry(BUNDLE theBundle); + + String getEntryRequestVerb(BUNDLEENTRY theEntry); + + String getFullUrl(BUNDLEENTRY theEntry); + + String getEntryIfNoneExist(BUNDLEENTRY theEntry); + + String getEntryRequestUrl(BUNDLEENTRY theEntry); + + void setResponseLocation(BUNDLEENTRY theEntry, String theResponseLocation); + + void setResponseETag(BUNDLEENTRY theEntry, String theEtag); + + String getEntryRequestIfMatch(BUNDLEENTRY theEntry); + + String getEntryRequestIfNoneExist(BUNDLEENTRY theEntry); + + String getEntryRequestIfNoneMatch(BUNDLEENTRY theEntry); + + void setResponseOutcome(BUNDLEENTRY theEntry, IBaseOperationOutcome theOperationOutcome); + + void setRequestVerb(BUNDLEENTRY theEntry, String theVerb); + + void setRequestUrl(BUNDLEENTRY theEntry, String theUrl); + } + + /** + * Transaction Order, per the spec: + *

+ * Process any DELETE interactions + * Process any POST interactions + * Process any PUT interactions + * Process any PATCH interactions + * Process any GET interactions + */ + //@formatter:off + public class TransactionSorter implements Comparator { + + private Set myPlaceholderIds; + + public TransactionSorter(Set thePlaceholderIds) { + myPlaceholderIds = thePlaceholderIds; + } + + @Override + public int compare(IBase theO1, IBase theO2) { + int o1 = toOrder(theO1); + int o2 = toOrder(theO2); + + if (o1 == o2) { + String matchUrl1 = toMatchUrl(theO1); + String matchUrl2 = toMatchUrl(theO2); + if (isBlank(matchUrl1) && isBlank(matchUrl2)) { + return 0; + } + if (isBlank(matchUrl1)) { + return -1; + } + if (isBlank(matchUrl2)) { + return 1; + } + + boolean match1containsSubstitutions = false; + boolean match2containsSubstitutions = false; + for (String nextPlaceholder : myPlaceholderIds) { + if (matchUrl1.contains(nextPlaceholder)) { + match1containsSubstitutions = true; + } + if (matchUrl2.contains(nextPlaceholder)) { + match2containsSubstitutions = true; + } + } + + if (match1containsSubstitutions && match2containsSubstitutions) { + return 0; + } + if (!match1containsSubstitutions && !match2containsSubstitutions) { + return 0; + } + if (match1containsSubstitutions) { + return 1; + } else { + return -1; + } + } + + return o1 - o2; + } + + private int toOrder(IBase theO1) { + int o1 = 0; + if (myVersionAdapter.getEntryRequestVerb(theO1) != null) { + switch (myVersionAdapter.getEntryRequestVerb(theO1)) { + case "DELETE": + o1 = 1; + break; + case "POST": + o1 = 2; + break; + case "PUT": + o1 = 3; + break; + case "PATCH": + o1 = 4; + break; + case "GET": + o1 = 5; + break; + default: + o1 = 0; + break; + } + } + return o1; + } + + } + + private static class BaseServerResponseExceptionHolder { + private BaseServerResponseException myException; + + public BaseServerResponseException getException() { + return myException; + } + + public void setException(BaseServerResponseException myException) { + this.myException = myException; + } + } + + public static boolean isPlaceholder(IIdType theId) { + if (theId != null && theId.getValue() != null) { + return theId.getValue().startsWith("urn:oid:") || theId.getValue().startsWith("urn:uuid:"); + } + return false; + } + + private static String toStatusString(int theStatusCode) { + return Integer.toString(theStatusCode) + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode)); + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoMethodOutcome.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoMethodOutcome.java index 6764e358801..f0a4a417d97 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoMethodOutcome.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoMethodOutcome.java @@ -20,13 +20,14 @@ package ca.uhn.fhir.jpa.dao; * #L% */ +import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.rest.api.MethodOutcome; import org.hl7.fhir.instance.model.api.IBaseResource; public class DaoMethodOutcome extends MethodOutcome { - private ResourceTable myEntity; + private IBasePersistedResource myEntity; private IBaseResource myPreviousResource; /** @@ -36,11 +37,11 @@ public class DaoMethodOutcome extends MethodOutcome { super(); } - public ResourceTable getEntity() { + public IBasePersistedResource getEntity() { return myEntity; } - public DaoMethodOutcome setEntity(ResourceTable theEntity) { + public DaoMethodOutcome setEntity(IBasePersistedResource theEntity) { myEntity = theEntity; return this; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoRegistry.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoRegistry.java index 4878f90e584..c4ddba66b4d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoRegistry.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoRegistry.java @@ -80,8 +80,7 @@ public class DaoRegistry implements ApplicationContextAware, IDaoRegistry { * @throws InvalidRequestException If the given resource type is not supported */ public IFhirResourceDao getResourceDao(String theResourceName) { - init(); - IFhirResourceDao retVal = myResourceNameToResourceDao.get(theResourceName); + IFhirResourceDao retVal = getResourceDaoOrNull(theResourceName); if (retVal == null) { List supportedResourceTypes = myResourceNameToResourceDao .keySet() @@ -126,12 +125,9 @@ public class DaoRegistry implements ApplicationContextAware, IDaoRegistry { } @Nullable - public IFhirResourceDao getResourceDaoOrNull(String theResourceType) { - try { - return (IFhirResourceDao) getResourceDao(theResourceType); - } catch (InvalidRequestException e) { - return null; - } + public IFhirResourceDao getResourceDaoOrNull(String theResourceName) { + init(); + return (IFhirResourceDao) myResourceNameToResourceDao.get(theResourceName); } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoSearchParamProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoSearchParamProvider.java index 8ff5b629a4f..34796ae6e73 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoSearchParamProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoSearchParamProvider.java @@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.dao; */ import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.searchparam.registry.BaseSearchParamRegistry; +import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; @@ -44,7 +44,7 @@ public class DaoSearchParamProvider implements ISearchParamProvider { } @Override - public int refreshCache(BaseSearchParamRegistry theSearchParamRegistry, long theRefreshInterval) { + public int refreshCache(SearchParamRegistryImpl theSearchParamRegistry, long theRefreshInterval) { TransactionTemplate txTemplate = new TransactionTemplate(myTxManager); return txTemplate.execute(t-> theSearchParamRegistry.doRefresh(theRefreshInterval)); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoBundleDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoBundleDstu2.java index 9d41830c5b4..b2d363a52fc 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoBundleDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoBundleDstu2.java @@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.dao; import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import org.hl7.fhir.instance.model.api.IBaseResource; import java.util.Set; @@ -31,16 +32,10 @@ import static org.apache.commons.lang3.StringUtils.defaultString; public class FhirResourceDaoBundleDstu2 extends BaseHapiFhirResourceDao { @Override - protected void preProcessResourceForStorage(Bundle theResource) { + protected void preProcessResourceForStorage(IBaseResource theResource) { super.preProcessResourceForStorage(theResource); - Set allowedBundleTypes = getConfig().getBundleTypesAllowedForStorage(); - if (!allowedBundleTypes.contains(defaultString(theResource.getType()))) { - String message = "Unable to store a Bundle resource on this server with a Bundle.type value of: " + (theResource.getType() != null ? theResource.getType() : "(missing)"); - throw new UnprocessableEntityException(message); - } - - for (Entry next : theResource.getEntry()) { + for (Entry next : ((Bundle)theResource).getEntry()) { next.setFullUrl((String) null); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java index 7315d2b3545..d0ccc38d50b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java @@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.dao; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao; +import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.SubscriptionTable; import ca.uhn.fhir.model.dstu2.resource.Subscription; @@ -73,12 +74,12 @@ public class FhirResourceDaoSubscriptionDstu2 extends BaseHapiFhirResourceDao findCodeSystemIdsContainingSystemAndCode(String theCode, String theSystem, RequestDetails theRequest) { if (theSystem != null && theSystem.startsWith("http://hl7.org/fhir/")) { - return Collections.singletonList((IIdType) new IdDt(theSystem)); + return Collections.singletonList(new IdDt(theSystem)); } List valueSetIds; - Set ids = searchForIds(new SearchParameterMap(ValueSet.SP_CODE, new TokenParam(theSystem, theCode)), theRequest); + Set ids = searchForIds(new SearchParameterMap(ValueSet.SP_CODE, new TokenParam(theSystem, theCode)), theRequest); valueSetIds = new ArrayList(); - for (Long next : ids) { - valueSetIds.add(new IdDt("ValueSet", next)); + for (ResourcePersistentId next : ids) { + IIdType id = myIdHelperService.translatePidIdToForcedId(myFhirContext, "ValueSet", next); + valueSetIds.add(id); } return valueSetIds; } @@ -309,10 +313,11 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao ids = searchForIds(new SearchParameterMap(ValueSet.SP_IDENTIFIER, new TokenParam(null, theValueSetIdentifier.getValue())), theRequest); + Set ids = searchForIds(new SearchParameterMap(ValueSet.SP_IDENTIFIER, new TokenParam(null, theValueSetIdentifier.getValue())), theRequest); valueSetIds = new ArrayList<>(); - for (Long next : ids) { - valueSetIds.add(new IdDt("ValueSet", next)); + for (ResourcePersistentId next : ids) { + IIdType id = myIdHelperService.translatePidIdToForcedId(myFhirContext, "ValueSet", next); + valueSetIds.add(id); } } else { if (theCode == null || theCode.isEmpty()) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java index 34d98c5de73..bc343aac3e8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java @@ -22,6 +22,9 @@ package ca.uhn.fhir.jpa.dao; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.jpa.delete.DeleteConflictList; +import ca.uhn.fhir.jpa.delete.DeleteConflictService; +import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.TagDefinition; import ca.uhn.fhir.rest.server.servlet.ServletSubRequestDetails; @@ -86,6 +89,8 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { private MatchUrlService myMatchUrlService; @Autowired private DaoRegistry myDaoRegistry; + @Autowired + private MatchResourceUrlService myMatchResourceUrlService; private Bundle batch(final RequestDetails theRequestDetails, Bundle theRequest) { ourLog.info("Beginning batch with {} resources", theRequest.getEntry().size()); @@ -213,9 +218,9 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { List deletedResources = new ArrayList<>(); DeleteConflictList deleteConflicts = new DeleteConflictList(); - Map entriesToProcess = new IdentityHashMap<>(); - Set nonUpdatedEntities = new HashSet(); - Set updatedEntities = new HashSet<>(); + Map entriesToProcess = new IdentityHashMap<>(); + Set nonUpdatedEntities = new HashSet<>(); + Set updatedEntities = new HashSet<>(); /* * Handle: GET/PUT/POST @@ -293,7 +298,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { } - for (Map.Entry nextEntry : entriesToProcess.entrySet()) { + for (Map.Entry nextEntry : entriesToProcess.entrySet()) { nextEntry.getKey().getResponse().setLocation(nextEntry.getValue().getIdDt().toUnqualified().getValue()); nextEntry.getKey().getResponse().setEtag(nextEntry.getValue().getIdDt().getVersionIdPart()); } @@ -301,13 +306,13 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { long delay = System.currentTimeMillis() - start; int numEntries = theRequest.getEntry().size(); long delayPer = delay / numEntries; - ourLog.info("{} completed in {}ms ({} entries at {}ms per entry)", new Object[] {theActionName, delay, numEntries, delayPer}); + ourLog.info("{} completed in {}ms ({} entries at {}ms per entry)", theActionName, delay, numEntries, delayPer); response.setType(BundleTypeEnum.TRANSACTION_RESPONSE); return response; } - private void handleTransactionWriteOperations(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName, Date theUpdateTime, Set theAllIds, Map theIdSubstitutions, Map theIdToPersistedOutcome, Bundle theResponse, IdentityHashMap theOriginalRequestOrder, List theDeletedResources, DeleteConflictList theDeleteConflicts, Map theEntriesToProcess, Set theNonUpdatedEntities, Set theUpdatedEntities) { + private void handleTransactionWriteOperations(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName, Date theUpdateTime, Set theAllIds, Map theIdSubstitutions, Map theIdToPersistedOutcome, Bundle theResponse, IdentityHashMap theOriginalRequestOrder, List theDeletedResources, DeleteConflictList theDeleteConflicts, Map theEntriesToProcess, Set theNonUpdatedEntities, Set theUpdatedEntities) { /* * Loop through the request and process any entries of type * PUT, POST or DELETE @@ -331,7 +336,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { } } - if (nextResourceId.hasIdPart() && nextResourceId.getIdPart().matches("[a-zA-Z]+\\:.*") && !isPlaceholder(nextResourceId)) { + if (nextResourceId.hasIdPart() && nextResourceId.getIdPart().matches("[a-zA-Z]+:.*") && !isPlaceholder(nextResourceId)) { throw new InvalidRequestException("Invalid placeholder ID found: " + nextResourceId.getIdPart() + " - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'"); } @@ -444,7 +449,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { */ theDeleteConflicts.removeIf(next -> theDeletedResources.contains(next.getTargetId().toVersionless())); - myDeleteConflictService.validateDeleteConflictsEmptyOrThrowException(theDeleteConflicts); + DeleteConflictService.validateDeleteConflictsEmptyOrThrowException(getContext(), theDeleteConflicts); /* * Perform ID substitutions and then index each resource we have saved @@ -508,8 +513,8 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { if (nextEntry.getRequest().getMethodElement().getValueAsEnum() == HTTPVerbEnum.POST) { String matchUrl = nextEntry.getRequest().getIfNoneExist(); if (isNotBlank(matchUrl)) { - IFhirResourceDao resourceDao = getDao(nextEntry.getResource().getClass()); - Set val = resourceDao.processMatchUrl(matchUrl, theRequestDetails); + Class resType = nextEntry.getResource().getClass(); + Set val = myMatchResourceUrlService.processMatchUrl(matchUrl, resType, theRequestDetails); if (val.size() > 1) { throw new InvalidRequestException( "Unable to process " + theActionName + " - Request would cause multiple resources to match URL: \"" + matchUrl + "\". Does transaction request contain duplicates?"); @@ -623,11 +628,11 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { } private Bundle transaction(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName) { - super.markRequestAsProcessingSubRequest(theRequestDetails); + markRequestAsProcessingSubRequest(theRequestDetails); try { return doTransaction(theRequestDetails, theRequest, theActionName); } finally { - super.clearRequestAsProcessingSubRequest(theRequestDetails); + clearRequestAsProcessingSubRequest(theRequestDetails); } } @@ -655,15 +660,13 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { private static boolean isPlaceholder(IdDt theId) { if (theId.getValue() != null) { - if (theId.getValue().startsWith("urn:oid:") || theId.getValue().startsWith("urn:uuid:")) { - return true; - } + return theId.getValue().startsWith("urn:oid:") || theId.getValue().startsWith("urn:uuid:"); } return false; } private static String toStatusString(int theStatusCode) { - return Integer.toString(theStatusCode) + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode)); + return theStatusCode + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode)); } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java index 49eb05a4e5f..8236bb2517f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java @@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.dao; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.api.IQueryParameterType; @@ -116,10 +117,10 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { } } - private List doSearch(String theResourceName, SearchParameterMap theParams, Long theReferencingPid) { + private List doSearch(String theResourceName, SearchParameterMap theParams, ResourcePersistentId theReferencingPid) { FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager); - List pids = null; + List pids = null; /* * Handle textual params @@ -202,12 +203,12 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { // execute search List result = jpaQuery.getResultList(); - ArrayList retVal = new ArrayList<>(); + ArrayList retVal = new ArrayList<>(); for (Object object : result) { Object[] nextArray = (Object[]) object; Long next = (Long) nextArray[0]; if (next != null) { - retVal.add(next); + retVal.add(new ResourcePersistentId(next)); } } @@ -215,9 +216,9 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { } @Override - public List everything(String theResourceName, SearchParameterMap theParams, RequestDetails theRequest) { + public List everything(String theResourceName, SearchParameterMap theParams, RequestDetails theRequest) { - Long pid = null; + ResourcePersistentId pid = null; if (theParams.get(IAnyResource.SP_RES_ID) != null) { String idParamValue; IQueryParameterType idParam = theParams.get(IAnyResource.SP_RES_ID).get(0).get(0); @@ -231,8 +232,8 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { pid = myIdHelperService.translateForcedIdToPid(theResourceName, idParamValue, theRequest); } - Long referencingPid = pid; - List retVal = doSearch(null, theParams, referencingPid); + ResourcePersistentId referencingPid = pid; + List retVal = doSearch(null, theParams, referencingPid); if (referencingPid != null) { retVal.add(referencingPid); } @@ -264,7 +265,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { @Transactional() @Override - public List search(String theResourceName, SearchParameterMap theParams) { + public List search(String theResourceName, SearchParameterMap theParams) { return doSearch(theResourceName, theParams, null); } @@ -281,7 +282,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { if (contextParts.length != 3 || "Patient".equals(contextParts[0]) == false || "$everything".equals(contextParts[2]) == false) { throw new InvalidRequestException("Invalid context: " + theContext); } - Long pid = myIdHelperService.translateForcedIdToPid(contextParts[0], contextParts[1], theRequest); + ResourcePersistentId pid = myIdHelperService.translateForcedIdToPid(contextParts[0], contextParts[1], theRequest); FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java index 843eb14b43a..128884b4527 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java @@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.dao; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.jpa.delete.DeleteConflictList; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.BaseHasResource; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; @@ -154,8 +155,6 @@ public interface IFhirResourceDao extends IDao { DaoMethodOutcome patch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, RequestDetails theRequestDetails); - Set processMatchUrl(String theMatchUrl, RequestDetails theRequest); - /** * Read a resource - Note that this variant of the method does not take in a {@link RequestDetails} and * therefore can not fire any interceptors. Use only for internal system calls @@ -165,7 +164,7 @@ public interface IFhirResourceDao extends IDao { /** * Read a resource by its internal PID */ - IBaseResource readByPid(Long thePid); + IBaseResource readByPid(ResourcePersistentId thePid); /** * @param theId @@ -205,7 +204,7 @@ public interface IFhirResourceDao extends IDao { @Transactional(propagation = Propagation.SUPPORTS) IBundleProvider search(SearchParameterMap theParams, RequestDetails theRequestDetails, HttpServletResponse theServletResponse); - Set searchForIds(SearchParameterMap theParams, RequestDetails theRequest); + Set searchForIds(SearchParameterMap theParams, RequestDetails theRequest); /** * Takes a map of incoming raw search parameters and translates/parses them into diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFulltextSearchSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFulltextSearchSvc.java index 0ab989e97cb..fed8de1d10e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFulltextSearchSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFulltextSearchSvc.java @@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.dao; import java.util.List; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.server.RequestDetails; @@ -30,9 +31,9 @@ public interface IFulltextSearchSvc { List suggestKeywords(String theContext, String theSearchParam, String theText, RequestDetails theRequest); - List search(String theResourceName, SearchParameterMap theParams); + List search(String theResourceName, SearchParameterMap theParams); - List everything(String theResourceName, SearchParameterMap theParams, RequestDetails theRequest); + List everything(String theResourceName, SearchParameterMap theParams, RequestDetails theRequest); boolean isDisabled(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IJpaDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IJpaDao.java index d90d97d969a..5ba308301d4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IJpaDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IJpaDao.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao; * #L% */ +import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.rest.api.server.RequestDetails; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -29,10 +30,10 @@ import java.util.Date; public interface IJpaDao { @SuppressWarnings("unchecked") - ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, ResourceTable + IBasePersistedResource updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry); - ResourceTable updateInternal(RequestDetails theRequestDetails, T theResource, boolean thePerformIndexing, boolean theForceUpdateVersion, - ResourceTable theEntity, IIdType theResourceId, IBaseResource theOldResource); + IBasePersistedResource updateInternal(RequestDetails theRequestDetails, T theResource, boolean thePerformIndexing, boolean theForceUpdateVersion, + IBasePersistedResource theEntity, IIdType theResourceId, IBaseResource theOldResource); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IResultIterator.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IResultIterator.java index bd4287f61a6..17358a34bad 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IResultIterator.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IResultIterator.java @@ -20,10 +20,12 @@ package ca.uhn.fhir.jpa.dao; * #L% */ +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; + import java.io.Closeable; import java.util.Iterator; -public interface IResultIterator extends Iterator, Closeable { +public interface IResultIterator extends Iterator, Closeable { int getSkippedCount(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java index 239e8b28f6c..2d1637c2244 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchBuilder.java @@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.dao; */ import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.api.Include; @@ -43,9 +44,9 @@ public interface ISearchBuilder { Iterator createCountQuery(SearchParameterMap theParams, String theSearchUuid, RequestDetails theRequest); - void loadResourcesByPid(Collection thePids, Collection theIncludedPids, List theResourceListToPopulate, boolean theForHistoryOperation, RequestDetails theDetails); + void loadResourcesByPid(Collection thePids, Collection theIncludedPids, List theResourceListToPopulate, boolean theForHistoryOperation, RequestDetails theDetails); - Set loadIncludes(FhirContext theContext, EntityManager theEntityManager, Collection theMatches, Set theRevIncludes, boolean theReverseMode, + Set loadIncludes(FhirContext theContext, EntityManager theEntityManager, Collection theMatches, Set theRevIncludes, boolean theReverseMode, DateRangeParam theLastUpdated, String theSearchIdOrDescription, RequestDetails theRequest); /** @@ -55,6 +56,6 @@ public interface ISearchBuilder { void setType(Class theResourceType, String theResourceName); - void setPreviouslyAddedResourcePids(List thePreviouslyAddedResourcePids); + void setPreviouslyAddedResourcePids(List thePreviouslyAddedResourcePids); } 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 dd4bba809b1..f6f2642c4eb 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 @@ -25,8 +25,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition; 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.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; @@ -37,7 +36,6 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.StopWatch; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.Request; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -54,7 +52,7 @@ public class MatchResourceUrlService { @Autowired private IInterceptorBroadcaster myInterceptorBroadcaster; - public Set processMatchUrl(String theMatchUrl, Class theResourceType, RequestDetails theRequest) { + public Set processMatchUrl(String theMatchUrl, Class theResourceType, RequestDetails theRequest) { StopWatch sw = new StopWatch(); RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(theResourceType); @@ -71,7 +69,7 @@ public class MatchResourceUrlService { throw new InternalErrorException("No DAO for resource type: " + theResourceType.getName()); } - Set retVal = dao.searchForIds(paramMap, theRequest); + Set retVal = dao.searchForIds(paramMap, theRequest); // Interceptor broadcast: JPA_PERFTRACE_INFO if (JpaInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_INFO, myInterceptorBroadcaster, theRequest)) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index 43c194050f6..7c3b306e392 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -20,7 +20,15 @@ package ca.uhn.fhir.jpa.dao; * #L% */ -import ca.uhn.fhir.context.*; +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; +import ca.uhn.fhir.context.RuntimeChildResourceDefinition; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; @@ -30,6 +38,7 @@ import ca.uhn.fhir.jpa.dao.data.IResourceTagDao; import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.entity.ResourceSearchView; import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; @@ -40,10 +49,20 @@ import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.util.SourceParam; -import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.VersionIndependentConcept; -import ca.uhn.fhir.jpa.util.*; -import ca.uhn.fhir.model.api.*; +import ca.uhn.fhir.jpa.term.api.ITermReadSvc; +import ca.uhn.fhir.jpa.util.BaseIterator; +import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener; +import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; +import ca.uhn.fhir.jpa.util.ScrollableResultsIterator; +import ca.uhn.fhir.jpa.util.SqlQueryList; +import ca.uhn.fhir.model.api.IPrimitiveDatatype; +import ca.uhn.fhir.model.api.IQueryParameterAnd; +import ca.uhn.fhir.model.api.IQueryParameterOr; +import ca.uhn.fhir.model.api.IQueryParameterType; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.model.base.composite.BaseIdentifierDt; import ca.uhn.fhir.model.base.composite.BaseQuantityDt; @@ -51,7 +70,11 @@ import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.rest.api.*; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.QualifiedParamList; +import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; +import ca.uhn.fhir.rest.api.SortOrderEnum; +import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.*; @@ -96,7 +119,11 @@ import java.util.Map.Entry; import java.util.stream.Collectors; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.defaultIfBlank; +import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.trim; /** * The SearchBuilder is responsible for actually forming the SQL query that handles @@ -106,14 +133,14 @@ import static org.apache.commons.lang3.StringUtils.*; @Scope("prototype") public class SearchBuilder implements ISearchBuilder { - private static final List EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList<>()); + private static final List EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList<>()); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchBuilder.class); /** * See loadResourcesByPid * for an explanation of why we use the constant 800 */ private static final int MAXIMUM_PAGE_SIZE = 800; - private static Long NO_MORE = -1L; + private static ResourcePersistentId NO_MORE = new ResourcePersistentId(-1L); private final boolean myDontUseHashesForSearch; private final DaoConfig myDaoConfig; @Autowired @@ -138,7 +165,7 @@ public class SearchBuilder implements ISearchBuilder { private ITermReadSvc myTerminologySvc; @Autowired private MatchUrlService myMatchUrlService; - private List myAlsoIncludePids; + private List myAlsoIncludePids; private CriteriaBuilder myBuilder; private BaseHapiFhirDao myCallingDao; private Map> myIndexJoins = Maps.newHashMap(); @@ -151,7 +178,7 @@ public class SearchBuilder implements ISearchBuilder { private String mySearchUuid; private int myFetchSize; private Integer myMaxResultsToFetch; - private Set myPidSet; + private Set myPidSet; private boolean myHaveIndexJoins = false; /** @@ -542,11 +569,11 @@ public class SearchBuilder implements ISearchBuilder { List codePredicates = new ArrayList<>(); // Resources by ID - List targetPids = myIdHelperService.translateForcedIdToPids(targetIds, theRequest); + List targetPids = myIdHelperService.translateForcedIdToPids(targetIds, theRequest); if (!targetPids.isEmpty()) { ourLog.debug("Searching for resource link with target PIDs: {}", targetPids); Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, join); - Predicate pidPredicate = join.get("myTargetResourcePid").in(targetPids); + Predicate pidPredicate = join.get("myTargetResourcePid").in(ResourcePersistentId.toLongList(targetPids)); codePredicates.add(myBuilder.and(pathPredicate, pidPredicate)); } @@ -573,7 +600,6 @@ public class SearchBuilder implements ISearchBuilder { private Predicate addPredicateReferenceWithChain(String theResourceName, String theParamName, List theList, Join theJoin, List theCodePredicates, ReferenceParam theRef, RequestDetails theRequest) { final List> resourceTypes; - String resourceId; if (!theRef.hasResourceType()) { RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName); @@ -631,14 +657,11 @@ public class SearchBuilder implements ISearchBuilder { } } - resourceId = theRef.getValue(); - } else { try { RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theRef.getResourceType()); resourceTypes = new ArrayList<>(1); resourceTypes.add(resDef.getImplementingClass()); - resourceId = theRef.getIdPart(); } catch (DataFormatException e) { throw new InvalidRequestException("Invalid resource type: " + theRef.getResourceType()); } @@ -712,6 +735,42 @@ public class SearchBuilder implements ISearchBuilder { return predicate; } + private void addPredicateSource(List> theAndOrParams, RequestDetails theRequest) { + for (List nextAnd : theAndOrParams) { + addPredicateSource(nextAnd, SearchFilterParser.CompareOperation.eq, theRequest); + } + } + + private Predicate addPredicateSource(List theList, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) { + if (myDaoConfig.getStoreMetaSourceInformation() == DaoConfig.StoreMetaSourceInformationEnum.NONE) { + String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, "sourceParamDisabled"); + throw new InvalidRequestException(msg); + } + + Join join = myResourceTableRoot.join("myProvenance", JoinType.LEFT); + + List codePredicates = new ArrayList<>(); + + for (IQueryParameterType nextParameter : theList) { + SourceParam sourceParameter = new SourceParam(nextParameter.getValueAsQueryToken(myContext)); + String sourceUri = sourceParameter.getSourceUri(); + String requestId = sourceParameter.getRequestId(); + Predicate sourceUriPredicate = myBuilder.equal(join.get("mySourceUri"), sourceUri); + Predicate requestIdPredicate = myBuilder.equal(join.get("myRequestId"), requestId); + if (isNotBlank(sourceUri) && isNotBlank(requestId)) { + codePredicates.add(myBuilder.and(sourceUriPredicate, requestIdPredicate)); + } else if (isNotBlank(sourceUri)) { + codePredicates.add(sourceUriPredicate); + } else if (isNotBlank(requestId)) { + codePredicates.add(requestIdPredicate); + } + } + + Predicate retVal = myBuilder.or(toArray(codePredicates)); + myPredicates.add(retVal); + return retVal; + } + private Subquery createLinkSubquery(boolean theFoundChainMatch, String theChain, String theSubResourceName, List theOrValues, RequestDetails theRequest) { Subquery subQ = myResourceTableQuery.subquery(Long.class); Root subQfrom = subQ.from(ResourceTable.class); @@ -772,8 +831,8 @@ public class SearchBuilder implements ISearchBuilder { return chainValue; } - private Predicate addPredicateResourceId(String theResourceName, List> theValues, RequestDetails theRequest) { - return addPredicateResourceId(theValues, theResourceName, null, theRequest); + private void addPredicateResourceId(String theResourceName, List> theValues, RequestDetails theRequest) { + addPredicateResourceId(theValues, theResourceName, null, theRequest); } private Predicate addPredicateResourceId(List> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) { @@ -792,10 +851,10 @@ public class SearchBuilder implements ISearchBuilder { private Predicate createPredicateResourceId(Root theRoot, String theResourceName, List> theValues, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) { Predicate nextPredicate = null; - Set allOrPids = null; + Set allOrPids = null; for (List nextValue : theValues) { - Set orPids = new HashSet<>(); + Set orPids = new HashSet<>(); boolean haveValue = false; for (IQueryParameterType next : nextValue) { String value = next.getValueAsQueryToken(myContext); @@ -807,7 +866,7 @@ public class SearchBuilder implements ISearchBuilder { if (isNotBlank(value)) { haveValue = true; try { - Long pid = myIdHelperService.translateForcedIdToPid(theResourceName, valueAsId.getIdPart(), theRequest); + ResourcePersistentId pid = myIdHelperService.translateForcedIdToPid(theResourceName, valueAsId.getIdPart(), theRequest); orPids.add(pid); } catch (ResourceNotFoundException e) { // This is not an error in a search, it just results in no matchesFhirResourceDaoR4InterceptorTest @@ -838,12 +897,12 @@ public class SearchBuilder implements ISearchBuilder { switch (operation) { default: case eq: - codePredicates.add(theRoot.get("myId").as(Long.class).in(allOrPids)); + codePredicates.add(theRoot.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(allOrPids))); codePredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), theResourceName)); nextPredicate = myBuilder.and(toArray(codePredicates)); break; case ne: - codePredicates.add(theRoot.get("myId").as(Long.class).in(allOrPids).not()); + codePredicates.add(theRoot.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(allOrPids)).not()); codePredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), theResourceName)); nextPredicate = myBuilder.and(toArray(codePredicates)); break; @@ -855,41 +914,10 @@ public class SearchBuilder implements ISearchBuilder { } - private Predicate addPredicateSource(List theList, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) { - if (myDaoConfig.getStoreMetaSourceInformation() == DaoConfig.StoreMetaSourceInformationEnum.NONE) { - String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, "sourceParamDisabled"); - throw new InvalidRequestException(msg); - } - - Join join = myResourceTableRoot.join("myProvenance", JoinType.LEFT); - - List codePredicates = new ArrayList<>(); - - for (IQueryParameterType nextParameter : theList) { - SourceParam sourceParameter = new SourceParam(nextParameter.getValueAsQueryToken(myContext)); - String sourceUri = sourceParameter.getSourceUri(); - String requestId = sourceParameter.getRequestId(); - Predicate sourceUriPredicate = myBuilder.equal(join.get("mySourceUri"), sourceUri); - Predicate requestIdPredicate = myBuilder.equal(join.get("myRequestId"), requestId); - if (isNotBlank(sourceUri) && isNotBlank(requestId)) { - codePredicates.add(myBuilder.and(sourceUriPredicate, requestIdPredicate)); - } else if (isNotBlank(sourceUri)) { - codePredicates.add(sourceUriPredicate); - } else if (isNotBlank(requestId)) { - codePredicates.add(requestIdPredicate); - } - } - - Predicate retVal = myBuilder.or(toArray(codePredicates)); - myPredicates.add(retVal); - return retVal; - } - - - private Predicate addPredicateString(String theResourceName, + private void addPredicateString(String theResourceName, String theParamName, List theList) { - return addPredicateString(theResourceName, + addPredicateString(theResourceName, theParamName, theList, SearchFilterParser.CompareOperation.sw); @@ -2015,7 +2043,7 @@ public class SearchBuilder implements ISearchBuilder { * @param thePidSet May be null */ @Override - public void setPreviouslyAddedResourcePids(@Nullable List thePidSet) { + public void setPreviouslyAddedResourcePids(@Nullable List thePidSet) { myPidSet = new HashSet<>(thePidSet); } @@ -2081,12 +2109,12 @@ public class SearchBuilder implements ISearchBuilder { if (myParams.get(IAnyResource.SP_RES_ID) != null) { StringParam idParm = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0); - Long pid = myIdHelperService.translateForcedIdToPid(myResourceName, idParm.getValue(), theRequest); + ResourcePersistentId pid = myIdHelperService.translateForcedIdToPid(myResourceName, idParm.getValue(), theRequest); if (myAlsoIncludePids == null) { myAlsoIncludePids = new ArrayList<>(1); } myAlsoIncludePids.add(pid); - myPredicates.add(myBuilder.equal(join.get("myTargetResourcePid").as(Long.class), pid)); + myPredicates.add(myBuilder.equal(join.get("myTargetResourcePid").as(Long.class), pid.getIdAsLong())); } else { Predicate targetTypePredicate = myBuilder.equal(join.get("myTargetResourceType").as(String.class), myResourceName); Predicate sourceTypePredicate = myBuilder.equal(myResourceTableRoot.get("myResourceType").as(String.class), myResourceName); @@ -2110,7 +2138,7 @@ public class SearchBuilder implements ISearchBuilder { } } - List pids; + List pids; if (myParams.getEverythingMode() != null) { pids = myFulltextSearchSvc.everything(myResourceName, myParams, theRequest); } else { @@ -2118,10 +2146,10 @@ public class SearchBuilder implements ISearchBuilder { } if (pids.isEmpty()) { // Will never match - pids = Collections.singletonList(-1L); + pids = Collections.singletonList(new ResourcePersistentId(-1L)); } - myPredicates.add(myResourceTableRoot.get("myId").as(Long.class).in(pids)); + myPredicates.add(myResourceTableRoot.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(pids))); } /* @@ -2315,21 +2343,21 @@ public class SearchBuilder implements ISearchBuilder { return retVal; } - private void doLoadPids(Collection thePids, Collection theIncludedPids, List theResourceListToPopulate, boolean theForHistoryOperation, - Map thePosition, RequestDetails theRequest) { + private void doLoadPids(Collection thePids, Collection theIncludedPids, List theResourceListToPopulate, boolean theForHistoryOperation, + Map thePosition, RequestDetails theRequest) { // -- get the resource from the searchView - Collection resourceSearchViewList = myResourceSearchViewDao.findByResourceIds(thePids); + Collection resourceSearchViewList = myResourceSearchViewDao.findByResourceIds(ResourcePersistentId.toLongList(thePids)); //-- preload all tags with tag definition if any - Map> tagMap = getResourceTagMap(resourceSearchViewList); + Map> tagMap = getResourceTagMap(resourceSearchViewList); - Long resourceId; + ResourcePersistentId resourceId; for (ResourceSearchView next : resourceSearchViewList) { Class resourceType = myContext.getResourceDefinition(next.getResourceType()).getImplementingClass(); - resourceId = next.getId(); + resourceId = new ResourcePersistentId(next.getId()); IBaseResource resource = myCallingDao.toResource(resourceType, next, tagMap.get(resourceId), theForHistoryOperation); if (resource == null) { @@ -2360,7 +2388,7 @@ public class SearchBuilder implements ISearchBuilder { } } - private Map> getResourceTagMap(Collection theResourceSearchViewList) { + private Map> getResourceTagMap(Collection theResourceSearchViewList) { List idList = new ArrayList<>(theResourceSearchViewList.size()); @@ -2370,7 +2398,7 @@ public class SearchBuilder implements ISearchBuilder { idList.add(resource.getId()); } - Map> tagMap = new HashMap<>(); + Map> tagMap = new HashMap<>(); //-- no tags if (idList.size() == 0) @@ -2380,11 +2408,11 @@ public class SearchBuilder implements ISearchBuilder { Collection tagList = myResourceTagDao.findByResourceIds(idList); //-- build the map, key = resourceId, value = list of ResourceTag - Long resourceId; + ResourcePersistentId resourceId; Collection tagCol; for (ResourceTag tag : tagList) { - resourceId = tag.getResourceId(); + resourceId = new ResourcePersistentId(tag.getResourceId()); tagCol = tagMap.get(resourceId); if (tagCol == null) { tagCol = new ArrayList<>(); @@ -2399,7 +2427,7 @@ public class SearchBuilder implements ISearchBuilder { } @Override - public void loadResourcesByPid(Collection thePids, Collection theIncludedPids, List theResourceListToPopulate, boolean theForHistoryOperation, RequestDetails theDetails) { + public void loadResourcesByPid(Collection thePids, Collection theIncludedPids, List theResourceListToPopulate, boolean theForHistoryOperation, RequestDetails theDetails) { if (thePids.isEmpty()) { ourLog.debug("The include pids are empty"); // return; @@ -2409,8 +2437,8 @@ public class SearchBuilder implements ISearchBuilder { // when running asserts assert new HashSet<>(thePids).size() == thePids.size() : "PID list contains duplicates: " + thePids; - Map position = new HashMap<>(); - for (Long next : thePids) { + Map position = new HashMap<>(); + for (ResourcePersistentId next : thePids) { position.put(next, theResourceListToPopulate.size()); theResourceListToPopulate.add(null); } @@ -2421,11 +2449,11 @@ public class SearchBuilder implements ISearchBuilder { * if it's lots of IDs. I suppose maybe we should be doing this as a join anyhow * but this should work too. Sigh. */ - List pids = new ArrayList<>(thePids); + List pids = new ArrayList<>(thePids); for (int i = 0; i < pids.size(); i += MAXIMUM_PAGE_SIZE) { int to = i + MAXIMUM_PAGE_SIZE; to = Math.min(to, pids.size()); - List pidsSubList = pids.subList(i, to); + List pidsSubList = pids.subList(i, to); doLoadPids(pidsSubList, theIncludedPids, theResourceListToPopulate, theForHistoryOperation, position, theDetails); } @@ -2436,7 +2464,7 @@ public class SearchBuilder implements ISearchBuilder { * so it can't be Collections.emptySet() or some such thing */ @Override - public HashSet loadIncludes(FhirContext theContext, EntityManager theEntityManager, Collection theMatches, Set theRevIncludes, + public HashSet loadIncludes(FhirContext theContext, EntityManager theEntityManager, Collection theMatches, Set theRevIncludes, boolean theReverseMode, DateRangeParam theLastUpdated, String theSearchIdOrDescription, RequestDetails theRequest) { if (theMatches.size() == 0) { return new HashSet<>(); @@ -2446,9 +2474,9 @@ public class SearchBuilder implements ISearchBuilder { } String searchFieldName = theReverseMode ? "myTargetResourcePid" : "mySourceResourcePid"; - Collection nextRoundMatches = theMatches; - HashSet allAdded = new HashSet<>(); - HashSet original = new HashSet<>(theMatches); + Collection nextRoundMatches = theMatches; + HashSet allAdded = new HashSet<>(); + HashSet original = new HashSet<>(theMatches); ArrayList includes = new ArrayList<>(theRevIncludes); int roundCounts = 0; @@ -2458,7 +2486,7 @@ public class SearchBuilder implements ISearchBuilder { do { roundCounts++; - HashSet pidsToInclude = new HashSet<>(); + HashSet pidsToInclude = new HashSet<>(); for (Iterator iter = includes.iterator(); iter.hasNext(); ) { Include nextInclude = iter.next(); @@ -2470,16 +2498,16 @@ public class SearchBuilder implements ISearchBuilder { if (matchAll) { String sql; sql = "SELECT r FROM ResourceLink r WHERE r." + searchFieldName + " IN (:target_pids) "; - List> partitions = partition(nextRoundMatches, MAXIMUM_PAGE_SIZE); - for (Collection nextPartition : partitions) { + List> partitions = partition(nextRoundMatches, MAXIMUM_PAGE_SIZE); + for (Collection nextPartition : partitions) { TypedQuery q = theEntityManager.createQuery(sql, ResourceLink.class); - q.setParameter("target_pids", nextPartition); + q.setParameter("target_pids", ResourcePersistentId.toLongList(nextPartition)); List results = q.getResultList(); for (ResourceLink resourceLink : results) { if (theReverseMode) { - pidsToInclude.add(resourceLink.getSourceResourcePid()); + pidsToInclude.add(new ResourcePersistentId(resourceLink.getSourceResourcePid())); } else { - pidsToInclude.add(resourceLink.getTargetResourcePid()); + pidsToInclude.add(new ResourcePersistentId(resourceLink.getTargetResourcePid())); } } } @@ -2523,11 +2551,11 @@ public class SearchBuilder implements ISearchBuilder { sql = "SELECT r FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r." + searchFieldName + " IN (:target_pids)"; } - List> partitions = partition(nextRoundMatches, MAXIMUM_PAGE_SIZE); - for (Collection nextPartition : partitions) { + List> partitions = partition(nextRoundMatches, MAXIMUM_PAGE_SIZE); + for (Collection nextPartition : partitions) { TypedQuery q = theEntityManager.createQuery(sql, ResourceLink.class); q.setParameter("src_path", nextPath); - q.setParameter("target_pids", nextPartition); + q.setParameter("target_pids", ResourcePersistentId.toLongList(nextPartition)); if (targetResourceType != null) { q.setParameter("target_resource_type", targetResourceType); } else if (haveTargetTypesDefinedByParam) { @@ -2538,12 +2566,12 @@ public class SearchBuilder implements ISearchBuilder { if (theReverseMode) { Long pid = resourceLink.getSourceResourcePid(); if (pid != null) { - pidsToInclude.add(pid); + pidsToInclude.add(new ResourcePersistentId(pid)); } } else { Long pid = resourceLink.getTargetResourcePid(); if (pid != null) { - pidsToInclude.add(pid); + pidsToInclude.add(new ResourcePersistentId(pid)); } } } @@ -2557,7 +2585,7 @@ public class SearchBuilder implements ISearchBuilder { pidsToInclude = new HashSet<>(filterResourceIdsByLastUpdated(theEntityManager, theLastUpdated, pidsToInclude)); } } - for (Long next : pidsToInclude) { + for (ResourcePersistentId next : pidsToInclude) { if (original.contains(next) == false && allAdded.contains(next) == false) { theMatches.add(next); } @@ -2573,7 +2601,7 @@ public class SearchBuilder implements ISearchBuilder { // This can be used to remove results from the search result details before // the user has a chance to know that they were in the results if (allAdded.size() > 0) { - List includedPidList = new ArrayList<>(allAdded); + List includedPidList = new ArrayList<>(allAdded); JpaPreResourceAccessDetails accessDetails = new JpaPreResourceAccessDetails(includedPidList, () -> this); HookParams params = new HookParams() .add(IPreResourceAccessDetails.class, accessDetails) @@ -2583,7 +2611,7 @@ public class SearchBuilder implements ISearchBuilder { for (int i = includedPidList.size() - 1; i >= 0; i--) { if (accessDetails.isDontReturnResourceAtIndex(i)) { - Long value = includedPidList.remove(i); + ResourcePersistentId value = includedPidList.remove(i); if (value != null) { theMatches.remove(value); } @@ -2596,14 +2624,14 @@ public class SearchBuilder implements ISearchBuilder { return allAdded; } - private List> partition(Collection theNextRoundMatches, int theMaxLoad) { + private List> partition(Collection theNextRoundMatches, int theMaxLoad) { if (theNextRoundMatches.size() <= theMaxLoad) { return Collections.singletonList(theNextRoundMatches); } else { - List> retVal = new ArrayList<>(); - Collection current = null; - for (Long next : theNextRoundMatches) { + List> retVal = new ArrayList<>(); + Collection current = null; + for (ResourcePersistentId next : theNextRoundMatches) { if (current == null) { current = new ArrayList<>(theMaxLoad); retVal.add(current); @@ -2834,28 +2862,28 @@ public class SearchBuilder implements ISearchBuilder { return null; } - private Predicate processFilter(SearchFilterParser.Filter filter, + private Predicate processFilter(SearchFilterParser.Filter theFilter, String theResourceName, RequestDetails theRequest) { - if (filter instanceof SearchFilterParser.FilterParameter) { - return processFilterParameter((SearchFilterParser.FilterParameter) filter, + if (theFilter instanceof SearchFilterParser.FilterParameter) { + return processFilterParameter((SearchFilterParser.FilterParameter) theFilter, theResourceName, theRequest); - } else if (filter instanceof SearchFilterParser.FilterLogical) { + } else if (theFilter instanceof SearchFilterParser.FilterLogical) { // Left side - Predicate leftPredicate = processFilter(((SearchFilterParser.FilterLogical) filter).getFilter1(), + Predicate xPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter1(), theResourceName, theRequest); // Right side - Predicate rightPredicate = processFilter(((SearchFilterParser.FilterLogical) filter).getFilter2(), + Predicate yPredicate = processFilter(((SearchFilterParser.FilterLogical) theFilter).getFilter2(), theResourceName, theRequest); - if (((SearchFilterParser.FilterLogical) filter).getOperation() == SearchFilterParser.FilterLogicalOperation.and) { - return myBuilder.and(leftPredicate, rightPredicate); - } else if (((SearchFilterParser.FilterLogical) filter).getOperation() == SearchFilterParser.FilterLogicalOperation.or) { - return myBuilder.or(leftPredicate, rightPredicate); + if (((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.and) { + return myBuilder.and(xPredicate, yPredicate); + } else if (((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.or) { + return myBuilder.or(xPredicate, yPredicate); } - } else if (filter instanceof SearchFilterParser.FilterParameterGroup) { - return processFilter(((SearchFilterParser.FilterParameterGroup) filter).getContained(), + } else if (theFilter instanceof SearchFilterParser.FilterParameterGroup) { + return processFilter(((SearchFilterParser.FilterParameterGroup) theFilter).getContained(), theResourceName, theRequest); } return null; @@ -2867,29 +2895,30 @@ public class SearchBuilder implements ISearchBuilder { return; } - if (theParamName.equals(IAnyResource.SP_RES_ID)) { - + switch (theParamName) { + case IAnyResource.SP_RES_ID: addPredicateResourceId(theResourceName, theAndOrParams, theRequest); + break; - } else if (theParamName.equals(IAnyResource.SP_RES_LANGUAGE)) { - + case IAnyResource.SP_RES_LANGUAGE: addPredicateLanguage(theAndOrParams); + break; - } else if (theParamName.equals(Constants.PARAM_HAS)) { - + case Constants.PARAM_HAS: addPredicateHas(theAndOrParams, theRequest); + break; - } else if (theParamName.equals(Constants.PARAM_TAG) || theParamName.equals(Constants.PARAM_PROFILE) || theParamName.equals(Constants.PARAM_SECURITY)) { - + case Constants.PARAM_TAG: + case Constants.PARAM_PROFILE: + case Constants.PARAM_SECURITY: addPredicateTag(theAndOrParams, theParamName); + break; - } else if (theParamName.equals(Constants.PARAM_SOURCE)) { + case Constants.PARAM_SOURCE: + addPredicateSource(theAndOrParams, theRequest); + break; - for (List nextAnd : theAndOrParams) { - addPredicateSource(nextAnd, SearchFilterParser.CompareOperation.eq, theRequest); - } - - } else { + default: RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName); if (nextParamDef != null) { @@ -2975,6 +3004,7 @@ public class SearchBuilder implements ISearchBuilder { throw new InvalidRequestException("Unknown search parameter " + theParamName + " for resource type " + theResourceName); } } + break; } } @@ -3079,16 +3109,16 @@ public class SearchBuilder implements ISearchBuilder { } - public class IncludesIterator extends BaseIterator implements Iterator { + public class IncludesIterator extends BaseIterator implements Iterator { private final RequestDetails myRequest; - private Iterator myCurrentIterator; + private Iterator myCurrentIterator; private int myCurrentOffset; - private ArrayList myCurrentPids; - private Long myNext; + private ArrayList myCurrentPids; + private ResourcePersistentId myNext; private int myPageSize = myDaoConfig.getEverythingIncludesFetchPageSize(); - IncludesIterator(Set thePidSet, RequestDetails theRequest) { + IncludesIterator(Set thePidSet, RequestDetails theRequest) { myCurrentPids = new ArrayList<>(thePidSet); myCurrentIterator = EMPTY_LONG_LIST.iterator(); myCurrentOffset = 0; @@ -3113,9 +3143,9 @@ public class SearchBuilder implements ISearchBuilder { break; } myCurrentOffset = end; - Collection pidsToScan = myCurrentPids.subList(start, end); + Collection pidsToScan = myCurrentPids.subList(start, end); Set includes = Collections.singleton(new Include("*", true)); - Set newPids = loadIncludes(myContext, myEntityManager, pidsToScan, includes, false, myParams.getLastUpdated(), mySearchUuid, myRequest); + Set newPids = loadIncludes(myContext, myEntityManager, pidsToScan, includes, false, myParams.getLastUpdated(), mySearchUuid, myRequest); myCurrentIterator = newPids.iterator(); } @@ -3128,16 +3158,16 @@ public class SearchBuilder implements ISearchBuilder { } @Override - public Long next() { + public ResourcePersistentId next() { fetchNext(); - Long retVal = myNext; + ResourcePersistentId retVal = myNext; myNext = null; return retVal; } } - private final class QueryIterator extends BaseIterator implements IResultIterator { + private final class QueryIterator extends BaseIterator implements IResultIterator { private final SearchRuntimeDetails mySearchRuntimeDetails; private final RequestDetails myRequest; @@ -3145,8 +3175,8 @@ public class SearchBuilder implements ISearchBuilder { private final boolean myHavePerftraceFoundIdHook; private boolean myFirst = true; private IncludesIterator myIncludesIterator; - private Long myNext; - private Iterator myPreResultsIterator; + private ResourcePersistentId myNext; + private Iterator myPreResultsIterator; private ScrollableResultsIterator myResultsIterator; private SortSpec mySort; private boolean myStillNeedToFetchIncludes; @@ -3199,7 +3229,7 @@ public class SearchBuilder implements ISearchBuilder { if (myPreResultsIterator != null && myPreResultsIterator.hasNext()) { while (myPreResultsIterator.hasNext()) { - Long next = myPreResultsIterator.next(); + ResourcePersistentId next = myPreResultsIterator.next(); if (next != null) if (myPidSet.add(next)) { myNext = next; @@ -3210,15 +3240,16 @@ public class SearchBuilder implements ISearchBuilder { if (myNext == null) { while (myResultsIterator.hasNext()) { - Long next = myResultsIterator.next(); + Long nextLong = myResultsIterator.next(); if (myHavePerftraceFoundIdHook) { HookParams params = new HookParams() .add(Integer.class, System.identityHashCode(this)) - .add(Object.class, next); + .add(Object.class, nextLong); JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, myRequest, Pointcut.JPA_PERFTRACE_SEARCH_FOUND_ID, params); } - if (next != null) { + if (nextLong != null) { + ResourcePersistentId next = new ResourcePersistentId(nextLong); if (myPidSet.add(next)) { myNext = next; break; @@ -3236,7 +3267,7 @@ public class SearchBuilder implements ISearchBuilder { } if (myIncludesIterator != null) { while (myIncludesIterator.hasNext()) { - Long next = myIncludesIterator.next(); + ResourcePersistentId next = myIncludesIterator.next(); if (next != null) if (myPidSet.add(next)) { myNext = next; @@ -3294,9 +3325,9 @@ public class SearchBuilder implements ISearchBuilder { } @Override - public Long next() { + public ResourcePersistentId next() { fetchNext(); - Long retVal = myNext; + ResourcePersistentId retVal = myNext; myNext = null; Validate.isTrue(!NO_MORE.equals(retVal), "No more elements"); return retVal; @@ -3430,7 +3461,7 @@ public class SearchBuilder implements ISearchBuilder { return likeExpression.replace("%", "[%]") + "%"; } - private static List filterResourceIdsByLastUpdated(EntityManager theEntityManager, final DateRangeParam theLastUpdated, Collection thePids) { + private static List filterResourceIdsByLastUpdated(EntityManager theEntityManager, final DateRangeParam theLastUpdated, Collection thePids) { if (thePids.isEmpty()) { return Collections.emptyList(); } @@ -3440,12 +3471,12 @@ public class SearchBuilder implements ISearchBuilder { cq.select(from.get("myId").as(Long.class)); List lastUpdatedPredicates = createLastUpdatedPredicates(theLastUpdated, builder, from); - lastUpdatedPredicates.add(from.get("myId").as(Long.class).in(thePids)); + lastUpdatedPredicates.add(from.get("myId").as(Long.class).in(ResourcePersistentId.toLongList(thePids))); cq.where(SearchBuilder.toArray(lastUpdatedPredicates)); TypedQuery query = theEntityManager.createQuery(cq); - return query.getResultList(); + return ResourcePersistentId.fromLongList(query.getResultList()); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TransactionProcessor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TransactionProcessor.java index e47cde1a144..2f28a86d27e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TransactionProcessor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/TransactionProcessor.java @@ -20,1184 +20,60 @@ package ca.uhn.fhir.jpa.dao; * #L% */ -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -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.jpa.config.HapiFhirHibernateJpaDialect; -import ca.uhn.fhir.jpa.delete.DeleteConflictList; -import ca.uhn.fhir.jpa.delete.DeleteConflictService; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; -import ca.uhn.fhir.jpa.util.DeleteConflict; -import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; -import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; -import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.PatchTypeEnum; -import ca.uhn.fhir.rest.api.PreferReturnEnum; -import ca.uhn.fhir.rest.api.RestOperationTypeEnum; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.param.ParameterUtil; -import ca.uhn.fhir.rest.server.RestfulServerUtils; -import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.exceptions.NotModifiedException; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; -import ca.uhn.fhir.rest.server.method.BaseMethodBinding; -import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding; -import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; -import ca.uhn.fhir.rest.server.servlet.ServletSubRequestDetails; -import ca.uhn.fhir.rest.server.util.ServletRequestUtil; -import ca.uhn.fhir.util.*; -import com.google.common.base.Charsets; -import com.google.common.collect.ArrayListMultimap; +import ca.uhn.fhir.util.StopWatch; import org.apache.commons.lang3.Validate; import org.hibernate.Session; import org.hibernate.internal.SessionImpl; -import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.instance.model.api.*; +import org.hl7.fhir.instance.model.api.IIdType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.support.TransactionTemplate; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType; import javax.persistence.PersistenceException; -import java.util.*; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; -import static org.apache.commons.lang3.StringUtils.*; +public class TransactionProcessor extends BaseTransactionProcessor { -public class TransactionProcessor { - - public static final String URN_PREFIX = "urn:"; private static final Logger ourLog = LoggerFactory.getLogger(TransactionProcessor.class); - private BaseHapiFhirDao myDao; - @Autowired - private PlatformTransactionManager myTxManager; + @PersistenceContext(type = PersistenceContextType.TRANSACTION) private EntityManager myEntityManager; - @Autowired - private FhirContext myContext; - @Autowired - private ITransactionProcessorVersionAdapter myVersionAdapter; - @Autowired - private DaoRegistry myDaoRegistry; @Autowired(required = false) private HapiFhirHibernateJpaDialect myHapiFhirHibernateJpaDialect; - @Autowired - private DeleteConflictService myDeleteConflictService; - @Autowired - private IInterceptorBroadcaster myInterceptorBroadcaster; - public BUNDLE transaction(RequestDetails theRequestDetails, BUNDLE theRequest) { - if (theRequestDetails != null) { - IServerInterceptor.ActionRequestDetails requestDetails = new IServerInterceptor.ActionRequestDetails(theRequestDetails, theRequest, "Bundle", null); - myDao.notifyInterceptors(RestOperationTypeEnum.TRANSACTION, requestDetails); - } + @Override + protected void validateDependencies() { + super.validateDependencies(); - String actionName = "Transaction"; - BUNDLE response = processTransactionAsSubRequest((ServletRequestDetails) theRequestDetails, theRequest, actionName); - - List entries = myVersionAdapter.getEntries(response); - for (int i = 0; i < entries.size(); i++) { - if (ElementUtil.isEmpty(entries.get(i))) { - entries.remove(i); - i--; - } - } - - return response; - } - - public BUNDLE collection(final RequestDetails theRequestDetails, BUNDLE theRequest) { - String transactionType = myVersionAdapter.getBundleType(theRequest); - - if (!org.hl7.fhir.r4.model.Bundle.BundleType.COLLECTION.toCode().equals(transactionType)) { - throw new InvalidRequestException("Can not process collection Bundle of type: " + transactionType); - } - - ourLog.info("Beginning storing collection with {} resources", myVersionAdapter.getEntries(theRequest).size()); - - TransactionTemplate txTemplate = new TransactionTemplate(myTxManager); - txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - - BUNDLE resp = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.BATCHRESPONSE.toCode()); - - List resources = new ArrayList<>(); - for (final BUNDLEENTRY nextRequestEntry : myVersionAdapter.getEntries(theRequest)) { - IBaseResource resource = myVersionAdapter.getResource(nextRequestEntry); - resources.add(resource); - } - - BUNDLE transactionBundle = myVersionAdapter.createBundle("transaction"); - for (IBaseResource next : resources) { - BUNDLEENTRY entry = myVersionAdapter.addEntry(transactionBundle); - myVersionAdapter.setResource(entry, next); - myVersionAdapter.setRequestVerb(entry, "PUT"); - myVersionAdapter.setRequestUrl(entry, next.getIdElement().toUnqualifiedVersionless().getValue()); - } - - transaction(theRequestDetails, transactionBundle); - - return resp; - } - - private void populateEntryWithOperationOutcome(BaseServerResponseException caughtEx, BUNDLEENTRY nextEntry) { - myVersionAdapter.populateEntryWithOperationOutcome(caughtEx, nextEntry); - } - - private void handleTransactionCreateOrUpdateOutcome(Map idSubstitutions, Map idToPersistedOutcome, IIdType nextResourceId, DaoMethodOutcome outcome, - BUNDLEENTRY newEntry, String theResourceType, IBaseResource theRes, ServletRequestDetails theRequestDetails) { - IIdType newId = outcome.getId().toUnqualifiedVersionless(); - IIdType resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless(); - if (newId.equals(resourceId) == false) { - idSubstitutions.put(resourceId, newId); - if (isPlaceholder(resourceId)) { - /* - * The correct way for substitution IDs to be is to be with no resource type, but we'll accept the qualified kind too just to be lenient. - */ - IIdType id = myContext.getVersion().newIdType(); - id.setValue(theResourceType + '/' + resourceId.getValue()); - idSubstitutions.put(id, newId); - } - } - idToPersistedOutcome.put(newId, outcome); - if (outcome.getCreated()) { - myVersionAdapter.setResponseStatus(newEntry, toStatusString(Constants.STATUS_HTTP_201_CREATED)); - } else { - myVersionAdapter.setResponseStatus(newEntry, toStatusString(Constants.STATUS_HTTP_200_OK)); - } - Date lastModifier = getLastModified(theRes); - myVersionAdapter.setResponseLastModified(newEntry, lastModifier); - - if (theRequestDetails != null) { - if (outcome.getResource() != null) { - String prefer = theRequestDetails.getHeader(Constants.HEADER_PREFER); - PreferReturnEnum preferReturn = RestfulServerUtils.parsePreferHeader(null, prefer).getReturn(); - if (preferReturn != null) { - if (preferReturn == PreferReturnEnum.REPRESENTATION) { - outcome.fireResourceViewCallbacks(); - myVersionAdapter.setResource(newEntry, outcome.getResource()); - } - } - } - } - - } - - private Date getLastModified(IBaseResource theRes) { - return theRes.getMeta().getLastUpdated(); - } - - private String performIdSubstitutionsInMatchUrl(Map theIdSubstitutions, String theMatchUrl) { - String matchUrl = theMatchUrl; - if (isNotBlank(matchUrl)) { - for (Map.Entry nextSubstitutionEntry : theIdSubstitutions.entrySet()) { - IIdType nextTemporaryId = nextSubstitutionEntry.getKey(); - IIdType nextReplacementId = nextSubstitutionEntry.getValue(); - String nextTemporaryIdPart = nextTemporaryId.getIdPart(); - String nextReplacementIdPart = nextReplacementId.getValueAsString(); - if (isUrn(nextTemporaryId) && nextTemporaryIdPart.length() > URN_PREFIX.length()) { - matchUrl = matchUrl.replace(nextTemporaryIdPart, nextReplacementIdPart); - String escapedUrlParam = UrlUtil.escapeUrlParam(nextTemporaryIdPart); - if (isNotBlank(escapedUrlParam)) { - matchUrl = matchUrl.replace(escapedUrlParam, nextReplacementIdPart); - } - } - } - } - return matchUrl; - } - - private boolean isUrn(IIdType theId) { - return defaultString(theId.getValue()).startsWith(URN_PREFIX); - } - - public void setDao(BaseHapiFhirDao theDao) { - myDao = theDao; - } - - private BUNDLE processTransactionAsSubRequest(ServletRequestDetails theRequestDetails, BUNDLE theRequest, String theActionName) { - BaseHapiFhirDao.markRequestAsProcessingSubRequest(theRequestDetails); - try { - return processTransaction(theRequestDetails, theRequest, theActionName); - } finally { - BaseHapiFhirDao.clearRequestAsProcessingSubRequest(theRequestDetails); - } - } - - private BUNDLE batch(final RequestDetails theRequestDetails, BUNDLE theRequest) { - ourLog.info("Beginning batch with {} resources", myVersionAdapter.getEntries(theRequest).size()); - long start = System.currentTimeMillis(); - - TransactionTemplate txTemplate = new TransactionTemplate(myTxManager); - txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - - BUNDLE resp = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.BATCHRESPONSE.toCode()); - - /* - * For batch, we handle each entry as a mini-transaction in its own database transaction so that if one fails, it doesn't prevent others - */ - - for (final BUNDLEENTRY nextRequestEntry : myVersionAdapter.getEntries(theRequest)) { - - BaseServerResponseExceptionHolder caughtEx = new BaseServerResponseExceptionHolder(); - - try { - BUNDLE subRequestBundle = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION.toCode()); - myVersionAdapter.addEntry(subRequestBundle, nextRequestEntry); - - BUNDLE nextResponseBundle = processTransactionAsSubRequest((ServletRequestDetails) theRequestDetails, subRequestBundle, "Batch sub-request"); - - BUNDLEENTRY subResponseEntry = myVersionAdapter.getEntries(nextResponseBundle).get(0); - myVersionAdapter.addEntry(resp, subResponseEntry); - - /* - * If the individual entry didn't have a resource in its response, bring the sub-transaction's OperationOutcome across so the client can see it - */ - if (myVersionAdapter.getResource(subResponseEntry) == null) { - BUNDLEENTRY nextResponseBundleFirstEntry = myVersionAdapter.getEntries(nextResponseBundle).get(0); - myVersionAdapter.setResource(subResponseEntry, myVersionAdapter.getResource(nextResponseBundleFirstEntry)); - } - - } catch (BaseServerResponseException e) { - caughtEx.setException(e); - } catch (Throwable t) { - ourLog.error("Failure during BATCH sub transaction processing", t); - caughtEx.setException(new InternalErrorException(t)); - } - - if (caughtEx.getException() != null) { - BUNDLEENTRY nextEntry = myVersionAdapter.addEntry(resp); - - populateEntryWithOperationOutcome(caughtEx.getException(), nextEntry); - - myVersionAdapter.setResponseStatus(nextEntry, toStatusString(caughtEx.getException().getStatusCode())); - } - - } - - long delay = System.currentTimeMillis() - start; - ourLog.info("Batch completed in {}ms", new Object[]{delay}); - - return resp; - } - - private BUNDLE processTransaction(final ServletRequestDetails theRequestDetails, final BUNDLE theRequest, final String theActionName) { - validateDependencies(); - - String transactionType = myVersionAdapter.getBundleType(theRequest); - - if (org.hl7.fhir.r4.model.Bundle.BundleType.BATCH.toCode().equals(transactionType)) { - return batch(theRequestDetails, theRequest); - } - - if (transactionType == null) { - String message = "Transaction Bundle did not specify valid Bundle.type, assuming " + Bundle.BundleType.TRANSACTION.toCode(); - ourLog.warn(message); - transactionType = org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION.toCode(); - } - if (!org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION.toCode().equals(transactionType)) { - throw new InvalidRequestException("Unable to process transaction where incoming Bundle.type = " + transactionType); - } - - ourLog.debug("Beginning {} with {} resources", theActionName, myVersionAdapter.getEntries(theRequest).size()); - - final Date updateTime = new Date(); - final StopWatch transactionStopWatch = new StopWatch(); - - final Set allIds = new LinkedHashSet<>(); - final Map idSubstitutions = new HashMap<>(); - final Map idToPersistedOutcome = new HashMap<>(); - List requestEntries = myVersionAdapter.getEntries(theRequest); - - // Do all entries have a verb? - for (int i = 0; i < myVersionAdapter.getEntries(theRequest).size(); i++) { - BUNDLEENTRY nextReqEntry = requestEntries.get(i); - String verb = myVersionAdapter.getEntryRequestVerb(nextReqEntry); - if (verb == null || !isValidVerb(verb)) { - throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionEntryHasInvalidVerb", verb, i)); - } - } - - /* - * We want to execute the transaction request bundle elements in the order - * specified by the FHIR specification (see TransactionSorter) so we save the - * original order in the request, then sort it. - * - * Entries with a type of GET are removed from the bundle so that they - * can be processed at the very end. We do this because the incoming resources - * are saved in a two-phase way in order to deal with interdependencies, and - * we want the GET processing to use the final indexing state - */ - final BUNDLE response = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTIONRESPONSE.toCode()); - List getEntries = new ArrayList<>(); - final IdentityHashMap originalRequestOrder = new IdentityHashMap<>(); - for (int i = 0; i < requestEntries.size(); i++) { - originalRequestOrder.put(requestEntries.get(i), i); - myVersionAdapter.addEntry(response); - if (myVersionAdapter.getEntryRequestVerb(requestEntries.get(i)).equals("GET")) { - getEntries.add(requestEntries.get(i)); - } - } - - /* - * See FhirSystemDaoDstu3Test#testTransactionWithPlaceholderIdInMatchUrl - * Basically if the resource has a match URL that references a placeholder, - * we try to handle the resource with the placeholder first. - */ - Set placeholderIds = new HashSet<>(); - final List entries = requestEntries; - for (BUNDLEENTRY nextEntry : entries) { - String fullUrl = myVersionAdapter.getFullUrl(nextEntry); - if (isNotBlank(fullUrl) && fullUrl.startsWith(URN_PREFIX)) { - placeholderIds.add(fullUrl); - } - } - entries.sort(new TransactionSorter(placeholderIds)); - - /* - * All of the write operations in the transaction (PUT, POST, etc.. basically anything - * except GET) are performed in their own database transaction before we do the reads. - * We do this because the reads (specifically the searches) often spawn their own - * secondary database transaction and if we allow that within the primary - * database transaction we can end up with deadlocks if the server is under - * heavy load with lots of concurrent transactions using all available - * database connections. - */ - TransactionTemplate txManager = new TransactionTemplate(myTxManager); - Map entriesToProcess = txManager.execute(status -> { - Map retVal = doTransactionWriteOperations(theRequestDetails, theActionName, updateTime, allIds, idSubstitutions, idToPersistedOutcome, response, originalRequestOrder, entries, transactionStopWatch); - - transactionStopWatch.startTask("Commit writes to database"); - return retVal; - }); - transactionStopWatch.endCurrentTask(); - - for (Map.Entry nextEntry : entriesToProcess.entrySet()) { - String responseLocation = nextEntry.getValue().getIdDt().toUnqualified().getValue(); - String responseEtag = nextEntry.getValue().getIdDt().getVersionIdPart(); - myVersionAdapter.setResponseLocation(nextEntry.getKey(), responseLocation); - myVersionAdapter.setResponseETag(nextEntry.getKey(), responseEtag); - } - - /* - * Loop through the request and process any entries of type GET - */ - if (getEntries.size() > 0) { - transactionStopWatch.startTask("Process " + getEntries.size() + " GET entries"); - } - for (BUNDLEENTRY nextReqEntry : getEntries) { - Integer originalOrder = originalRequestOrder.get(nextReqEntry); - BUNDLEENTRY nextRespEntry = myVersionAdapter.getEntries(response).get(originalOrder); - - ArrayListMultimap paramValues = ArrayListMultimap.create(); - - String transactionUrl = extractTransactionUrlOrThrowException(nextReqEntry, "GET"); - - ServletSubRequestDetails requestDetails = ServletRequestUtil.getServletSubRequestDetails(theRequestDetails, transactionUrl, paramValues); - - String url = requestDetails.getRequestPath(); - - BaseMethodBinding method = theRequestDetails.getServer().determineResourceMethod(requestDetails, url); - if (method == null) { - throw new IllegalArgumentException("Unable to handle GET " + url); - } - - if (isNotBlank(myVersionAdapter.getEntryRequestIfMatch(nextReqEntry))) { - requestDetails.addHeader(Constants.HEADER_IF_MATCH, myVersionAdapter.getEntryRequestIfMatch(nextReqEntry)); - } - if (isNotBlank(myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry))) { - requestDetails.addHeader(Constants.HEADER_IF_NONE_EXIST, myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry)); - } - if (isNotBlank(myVersionAdapter.getEntryRequestIfNoneMatch(nextReqEntry))) { - requestDetails.addHeader(Constants.HEADER_IF_NONE_MATCH, myVersionAdapter.getEntryRequestIfNoneMatch(nextReqEntry)); - } - - Validate.isTrue(method instanceof BaseResourceReturningMethodBinding, "Unable to handle GET {}", url); - try { - - BaseResourceReturningMethodBinding methodBinding = (BaseResourceReturningMethodBinding) method; - requestDetails.setRestOperationType(methodBinding.getRestOperationType()); - - IBaseResource resource = methodBinding.doInvokeServer(theRequestDetails.getServer(), requestDetails); - if (paramValues.containsKey(Constants.PARAM_SUMMARY) || paramValues.containsKey(Constants.PARAM_CONTENT)) { - resource = filterNestedBundle(requestDetails, resource); - } - myVersionAdapter.setResource(nextRespEntry, resource); - myVersionAdapter.setResponseStatus(nextRespEntry, toStatusString(Constants.STATUS_HTTP_200_OK)); - } catch (NotModifiedException e) { - myVersionAdapter.setResponseStatus(nextRespEntry, toStatusString(Constants.STATUS_HTTP_304_NOT_MODIFIED)); - } catch (BaseServerResponseException e) { - ourLog.info("Failure processing transaction GET {}: {}", url, e.toString()); - myVersionAdapter.setResponseStatus(nextRespEntry, toStatusString(e.getStatusCode())); - populateEntryWithOperationOutcome(e, nextRespEntry); - } - - } - transactionStopWatch.endCurrentTask(); - - // Interceptor broadcast: JPA_PERFTRACE_INFO - if (JpaInterceptorBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_INFO, myInterceptorBroadcaster, theRequestDetails)) { - String taskDurations = transactionStopWatch.formatTaskDurations(); - StorageProcessingMessage message = new StorageProcessingMessage(); - message.setMessage("Transaction timing:\n" + taskDurations); - HookParams params = new HookParams() - .add(RequestDetails.class, theRequestDetails) - .addIfMatchesType(ServletRequestDetails.class, theRequestDetails) - .add(StorageProcessingMessage.class, message); - JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.JPA_PERFTRACE_INFO, params); - } - - return response; - } - - private boolean isValidVerb(String theVerb) { - try { - return org.hl7.fhir.r4.model.Bundle.HTTPVerb.fromCode(theVerb) != null; - } catch (FHIRException theE) { - return false; - } - } - - /** - * This method is called for nested bundles (e.g. if we received a transaction with an entry that - * was a GET search, this method is called on the bundle for the search result, that will be placed in the - * outer bundle). This method applies the _summary and _content parameters to the output of - * that bundle. - *

- * TODO: This isn't the most efficient way of doing this.. hopefully we can come up with something better in the future. - */ - private IBaseResource filterNestedBundle(RequestDetails theRequestDetails, IBaseResource theResource) { - IParser p = myContext.newJsonParser(); - RestfulServerUtils.configureResponseParser(theRequestDetails, p); - return p.parseResource(theResource.getClass(), p.encodeResourceToString(theResource)); - } - - private void validateDependencies() { Validate.notNull(myEntityManager); - Validate.notNull(myContext); - Validate.notNull(myDao); - Validate.notNull(myTxManager); - } - - private IIdType newIdType(String theValue) { - return myContext.getVersion().newIdType().setValue(theValue); } - private Map doTransactionWriteOperations(final ServletRequestDetails theRequest, String theActionName, Date theUpdateTime, Set theAllIds, - Map theIdSubstitutions, Map theIdToPersistedOutcome, BUNDLE theResponse, IdentityHashMap theOriginalRequestOrder, List theEntries, StopWatch theTransactionStopWatch) { - - if (theRequest != null) { - theRequest.startDeferredOperationCallback(); - } + @Override + protected void flushSession(Map theIdToPersistedOutcome) { try { + SessionImpl session = (SessionImpl) myEntityManager.unwrap(Session.class); + int insertionCount = session.getActionQueue().numberOfInsertions(); + int updateCount = session.getActionQueue().numberOfUpdates(); - Set deletedResources = new HashSet<>(); - DeleteConflictList deleteConflicts = new DeleteConflictList(); - Map entriesToProcess = new IdentityHashMap<>(); - Set nonUpdatedEntities = new HashSet<>(); - Set updatedEntities = new HashSet<>(); - List updatedResources = new ArrayList<>(); - Map> conditionalRequestUrls = new HashMap<>(); - - /* - * Look for duplicate conditional creates and consolidate them - */ - final HashMap keyToUuid = new HashMap<>(); - for (int index = 0, originalIndex = 0; index < theEntries.size(); index++, originalIndex++) { - BUNDLEENTRY nextReqEntry = theEntries.get(index); - IBaseResource resource = myVersionAdapter.getResource(nextReqEntry); - if (resource != null) { - String verb = myVersionAdapter.getEntryRequestVerb(nextReqEntry); - String entryUrl = myVersionAdapter.getFullUrl(nextReqEntry); - String requestUrl = myVersionAdapter.getEntryRequestUrl(nextReqEntry); - String ifNoneExist = myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry); - String key = verb + "|" + requestUrl + "|" + ifNoneExist; - - // Conditional UPDATE - boolean consolidateEntry = false; - if ("PUT".equals(verb)) { - if (isNotBlank(entryUrl) && isNotBlank(requestUrl)) { - int questionMarkIndex = requestUrl.indexOf('?'); - if (questionMarkIndex >= 0 && requestUrl.length() > (questionMarkIndex + 1)) { - consolidateEntry = true; - } - } - } - - // Conditional CREATE - if ("POST".equals(verb)) { - if (isNotBlank(entryUrl) && isNotBlank(requestUrl) && isNotBlank(ifNoneExist)) { - if (!entryUrl.equals(requestUrl)) { - consolidateEntry = true; - } - } - } - - if (consolidateEntry) { - if (!keyToUuid.containsKey(key)) { - keyToUuid.put(key, entryUrl); - } else { - ourLog.info("Discarding transaction bundle entry {} as it contained a duplicate conditional {}", originalIndex, verb); - theEntries.remove(index); - index--; - String existingUuid = keyToUuid.get(key); - for (BUNDLEENTRY nextEntry : theEntries) { - IBaseResource nextResource = myVersionAdapter.getResource(nextEntry); - for (ResourceReferenceInfo nextReference : myContext.newTerser().getAllResourceReferences(nextResource)) { - // We're interested in any references directly to the placeholder ID, but also - // references that have a resource target that has the placeholder ID. - String nextReferenceId = nextReference.getResourceReference().getReferenceElement().getValue(); - if (isBlank(nextReferenceId) && nextReference.getResourceReference().getResource() != null) { - nextReferenceId = nextReference.getResourceReference().getResource().getIdElement().getValue(); - } - if (entryUrl.equals(nextReferenceId)) { - nextReference.getResourceReference().setReference(existingUuid); - nextReference.getResourceReference().setResource(null); - } - } - } - } - } - } - } - - - /* - * Loop through the request and process any entries of type - * PUT, POST or DELETE - */ - for (int i = 0; i < theEntries.size(); i++) { - - if (i % 250 == 0) { - ourLog.info("Processed {} non-GET entries out of {} in transaction", i, theEntries.size()); - } - - BUNDLEENTRY nextReqEntry = theEntries.get(i); - IBaseResource res = myVersionAdapter.getResource(nextReqEntry); - IIdType nextResourceId = null; - if (res != null) { - - nextResourceId = res.getIdElement(); - - if (!nextResourceId.hasIdPart()) { - if (isNotBlank(myVersionAdapter.getFullUrl(nextReqEntry))) { - nextResourceId = newIdType(myVersionAdapter.getFullUrl(nextReqEntry)); - } - } - - if (nextResourceId.hasIdPart() && nextResourceId.getIdPart().matches("[a-zA-Z]+:.*") && !isPlaceholder(nextResourceId)) { - throw new InvalidRequestException("Invalid placeholder ID found: " + nextResourceId.getIdPart() + " - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'"); - } - - if (nextResourceId.hasIdPart() && !nextResourceId.hasResourceType() && !isPlaceholder(nextResourceId)) { - nextResourceId = newIdType(toResourceName(res.getClass()), nextResourceId.getIdPart()); - res.setId(nextResourceId); - } - - /* - * Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness - */ - if (isPlaceholder(nextResourceId)) { - if (!theAllIds.add(nextResourceId)) { - throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextResourceId)); - } - } else if (nextResourceId.hasResourceType() && nextResourceId.hasIdPart()) { - IIdType nextId = nextResourceId.toUnqualifiedVersionless(); - if (!theAllIds.add(nextId)) { - throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextId)); - } - } - - } - - String verb = myVersionAdapter.getEntryRequestVerb(nextReqEntry); - String resourceType = res != null ? myContext.getResourceDefinition(res).getName() : null; - Integer order = theOriginalRequestOrder.get(nextReqEntry); - BUNDLEENTRY nextRespEntry = myVersionAdapter.getEntries(theResponse).get(order); - - theTransactionStopWatch.startTask("Bundle.entry[" + i + "]: " + verb + " " + defaultString(resourceType)); - - switch (verb) { - case "POST": { - // CREATE - validateResourcePresent(res, order, verb); - @SuppressWarnings("rawtypes") - IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass()); - res.setId((String) null); - DaoMethodOutcome outcome; - String matchUrl = myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry); - matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl); - outcome = resourceDao.create(res, matchUrl, false, theUpdateTime, theRequest); - if (nextResourceId != null) { - handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequest); - } - entriesToProcess.put(nextRespEntry, outcome.getEntity()); - if (outcome.getCreated() == false) { - nonUpdatedEntities.add(outcome.getEntity()); - } else { - if (isNotBlank(matchUrl)) { - conditionalRequestUrls.put(matchUrl, res.getClass()); - } - } - - break; - } - case "DELETE": { - // DELETE - String url = extractTransactionUrlOrThrowException(nextReqEntry, verb); - UrlUtil.UrlParts parts = UrlUtil.parseUrl(url); - ca.uhn.fhir.jpa.dao.IFhirResourceDao dao = toDao(parts, verb, url); - int status = Constants.STATUS_HTTP_204_NO_CONTENT; - if (parts.getResourceId() != null) { - IIdType deleteId = newIdType(parts.getResourceType(), parts.getResourceId()); - if (!deletedResources.contains(deleteId.getValueAsString())) { - DaoMethodOutcome outcome = dao.delete(deleteId, deleteConflicts, theRequest); - if (outcome.getEntity() != null) { - deletedResources.add(deleteId.getValueAsString()); - entriesToProcess.put(nextRespEntry, outcome.getEntity()); - } - } - } else { - String matchUrl = parts.getResourceType() + '?' + parts.getParams(); - matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl); - DeleteMethodOutcome deleteOutcome = dao.deleteByUrl(matchUrl, deleteConflicts, theRequest); - List allDeleted = deleteOutcome.getDeletedEntities(); - for (ResourceTable deleted : allDeleted) { - deletedResources.add(deleted.getIdDt().toUnqualifiedVersionless().getValueAsString()); - } - if (allDeleted.isEmpty()) { - status = Constants.STATUS_HTTP_204_NO_CONTENT; - } - - myVersionAdapter.setResponseOutcome(nextRespEntry, deleteOutcome.getOperationOutcome()); - } - - myVersionAdapter.setResponseStatus(nextRespEntry, toStatusString(status)); - - break; - } - case "PUT": { - // UPDATE - validateResourcePresent(res, order, verb); - @SuppressWarnings("rawtypes") - IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass()); - - String url = extractTransactionUrlOrThrowException(nextReqEntry, verb); - - DaoMethodOutcome outcome; - UrlUtil.UrlParts parts = UrlUtil.parseUrl(url); - if (isNotBlank(parts.getResourceId())) { - String version = null; - if (isNotBlank(myVersionAdapter.getEntryRequestIfMatch(nextReqEntry))) { - version = ParameterUtil.parseETagValue(myVersionAdapter.getEntryRequestIfMatch(nextReqEntry)); - } - res.setId(newIdType(parts.getResourceType(), parts.getResourceId(), version)); - outcome = resourceDao.update(res, null, false, false, theRequest); - } else { - res.setId((String) null); - String matchUrl; - if (isNotBlank(parts.getParams())) { - matchUrl = parts.getResourceType() + '?' + parts.getParams(); - } else { - matchUrl = parts.getResourceType(); - } - matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl); - outcome = resourceDao.update(res, matchUrl, false, false, theRequest); - if (Boolean.TRUE.equals(outcome.getCreated())) { - conditionalRequestUrls.put(matchUrl, res.getClass()); - } - } - - if (outcome.getCreated() == Boolean.FALSE - || (outcome.getCreated() == Boolean.TRUE && outcome.getId().getVersionIdPartAsLong() > 1)) { - updatedEntities.add(outcome.getEntity()); - if (outcome.getResource() != null) { - updatedResources.add(outcome.getResource()); - } - } - - handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequest); - entriesToProcess.put(nextRespEntry, outcome.getEntity()); - break; - } - case "PATCH": { - // PATCH - validateResourcePresent(res, order, verb); - - String url = extractTransactionUrlOrThrowException(nextReqEntry, verb); - UrlUtil.UrlParts parts = UrlUtil.parseUrl(url); - - String matchUrl = toMatchUrl(nextReqEntry); - matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl); - String patchBody = null; - String contentType = null; - - if (res instanceof IBaseBinary) { - IBaseBinary binary = (IBaseBinary) res; - if (binary.getContent() != null && binary.getContent().length > 0) { - patchBody = new String(binary.getContent(), Charsets.UTF_8); - } - contentType = binary.getContentType(); - } - - if (isBlank(patchBody)) { - String msg = myContext.getLocalizer().getMessage(TransactionProcessor.class, "missingPatchBody"); - throw new InvalidRequestException(msg); - } - if (isBlank(contentType)) { - String msg = myContext.getLocalizer().getMessage(TransactionProcessor.class, "missingPatchContentType"); - throw new InvalidRequestException(msg); - } - - ca.uhn.fhir.jpa.dao.IFhirResourceDao dao = toDao(parts, verb, url); - PatchTypeEnum patchType = PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(contentType); - IIdType patchId = myContext.getVersion().newIdType().setValue(parts.getResourceId()); - DaoMethodOutcome outcome = dao.patch(patchId, matchUrl, patchType, patchBody, theRequest); - updatedEntities.add(outcome.getEntity()); - if (outcome.getResource() != null) { - updatedResources.add(outcome.getResource()); - } - - break; - } - case "GET": - break; - default: - throw new InvalidRequestException("Unable to handle verb in transaction: " + verb); - - } - - theTransactionStopWatch.endCurrentTask(); - } - - - /* - * Make sure that there are no conflicts from deletions. E.g. we can't delete something - * if something else has a reference to it.. Unless the thing that has a reference to it - * was also deleted as a part of this transaction, which is why we check this now at the - * end. - */ - for (Iterator iter = deleteConflicts.iterator(); iter.hasNext(); ) { - DeleteConflict nextDeleteConflict = iter.next(); - - /* - * If we have a conflict, it means we can't delete Resource/A because - * Resource/B has a reference to it. We'll ignore that conflict though - * if it turns out we're also deleting Resource/B in this transaction. - */ - if (deletedResources.contains(nextDeleteConflict.getSourceId().toUnqualifiedVersionless().getValue())) { - iter.remove(); - continue; - } - - /* - * And then, this is kind of a last ditch check. It's also ok to delete - * Resource/A if Resource/B isn't being deleted, but it is being UPDATED - * in this transaction, and the updated version of it has no references - * to Resource/A any more. - */ - String sourceId = nextDeleteConflict.getSourceId().toUnqualifiedVersionless().getValue(); - String targetId = nextDeleteConflict.getTargetId().toUnqualifiedVersionless().getValue(); - Optional updatedSource = updatedResources - .stream() - .filter(t -> sourceId.equals(t.getIdElement().toUnqualifiedVersionless().getValue())) - .findFirst(); - if (updatedSource.isPresent()) { - List referencesInSource = myContext.newTerser().getAllResourceReferences(updatedSource.get()); - boolean sourceStillReferencesTarget = referencesInSource - .stream() - .anyMatch(t -> targetId.equals(t.getResourceReference().getReferenceElement().toUnqualifiedVersionless().getValue())); - if (!sourceStillReferencesTarget) { - iter.remove(); - } - } - } - myDeleteConflictService.validateDeleteConflictsEmptyOrThrowException(deleteConflicts); - - /* - * Perform ID substitutions and then index each resource we have saved - */ - - FhirTerser terser = myContext.newTerser(); - theTransactionStopWatch.startTask("Index " + theIdToPersistedOutcome.size() + " resources"); - int i = 0; - for (DaoMethodOutcome nextOutcome : theIdToPersistedOutcome.values()) { - - if (i++ % 250 == 0) { - ourLog.info("Have indexed {} entities out of {} in transaction", i, theIdToPersistedOutcome.values().size()); - } - - IBaseResource nextResource = nextOutcome.getResource(); - if (nextResource == null) { - continue; - } - - // References - List allRefs = terser.getAllResourceReferences(nextResource); - for (ResourceReferenceInfo nextRef : allRefs) { - IIdType nextId = nextRef.getResourceReference().getReferenceElement(); - if (!nextId.hasIdPart()) { - continue; - } - if (theIdSubstitutions.containsKey(nextId)) { - IIdType newId = theIdSubstitutions.get(nextId); - ourLog.debug(" * Replacing resource ref {} with {}", nextId, newId); - nextRef.getResourceReference().setReference(newId.getValue()); - } else if (nextId.getValue().startsWith("urn:")) { - throw new InvalidRequestException("Unable to satisfy placeholder ID " + nextId.getValue() + " found in element named '" + nextRef.getName() + "' within resource of type: " + nextResource.getIdElement().getResourceType()); - } else { - ourLog.debug(" * Reference [{}] does not exist in bundle", nextId); - } - } - - // URIs - Class> uriType = (Class>) myContext.getElementDefinition("uri").getImplementingClass(); - List> allUris = terser.getAllPopulatedChildElementsOfType(nextResource, uriType); - for (IPrimitiveType nextRef : allUris) { - if (nextRef instanceof IIdType) { - continue; // No substitution on the resource ID itself! - } - IIdType nextUriString = newIdType(nextRef.getValueAsString()); - if (theIdSubstitutions.containsKey(nextUriString)) { - IIdType newId = theIdSubstitutions.get(nextUriString); - ourLog.debug(" * Replacing resource ref {} with {}", nextUriString, newId); - nextRef.setValueAsString(newId.getValue()); - } else { - ourLog.debug(" * Reference [{}] does not exist in bundle", nextUriString); - } - } - - IPrimitiveType deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) nextResource); - Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null; - - IFhirResourceDao dao = myDaoRegistry.getResourceDao(nextResource.getClass()); - IJpaDao jpaDao = (IJpaDao) dao; - - if (updatedEntities.contains(nextOutcome.getEntity())) { - jpaDao.updateInternal(theRequest, nextResource, true, false, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource()); - } else if (!nonUpdatedEntities.contains(nextOutcome.getEntity())) { - jpaDao.updateEntity(theRequest, nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, theUpdateTime, false, true); - } - } - - theTransactionStopWatch.endCurrentTask(); - theTransactionStopWatch.startTask("Flush writes to database"); - - try { - flushJpaSession(); - } catch (PersistenceException e) { - if (myHapiFhirHibernateJpaDialect != null) { - List types = theIdToPersistedOutcome.keySet().stream().filter(t -> t != null).map(t -> t.getResourceType()).collect(Collectors.toList()); - String message = "Error flushing transaction with resource types: " + types; - throw myHapiFhirHibernateJpaDialect.translate(e, message); - } - throw e; - } - - theTransactionStopWatch.endCurrentTask(); - if (conditionalRequestUrls.size() > 0) { - theTransactionStopWatch.startTask("Check for conflicts in conditional resources"); - } - - /* - * Double check we didn't allow any duplicates we shouldn't have - */ - for (Map.Entry> nextEntry : conditionalRequestUrls.entrySet()) { - String matchUrl = nextEntry.getKey(); - Class resType = nextEntry.getValue(); - if (isNotBlank(matchUrl)) { - IFhirResourceDao resourceDao = myDao.getDao(resType); - Set val = resourceDao.processMatchUrl(matchUrl, theRequest); - if (val.size() > 1) { - throw new InvalidRequestException( - "Unable to process " + theActionName + " - Request would cause multiple resources to match URL: \"" + matchUrl + "\". Does transaction request contain duplicates?"); - } - } - } - - theTransactionStopWatch.endCurrentTask(); - - for (IIdType next : theAllIds) { - IIdType replacement = theIdSubstitutions.get(next); - if (replacement == null) { - continue; - } - if (replacement.equals(next)) { - continue; - } - ourLog.debug("Placeholder resource ID \"{}\" was replaced with permanent ID \"{}\"", next, replacement); - } - return entriesToProcess; - - } finally { - if (theRequest != null) { - theRequest.stopDeferredRequestOperationCallbackAndRunDeferredItems(); + StopWatch sw = new StopWatch(); + myEntityManager.flush(); + ourLog.debug("Session flush took {}ms for {} inserts and {} updates", sw.getMillis(), insertionCount, updateCount); + } catch (PersistenceException e) { + if (myHapiFhirHibernateJpaDialect != null) { + List types = theIdToPersistedOutcome.keySet().stream().filter(t -> t != null).map(t -> t.getResourceType()).collect(Collectors.toList()); + String message = "Error flushing transaction with resource types: " + types; + throw myHapiFhirHibernateJpaDialect.translate(e, message); } + throw e; } } - private void validateResourcePresent(IBaseResource theResource, Integer theOrder, String theVerb) { - if (theResource == null) { - String msg = myContext.getLocalizer().getMessage(TransactionProcessor.class, "missingMandatoryResource", theVerb, theOrder); - throw new InvalidRequestException(msg); - } - } - - private IIdType newIdType(String theResourceType, String theResourceId, String theVersion) { - org.hl7.fhir.r4.model.IdType id = new org.hl7.fhir.r4.model.IdType(theResourceType, theResourceId, theVersion); - return myContext.getVersion().newIdType().setValue(id.getValue()); - } - - private IIdType newIdType(String theToResourceName, String theIdPart) { - return newIdType(theToResourceName, theIdPart, null); - } - - private IFhirResourceDao getDaoOrThrowException(Class theClass) { - return myDaoRegistry.getResourceDao(theClass); - } - - protected void flushJpaSession() { - SessionImpl session = (SessionImpl) myEntityManager.unwrap(Session.class); - int insertionCount = session.getActionQueue().numberOfInsertions(); - int updateCount = session.getActionQueue().numberOfUpdates(); - - StopWatch sw = new StopWatch(); - myEntityManager.flush(); - ourLog.debug("Session flush took {}ms for {} inserts and {} updates", sw.getMillis(), insertionCount, updateCount); - } - - protected String toResourceName(Class theResourceType) { - return myContext.getResourceDefinition(theResourceType).getName(); - } - - public void setContext(FhirContext theContext) { - myContext = theContext; - } - - private String extractTransactionUrlOrThrowException(BUNDLEENTRY nextEntry, String verb) { - String url = myVersionAdapter.getEntryRequestUrl(nextEntry); - if (isBlank(url)) { - throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionMissingUrl", verb)); - } - return url; - } - - private ca.uhn.fhir.jpa.dao.IFhirResourceDao toDao(UrlUtil.UrlParts theParts, String theVerb, String theUrl) { - RuntimeResourceDefinition resType; - try { - resType = myContext.getResourceDefinition(theParts.getResourceType()); - } catch (DataFormatException e) { - String msg = myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl); - throw new InvalidRequestException(msg); - } - IFhirResourceDao dao = null; - if (resType != null) { - dao = myDao.getDao(resType.getImplementingClass()); - } - if (dao == null) { - String msg = myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl); - throw new InvalidRequestException(msg); - } - - // if (theParts.getResourceId() == null && theParts.getParams() == null) { - // String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl); - // throw new InvalidRequestException(msg); - // } - - return dao; - } - - public interface ITransactionProcessorVersionAdapter { - - void setResponseStatus(BUNDLEENTRY theBundleEntry, String theStatus); - - void setResponseLastModified(BUNDLEENTRY theBundleEntry, Date theLastModified); - - void setResource(BUNDLEENTRY theBundleEntry, IBaseResource theResource); - - IBaseResource getResource(BUNDLEENTRY theBundleEntry); - - String getBundleType(BUNDLE theRequest); - - void populateEntryWithOperationOutcome(BaseServerResponseException theCaughtEx, BUNDLEENTRY theEntry); - - BUNDLE createBundle(String theBundleType); - - List getEntries(BUNDLE theRequest); - - void addEntry(BUNDLE theBundle, BUNDLEENTRY theEntry); - - BUNDLEENTRY addEntry(BUNDLE theBundle); - - String getEntryRequestVerb(BUNDLEENTRY theEntry); - - String getFullUrl(BUNDLEENTRY theEntry); - - String getEntryIfNoneExist(BUNDLEENTRY theEntry); - - String getEntryRequestUrl(BUNDLEENTRY theEntry); - - void setResponseLocation(BUNDLEENTRY theEntry, String theResponseLocation); - - void setResponseETag(BUNDLEENTRY theEntry, String theEtag); - - String getEntryRequestIfMatch(BUNDLEENTRY theEntry); - - String getEntryRequestIfNoneExist(BUNDLEENTRY theEntry); - - String getEntryRequestIfNoneMatch(BUNDLEENTRY theEntry); - - void setResponseOutcome(BUNDLEENTRY theEntry, IBaseOperationOutcome theOperationOutcome); - - void setRequestVerb(BUNDLEENTRY theEntry, String theVerb); - - void setRequestUrl(BUNDLEENTRY theEntry, String theUrl); - } - - /** - * Transaction Order, per the spec: - *

- * Process any DELETE interactions - * Process any POST interactions - * Process any PUT interactions - * Process any PATCH interactions - * Process any GET interactions - */ - //@formatter:off - public class TransactionSorter implements Comparator { - - private Set myPlaceholderIds; - - public TransactionSorter(Set thePlaceholderIds) { - myPlaceholderIds = thePlaceholderIds; - } - - @Override - public int compare(BUNDLEENTRY theO1, BUNDLEENTRY theO2) { - int o1 = toOrder(theO1); - int o2 = toOrder(theO2); - - if (o1 == o2) { - String matchUrl1 = toMatchUrl(theO1); - String matchUrl2 = toMatchUrl(theO2); - if (isBlank(matchUrl1) && isBlank(matchUrl2)) { - return 0; - } - if (isBlank(matchUrl1)) { - return -1; - } - if (isBlank(matchUrl2)) { - return 1; - } - - boolean match1containsSubstitutions = false; - boolean match2containsSubstitutions = false; - for (String nextPlaceholder : myPlaceholderIds) { - if (matchUrl1.contains(nextPlaceholder)) { - match1containsSubstitutions = true; - } - if (matchUrl2.contains(nextPlaceholder)) { - match2containsSubstitutions = true; - } - } - - if (match1containsSubstitutions && match2containsSubstitutions) { - return 0; - } - if (!match1containsSubstitutions && !match2containsSubstitutions) { - return 0; - } - if (match1containsSubstitutions) { - return 1; - } else { - return -1; - } - } - - return o1 - o2; - } - - private int toOrder(BUNDLEENTRY theO1) { - int o1 = 0; - if (myVersionAdapter.getEntryRequestVerb(theO1) != null) { - switch (myVersionAdapter.getEntryRequestVerb(theO1)) { - case "DELETE": - o1 = 1; - break; - case "POST": - o1 = 2; - break; - case "PUT": - o1 = 3; - break; - case "PATCH": - o1 = 4; - break; - case "GET": - o1 = 5; - break; - default: - o1 = 0; - break; - } - } - return o1; - } - - } - - private static class BaseServerResponseExceptionHolder { - private BaseServerResponseException myException; - - public BaseServerResponseException getException() { - return myException; - } - - public void setException(BaseServerResponseException myException) { - this.myException = myException; - } - } - - public static boolean isPlaceholder(IIdType theId) { - if (theId != null && theId.getValue() != null) { - return theId.getValue().startsWith("urn:oid:") || theId.getValue().startsWith("urn:uuid:"); - } - return false; - } - - private static String toStatusString(int theStatusCode) { - return Integer.toString(theStatusCode) + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode)); - } - - private String toMatchUrl(BUNDLEENTRY theEntry) { - String verb = myVersionAdapter.getEntryRequestVerb(theEntry); - if (verb.equals("POST")) { - return myVersionAdapter.getEntryIfNoneExist(theEntry); - } - if (verb.equals("PATCH")) { - return myVersionAdapter.getEntryRequestIfMatch(theEntry); - } - if (verb.equals("PUT") || verb.equals("DELETE")) { - String url = extractTransactionUrlOrThrowException(theEntry, verb); - UrlUtil.UrlParts parts = UrlUtil.parseUrl(url); - if (isBlank(parts.getResourceId())) { - return parts.getResourceType() + '?' + parts.getParams(); - } - } - return null; - } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamStringDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamStringDao.java index 60138ee6002..59984d65bb5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamStringDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamStringDao.java @@ -27,10 +27,9 @@ import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -public interface IResourceIndexedSearchParamStringDao extends JpaRepository { +import java.util.List; - @Query("select count(*) from ResourceIndexedSearchParamString t WHERE t.myResourcePid = :resid") - int countForResourceId(@Param("resid") Long theResourcePid); +public interface IResourceIndexedSearchParamStringDao extends JpaRepository { @Modifying @Query("delete from ResourceIndexedSearchParamString t WHERE t.myResourcePid = :resid") diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoBundleDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoBundleDstu3.java index 6f82ffac44c..d65663d7ae7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoBundleDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoBundleDstu3.java @@ -32,18 +32,6 @@ import static org.apache.commons.lang3.StringUtils.defaultString; public class FhirResourceDaoBundleDstu3 extends BaseHapiFhirResourceDao { - @Override - protected void preProcessResourceForStorage(Bundle theResource) { - super.preProcessResourceForStorage(theResource); - - Set allowedBundleTypes = getConfig().getBundleTypesAllowedForStorage(); - if (theResource.getType() == null || !allowedBundleTypes.contains(defaultString(theResource.getType().toCode()))) { - String message = "Unable to store a Bundle resource on this server with a Bundle.type value of: " + (theResource.getType() != null ? theResource.getType().toCode() : "(missing)"); - throw new UnprocessableEntityException(message); - } - - } - - + // nothing } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java index 00d6fbce70c..996b2510b3a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java @@ -20,9 +20,12 @@ package ca.uhn.fhir.jpa.dao.dstu3; * #L% */ +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.IContextValidationSupport; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; +import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.model.entity.ResourceTable; @@ -37,7 +40,6 @@ import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain; import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.Coding; -import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; @@ -63,6 +65,8 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao findCodeSystemIdsContainingSystemAndCode(String theCode, String theSystem, RequestDetails theRequest) { - Set ids = searchForIds(new SearchParameterMap(CodeSystem.SP_CODE, new TokenParam(theSystem, theCode)), theRequest ); + Set ids = searchForIds(new SearchParameterMap(CodeSystem.SP_CODE, new TokenParam(theSystem, theCode)), theRequest ); List valueSetIds = new ArrayList<>(); - for (Long next : ids) { - valueSetIds.add(new IdType("CodeSystem", next)); + for (ResourcePersistentId next : ids) { + IIdType id = myIdHelperService.translatePidIdToForcedId(myFhirContext, "CodeSystem", next); + valueSetIds.add(id); } return valueSetIds; } @@ -135,7 +140,7 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao { @Autowired - private TransactionProcessor myTransactionProcessor; + private TransactionProcessor myTransactionProcessor; @Override @PostConstruct diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java index bd440e4a18e..8adce0073d1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java @@ -26,6 +26,7 @@ import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver; import ca.uhn.fhir.rest.api.server.RequestDetails; @@ -60,7 +61,7 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver { @Override public ResourceTable findTargetResource(RuntimeSearchParam theNextSpDef, String theNextPathsUnsplit, IIdType theNextId, String theTypeString, Class theType, String theId, RequestDetails theRequest) { ResourceTable target; - Long valueOf; + ResourcePersistentId valueOf; try { valueOf = myIdHelperService.translateForcedIdToPid(theTypeString, theId, theRequest); ourLog.trace("Translated {}/{} to resource PID {}", theType, theId, valueOf); @@ -76,12 +77,12 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver { newResource.setId(resName + "/" + theId); IFhirResourceDao placeholderResourceDao = (IFhirResourceDao) myDaoRegistry.getResourceDao(newResource.getClass()); ourLog.debug("Automatically creating empty placeholder resource: {}", newResource.getIdElement().getValue()); - valueOf = placeholderResourceDao.update(newResource).getEntity().getId(); + valueOf = placeholderResourceDao.update(newResource).getEntity().getPersistentId(); } else { throw new InvalidRequestException("Resource " + resName + "/" + theId + " not found, specified in path: " + theNextPathsUnsplit); } } - target = myEntityManager.find(ResourceTable.class, valueOf); + target = myEntityManager.find(ResourceTable.class, valueOf.getIdAsLong()); RuntimeResourceDefinition targetResourceDef = myContext.getResourceDefinition(theType); if (target == null) { String resName = targetResourceDef.getName(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java index bb259188f4b..97b4d89df9d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java @@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.dao.index; * #L% */ +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.model.entity.ForcedId; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; @@ -61,7 +63,7 @@ public class IdHelperService { * @throws ResourceNotFoundException If the ID can not be found */ @Nonnull - public Long translateForcedIdToPid(IIdType theId, RequestDetails theRequestDetails) { + public ResourcePersistentId translateForcedIdToPid(IIdType theId, RequestDetails theRequestDetails) { return translateForcedIdToPid(theId.getResourceType(), theId.getIdPart(), theRequestDetails); } @@ -69,10 +71,10 @@ public class IdHelperService { * @throws ResourceNotFoundException If the ID can not be found */ @Nonnull - public Long translateForcedIdToPid(String theResourceName, String theResourceId, RequestDetails theRequestDetails) throws ResourceNotFoundException { + public ResourcePersistentId translateForcedIdToPid(String theResourceName, String theResourceId, RequestDetails theRequestDetails) throws ResourceNotFoundException { // We only pass 1 input in so only 0..1 will come back IdDt id = new IdDt(theResourceName, theResourceId); - List matches = translateForcedIdToPids(myDaoConfig, myInterceptorBroadcaster, theRequestDetails, myForcedIdDao, Collections.singletonList(id)); + List matches = translateForcedIdToPids(myDaoConfig, myInterceptorBroadcaster, theRequestDetails, myForcedIdDao, Collections.singletonList(id)); assert matches.size() <= 1; if (matches.isEmpty()) { throw new ResourceNotFoundException(id); @@ -80,23 +82,23 @@ public class IdHelperService { return matches.get(0); } - public List translateForcedIdToPids(Collection theId, RequestDetails theRequestDetails) { + public List translateForcedIdToPids(Collection theId, RequestDetails theRequestDetails) { return IdHelperService.translateForcedIdToPids(myDaoConfig, myInterceptorBroadcaster, theRequestDetails, myForcedIdDao, theId); } - private static List translateForcedIdToPids(DaoConfig theDaoConfig, IInterceptorBroadcaster theInterceptorBroadcaster, RequestDetails theRequest, IForcedIdDao theForcedIdDao, Collection theId) { + private static List translateForcedIdToPids(DaoConfig theDaoConfig, IInterceptorBroadcaster theInterceptorBroadcaster, RequestDetails theRequest, IForcedIdDao theForcedIdDao, Collection theId) { theId.forEach(id -> Validate.isTrue(id.hasIdPart())); if (theId.isEmpty()) { return Collections.emptyList(); } - List retVal = new ArrayList<>(); + List retVal = new ArrayList<>(); ListMultimap typeToIds = MultimapBuilder.hashKeys().arrayListValues().build(); for (IIdType nextId : theId) { if (theDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY && isValidPid(nextId)) { - retVal.add(nextId.getIdPartAsLong()); + retVal.add(new ResourcePersistentId(nextId.getIdPartAsLong())); } else { if (nextId.hasResourceType()) { typeToIds.put(nextId.getResourceType(), nextId.getIdPart()); @@ -119,18 +121,34 @@ public class IdHelperService { .add(StorageProcessingMessage.class, msg); JpaInterceptorBroadcaster.doCallHooks(theInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_WARNING, params); - retVal.addAll(theForcedIdDao.findByForcedId(nextIds)); + theForcedIdDao + .findByForcedId(nextIds) + .stream() + .map(t->new ResourcePersistentId(t)) + .forEach(t->retVal.add(t)); } else { - retVal.addAll(theForcedIdDao.findByTypeAndForcedId(nextResourceType, nextIds)); + + theForcedIdDao + .findByTypeAndForcedId(nextResourceType, nextIds) + .stream() + .map(t->new ResourcePersistentId(t)) + .forEach(t->retVal.add(t)); + } } return retVal; } - String translatePidIdToForcedId(String theResourceType, Long theId) { - ForcedId forcedId = myForcedIdDao.findByResourcePid(theId); + public IIdType translatePidIdToForcedId(FhirContext theCtx, String theResourceType, ResourcePersistentId theId) { + IIdType retVal = theCtx.getVersion().newIdType(); + retVal.setValue(translatePidIdToForcedId(theResourceType, theId)); + return retVal; + } + + public String translatePidIdToForcedId(String theResourceType, ResourcePersistentId theId) { + ForcedId forcedId = myForcedIdDao.findByResourcePid(theId.getIdAsLong()); if (forcedId != null) { return forcedId.getResourceType() + '/' + forcedId.getForcedId(); } else { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java index 1a1b7eae5ce..f8a7fd5352a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java @@ -25,9 +25,9 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IDao; -import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao; import ca.uhn.fhir.jpa.dao.MatchResourceUrlService; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique; import ca.uhn.fhir.jpa.model.entity.ResourceLink; @@ -54,7 +54,14 @@ import org.springframework.stereotype.Service; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -238,7 +245,7 @@ public class SearchParamWithInlineReferencesExtractor { throw new InvalidRequestException(msg); } Class matchResourceType = matchResourceDef.getImplementingClass(); - Set matches = myMatchResourceUrlService.processMatchUrl(nextIdText, matchResourceType, theRequest); + Set matches = myMatchResourceUrlService.processMatchUrl(nextIdText, matchResourceType, theRequest); if (matches.isEmpty()) { String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlNoMatches", nextId.getValue()); throw new ResourceNotFoundException(msg); @@ -247,7 +254,7 @@ public class SearchParamWithInlineReferencesExtractor { String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlMultipleMatches", nextId.getValue()); throw new PreconditionFailedException(msg); } - Long next = matches.iterator().next(); + ResourcePersistentId next = matches.iterator().next(); String newId = myIdHelperService.translatePidIdToForcedId(resourceTypeString, next); ourLog.debug("Replacing inline match URL[{}] with ID[{}}", nextId.getValue(), newId); nextRef.setReference(newId); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoBundleR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoBundleR4.java index 693d6e4a904..388df8a46da 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoBundleR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoBundleR4.java @@ -33,16 +33,6 @@ import static org.apache.commons.lang3.StringUtils.defaultString; public class FhirResourceDaoBundleR4 extends BaseHapiFhirResourceDao { - @Override - protected void preProcessResourceForStorage(Bundle theResource) { - super.preProcessResourceForStorage(theResource); - - Set allowedBundleTypes = getConfig().getBundleTypesAllowedForStorage(); - if (theResource.getType() == null || !allowedBundleTypes.contains(defaultString(theResource.getType().toCode()))) { - String message = "Unable to store a Bundle resource on this server with a Bundle.type value of: " + (theResource.getType() != null ? theResource.getType().toCode() : "(missing)"); - throw new UnprocessableEntityException(message); - } - - } + // nothing for now } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java index 35a7cbf3a3b..47f5a4b37d2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java @@ -20,9 +20,12 @@ package ca.uhn.fhir.jpa.dao.r4; * #L% */ +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.IContextValidationSupport; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; +import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.model.entity.ResourceTable; @@ -39,7 +42,6 @@ import org.hl7.fhir.r4.hapi.validation.ValidationSupportChain; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.IdType; import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.Nonnull; @@ -60,14 +62,17 @@ public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao findCodeSystemIdsContainingSystemAndCode(String theCode, String theSystem, RequestDetails theRequest) { List valueSetIds; - Set ids = searchForIds(new SearchParameterMap(CodeSystem.SP_CODE, new TokenParam(theSystem, theCode)), theRequest); + Set ids = searchForIds(new SearchParameterMap(CodeSystem.SP_CODE, new TokenParam(theSystem, theCode)), theRequest); valueSetIds = new ArrayList<>(); - for (Long next : ids) { - valueSetIds.add(new IdType("CodeSystem", next)); + for (ResourcePersistentId next : ids) { + IIdType id = myIdHelperService.translatePidIdToForcedId(myFhirContext, "CodeSystem", next); + valueSetIds.add(id); } return valueSetIds; } @@ -132,7 +137,7 @@ public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao } @Override - public ResourceTable updateEntity(RequestDetails theRequestDetails, IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, + public ResourceTable updateEntity(RequestDetails theRequestDetails, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { ResourceTable retVal = super.updateEntity(theRequestDetails, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java index 9faadcc0b6f..ccbc4f873d7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4.java @@ -45,7 +45,7 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoR4.class); @Autowired - private TransactionProcessor myTransactionProcessor; + private TransactionProcessor myTransactionProcessor; @Override @PostConstruct diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoBundleR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoBundleR5.java index 8431709466a..26468114ca6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoBundleR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoBundleR5.java @@ -30,16 +30,6 @@ import static org.apache.commons.lang3.StringUtils.defaultString; public class FhirResourceDaoBundleR5 extends BaseHapiFhirResourceDao { - @Override - protected void preProcessResourceForStorage(Bundle theResource) { - super.preProcessResourceForStorage(theResource); - - Set allowedBundleTypes = getConfig().getBundleTypesAllowedForStorage(); - if (theResource.getType() == null || !allowedBundleTypes.contains(defaultString(theResource.getType().toCode()))) { - String message = "Unable to store a Bundle resource on this server with a Bundle.type value of: " + (theResource.getType() != null ? theResource.getType().toCode() : "(missing)"); - throw new UnprocessableEntityException(message); - } - - } + // nothing } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java index 8e030595e0f..ff7916f9c20 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java @@ -20,10 +20,14 @@ package ca.uhn.fhir.jpa.dao.r5; * #L% */ +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.IContextValidationSupport; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; +import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; @@ -39,7 +43,6 @@ import org.hl7.fhir.r5.hapi.validation.ValidationSupportChain; import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.Coding; -import org.hl7.fhir.r5.model.IdType; import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.Nonnull; @@ -60,14 +63,19 @@ public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao findCodeSystemIdsContainingSystemAndCode(String theCode, String theSystem, RequestDetails theRequest) { List valueSetIds; - Set ids = searchForIds(new SearchParameterMap(CodeSystem.SP_CODE, new TokenParam(theSystem, theCode)), theRequest); + Set ids = searchForIds(new SearchParameterMap(CodeSystem.SP_CODE, new TokenParam(theSystem, theCode)), theRequest); valueSetIds = new ArrayList<>(); - for (Long next : ids) { - valueSetIds.add(new IdType("CodeSystem", next)); + for (ResourcePersistentId next : ids) { + IIdType id = myIdHelperService.translatePidIdToForcedId(myFhirContext, "CodeSystem", next); + valueSetIds.add(id); } return valueSetIds; } @@ -132,7 +140,7 @@ public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao } @Override - public ResourceTable updateEntity(RequestDetails theRequestDetails, IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, + public ResourceTable updateEntity(RequestDetails theRequestDetails, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion, Date theUpdateTime, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { ResourceTable retVal = super.updateEntity(theRequestDetails, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime, theForceUpdate, theCreateNewHistoryEntry); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirSystemDaoR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirSystemDaoR5.java index 3143e98d89d..f571d9703ca 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirSystemDaoR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirSystemDaoR5.java @@ -45,7 +45,7 @@ public class FhirSystemDaoR5 extends BaseHapiFhirSystemDao { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoR5.class); @Autowired - private TransactionProcessor myTransactionProcessor; + private TransactionProcessor myTransactionProcessor; @Override @PostConstruct diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictService.java index 7f8f18429e6..82e45f64832 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictService.java @@ -132,12 +132,12 @@ public class DeleteConflictService { } } - public void validateDeleteConflictsEmptyOrThrowException(DeleteConflictList theDeleteConflicts) { + public static void validateDeleteConflictsEmptyOrThrowException(FhirContext theFhirContext, DeleteConflictList theDeleteConflicts) { if (theDeleteConflicts.isEmpty()) { return; } - IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(myFhirContext); + IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(theFhirContext); String firstMsg = null; Iterator iterator = theDeleteConflicts.iterator(); @@ -154,7 +154,7 @@ public class DeleteConflictService { if (firstMsg == null) { firstMsg = msg; } - OperationOutcomeUtil.addIssue(myFhirContext, oo, BaseHapiFhirDao.OO_SEVERITY_ERROR, msg, null, "processing"); + OperationOutcomeUtil.addIssue(theFhirContext, oo, BaseHapiFhirDao.OO_SEVERITY_ERROR, msg, null, "processing"); } throw new ResourceVersionConflictException(firstMsg, oo); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/graphql/JpaStorageServices.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/graphql/JpaStorageServices.java index 01636abdd44..92614f3b61b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/graphql/JpaStorageServices.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/graphql/JpaStorageServices.java @@ -42,6 +42,7 @@ import org.hl7.fhir.utilities.graphql.Value; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import javax.annotation.Nullable; import java.util.List; public class JpaStorageServices extends BaseHapiFhirDao implements IGraphQLStorageServices { @@ -145,4 +146,10 @@ public class JpaStorageServices extends BaseHapiFhirDao implement public IBaseBundle search(Object theAppInfo, String theType, List theSearchParams) throws FHIRException { throw new NotImplementedOperationException("Not yet able to handle this GraphQL request"); } + + @Nullable + @Override + protected String getResourceName() { + return null; + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/JpaPreResourceAccessDetails.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/JpaPreResourceAccessDetails.java index 4e40d3cea30..e8b68472fbe 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/JpaPreResourceAccessDetails.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/JpaPreResourceAccessDetails.java @@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.interceptor; */ import ca.uhn.fhir.jpa.dao.ISearchBuilder; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails; import ca.uhn.fhir.util.ICallable; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -36,12 +37,12 @@ import java.util.List; @NotThreadSafe public class JpaPreResourceAccessDetails implements IPreResourceAccessDetails { - private final List myResourcePids; + private final List myResourcePids; private final boolean[] myBlocked; private final ICallable mySearchBuilderSupplier; private List myResources; - public JpaPreResourceAccessDetails(List theResourcePids, ICallable theSearchBuilderSupplier) { + public JpaPreResourceAccessDetails(List theResourcePids, ICallable theSearchBuilderSupplier) { myResourcePids = theResourcePids; myBlocked = new boolean[myResourcePids.size()]; mySearchBuilderSupplier = theSearchBuilderSupplier; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/ISearchCoordinatorSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/ISearchCoordinatorSvc.java index eed92afe78a..c22604fc868 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/ISearchCoordinatorSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/ISearchCoordinatorSvc.java @@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.search; */ import ca.uhn.fhir.jpa.dao.IDao; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.server.IBundleProvider; @@ -34,7 +35,7 @@ public interface ISearchCoordinatorSvc { void cancelAllActiveSearches(); - List getResources(String theUuid, int theFrom, int theTo, @Nullable RequestDetails theRequestDetails); + List getResources(String theUuid, int theFrom, int theTo, @Nullable RequestDetails theRequestDetails); IBundleProvider registerSearch(IDao theCallingDao, SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective, @Nullable RequestDetails theRequestDetails); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java index e64d8b128a8..75eea399406 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java @@ -26,6 +26,7 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.jpa.dao.IDao; import ca.uhn.fhir.jpa.dao.ISearchBuilder; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.SearchTypeEnum; import ca.uhn.fhir.jpa.model.entity.BaseHasResource; @@ -175,7 +176,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider { Class resourceType = myContext.getResourceDefinition(resourceName).getImplementingClass(); sb.setType(resourceType, resourceName); - final List pidsSubList = mySearchCoordinatorSvc.getResources(myUuid, theFromIndex, theToIndex, myRequest); + final List pidsSubList = mySearchCoordinatorSvc.getResources(myUuid, theFromIndex, theToIndex, myRequest); TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); @@ -310,15 +311,15 @@ public class PersistedJpaBundleProvider implements IBundleProvider { // Note: Leave as protected, HSPC depends on this @SuppressWarnings("WeakerAccess") - protected List toResourceList(ISearchBuilder theSearchBuilder, List thePids) { - Set includedPids = new HashSet<>(); + protected List toResourceList(ISearchBuilder theSearchBuilder, List thePids) { + Set includedPids = new HashSet<>(); if (mySearchEntity.getSearchType() == SearchTypeEnum.SEARCH) { includedPids.addAll(theSearchBuilder.loadIncludes(myContext, myEntityManager, thePids, mySearchEntity.toRevIncludesList(), true, mySearchEntity.getLastUpdated(), myUuid, myRequest)); includedPids.addAll(theSearchBuilder.loadIncludes(myContext, myEntityManager, thePids, mySearchEntity.toIncludesList(), false, mySearchEntity.getLastUpdated(), myUuid, myRequest)); } - List includedPidList = new ArrayList<>(includedPids); + List includedPidList = new ArrayList<>(includedPids); // Execute the query and make sure we return distinct results List resources = new ArrayList<>(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java index edeff0255e1..23a95ae587e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java @@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.search; import ca.uhn.fhir.jpa.dao.IDao; import ca.uhn.fhir.jpa.dao.ISearchBuilder; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl.SearchTask; @@ -66,7 +67,7 @@ public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundl mySearchTask.awaitInitialSync(); ourLog.trace("Fetching search resource PIDs from task: {}", mySearchTask.getClass()); - final List pids = mySearchTask.getResourcePids(theFromIndex, theToIndex); + final List pids = mySearchTask.getResourcePids(theFromIndex, theToIndex); ourLog.trace("Done fetching search resource PIDs"); TransactionTemplate txTemplate = new TransactionTemplate(myTxManager); 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 0c8f48f97fb..682c0acb068 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 @@ -29,6 +29,7 @@ import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.SearchInclude; import ca.uhn.fhir.jpa.entity.SearchTypeEnum; import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc; @@ -173,7 +174,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { */ @Override @Transactional(propagation = Propagation.NEVER) - public List getResources(final String theUuid, int theFrom, int theTo, @Nullable RequestDetails theRequestDetails) { + public List getResources(final String theUuid, int theFrom, int theTo, @Nullable RequestDetails theRequestDetails) { TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); @@ -193,7 +194,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { if (myNeverUseLocalSearchForUnitTests == false) { if (searchTask != null) { ourLog.trace("Local search found"); - List resourcePids = searchTask.getResourcePids(theFrom, theTo); + List resourcePids = searchTask.getResourcePids(theFrom, theTo); ourLog.trace("Local search returned {} pids, wanted {}-{} - Search: {}", resourcePids.size(), theFrom, theTo, searchTask.getSearch()); /* @@ -249,7 +250,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { ourLog.trace("Finished looping"); - List pids = mySearchResultCacheSvc.fetchResultPids(search, theFrom, theTo); + List pids = mySearchResultCacheSvc.fetchResultPids(search, theFrom, theTo); if (pids == null) { throw newResourceGoneException(theUuid); } @@ -446,7 +447,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { return txTemplate.execute(t -> { // Load the results synchronously - final List pids = new ArrayList<>(); + final List pids = new ArrayList<>(); try (IResultIterator resultIter = theSb.createQuery(theParams, searchRuntimeDetails, theRequestDetails)) { while (resultIter.hasNext()) { @@ -485,10 +486,10 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { * On the other hand for async queries we load includes/revincludes * individually for pages as we return them to clients */ - final Set includedPids = new HashSet<>(); + final Set includedPids = new HashSet<>(); includedPids.addAll(theSb.loadIncludes(myContext, myEntityManager, pids, theParams.getRevIncludes(), true, theParams.getLastUpdated(), "(synchronous)", theRequestDetails)); includedPids.addAll(theSb.loadIncludes(myContext, myEntityManager, pids, theParams.getIncludes(), false, theParams.getLastUpdated(), "(synchronous)", theRequestDetails)); - List includedPidsList = new ArrayList<>(includedPids); + List includedPidsList = new ArrayList<>(includedPids); List resources = new ArrayList<>(); theSb.loadResourcesByPid(pids, includedPidsList, resources, false, theRequestDetails); @@ -580,10 +581,10 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { private final SearchParameterMap myParams; private final IDao myCallingDao; private final String myResourceType; - private final ArrayList mySyncedPids = new ArrayList<>(); + private final ArrayList mySyncedPids = new ArrayList<>(); private final CountDownLatch myInitialCollectionLatch = new CountDownLatch(1); private final CountDownLatch myCompletionLatch; - private final ArrayList myUnsyncedPids = new ArrayList<>(); + private final ArrayList myUnsyncedPids = new ArrayList<>(); private final RequestDetails myRequest; private Search mySearch; private boolean myAbortRequested; @@ -591,7 +592,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { private int myCountSavedThisPass = 0; private int myCountBlockedThisPass = 0; private boolean myAdditionalPrefetchThresholdsRemaining; - private List myPreviouslyAddedResourcePids; + private List myPreviouslyAddedResourcePids; private Integer myMaxResultsToFetch; private SearchRuntimeDetails mySearchRuntimeDetails; @@ -635,7 +636,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { return myInitialCollectionLatch; } - void setPreviouslyAddedResourcePids(List thePreviouslyAddedResourcePids) { + void setPreviouslyAddedResourcePids(List thePreviouslyAddedResourcePids) { myPreviouslyAddedResourcePids = thePreviouslyAddedResourcePids; myCountSavedTotal = myPreviouslyAddedResourcePids.size(); } @@ -649,7 +650,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { } @Nonnull - List getResourcePids(int theFromIndex, int theToIndex) { + List getResourcePids(int theFromIndex, int theToIndex) { ourLog.debug("Requesting search PIDs from {}-{}", theFromIndex, theToIndex); boolean keepWaiting; @@ -673,6 +674,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { break; case FAILED: case FINISHED: + case GONE: default: keepWaiting = false; break; @@ -690,7 +692,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { ourLog.debug("Proceeding, as we have {} results", mySyncedPids.size()); - ArrayList retVal = new ArrayList<>(); + ArrayList retVal = new ArrayList<>(); synchronized (mySyncedPids) { verifySearchHasntFailedOrThrowInternalErrorException(mySearch); @@ -713,7 +715,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); txTemplate.execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(@NotNull TransactionStatus theArg0) { + protected void doInTransactionWithoutResult(@Nonnull @NotNull TransactionStatus theArg0) { doSaveSearch(); } @@ -725,12 +727,12 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); txTemplate.execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(@NotNull TransactionStatus theArg0) { + protected void doInTransactionWithoutResult(@Nonnull @NotNull TransactionStatus theArg0) { if (mySearch.getId() == null) { doSaveSearch(); } - ArrayList unsyncedPids = myUnsyncedPids; + ArrayList unsyncedPids = myUnsyncedPids; int countBlocked = 0; // Interceptor call: STORAGE_PREACCESS_RESOURCES @@ -847,7 +849,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { txTemplate.execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { doSearch(); } }); @@ -962,7 +964,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager); txTemplate.execute(new TransactionCallbackWithoutResult() { @Override - protected void doInTransactionWithoutResult(TransactionStatus theArg0) { + protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theArg0) { mySearch.setTotalCount(count.intValue()); if (wantOnlyCount) { mySearch.setStatus(SearchStatusEnum.FINISHED); @@ -1093,7 +1095,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager); txTemplate.afterPropertiesSet(); txTemplate.execute(t -> { - List previouslyAddedResourcePids = mySearchResultCacheSvc.fetchAllResultPids(getSearch()); + List previouslyAddedResourcePids = mySearchResultCacheSvc.fetchAllResultPids(getSearch()); if (previouslyAddedResourcePids == null) { throw newResourceGoneException(getSearch().getUuid()); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/cache/DatabaseSearchResultCacheSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/cache/DatabaseSearchResultCacheSvcImpl.java index b1fb0258642..084b6eb9493 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/cache/DatabaseSearchResultCacheSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/cache/DatabaseSearchResultCacheSvcImpl.java @@ -20,19 +20,17 @@ package ca.uhn.fhir.jpa.search.cache; * #L% */ +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.SearchResult; -import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; -import org.apache.commons.lang3.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; import javax.transaction.Transactional; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -46,7 +44,7 @@ public class DatabaseSearchResultCacheSvcImpl implements ISearchResultCacheSvc { @Override @Transactional(Transactional.TxType.REQUIRED) - public List fetchResultPids(Search theSearch, int theFrom, int theTo) { + public List fetchResultPids(Search theSearch, int theFrom, int theTo) { final Pageable page = toPage(theFrom, theTo); if (page == null) { return Collections.emptyList(); @@ -58,28 +56,28 @@ public class DatabaseSearchResultCacheSvcImpl implements ISearchResultCacheSvc { ourLog.debug("fetchResultPids for range {}-{} returned {} pids", theFrom, theTo, retVal.size()); - return new ArrayList<>(retVal); + return ResourcePersistentId.fromLongList(retVal); } @Override @Transactional(Transactional.TxType.REQUIRED) - public List fetchAllResultPids(Search theSearch) { + public List fetchAllResultPids(Search theSearch) { List retVal = mySearchResultDao.findWithSearchPidOrderIndependent(theSearch.getId()); ourLog.trace("fetchAllResultPids returned {} pids", retVal.size()); - return retVal; + return ResourcePersistentId.fromLongList(retVal); } @Override @Transactional(Transactional.TxType.REQUIRED) - public void storeResults(Search theSearch, List thePreviouslyStoredResourcePids, List theNewResourcePids) { + public void storeResults(Search theSearch, List thePreviouslyStoredResourcePids, List theNewResourcePids) { List resultsToSave = Lists.newArrayList(); ourLog.trace("Storing {} results with {} previous for search", theNewResourcePids.size(), thePreviouslyStoredResourcePids.size()); int order = thePreviouslyStoredResourcePids.size(); - for (Long nextPid : theNewResourcePids) { + for (ResourcePersistentId nextPid : theNewResourcePids) { SearchResult nextResult = new SearchResult(theSearch); - nextResult.setResourcePid(nextPid); + nextResult.setResourcePid(nextPid.getIdAsLong()); nextResult.setOrder(order); resultsToSave.add(nextResult); ourLog.trace("Saving ORDER[{}] Resource {}", order, nextResult.getResourcePid()); @@ -90,10 +88,4 @@ public class DatabaseSearchResultCacheSvcImpl implements ISearchResultCacheSvc { mySearchResultDao.saveAll(resultsToSave); } - @VisibleForTesting - void setSearchDaoResultForUnitTest(ISearchResultDao theSearchResultDao) { - mySearchResultDao = theSearchResultDao; - } - - } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/cache/ISearchResultCacheSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/cache/ISearchResultCacheSvc.java index ef1b0e0ef17..7340d5b7b7d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/cache/ISearchResultCacheSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/cache/ISearchResultCacheSvc.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.search.cache; * #L% */ +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.entity.Search; import javax.annotation.Nullable; @@ -31,7 +32,7 @@ public interface ISearchResultCacheSvc { * @param thePreviouslyStoredResourcePids A list of resource PIDs that have previously been saved to this search * @param theNewResourcePids A list of new resoure PIDs to add to this search (these ones have not been previously saved) */ - void storeResults(Search theSearch, List thePreviouslyStoredResourcePids, List theNewResourcePids); + void storeResults(Search theSearch, List thePreviouslyStoredResourcePids, List theNewResourcePids); /** * Fetch a sunset of the search result IDs from the cache @@ -43,7 +44,7 @@ public interface ISearchResultCacheSvc { * have been removed from the cache for some reason, such as expiry or manual purge) */ @Nullable - List fetchResultPids(Search theSearch, int theFrom, int theTo); + List fetchResultPids(Search theSearch, int theFrom, int theTo); /** * Fetch all result PIDs for a given search with no particular order required @@ -53,6 +54,6 @@ public interface ISearchResultCacheSvc { * have been removed from the cache for some reason, such as expiry or manual purge) */ @Nullable - List fetchAllResultPids(Search theSearch); + List fetchAllResultPids(Search theSearch); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringSvcImpl.java index 48bf8859030..756ad775c24 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringSvcImpl.java @@ -25,6 +25,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.sched.FireAtIntervalJob; import ca.uhn.fhir.jpa.model.sched.ISchedulerService; import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition; @@ -37,8 +38,6 @@ import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.rest.param.UriParam; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; @@ -252,12 +251,12 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc toIndex = Math.min(toIndex, theJobDetails.getCurrentSearchCount()); } ourLog.info("Triggering job[{}] search {} requesting resources {} - {}", theJobDetails.getJobId(), theJobDetails.getCurrentSearchUuid(), fromIndex, toIndex); - List resourceIds = mySearchCoordinatorSvc.getResources(theJobDetails.getCurrentSearchUuid(), fromIndex, toIndex, null); + List resourceIds = mySearchCoordinatorSvc.getResources(theJobDetails.getCurrentSearchUuid(), fromIndex, toIndex, null); ourLog.info("Triggering job[{}] delivering {} resources", theJobDetails.getJobId(), resourceIds.size()); int highestIndexSubmitted = theJobDetails.getCurrentSearchLastUploadedIndex(); - for (Long next : resourceIds) { + for (ResourcePersistentId next : resourceIds) { IBaseResource nextResource = resourceDao.readByPid(next); Future future = submitResource(theJobDetails.getSubscriptionId(), nextResource); futures.add(Pair.of(nextResource.getIdElement().getIdPart(), future)); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java index c3f13b35ade..c1abe4308d1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java @@ -33,6 +33,7 @@ import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.data.*; import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.sched.ISchedulerService; import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition; @@ -338,8 +339,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo Optional optionalTermValueSet; if (theValueSetToExpand.hasId()) { - Long valueSetResourcePid = myConceptStorageSvc.getValueSetResourcePid(theValueSetToExpand.getIdElement()); - optionalTermValueSet = myValueSetDao.findByResourcePid(valueSetResourcePid); + ResourcePersistentId valueSetResourcePid = myConceptStorageSvc.getValueSetResourcePid(theValueSetToExpand.getIdElement()); + optionalTermValueSet = myValueSetDao.findByResourcePid(valueSetResourcePid.getIdAsLong()); } else if (theValueSetToExpand.hasUrl()) { optionalTermValueSet = myValueSetDao.findByUrl(theValueSetToExpand.getUrl()); } else { @@ -1113,8 +1114,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo @Override public boolean isValueSetPreExpandedForCodeValidation(ValueSet theValueSet) { - Long valueSetResourcePid = myConceptStorageSvc.getValueSetResourcePid(theValueSet.getIdElement()); - Optional optionalTermValueSet = myValueSetDao.findByResourcePid(valueSetResourcePid); + ResourcePersistentId valueSetResourcePid = myConceptStorageSvc.getValueSetResourcePid(theValueSet.getIdElement()); + Optional optionalTermValueSet = myValueSetDao.findByResourcePid(valueSetResourcePid.getIdAsLong()); if (!optionalTermValueSet.isPresent()) { ourLog.warn("ValueSet is not present in terminology tables. Will perform in-memory code validation. {}", getValueSetInfo(theValueSet)); @@ -1136,14 +1137,14 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo ValueSet theValueSet, String theSystem, String theCode, String theDisplay, Coding theCoding, CodeableConcept theCodeableConcept) { ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet.hasId(), "ValueSet.id is required"); - Long valueSetResourcePid = myConceptStorageSvc.getValueSetResourcePid(theValueSet.getIdElement()); + ResourcePersistentId valueSetResourcePid = myConceptStorageSvc.getValueSetResourcePid(theValueSet.getIdElement()); List concepts = new ArrayList<>(); if (isNotBlank(theCode)) { - if (isNotBlank(theSystem)) { + if (Constants.codeSystemNotNeeded(theSystem)) { + concepts.addAll(myValueSetConceptDao.findByValueSetResourcePidAndCode(valueSetResourcePid.getIdAsLong(), theCode)); + } else if (isNotBlank(theSystem)) { concepts.addAll(findByValueSetResourcePidSystemAndCode(valueSetResourcePid, theSystem, theCode)); - } if (Constants.codeSystemNotNeeded(theSystem)) { - concepts.addAll(myValueSetConceptDao.findByValueSetResourcePidAndCode(valueSetResourcePid, theCode)); } } else if (theCoding != null) { if (theCoding.hasSystem() && theCoding.hasCode()) { @@ -1173,9 +1174,9 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo return null; } - private List findByValueSetResourcePidSystemAndCode(Long theResourcePid, String theSystem, String theCode) { + private List findByValueSetResourcePidSystemAndCode(ResourcePersistentId theResourcePid, String theSystem, String theCode) { List retVal = new ArrayList<>(); - Optional optionalTermValueSetConcept = myValueSetConceptDao.findByValueSetResourcePidSystemAndCode(theResourcePid, theSystem, theCode); + Optional optionalTermValueSetConcept = myValueSetConceptDao.findByValueSetResourcePidSystemAndCode(theResourcePid.getIdAsLong(), theSystem, theCode); optionalTermValueSetConcept.ifPresent(retVal::add); return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java index 1049ca238f5..91065d40a92 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java @@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.dao.data.*; import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.entity.*; @@ -114,11 +115,11 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { private IResourceTableDao myResourceTableDao; @Override - public Long getValueSetResourcePid(IIdType theIdType) { + public ResourcePersistentId getValueSetResourcePid(IIdType theIdType) { return getValueSetResourcePid(theIdType, null); } - private Long getValueSetResourcePid(IIdType theIdType, RequestDetails theRequestDetails) { + private ResourcePersistentId getValueSetResourcePid(IIdType theIdType, RequestDetails theRequestDetails) { return myIdHelperService.translateForcedIdToPid(theIdType, theRequestDetails); } @@ -315,7 +316,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { if (theCodeSystem.getContent() == CodeSystem.CodeSystemContentMode.COMPLETE || theCodeSystem.getContent() == null || theCodeSystem.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) { ourLog.info("CodeSystem {} has a status of {}, going to store concepts in terminology tables", theResourceEntity.getIdDt().getValue(), theCodeSystem.getContentElement().getValueAsString()); - Long codeSystemResourcePid = getCodeSystemResourcePid(theCodeSystem.getIdElement()); + ResourcePersistentId codeSystemResourcePid = getCodeSystemResourcePid(theCodeSystem.getIdElement()); /* * If this is a not-present codesystem, we don't want to store a new version if one @@ -349,8 +350,8 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL"); IIdType csId = myTerminologyVersionAdapterSvc.createOrUpdateCodeSystem(theCodeSystemResource); - Long codeSystemResourcePid = myIdHelperService.translateForcedIdToPid(csId, theRequest); - ResourceTable resource = myResourceTableDao.getOne(codeSystemResourcePid); + ResourcePersistentId codeSystemResourcePid = myIdHelperService.translateForcedIdToPid(csId, theRequest); + ResourceTable resource = myResourceTableDao.getOne(codeSystemResourcePid.getIdAsLong()); ourLog.info("CodeSystem resource has ID: {}", csId.getValue()); @@ -366,14 +367,14 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { @Override @Transactional(propagation = Propagation.REQUIRED) - public void storeNewCodeSystemVersion(Long theCodeSystemResourcePid, String theSystemUri, String theSystemName, String theSystemVersionId, TermCodeSystemVersion theCodeSystemVersion, ResourceTable theCodeSystemResourceTable) { + public void storeNewCodeSystemVersion(ResourcePersistentId theCodeSystemResourcePid, String theSystemUri, String theSystemName, String theSystemVersionId, TermCodeSystemVersion theCodeSystemVersion, ResourceTable theCodeSystemResourceTable) { ourLog.debug("Storing code system"); ValidateUtil.isTrueOrThrowInvalidRequest(theCodeSystemVersion.getResource() != null, "No resource supplied"); ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystemUri, "No system URI supplied"); // Grab the existing versions so we can delete them later - List existing = myCodeSystemVersionDao.findByCodeSystemResourcePid(theCodeSystemResourcePid); + List existing = myCodeSystemVersionDao.findByCodeSystemResourcePid(theCodeSystemResourcePid.getIdAsLong()); /* * For now we always delete old versions. At some point it would be nice to allow configuration to keep old versions. @@ -598,11 +599,11 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { } - private Long getCodeSystemResourcePid(IIdType theIdType) { + private ResourcePersistentId getCodeSystemResourcePid(IIdType theIdType) { return getCodeSystemResourcePid(theIdType, null); } - private Long getCodeSystemResourcePid(IIdType theIdType, RequestDetails theRequestDetails) { + private ResourcePersistentId getCodeSystemResourcePid(IIdType theIdType, RequestDetails theRequestDetails) { return myIdHelperService.translateForcedIdToPid(theIdType, theRequestDetails); } @@ -676,10 +677,10 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { } @Nonnull - private TermCodeSystem getOrCreateTermCodeSystem(Long theCodeSystemResourcePid, String theSystemUri, String theSystemName, ResourceTable theCodeSystemResourceTable) { + private TermCodeSystem getOrCreateTermCodeSystem(ResourcePersistentId theCodeSystemResourcePid, String theSystemUri, String theSystemName, ResourceTable theCodeSystemResourceTable) { TermCodeSystem codeSystem = myCodeSystemDao.findByCodeSystemUri(theSystemUri); if (codeSystem == null) { - codeSystem = myCodeSystemDao.findByResourcePid(theCodeSystemResourcePid); + codeSystem = myCodeSystemDao.findByResourcePid(theCodeSystemResourcePid.getIdAsLong()); if (codeSystem == null) { codeSystem = new TermCodeSystem(); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermCodeSystemStorageSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermCodeSystemStorageSvc.java index 58ec296c2fb..46c11a56226 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermCodeSystemStorageSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermCodeSystemStorageSvc.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.term.api; * #L% */ +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; @@ -40,7 +41,7 @@ public interface ITermCodeSystemStorageSvc { void deleteCodeSystem(TermCodeSystem theCodeSystem); - void storeNewCodeSystemVersion(Long theCodeSystemResourcePid, String theSystemUri, String theSystemName, String theSystemVersionId, TermCodeSystemVersion theCodeSystemVersion, ResourceTable theCodeSystemResourceTable); + void storeNewCodeSystemVersion(ResourcePersistentId theCodeSystemResourcePid, String theSystemUri, String theSystemName, String theSystemVersionId, TermCodeSystemVersion theCodeSystemVersion, ResourceTable theCodeSystemResourceTable); /** * @return Returns the ID of the created/updated code system @@ -55,5 +56,5 @@ public interface ITermCodeSystemStorageSvc { int saveConcept(TermConcept theNextConcept); - Long getValueSetResourcePid(IIdType theIdElement); + ResourcePersistentId getValueSetResourcePid(IIdType theIdElement); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java index abc6c727057..fb81ca3896f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java @@ -24,7 +24,7 @@ import java.sql.SQLException; import java.util.Properties; import java.util.concurrent.TimeUnit; -import static org.junit.Assert.*; +import static org.junit.Assert.fail; @Configuration @Import(TestJPAConfig.class) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java index f43f47a5522..788f55b04de 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2SearchNoFtTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.dao.dstu2; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao; @@ -1537,7 +1538,7 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test { String methodName = "testSearchValueQuantity"; QuantityParam param; - Set found; + Set found; param = new QuantityParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, new BigDecimal("10"), null, null); found = myObservationDao.searchForIds(new SearchParameterMap("value-quantity", param), null); int initialSize = found.size(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java index e529b3bebd3..a8a2d2ec5e6 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java @@ -3,7 +3,7 @@ package ca.uhn.fhir.jpa.dao.dstu2; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.JpaResourceDao; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoDstu3Test; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; @@ -221,8 +221,8 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test { assertThat(toUnqualifiedVersionlessIdValues(found), hasItem(id2.getValue())); } { - Set found = myObservationDao.searchForIds(new SearchParameterMap(Observation.SP_DATE, new DateParam(">2016-01-02")), null); - assertThat(found, not(hasItem(id2.getIdPartAsLong()))); + Set found = myObservationDao.searchForIds(new SearchParameterMap(Observation.SP_DATE, new DateParam(">2016-01-02")), null); + assertThat(ResourcePersistentId.toLongList(found), not(hasItem(id2.getIdPartAsLong()))); } } @@ -1548,7 +1548,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test { "}\n"; //@formatter:on - Set val = myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam("P")), null); + Set val = myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam("P")), null); int initial = val.size(); Organization org = myFhirCtx.newJsonParser().parseResource(Organization.class, inputStr); @@ -2677,7 +2677,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test { assertThat(str.length(), greaterThan(ResourceIndexedSearchParamString.MAX_LENGTH)); - Set val = myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam("P")), null); + Set val = myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam("P")), null); int initial = val.size(); myOrganizationDao.create(org, mySrd); @@ -2825,7 +2825,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test { String subStr1 = longStr1.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); String subStr2 = longStr2.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); - Set val = myOrganizationDao.searchForIds(new SearchParameterMap("type", new IdentifierDt(subStr1, subStr2)), null); + Set val = myOrganizationDao.searchForIds(new SearchParameterMap("type", new IdentifierDt(subStr1, subStr2)), null); int initial = val.size(); myOrganizationDao.create(org, mySrd); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2UpdateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2UpdateTest.java index e604dea308d..7492380a238 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2UpdateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2UpdateTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.dao.dstu2; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.Tag; @@ -166,9 +167,9 @@ public class FhirResourceDaoDstu2UpdateTest extends BaseJpaDstu2Test { p2.addName().addFamily("Tester").addGiven("testUpdateMaintainsSearchParamsDstu2BBB"); myPatientDao.create(p2, mySrd); - Set ids = myPatientDao.searchForIds(new SearchParameterMap(Patient.SP_GIVEN, new StringDt("testUpdateMaintainsSearchParamsDstu2AAA")), null); + Set ids = myPatientDao.searchForIds(new SearchParameterMap(Patient.SP_GIVEN, new StringDt("testUpdateMaintainsSearchParamsDstu2AAA")), null); assertEquals(1, ids.size()); - assertThat(ids, contains(p1id.getIdPartAsLong())); + assertThat(ResourcePersistentId.toLongList(ids), contains(p1id.getIdPartAsLong())); // Update the name p1.getNameFirstRep().getGivenFirstRep().setValue("testUpdateMaintainsSearchParamsDstu2BBB"); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirSearchDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirSearchDaoDstu2Test.java index a7c6583e245..dbf5c46345e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirSearchDaoDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirSearchDaoDstu2Test.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertThat; import java.util.List; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import org.junit.AfterClass; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -65,8 +66,8 @@ public class FhirSearchDaoDstu2Test extends BaseJpaDstu2Test { content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS"))); map.add(Constants.PARAM_CONTENT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1)); } // OR { @@ -74,8 +75,8 @@ public class FhirSearchDaoDstu2Test extends BaseJpaDstu2Test { content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS")).addOr(new StringParam("AAAB"))); map.add(Constants.PARAM_CONTENT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1, id2)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1, id2)); } // AND { @@ -84,8 +85,8 @@ public class FhirSearchDaoDstu2Test extends BaseJpaDstu2Test { content.addAnd(new StringOrListParam().addOr(new StringParam("CCC"))); map.add(Constants.PARAM_CONTENT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1)); } // AND OR { @@ -94,8 +95,8 @@ public class FhirSearchDaoDstu2Test extends BaseJpaDstu2Test { content.addAnd(new StringOrListParam().addOr(new StringParam("CCC"))); map.add(Constants.PARAM_CONTENT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1, id2)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1, id2)); } // All Resource Types { @@ -103,8 +104,8 @@ public class FhirSearchDaoDstu2Test extends BaseJpaDstu2Test { content.addAnd(new StringOrListParam().addOr(new StringParam("CCC")).addOr(new StringParam("DDD"))); map.add(Constants.PARAM_CONTENT, content); - List found = mySearchDao.search(null, map); - assertThat(found, containsInAnyOrder(id1, id2, id3)); + List found = mySearchDao.search(null, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1, id2, id3)); } } @@ -133,8 +134,8 @@ public class FhirSearchDaoDstu2Test extends BaseJpaDstu2Test { content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS"))); map.add(Constants.PARAM_TEXT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1)); } // OR { @@ -142,8 +143,8 @@ public class FhirSearchDaoDstu2Test extends BaseJpaDstu2Test { content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS")).addOr(new StringParam("AAAB"))); map.add(Constants.PARAM_TEXT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1, id2)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1, id2)); } // AND { @@ -152,8 +153,8 @@ public class FhirSearchDaoDstu2Test extends BaseJpaDstu2Test { content.addAnd(new StringOrListParam().addOr(new StringParam("CCC"))); map.add(Constants.PARAM_TEXT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1)); } // AND OR { @@ -162,8 +163,8 @@ public class FhirSearchDaoDstu2Test extends BaseJpaDstu2Test { content.addAnd(new StringOrListParam().addOr(new StringParam("CCC"))); map.add(Constants.PARAM_TEXT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1, id2)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1, id2)); } // Tag Contents { @@ -171,8 +172,8 @@ public class FhirSearchDaoDstu2Test extends BaseJpaDstu2Test { content.addAnd(new StringOrListParam().addOr(new StringParam("div"))); map.add(Constants.PARAM_TEXT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, empty()); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), empty()); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java index c16b46c66f4..4ba4f35f181 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java @@ -5,6 +5,7 @@ import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl; @@ -101,7 +102,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { TermConcept childCA = new TermConcept(cs, "childCA").setDisplay("Child CA"); parentC.addChild(childCA, RelationshipTypeEnum.ISA); - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); return codeSystem; } @@ -132,7 +133,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { parentB.addChild(childI, RelationshipTypeEnum.ISA); } - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); } private void createExternalCsAndLocalVs() { @@ -167,7 +168,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { TermConcept beagle = new TermConcept(cs, "beagle").setDisplay("Beagle"); dogs.addChild(beagle, RelationshipTypeEnum.ISA); - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM,"SYSTEM NAME", "SYSTEM VERSION" , cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM,"SYSTEM NAME", "SYSTEM VERSION" , cs, table); return codeSystem; } @@ -686,7 +687,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { cs.setResource(table); TermConcept parentA = new TermConcept(cs, "ParentA").setDisplay("Parent A"); cs.getConcepts().add(parentA); - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), "http://snomed.info/sct", "Snomed CT", "SYSTEM VERSION" , cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), "http://snomed.info/sct", "Snomed CT", "SYSTEM VERSION" , cs, table); StringType code = new StringType("ParentA"); StringType system = new StringType("http://snomed.info/sct"); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java index a4e00e8c9c2..c527f3d1528 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; @@ -43,6 +44,7 @@ import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; +import java.nio.charset.StandardCharsets; import java.util.*; import static org.apache.commons.lang3.StringUtils.defaultString; @@ -214,12 +216,12 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { IIdType id2 = myObservationDao.create(o2, mySrd).getId(); { - Set found = myObservationDao.searchForIds(new SearchParameterMap(Observation.SP_DATE, new DateParam(">2001-01-02")), null); - assertThat(found, hasItem(id2.getIdPartAsLong())); + Set found = myObservationDao.searchForIds(new SearchParameterMap(Observation.SP_DATE, new DateParam(">2001-01-02")), null); + assertThat(ResourcePersistentId.toLongList(found), hasItem(id2.getIdPartAsLong())); } { - Set found = myObservationDao.searchForIds(new SearchParameterMap(Observation.SP_DATE, new DateParam(">2016-01-02")), null); - assertThat(found, not(hasItem(id2.getIdPartAsLong()))); + Set found = myObservationDao.searchForIds(new SearchParameterMap(Observation.SP_DATE, new DateParam(">2016-01-02")), null); + assertThat(ResourcePersistentId.toLongList(found), not(hasItem(id2.getIdPartAsLong()))); } } @@ -421,7 +423,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { String name = "profiles-resources"; ourLog.info("Uploading " + name); String vsContents; - vsContents = IOUtils.toString(FhirResourceDaoDstu3Test.class.getResourceAsStream("/org/hl7/fhir/dstu3/model/profile/" + name + ".xml"), "UTF-8"); + vsContents = IOUtils.toString(FhirResourceDaoDstu3Test.class.getResourceAsStream("/org/hl7/fhir/dstu3/model/profile/" + name + ".xml"), StandardCharsets.UTF_8); bundle = myFhirCtx.newXmlParser().parseResource(org.hl7.fhir.dstu3.model.Bundle.class, vsContents); for (BundleEntryComponent i : bundle.getEntry()) { @@ -603,13 +605,13 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { * be fixed. */ assertEquals(org.hl7.fhir.dstu2.model.OperationOutcome.IssueSeverity.ERROR.toCode(), BaseHapiFhirDao.OO_SEVERITY_ERROR); - assertEquals(org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity.ERROR.toCode(), BaseHapiFhirResourceDao.OO_SEVERITY_ERROR); + assertEquals(org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity.ERROR.toCode(), BaseHapiFhirDao.OO_SEVERITY_ERROR); assertEquals(org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity.ERROR.toCode(), BaseHapiFhirDao.OO_SEVERITY_ERROR); assertEquals(org.hl7.fhir.dstu2.model.OperationOutcome.IssueSeverity.INFORMATION.toCode(), BaseHapiFhirDao.OO_SEVERITY_INFO); - assertEquals(org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity.INFORMATION.toCode(), BaseHapiFhirResourceDao.OO_SEVERITY_INFO); + assertEquals(org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity.INFORMATION.toCode(), BaseHapiFhirDao.OO_SEVERITY_INFO); assertEquals(org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity.INFORMATION.toCode(), BaseHapiFhirDao.OO_SEVERITY_INFO); assertEquals(org.hl7.fhir.dstu2.model.OperationOutcome.IssueSeverity.WARNING.toCode(), BaseHapiFhirDao.OO_SEVERITY_WARN); - assertEquals(org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity.WARNING.toCode(), BaseHapiFhirResourceDao.OO_SEVERITY_WARN); + assertEquals(org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity.WARNING.toCode(), BaseHapiFhirDao.OO_SEVERITY_WARN); assertEquals(org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity.WARNING.toCode(), BaseHapiFhirDao.OO_SEVERITY_WARN); } @@ -985,7 +987,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { @Test public void testDeleteResource() { - int initialHistory = myPatientDao.history((Date) null, null, mySrd).size(); + int initialHistory = myPatientDao.history(null, null, mySrd).size(); IIdType id1; IIdType id2; @@ -1007,7 +1009,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { patient.addIdentifier().setSystem("ZZZZZZZ").setValue("ZZZZZZZZZ"); id2b = myPatientDao.update(patient, mySrd).getId(); } - ourLog.info("ID1:{} ID2:{} ID2b:{}", new Object[]{id1, id2, id2b}); + ourLog.info("ID1:{} ID2:{} ID2b:{}", id1, id2, id2b); SearchParameterMap params = new SearchParameterMap(); params.setLoadSynchronous(true); @@ -1028,7 +1030,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { // good } - IBundleProvider history = myPatientDao.history((Date) null, null, mySrd); + IBundleProvider history = myPatientDao.history(null, null, mySrd); assertEquals(4 + initialHistory, history.size().intValue()); List resources = history.getResources(0, 4); assertNotNull(ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) resources.get(0))); @@ -1400,7 +1402,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { } // By type - history = myPatientDao.history((Date) null, null, mySrd); + history = myPatientDao.history(null, null, mySrd); assertEquals(fullSize + 1, history.size().intValue()); for (int i = 0; i < fullSize; i++) { String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue(); @@ -1467,7 +1469,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { } // By type - history = myPatientDao.history((Date) null, null, mySrd); + history = myPatientDao.history(null, null, mySrd); assertEquals(fullSize + 1, history.size().intValue()); for (int i = 0; i < fullSize; i++) { String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue(); @@ -1524,7 +1526,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { inPatient.getMeta().addProfile("http://example.com/1"); IIdType id = myPatientDao.create(inPatient, mySrd).getId().toUnqualifiedVersionless(); - IBundleProvider history = myPatientDao.history((Date) null, null, mySrd); + IBundleProvider history = myPatientDao.history(null, null, mySrd); assertEquals(1, history.size().intValue()); Patient outPatient = (Patient) history.getResources(0, 1).get(0); assertEquals("version1", inPatient.getName().get(0).getFamily()); @@ -1538,7 +1540,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { inPatient.getMeta().addProfile("http://example.com/2"); myPatientDao.metaAddOperation(id, inPatient.getMeta(), mySrd); - history = myPatientDao.history((Date) null, null, mySrd); + history = myPatientDao.history(null, null, mySrd); assertEquals(1, history.size().intValue()); outPatient = (Patient) history.getResources(0, 1).get(0); assertEquals("version1", inPatient.getName().get(0).getFamily()); @@ -1554,7 +1556,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { inPatient.getName().get(0).setFamily("version2"); myPatientDao.update(inPatient, mySrd); - history = myPatientDao.history((Date) null, null, mySrd); + history = myPatientDao.history(null, null, mySrd); assertEquals(2, history.size().intValue()); outPatient = (Patient) history.getResources(0, 2).get(0); assertEquals("version2", outPatient.getName().get(0).getFamily()); @@ -1582,9 +1584,9 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { IBundleProvider history = myPatientDao.history(id, null, null, mySrd); List entries = history.getResources(0, 3); - ourLog.info(((IAnyResource) entries.get(0)).getIdElement() + " - " + ((IAnyResource) entries.get(0)).getMeta().getLastUpdated()); - ourLog.info(((IAnyResource) entries.get(1)).getIdElement() + " - " + ((IAnyResource) entries.get(1)).getMeta().getLastUpdated()); - ourLog.info(((IAnyResource) entries.get(2)).getIdElement() + " - " + ((IAnyResource) entries.get(2)).getMeta().getLastUpdated()); + ourLog.info(entries.get(0).getIdElement() + " - " + entries.get(0).getMeta().getLastUpdated()); + ourLog.info(entries.get(1).getIdElement() + " - " + entries.get(1).getMeta().getLastUpdated()); + ourLog.info(entries.get(2).getIdElement() + " - " + entries.get(2).getMeta().getLastUpdated()); assertEquals(3, history.size().intValue()); assertEquals(id.withVersion("3"), entries.get(0).getIdElement()); @@ -1648,7 +1650,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { // No since - IBundleProvider history = myPatientDao.history((Date) null, null, mySrd); + IBundleProvider history = myPatientDao.history(null, null, mySrd); assertEquals(1, history.size().intValue()); Patient outPatient = (Patient) history.getResources(0, 1).get(0); assertEquals("version1", inPatient.getName().get(0).getFamily()); @@ -1999,7 +2001,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { "}\n"; //@formatter:on - Set val = myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam("P")), null); + Set val = myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam("P")), null); int initial = val.size(); Organization org = myFhirCtx.newJsonParser().parseResource(Organization.class, inputStr); @@ -2050,7 +2052,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { dr01.setSubject(new Reference(patientId01)); IIdType drId01 = myDiagnosticReportDao.create(dr01, mySrd).getId(); - ourLog.info("P1[{}] P2[{}] O1[{}] O2[{}] D1[{}]", new Object[]{patientId01, patientId02, obsId01, obsId02, drId01}); + ourLog.info("P1[{}] P2[{}] O1[{}] O2[{}] D1[{}]", patientId01, patientId02, obsId01, obsId02, drId01); List result = toList(myObservationDao.search(new SearchParameterMap(Observation.SP_SUBJECT, new ReferenceParam(patientId01.getIdPart())).setLoadSynchronous(true))); assertEquals(1, result.size()); @@ -2061,7 +2063,6 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { assertEquals(obsId02.getIdPart(), result.get(0).getIdElement().getIdPart()); result = toList(myObservationDao.search(new SearchParameterMap(Observation.SP_SUBJECT, new ReferenceParam("999999999999")).setLoadSynchronous(true))); - ; assertEquals(0, result.size()); } @@ -2145,7 +2146,6 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { TokenParam value = new TokenParam("urn:system", "001testPersistSearchParams"); List found = toList(myPatientDao.search(new SearchParameterMap(Patient.SP_IDENTIFIER, value).setLoadSynchronous(true))); - ; assertEquals(1, found.size()); assertEquals(id, found.get(0).getIdElement().getIdPartAsLong().longValue()); @@ -2402,7 +2402,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { id1 = myPatientDao.create(patient, mySrd).getId(); Meta metaAdd = new Meta(); - metaAdd.addTag().setSystem((String) null).setCode("Dog").setDisplay("Puppies"); + metaAdd.addTag().setSystem(null).setCode("Dog").setDisplay("Puppies"); metaAdd.addSecurity().setSystem("seclabel:sys:1").setCode("seclabel:code:1").setDisplay("seclabel:dis:1"); metaAdd.addProfile("http://profile/1"); myPatientDao.metaAddOperation(id1, metaAdd, mySrd); @@ -2472,7 +2472,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { { Meta metaDel = new Meta(); - metaDel.addTag().setSystem((String) null).setCode("Dog"); + metaDel.addTag().setSystem(null).setCode("Dog"); metaDel.addSecurity().setSystem("seclabel:sys:1").setCode("seclabel:code:1"); metaDel.addProfile("http://profile/1"); myPatientDao.metaDeleteOperation(id1, metaDel, mySrd); @@ -3192,7 +3192,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { assertThat(str.length(), greaterThan(ResourceIndexedSearchParamString.MAX_LENGTH)); - Set val = myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam("P")), null); + Set val = myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam("P")), null); int initial = val.size(); myOrganizationDao.create(org, mySrd); @@ -3371,7 +3371,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { String subStr1 = longStr1.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); String subStr2 = longStr2.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); - Set val = myOrganizationDao.searchForIds(new SearchParameterMap("type", new TokenParam(subStr1, subStr2)), null); + Set val = myOrganizationDao.searchForIds(new SearchParameterMap("type", new TokenParam(subStr1, subStr2)), null); int initial = val.size(); myOrganizationDao.create(org, mySrd); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java index 3f8cf96ecdd..154b807ced7 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.jpa.dao.dstu3; import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.rest.api.MethodOutcome; @@ -41,7 +42,7 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { } @Test - public void testCreateAndUpdateWithoutRequest() throws Exception { + public void testCreateAndUpdateWithoutRequest() { String methodName = "testUpdateByUrl"; Patient p = new Patient(); @@ -447,9 +448,9 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { p2.addName().setFamily("Tester").addGiven("testUpdateMaintainsSearchParamsDstu2BBB"); myPatientDao.create(p2, mySrd).getId(); - Set ids = myPatientDao.searchForIds(new SearchParameterMap(Patient.SP_GIVEN, new StringParam("testUpdateMaintainsSearchParamsDstu2AAA")), null); + Set ids = myPatientDao.searchForIds(new SearchParameterMap(Patient.SP_GIVEN, new StringParam("testUpdateMaintainsSearchParamsDstu2AAA")), null); assertEquals(1, ids.size()); - assertThat(ids, contains(p1id.getIdPartAsLong())); + assertThat(ResourcePersistentId.toLongList(ids), contains(p1id.getIdPartAsLong())); // Update the name p1.getName().get(0).getGiven().get(0).setValue("testUpdateMaintainsSearchParamsDstu2BBB"); @@ -503,7 +504,7 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { { Patient p1 = myPatientDao.read(p1id, mySrd); List tagList = p1.getMeta().getTag(); - Set secListValues = new HashSet(); + Set secListValues = new HashSet<>(); for (Coding next : tagList) { secListValues.add(next.getSystemElement().getValue() + "|" + next.getCodeElement().getValue()); } @@ -528,7 +529,7 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { Patient patient = new Patient(); patient.addName().setFamily(name); - List tl = new ArrayList(); + List tl = new ArrayList<>(); tl.add(new IdType("http://foo/bar")); patient.getMeta().getProfile().addAll(tl); @@ -549,7 +550,7 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { patient.setId(id); patient.addName().setFamily(name); - List tl = new ArrayList(); + List tl = new ArrayList<>(); tl.add(new IdType("http://foo/baz")); patient.getMeta().getProfile().clear(); patient.getMeta().getProfile().addAll(tl); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSearchDaoDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSearchDaoDstu3Test.java index 2e54e5dd1fe..467415a144b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSearchDaoDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSearchDaoDstu3Test.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertThat; import java.util.List; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import org.hl7.fhir.dstu3.model.Organization; import org.hl7.fhir.dstu3.model.Patient; import org.junit.AfterClass; @@ -65,8 +66,8 @@ public class FhirSearchDaoDstu3Test extends BaseJpaDstu3Test { content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS"))); map.add(Constants.PARAM_CONTENT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1)); } // OR { @@ -74,8 +75,8 @@ public class FhirSearchDaoDstu3Test extends BaseJpaDstu3Test { content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS")).addOr(new StringParam("AAAB"))); map.add(Constants.PARAM_CONTENT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1, id2)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1, id2)); } // AND { @@ -84,8 +85,8 @@ public class FhirSearchDaoDstu3Test extends BaseJpaDstu3Test { content.addAnd(new StringOrListParam().addOr(new StringParam("CCC"))); map.add(Constants.PARAM_CONTENT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1)); } // AND OR { @@ -94,8 +95,8 @@ public class FhirSearchDaoDstu3Test extends BaseJpaDstu3Test { content.addAnd(new StringOrListParam().addOr(new StringParam("CCC"))); map.add(Constants.PARAM_CONTENT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1, id2)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1, id2)); } // All Resource Types { @@ -103,8 +104,8 @@ public class FhirSearchDaoDstu3Test extends BaseJpaDstu3Test { content.addAnd(new StringOrListParam().addOr(new StringParam("CCC")).addOr(new StringParam("DDD"))); map.add(Constants.PARAM_CONTENT, content); - List found = mySearchDao.search(null, map); - assertThat(found, containsInAnyOrder(id1, id2, id3)); + List found = mySearchDao.search(null, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1, id2, id3)); } } @@ -138,8 +139,8 @@ public class FhirSearchDaoDstu3Test extends BaseJpaDstu3Test { content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS"))); map.add(Constants.PARAM_TEXT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1)); } // OR { @@ -147,8 +148,8 @@ public class FhirSearchDaoDstu3Test extends BaseJpaDstu3Test { content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS")).addOr(new StringParam("AAAB"))); map.add(Constants.PARAM_TEXT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1, id2)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1, id2)); } // AND { @@ -157,8 +158,8 @@ public class FhirSearchDaoDstu3Test extends BaseJpaDstu3Test { content.addAnd(new StringOrListParam().addOr(new StringParam("CCC"))); map.add(Constants.PARAM_TEXT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1)); } // AND OR { @@ -167,8 +168,8 @@ public class FhirSearchDaoDstu3Test extends BaseJpaDstu3Test { content.addAnd(new StringOrListParam().addOr(new StringParam("CCC"))); map.add(Constants.PARAM_TEXT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1, id2)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1, id2)); } // Tag Contents { @@ -176,8 +177,8 @@ public class FhirSearchDaoDstu3Test extends BaseJpaDstu3Test { content.addAnd(new StringOrListParam().addOr(new StringParam("div"))); map.add(Constants.PARAM_TEXT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, empty()); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), empty()); } } 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 d40d4bef4a9..7dfffc7dfb9 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 @@ -22,7 +22,7 @@ import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc; -import ca.uhn.fhir.jpa.searchparam.registry.BaseSearchParamRegistry; +import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; import ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl; import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl; @@ -277,7 +277,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @Qualifier("mySearchParameterDaoR4") protected IFhirResourceDao mySearchParameterDao; @Autowired - protected BaseSearchParamRegistry mySearchParamRegistry; + protected SearchParamRegistryImpl mySearchParamRegistry; @Autowired protected IStaleSearchDeletingSvc myStaleSearchDeletingSvc; @Autowired diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCreatePlaceholdersR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCreatePlaceholdersR4Test.java index 02c7e6f5472..ef1fa4282aa 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCreatePlaceholdersR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCreatePlaceholdersR4Test.java @@ -19,6 +19,7 @@ import org.junit.Test; import java.util.List; +import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.Matchers.contains; import static org.junit.Assert.*; @@ -43,7 +44,7 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test { myObservationDao.create(o, mySrd); fail(); } catch (InvalidRequestException e) { - assertEquals("Resource Patient/FOO not found, specified in path: Observation.subject", e.getMessage()); + assertThat(e.getMessage(), startsWith("Resource Patient/FOO not found, specified in path: Observation.subject")); } } @@ -98,7 +99,7 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test { myObservationDao.update(o, mySrd); fail(); } catch (InvalidRequestException e) { - assertEquals("Resource Patient/FOO not found, specified in path: Observation.subject", e.getMessage()); + assertThat(e.getMessage(), startsWith("Resource Patient/FOO not found, specified in path: Observation.subject")); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4DeleteTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4DeleteTest.java index f0a1c5acee8..65a50f3c335 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4DeleteTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4DeleteTest.java @@ -32,12 +32,14 @@ public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test { runInTransaction(()->{ ResourceTable resourceTable = myResourceTableDao.findById(id.getIdPartAsLong()).get(); assertNotNull(resourceTable.getDeleted()); + assertTrue(resourceTable.isDeleted()); }); // Current version should be marked as deleted runInTransaction(()->{ ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 1); assertNull(resourceTable.getDeleted()); + assertNotNull(resourceTable.getPersistentId()); }); runInTransaction(()->{ ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 2); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java index 7cac003f8fa..1782e1f088c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java @@ -3867,6 +3867,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { result = myValueSetDao.search(new SearchParameterMap().setLoadSynchronous(true).add(ValueSet.SP_URL, new UriParam("http://hl7.org/fhir/ValueSet/FOOOOOO"))); assertThat(toUnqualifiedVersionlessIds(result), empty()); + } @Test diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchTest.java index d820b1ee805..33fedeeb05e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchTest.java @@ -8,6 +8,7 @@ import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; @@ -181,7 +182,7 @@ public class FhirResourceDaoR4SearchWithElasticSearchTest extends BaseJpaTest { TermConcept childCA = new TermConcept(cs, "childCA").setDisplay("Child CA"); parentC.addChild(childCA, TermConceptParentChildLink.RelationshipTypeEnum.ISA); - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); return codeSystem; } @@ -199,7 +200,7 @@ public class FhirResourceDaoR4SearchWithElasticSearchTest extends BaseJpaTest { } private ArrayList toCodesContains(List theContains) { - ArrayList retVal = new ArrayList(); + ArrayList retVal = new ArrayList<>(); for (ValueSet.ValueSetExpansionContainsComponent next : theContains) { retVal.add(next.getCode()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java index 49fea334f9b..1b72bfc8f7e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java @@ -5,6 +5,7 @@ import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl; @@ -95,7 +96,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { TermConcept childCA = new TermConcept(cs, "childCA").setDisplay("Child CA"); parentC.addChild(childCA, RelationshipTypeEnum.ISA); - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); return codeSystem; } @@ -131,7 +132,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { TermConcept beagle = new TermConcept(cs, "beagle").setDisplay("Beagle"); dogs.addChild(beagle, RelationshipTypeEnum.ISA); - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); return codeSystem; } @@ -162,7 +163,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { parentB.addChild(childI, RelationshipTypeEnum.ISA); } - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); } private void createLocalCsAndVs() { @@ -474,7 +475,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { concept = new TermConcept(cs, "LA9999-7"); cs.getConcepts().add(concept); - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); ValueSet valueSet = new ValueSet(); valueSet.setUrl(URL_MY_VALUE_SET); @@ -801,7 +802,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { cs.setResource(table); TermConcept parentA = new TermConcept(cs, "ParentA").setDisplay("Parent A"); cs.getConcepts().add(parentA); - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), "http://snomed.info/sct", "Snomed CT", "SYSTEM VERSION" , cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), "http://snomed.info/sct", "Snomed CT", "SYSTEM VERSION" , cs, table); StringType code = new StringType("ParentA"); StringType system = new StringType("http://snomed.info/sct"); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java index 09beea31823..f0f88de223b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.jpa.dao.*; import ca.uhn.fhir.jpa.entity.Search; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; @@ -317,12 +318,12 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test { IIdType id2 = myObservationDao.create(o2, mySrd).getId(); { - Set found = myObservationDao.searchForIds(new SearchParameterMap(Observation.SP_DATE, new DateParam(">2001-01-02")), null); - assertThat(found, hasItem(id2.getIdPartAsLong())); + Set found = myObservationDao.searchForIds(new SearchParameterMap(Observation.SP_DATE, new DateParam(">2001-01-02")), null); + assertThat(ResourcePersistentId.toLongList(found), hasItem(id2.getIdPartAsLong())); } { - Set found = myObservationDao.searchForIds(new SearchParameterMap(Observation.SP_DATE, new DateParam(">2016-01-02")), null); - assertThat(found, not(hasItem(id2.getIdPartAsLong()))); + Set found = myObservationDao.searchForIds(new SearchParameterMap(Observation.SP_DATE, new DateParam(">2016-01-02")), null); + assertThat(ResourcePersistentId.toLongList(found), not(hasItem(id2.getIdPartAsLong()))); } } @@ -2297,7 +2298,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test { "}\n"; //@formatter:on - Set val = myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam("P")), null); + Set val = myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam("P")), null); int initial = val.size(); Organization org = myFhirCtx.newJsonParser().parseResource(Organization.class, inputStr); @@ -3592,7 +3593,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test { assertThat(str.length(), greaterThan(ResourceIndexedSearchParamString.MAX_LENGTH)); - Set val = myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam("P")), null); + Set val = myOrganizationDao.searchForIds(new SearchParameterMap("name", new StringParam("P")), null); int initial = val.size(); myOrganizationDao.create(org, mySrd); @@ -3771,7 +3772,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test { String subStr1 = longStr1.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); String subStr2 = longStr2.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); - Set val = myOrganizationDao.searchForIds(new SearchParameterMap("type", new TokenParam(subStr1, subStr2)), null); + Set val = myOrganizationDao.searchForIds(new SearchParameterMap("type", new TokenParam(subStr1, subStr2)), null); int initial = val.size(); myOrganizationDao.create(org, mySrd); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java index 22208bacc05..3a3fd53d04f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; @@ -568,9 +569,9 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test { p2.addName().setFamily("Tester").addGiven("testUpdateMaintainsSearchParamsDstu2BBB"); myPatientDao.create(p2, mySrd).getId(); - Set ids = myPatientDao.searchForIds(new SearchParameterMap(Patient.SP_GIVEN, new StringParam("testUpdateMaintainsSearchParamsDstu2AAA")), null); + Set ids = myPatientDao.searchForIds(new SearchParameterMap(Patient.SP_GIVEN, new StringParam("testUpdateMaintainsSearchParamsDstu2AAA")), null); assertEquals(1, ids.size()); - assertThat(ids, contains(p1id.getIdPartAsLong())); + assertThat(ResourcePersistentId.toLongList(ids), contains(p1id.getIdPartAsLong())); // Update the name p1.getName().get(0).getGiven().get(0).setValue("testUpdateMaintainsSearchParamsDstu2BBB"); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSearchDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSearchDaoR4Test.java index 996c0dd7b32..8b316c55674 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSearchDaoR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSearchDaoR4Test.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.param.StringAndListParam; @@ -66,8 +67,8 @@ public class FhirSearchDaoR4Test extends BaseJpaR4Test { content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS"))); map.add(Constants.PARAM_CONTENT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1)); } // OR { @@ -75,8 +76,8 @@ public class FhirSearchDaoR4Test extends BaseJpaR4Test { content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS")).addOr(new StringParam("AAAB"))); map.add(Constants.PARAM_CONTENT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1, id2)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1, id2)); } // AND { @@ -85,8 +86,8 @@ public class FhirSearchDaoR4Test extends BaseJpaR4Test { content.addAnd(new StringOrListParam().addOr(new StringParam("CCC"))); map.add(Constants.PARAM_CONTENT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1)); } // AND OR { @@ -95,8 +96,8 @@ public class FhirSearchDaoR4Test extends BaseJpaR4Test { content.addAnd(new StringOrListParam().addOr(new StringParam("CCC"))); map.add(Constants.PARAM_CONTENT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1, id2)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1, id2)); } // All Resource Types { @@ -104,8 +105,8 @@ public class FhirSearchDaoR4Test extends BaseJpaR4Test { content.addAnd(new StringOrListParam().addOr(new StringParam("CCC")).addOr(new StringParam("DDD"))); map.add(Constants.PARAM_CONTENT, content); - List found = mySearchDao.search(null, map); - assertThat(found, containsInAnyOrder(id1, id2, id3)); + List found = mySearchDao.search(null, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1, id2, id3)); } } @@ -139,8 +140,8 @@ public class FhirSearchDaoR4Test extends BaseJpaR4Test { content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS"))); map.add(Constants.PARAM_TEXT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1)); } // OR { @@ -148,8 +149,8 @@ public class FhirSearchDaoR4Test extends BaseJpaR4Test { content.addAnd(new StringOrListParam().addOr(new StringParam("AAAS")).addOr(new StringParam("AAAB"))); map.add(Constants.PARAM_TEXT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1, id2)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1, id2)); } // AND { @@ -158,8 +159,8 @@ public class FhirSearchDaoR4Test extends BaseJpaR4Test { content.addAnd(new StringOrListParam().addOr(new StringParam("CCC"))); map.add(Constants.PARAM_TEXT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1)); } // AND OR { @@ -168,8 +169,8 @@ public class FhirSearchDaoR4Test extends BaseJpaR4Test { content.addAnd(new StringOrListParam().addOr(new StringParam("CCC"))); map.add(Constants.PARAM_TEXT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, containsInAnyOrder(id1, id2)); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), containsInAnyOrder(id1, id2)); } // Tag Contents { @@ -177,8 +178,8 @@ public class FhirSearchDaoR4Test extends BaseJpaR4Test { content.addAnd(new StringOrListParam().addOr(new StringParam("div"))); map.add(Constants.PARAM_TEXT, content); - List found = mySearchDao.search(resourceName, map); - assertThat(found, empty()); + List found = mySearchDao.search(resourceName, map); + assertThat(ResourcePersistentId.toLongList(found), empty()); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java index 10f7cf59452..2fb74964ecf 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java @@ -3,8 +3,12 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; +import ca.uhn.fhir.jpa.model.entity.ModelConfig; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; +import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; import ca.uhn.fhir.jpa.searchparam.extractor.PathAndRef; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; @@ -13,7 +17,15 @@ import ca.uhn.fhir.util.TestUtil; import com.google.common.collect.Sets; import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Consent; +import org.hl7.fhir.r4.model.Encounter; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Quantity; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.SearchParameter; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -21,7 +33,13 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; @@ -32,7 +50,7 @@ public class SearchParamExtractorR4Test { private static final Logger ourLog = LoggerFactory.getLogger(SearchParamExtractorR4Test.class); private static FhirContext ourCtx = FhirContext.forR4(); private static IValidationSupport ourValidationSupport; - private ISearchParamRegistry mySearchParamRegistry; + private MySearchParamRegistry mySearchParamRegistry; @Before public void before() { @@ -77,10 +95,11 @@ public class SearchParamExtractorR4Test { SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry); RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Encounter", "location"); assertNotNull(param); - List links = extractor.extractResourceLinks(enc, param); + ISearchParamExtractor.SearchParamSet links = extractor.extractResourceLinks(enc); assertEquals(1, links.size()); - assertEquals("Encounter.location.location", links.get(0).getPath()); - assertEquals("Location/123", ((Reference) links.get(0).getRef()).getReference()); + assertEquals("location", links.iterator().next().getSearchParamName()); + assertEquals("Encounter.location.location", links.iterator().next().getPath()); + assertEquals("Location/123", ((Reference) links.iterator().next().getRef()).getReference()); } @Test @@ -91,10 +110,10 @@ public class SearchParamExtractorR4Test { SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry); RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Consent", Consent.SP_SOURCE_REFERENCE); assertNotNull(param); - List links = extractor.extractResourceLinks(consent, param); + ISearchParamExtractor.SearchParamSet links = extractor.extractResourceLinks(consent); assertEquals(1, links.size()); - assertEquals("Consent.source", links.get(0).getPath()); - assertEquals("Consent/999", ((Reference) links.get(0).getRef()).getReference()); + assertEquals("Consent.source", links.iterator().next().getPath()); + assertEquals("Consent/999", ((Reference) links.iterator().next().getRef()).getReference()); } @@ -103,13 +122,15 @@ public class SearchParamExtractorR4Test { String path = "Patient.extension('http://patext').value.as(Reference)"; RuntimeSearchParam sp = new RuntimeSearchParam("extpat", "Patient SP", path, RestSearchParameterTypeEnum.REFERENCE, new HashSet<>(), Sets.newHashSet("Patient"), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE); + mySearchParamRegistry.addSearchParam(sp); Patient patient = new Patient(); patient.addExtension("http://patext", new Reference("Organization/AAA")); SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry); - List links = extractor.extractResourceLinks(patient, sp); + ISearchParamExtractor.SearchParamSet links = extractor.extractResourceLinks(patient); assertEquals(1, links.size()); + } @Test @@ -129,6 +150,10 @@ public class SearchParamExtractorR4Test { } private static class MySearchParamRegistry implements ISearchParamRegistry { + + + private List myExtraSearchParams = new ArrayList<>(); + @Override public void forceRefresh() { // nothing @@ -158,9 +183,17 @@ public class SearchParamExtractorR4Test { sps.put(nextSp.getName(), nextSp); } + for (RuntimeSearchParam next : myExtraSearchParams) { + sps.put(next.getName(), next); + } + return sps; } + public void addSearchParam(RuntimeSearchParam theSearchParam) { + myExtraSearchParams.add(theSearchParam); + } + @Override public List getActiveUniqueSearchParams(String theResourceName, Set theParamNames) { throw new UnsupportedOperationException(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java index 90f0d117d45..1af9a906c98 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java @@ -19,7 +19,7 @@ import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc; -import ca.uhn.fhir.jpa.searchparam.registry.BaseSearchParamRegistry; +import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; import ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl; import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl; @@ -261,7 +261,7 @@ public abstract class BaseJpaR5Test extends BaseJpaTest { @Qualifier("mySearchParameterDaoR5") protected IFhirResourceDao mySearchParameterDao; @Autowired - protected BaseSearchParamRegistry mySearchParamRegistry; + protected SearchParamRegistryImpl mySearchParamRegistry; @Autowired protected IStaleSearchDeletingSvc myStaleSearchDeletingSvc; @Autowired diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java index 891be247054..9b74dbdb779 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java @@ -7,7 +7,7 @@ import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryDstu3; +import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.parser.StrictErrorHandler; @@ -58,7 +58,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { protected static RestfulServer ourRestServer; protected static String ourServerBase; protected static GenericWebApplicationContext ourWebApplicationContext; - protected static SearchParamRegistryDstu3 ourSearchParamRegistry; + protected static SearchParamRegistryImpl ourSearchParamRegistry; protected static DatabaseBackedPagingProvider ourPagingProvider; protected static ISearchCoordinatorSvc ourSearchCoordinatorSvc; private static Server ourServer; @@ -153,7 +153,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(subsServletHolder.getServlet().getServletConfig().getServletContext()); myValidationSupport = wac.getBean(JpaValidationSupportChainDstu3.class); ourSearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class); - ourSearchParamRegistry = wac.getBean(SearchParamRegistryDstu3.class); + ourSearchParamRegistry = wac.getBean(SearchParamRegistryImpl.class); ourSubscriptionTriggeringProvider = wac.getBean(SubscriptionTriggeringProvider.class); myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java index e24b99a3298..0fd5a1d0a71 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java @@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -151,7 +152,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 code.addPropertyString("HELLO", "12345-2"); cs.getConcepts().add(code); - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getPersistentId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table); }); } @@ -849,7 +850,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B"); cs.getConcepts().add(parentB); - theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table); return codeSystem; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java index fe9b2d6eec9..33463d346ae 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java @@ -7,7 +7,7 @@ import ca.uhn.fhir.jpa.provider.GraphQLProvider; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR4; +import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; import ca.uhn.fhir.jpa.subscription.SubscriptionMatcherInterceptor; import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader; import ca.uhn.fhir.jpa.util.ResourceCountCache; @@ -61,7 +61,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test { protected static int ourPort; protected static RestfulServer ourRestServer; protected static String ourServerBase; - protected static SearchParamRegistryR4 ourSearchParamRegistry; + protected static SearchParamRegistryImpl ourSearchParamRegistry; private static DatabaseBackedPagingProvider ourPagingProvider; protected static ISearchCoordinatorSvc mySearchCoordinatorSvc; private static GenericWebApplicationContext ourWebApplicationContext; @@ -163,7 +163,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test { WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(subsServletHolder.getServlet().getServletConfig().getServletContext()); myValidationSupport = wac.getBean(JpaValidationSupportChainR4.class); mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class); - ourSearchParamRegistry = wac.getBean(SearchParamRegistryR4.class); + ourSearchParamRegistry = wac.getBean(SearchParamRegistryImpl.class); ourSubscriptionMatcherInterceptor = wac.getBean(SubscriptionMatcherInterceptor.class); ourSubscriptionMatcherInterceptor.start(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java index fa97690fc34..81e0d53cffe 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java @@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.entity.ResourceReindexJobEntity; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.model.entity.ModelConfig; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; @@ -37,13 +38,16 @@ import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; +import javax.persistence.Query; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.in; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -59,15 +63,14 @@ public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProvide @After public void after() throws Exception { super.after(); - myModelConfig.setDefaultSearchParamsCanBeOverridden(new ModelConfig().isDefaultSearchParamsCanBeOverridden()); myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches()); - } @Override public void before() throws Exception { super.before(); + myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches()); } @Override @@ -462,8 +465,6 @@ public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProvide Interceptor interceptor = new Interceptor(); myInterceptorRegistry.registerInterceptor(interceptor); try { - myDaoConfig.setAllowContainsSearches(true); - // Add a custom search parameter SearchParameter fooSp = new SearchParameter(); fooSp.addBase("Questionnaire"); @@ -520,6 +521,8 @@ public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProvide ids.add(myQuestionnaireDao.create(q).getId().getIdPartAsLong()); } + myCaptureQueriesListener.clear(); + int foundCount = 0; Bundle bundle = null; List actualIds = new ArrayList<>(); @@ -543,17 +546,42 @@ public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProvide } while (bundle.getLink("next") != null); + String queries = " * " + myCaptureQueriesListener + .getSelectQueries() + .stream() + .map(t->t.getSql(true, false)) + .collect(Collectors.joining("\n * ")); + ourLog.info("Found: {}", found); runInTransaction(() -> { + List currentResults = myEntityManager.createNativeQuery("select distinct resourceta0_.RES_ID as col_0_0_ from HFJ_RESOURCE resourceta0_ left outer join HFJ_SPIDX_STRING myparamsst1_ on resourceta0_.RES_ID=myparamsst1_.RES_ID where myparamsst1_.HASH_NORM_PREFIX='5901791607832193956' and (myparamsst1_.SP_VALUE_NORMALIZED like 'SECTION%') limit '500'") .getResultList(); + List currentResources = myEntityManager.createNativeQuery("select resourceta0_.RES_ID as col_0_0_ from HFJ_RESOURCE resourceta0_") .getResultList(); + List searches = mySearchEntityDao.findAll(); assertEquals(1, searches.size()); Search search = searches.get(0); - String message = "\nWanted: " + (ids) + "\n" + - "Actual: " + (actualIds) + "\n" + - "Found : " + (found) + "\n" + - search.toString(); + String message = "\nWanted : " + (ids) + "\n" + + "Actual : " + (actualIds) + "\n" + + "Found : " + (found) + "\n" + + "Current: " + currentResults + "\n" + + "Current: " + currentResources + "\n" + + search.toString() + + "\nQueries :\n" + queries; + + for (Long next :ids) { + if (!actualIds.contains(next)) { + List indexes = myResourceIndexedSearchParamStringDao + .findAll() + .stream() + .filter(t->t.getResourcePid().equals(next)) + .collect(Collectors.toList()); + message += "\n\nResource " + next + " has prefixes:\n * " + indexes.stream().map(t->t.toString()).collect(Collectors.joining("\n * ")); + break; + } + } + assertEquals(message, 200, search.getNumFound()); assertEquals(message, 200, search.getTotalCount().intValue()); assertEquals(message, SearchStatusEnum.FINISHED, search.getStatus()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java index a6f5d44f38d..ef683ba265f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java @@ -5,6 +5,7 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -1053,7 +1054,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B"); cs.getConcepts().add(parentB); - theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); + theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); return codeSystem; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/BaseResourceProviderR5Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/BaseResourceProviderR5Test.java index 534899c45cb..b9c8f576308 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/BaseResourceProviderR5Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/BaseResourceProviderR5Test.java @@ -7,7 +7,7 @@ import ca.uhn.fhir.jpa.provider.GraphQLProvider; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR5; +import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; import ca.uhn.fhir.jpa.subscription.SubscriptionMatcherInterceptor; import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader; import ca.uhn.fhir.jpa.util.ResourceCountCache; @@ -61,7 +61,7 @@ public abstract class BaseResourceProviderR5Test extends BaseJpaR5Test { protected static int ourPort; protected static RestfulServer ourRestServer; protected static String ourServerBase; - protected static SearchParamRegistryR5 ourSearchParamRegistry; + protected static SearchParamRegistryImpl ourSearchParamRegistry; private static DatabaseBackedPagingProvider ourPagingProvider; protected static ISearchCoordinatorSvc mySearchCoordinatorSvc; private static GenericWebApplicationContext ourWebApplicationContext; @@ -164,7 +164,7 @@ public abstract class BaseResourceProviderR5Test extends BaseJpaR5Test { WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(subsServletHolder.getServlet().getServletConfig().getServletContext()); myValidationSupport = wac.getBean(JpaValidationSupportChainR5.class); mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class); - ourSearchParamRegistry = wac.getBean(SearchParamRegistryR5.class); + ourSearchParamRegistry = wac.getBean(SearchParamRegistryImpl.class); ourSubscriptionMatcherInterceptor = wac.getBean(SubscriptionMatcherInterceptor.class); ourSubscriptionMatcherInterceptor.start(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java index 1e4f77dab77..de6cd21ce43 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java @@ -5,6 +5,7 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; @@ -1063,7 +1064,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test { TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B"); cs.getConcepts().add(parentB); - theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); + theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); return codeSystem; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java index 33459ed691c..72c7f5eab73 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java @@ -5,6 +5,7 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.jpa.dao.*; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.SearchTypeEnum; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc; import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc; @@ -110,19 +111,19 @@ public class SearchCoordinatorSvcImplTest { }).when(myCallingDao).injectDependenciesIntoBundleProvider(any(PersistedJpaBundleProvider.class)); } - private List createPidSequence(int to) { - List pids = new ArrayList<>(); + private List createPidSequence(int to) { + List pids = new ArrayList<>(); for (long i = 10; i < to; i++) { - pids.add(i); + pids.add(new ResourcePersistentId(i)); } return pids; } private Answer loadPids() { return theInvocation -> { - List pids = (List) theInvocation.getArguments()[0]; + List pids = (List) theInvocation.getArguments()[0]; List resources = (List) theInvocation.getArguments()[2]; - for (Long nextPid : pids) { + for (ResourcePersistentId nextPid : pids) { Patient pt = new Patient(); pt.setId(nextPid.toString()); resources.add(pt); @@ -136,7 +137,7 @@ public class SearchCoordinatorSvcImplTest { SearchParameterMap params = new SearchParameterMap(); params.add("name", new StringParam("ANAME")); - List pids = createPidSequence(800); + List pids = createPidSequence(800); IResultIterator iter = new FailAfterNIterator(new SlowIterator(pids.iterator(), 2), 300); when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); @@ -154,10 +155,10 @@ public class SearchCoordinatorSvcImplTest { @Test public void testAsyncSearchLargeResultSetBigCountSameCoordinator() { - List allResults = new ArrayList<>(); + List allResults = new ArrayList<>(); doAnswer(t -> { - List oldResults = t.getArgument(1, List.class); - List newResults = t.getArgument(2, List.class); + List oldResults = t.getArgument(1, List.class); + List newResults = t.getArgument(2, List.class); ourLog.info("Saving {} new results - have {} old results", newResults.size(), oldResults.size()); assertEquals(allResults.size(), oldResults.size()); allResults.addAll(newResults); @@ -168,13 +169,13 @@ public class SearchCoordinatorSvcImplTest { SearchParameterMap params = new SearchParameterMap(); params.add("name", new StringParam("ANAME")); - List pids = createPidSequence(800); + List pids = createPidSequence(800); SlowIterator iter = new SlowIterator(pids.iterator(), 1); when(mySearchBuilder.createQuery(any(), any(), any())).thenReturn(iter); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); when(mySearchResultCacheSvc.fetchResultPids(any(), anyInt(), anyInt())).thenAnswer(t -> { - List returnedValues = iter.getReturnedValues(); + List returnedValues = iter.getReturnedValues(); int offset = t.getArgument(1, Integer.class); int end = t.getArgument(2, Integer.class); end = Math.min(end, returnedValues.size()); @@ -218,8 +219,8 @@ public class SearchCoordinatorSvcImplTest { verify(mySearchCacheSvc, atLeastOnce()).save(searchCaptor.capture()); assertEquals(790, allResults.size()); - assertEquals(10, allResults.get(0).longValue()); - assertEquals(799, allResults.get(789).longValue()); + assertEquals(10, allResults.get(0).getIdAsLong().longValue()); + assertEquals(799, allResults.get(789).getIdAsLong().longValue()); myExpectedNumberOfSearchBuildersCreated = 4; } @@ -274,7 +275,7 @@ public class SearchCoordinatorSvcImplTest { SearchParameterMap params = new SearchParameterMap(); params.add("name", new StringParam("ANAME")); - List pids = createPidSequence(800); + List pids = createPidSequence(800); SlowIterator iter = new SlowIterator(pids.iterator(), 2); when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); @@ -298,7 +299,7 @@ public class SearchCoordinatorSvcImplTest { SearchParameterMap params = new SearchParameterMap(); params.add("name", new StringParam("ANAME")); - List pids = createPidSequence(800); + List pids = createPidSequence(800); SlowIterator iter = new SlowIterator(pids.iterator(), 500); when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); @@ -342,7 +343,7 @@ public class SearchCoordinatorSvcImplTest { SearchParameterMap params = new SearchParameterMap(); params.add("name", new StringParam("ANAME")); - List pids = createPidSequence(800); + List pids = createPidSequence(800); IResultIterator iter = new SlowIterator(pids.iterator(), 2); when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); when(mySearchCacheSvc.save(any())).thenAnswer(t -> t.getArguments()[0]); @@ -387,7 +388,7 @@ public class SearchCoordinatorSvcImplTest { SearchParameterMap params = new SearchParameterMap(); params.add("name", new StringParam("ANAME")); - List pids = createPidSequence(100); + List pids = createPidSequence(100); SlowIterator iter = new SlowIterator(pids.iterator(), 2); when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); @@ -435,9 +436,10 @@ public class SearchCoordinatorSvcImplTest { } when(mySearchResultCacheSvc.fetchResultPids(any(Search.class), anyInt(), anyInt())).thenAnswer(theInvocation -> { - ArrayList results = new ArrayList<>(); + ArrayList results = new ArrayList<>(); for (long i = theInvocation.getArgument(1, Integer.class); i < theInvocation.getArgument(2, Integer.class); i++) { - results.add(i + 10L); + Long nextPid = i + 10L; + results.add(new ResourcePersistentId(nextPid)); } return results; @@ -470,7 +472,7 @@ public class SearchCoordinatorSvcImplTest { params.setLoadSynchronous(true); params.add("name", new StringParam("ANAME")); - List pids = createPidSequence(800); + List pids = createPidSequence(800); when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(new ResultIterator(pids.iterator())); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); @@ -491,7 +493,7 @@ public class SearchCoordinatorSvcImplTest { params.setLoadSynchronousUpTo(100); params.add("name", new StringParam("ANAME")); - List pids = createPidSequence(800); + List pids = createPidSequence(800); when(mySearchBuilder.createQuery(same(params), any(), nullable(RequestDetails.class))).thenReturn(new ResultIterator(pids.iterator())); pids = createPidSequence(110); @@ -563,7 +565,7 @@ public class SearchCoordinatorSvcImplTest { } - public static class FailAfterNIterator extends BaseIterator implements IResultIterator { + public static class FailAfterNIterator extends BaseIterator implements IResultIterator { private int myCount; private IResultIterator myWrap; @@ -579,7 +581,7 @@ public class SearchCoordinatorSvcImplTest { } @Override - public Long next() { + public ResourcePersistentId next() { myCount--; if (myCount == 0) { throw new NullPointerException("FAILED"); @@ -598,11 +600,11 @@ public class SearchCoordinatorSvcImplTest { } } - public static class ResultIterator extends BaseIterator implements IResultIterator { + public static class ResultIterator extends BaseIterator implements IResultIterator { - private final Iterator myWrap; + private final Iterator myWrap; - ResultIterator(Iterator theWrap) { + ResultIterator(Iterator theWrap) { myWrap = theWrap; } @@ -612,7 +614,7 @@ public class SearchCoordinatorSvcImplTest { } @Override - public Long next() { + public ResourcePersistentId next() { return myWrap.next(); } @@ -633,22 +635,22 @@ public class SearchCoordinatorSvcImplTest { *

* Don't use it in real code! */ - public static class SlowIterator extends BaseIterator implements IResultIterator { + public static class SlowIterator extends BaseIterator implements IResultIterator { private static final Logger ourLog = LoggerFactory.getLogger(SlowIterator.class); private final IResultIterator myResultIteratorWrap; private int myDelay; - private Iterator myWrap; - private List myReturnedValues = new ArrayList<>(); + private Iterator myWrap; + private List myReturnedValues = new ArrayList<>(); private AtomicInteger myCountReturned = new AtomicInteger(0); - SlowIterator(Iterator theWrap, int theDelay) { + SlowIterator(Iterator theWrap, int theDelay) { myWrap = theWrap; myDelay = theDelay; myResultIteratorWrap = null; } - List getReturnedValues() { + List getReturnedValues() { return myReturnedValues; } @@ -666,13 +668,13 @@ public class SearchCoordinatorSvcImplTest { } @Override - public Long next() { + public ResourcePersistentId next() { try { Thread.sleep(myDelay); } catch (InterruptedException e) { // ignore } - Long retVal = myWrap.next(); + ResourcePersistentId retVal = myWrap.next(); myReturnedValues.add(retVal); myCountReturned.incrementAndGet(); return retVal; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/BaseTermR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/BaseTermR4Test.java index 8d82312a66c..f43923fc255 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/BaseTermR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/BaseTermR4Test.java @@ -5,6 +5,7 @@ import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.CodeSystem; @@ -90,7 +91,7 @@ public abstract class BaseTermR4Test extends BaseJpaR4Test { TermConcept parentB = new TermConcept(cs, "ParentB"); cs.getConcepts().add(parentB); - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table); return id; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java index 2a7a7cd18c5..3f2e784734a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java @@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; @@ -97,7 +98,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { TermConcept parentB = new TermConcept(cs, "ParentB"); cs.getConcepts().add(parentB); - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table); return id; } @@ -116,7 +117,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { TermConcept parentA = new TermConcept(cs, "CS2"); cs.getConcepts().add(parentA); - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL_2, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), CS_URL_2, "SYSTEM NAME", "SYSTEM VERSION", cs, table); } @@ -185,7 +186,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { code2.getDisplay()); cs.getConcepts().add(code4); - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), LOINC_URI, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), LOINC_URI, "SYSTEM NAME", "SYSTEM VERSION", cs, table); }); } @@ -201,7 +202,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { TermCodeSystemVersion cs = new TermCodeSystemVersion(); cs.setResource(table); - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table); // Update cs = new TermCodeSystemVersion(); @@ -210,7 +211,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { id = myCodeSystemDao.update(codeSystem, null, true, true, mySrd).getId().toUnqualified(); table = myResourceTableDao.findById(id.getIdPartAsLong()).orElseThrow(IllegalArgumentException::new); cs.setResource(table); - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getPersistentId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table); // Try to update to a different resource codeSystem = new CodeSystem(); @@ -1780,7 +1781,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { child.addChild(parent, RelationshipTypeEnum.ISA); try { - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), "http://foo", "SYSTEM NAME", "SYSTEM VERSION", cs, table); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getPersistentId(), "http://foo", "SYSTEM NAME", "SYSTEM VERSION", cs, table); fail(); } catch (InvalidRequestException e) { assertEquals("CodeSystem contains circular reference around code parent", e.getMessage()); @@ -1905,7 +1906,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { runInTransaction(() -> { ResourceTable resTable = myEntityManager.find(ResourceTable.class, csId.getIdPartAsLong()); version.setResource(resTable); - myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(csId.getIdPartAsLong(), cs.getUrl(), "My System", "SYSTEM VERSION", version, resTable); + myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(csId.getIdPartAsLong()), cs.getUrl(), "My System", "SYSTEM VERSION", version, resTable); }); org.hl7.fhir.dstu3.model.ValueSet vs = new org.hl7.fhir.dstu3.model.ValueSet(); diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/IBasePersistedResource.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/IBasePersistedResource.java new file mode 100644 index 00000000000..c2386009848 --- /dev/null +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/IBasePersistedResource.java @@ -0,0 +1,40 @@ +package ca.uhn.fhir.jpa.model.cross; + +/*- + * #%L + * HAPI FHIR Model + * %% + * Copyright (C) 2014 - 2019 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import org.hl7.fhir.instance.model.api.IIdType; + +import java.util.Date; + +public interface IBasePersistedResource { + + IIdType getIdDt(); + + boolean isDeleted(); + + /** + * If the resource is deleted, returns the date/time that the resource was deleted at. Otherwie, returns null + */ + Date getDeleted(); + + ResourcePersistentId getPersistentId(); + +} diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/ResourcePersistentId.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/ResourcePersistentId.java new file mode 100644 index 00000000000..9e1408f43ce --- /dev/null +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/ResourcePersistentId.java @@ -0,0 +1,88 @@ +package ca.uhn.fhir.jpa.model.cross; + +/*- + * #%L + * HAPI FHIR Model + * %% + * Copyright (C) 2014 - 2019 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.util.ObjectUtil; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * This class is an abstraction for however primary keys are stored in the underlying storage engine. This might be + * a Long, a String, or something else. + */ +public class ResourcePersistentId { + + private Object myId; + + public ResourcePersistentId(Object theId) { + myId = theId; + } + + @Override + public boolean equals(Object theO) { + if (!(theO instanceof ResourcePersistentId)) { + return false; + } + ResourcePersistentId that = (ResourcePersistentId) theO; + + return ObjectUtil.equals(myId, that.myId); + } + + @Override + public int hashCode() { + return myId.hashCode(); + } + + public Object getId() { + return myId; + } + + public void setId(Object theId) { + myId = theId; + } + + public Long getIdAsLong() { + return (Long) myId; + } + + @Override + public String toString() { + return myId.toString(); + } + + public static List toLongList(Collection thePids) { + List retVal = new ArrayList<>(thePids.size()); + for (ResourcePersistentId next : thePids) { + retVal.add(next.getIdAsLong()); + } + return retVal; + } + + public static List fromLongList(List theResultList) { + List retVal = new ArrayList<>(theResultList.size()); + for (Long next : theResultList) { + retVal.add(new ResourcePersistentId(next)); + } + return retVal; + } +} diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseHasResource.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseHasResource.java index e6241d40d5e..64d29094ef7 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseHasResource.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/BaseHasResource.java @@ -21,6 +21,8 @@ package ca.uhn.fhir.jpa.model.entity; */ import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.rest.api.Constants; @@ -31,7 +33,7 @@ import java.util.Collection; import java.util.Date; @MappedSuperclass -public abstract class BaseHasResource implements IBaseResourceEntity { +public abstract class BaseHasResource implements IBaseResourceEntity, IBasePersistedResource { @Column(name = "RES_DELETED_AT", nullable = true) @Temporal(TemporalType.TIMESTAMP) @@ -85,10 +87,6 @@ public abstract class BaseHasResource implements IBaseResourceEntity { return cloneDate(myDeleted); } - public void setDeleted(Date theDate) { - myDeleted = theDate; - } - @Override public FhirVersionEnum getFhirVersion() { return myFhirVersion; @@ -121,6 +119,15 @@ public abstract class BaseHasResource implements IBaseResourceEntity { } } + @Override + public boolean isDeleted() { + return myDeleted != null; + } + + public void setDeleted(Date theDate) { + myDeleted = theDate; + } + @Override public InstantDt getPublished() { if (myPublished != null) { diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java index f4b58b28dd1..fb3b2c5d9f0 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java @@ -61,6 +61,13 @@ public class ModelConfig { private boolean mySubscriptionMatchingEnabled = true; private String myWebsocketContextPath = DEFAULT_WEBSOCKET_CONTEXT_PATH; + /** + * Constructor + */ + public ModelConfig() { + super(); + } + /** * If set to {@code true} the default search params (i.e. the search parameters that are * defined by the FHIR specification itself) may be overridden by uploading search diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java index 10e6c514a74..3ca9e09c404 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity; * #L% */ +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import org.hibernate.annotations.OptimisticLock; import javax.persistence.*; @@ -160,4 +161,8 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl myResourceVersion = theVersion; } + @Override + public ResourcePersistentId getPersistentId() { + return new ResourcePersistentId(myResourceId); + } } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java index 8977a9a0049..d5c15a13ff0 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java @@ -288,7 +288,8 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); b.append("paramName", getParamName()); b.append("resourceId", getResourcePid()); - b.append("value", getValueNormalized()); + b.append("hashNormalizedPrefix", getHashNormalizedPrefix()); + b.append("valueNormalized", getValueNormalized()); return b.build(); } @@ -308,7 +309,8 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP hashPrefixLength = 0; } - return hash(theResourceType, theParamName, left(theValueNormalized, hashPrefixLength)); + long hash = hash(theResourceType, theParamName, left(theValueNormalized, hashPrefixLength)); + return hash; } @Override diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java index 54b9c8a2bf5..70c3e26833b 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java @@ -20,9 +20,9 @@ package ca.uhn.fhir.jpa.model.entity; * #L% */ +import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; +import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.search.IndexNonDeletedInterceptor; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -46,7 +46,7 @@ import static org.apache.commons.lang3.StringUtils.defaultString; @Index(name = "IDX_RES_TYPE", columnList = "RES_TYPE"), @Index(name = "IDX_INDEXSTATUS", columnList = "SP_INDEX_STATUS") }) -public class ResourceTable extends BaseHasResource implements Serializable { +public class ResourceTable extends BaseHasResource implements Serializable, IBasePersistedResource { public static final int RESTYPE_LEN = 40; private static final int MAX_LANGUAGE_LENGTH = 20; private static final int MAX_PROFILE_LENGTH = 200; @@ -219,13 +219,6 @@ public class ResourceTable extends BaseHasResource implements Serializable { @Transient private transient ResourceHistoryTable myCurrentVersionEntity; - public Collection getResourceLinksAsTarget() { - if (myResourceLinksAsTarget == null) { - myResourceLinksAsTarget = new ArrayList<>(); - } - return myResourceLinksAsTarget; - } - @Override public ResourceTag addTag(TagDefinition theTag) { for (ResourceTag next : getTags()) { @@ -238,14 +231,6 @@ public class ResourceTable extends BaseHasResource implements Serializable { return tag; } -// public ResourceEncodingEnum getEncoding() { -// Validate.notNull(myEncoding, "myEncoding is null"); -// return myEncoding; -// } -// -// public void setEncoding(ResourceEncodingEnum theEncoding) { -// myEncoding = theEncoding; -// } public String getHashSha256() { return myHashSha256; @@ -290,10 +275,6 @@ public class ResourceTable extends BaseHasResource implements Serializable { return myParamsCompositeStringUnique; } - public void setParamsCompositeStringUnique(Collection theParamsCompositeStringUnique) { - myParamsCompositeStringUnique = theParamsCompositeStringUnique; - } - public Collection getParamsCoords() { if (myParamsCoords == null) { myParamsCoords = new ArrayList<>(); @@ -415,15 +396,6 @@ public class ResourceTable extends BaseHasResource implements Serializable { return getId(); } -// public byte[] getResource() { -// Validate.notNull(myEncoding, "myEncoding is null"); -// return myResource; -// } -// -// public void setResource(byte[] theResource) { -// myResource = theResource; -// } - public Collection getResourceLinks() { if (myResourceLinks == null) { myResourceLinks = new ArrayList<>(); @@ -629,4 +601,9 @@ public class ResourceTable extends BaseHasResource implements Serializable { public ResourceHistoryTable getCurrentVersionEntity() { return myCurrentVersionEntity; } + + @Override + public ResourcePersistentId getPersistentId() { + return new ResourcePersistentId(getId()); + } } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java index 09a260d5f4d..dc7914deadb 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java @@ -31,9 +31,8 @@ import ca.uhn.fhir.rest.api.QualifiedParamList; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.ParameterUtil; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.util.CoverageIgnore; +import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.UrlUtil; import com.google.common.collect.ArrayListMultimap; import org.apache.http.NameValuePair; @@ -53,8 +52,7 @@ public class MatchUrlService { @Autowired private ISearchParamRegistry mySearchParamRegistry; - public SearchParameterMap translateMatchUrl(String - theMatchUrl, RuntimeResourceDefinition resourceDef) { + public SearchParameterMap translateMatchUrl(String theMatchUrl, RuntimeResourceDefinition theResourceDefinition) { SearchParameterMap paramMap = new SearchParameterMap(); List parameters = translateMatchUrl(theMatchUrl); @@ -118,10 +116,10 @@ public class MatchUrlService { } else if (nextParamName.startsWith("_")) { // ignore these since they aren't search params (e.g. _sort) } else { - RuntimeSearchParam paramDef = mySearchParamRegistry.getSearchParamByName(resourceDef, nextParamName); + RuntimeSearchParam paramDef = mySearchParamRegistry.getSearchParamByName(theResourceDefinition, nextParamName); if (paramDef == null) { throw new InvalidRequestException( - "Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName); + "Failed to parse match URL[" + theMatchUrl + "] - Resource type " + theResourceDefinition.getName() + " does not have a parameter with name: " + nextParamName); } IQueryParameterAnd param = ParameterUtil.parseQueryParams(myContext, paramDef, nextParamName, paramList); @@ -135,27 +133,13 @@ public class MatchUrlService { return UrlUtil.translateMatchUrl(theMatchUrl); } - @CoverageIgnore - protected IQueryParameterAnd newInstanceAnd(String chain) { - IQueryParameterAnd type; - Class clazz = ResourceMetaParams.RESOURCE_META_AND_PARAMS.get(chain); - try { - type = clazz.newInstance(); - } catch (Exception e) { - throw new InternalErrorException("Failure creating instance of " + clazz, e); - } - return type; + private IQueryParameterAnd newInstanceAnd(String theParamType) { + Class clazz = ResourceMetaParams.RESOURCE_META_AND_PARAMS.get(theParamType); + return ReflectionUtil.newInstance(clazz); } - @CoverageIgnore - public IQueryParameterType newInstanceType(String chain) { - IQueryParameterType type; - Class clazz = ResourceMetaParams.RESOURCE_META_PARAMS.get(chain); - try { - type = clazz.newInstance(); - } catch (Exception e) { - throw new InternalErrorException("Failure creating instance of " + clazz, e); - } - return type; + public IQueryParameterType newInstanceType(String theParamType) { + Class clazz = ResourceMetaParams.RESOURCE_META_PARAMS.get(theParamType); + return ReflectionUtil.newInstance(clazz); } } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/ResourceMetaParams.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/ResourceMetaParams.java index 432efd1bc2e..698201476d0 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/ResourceMetaParams.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/ResourceMetaParams.java @@ -23,10 +23,19 @@ package ca.uhn.fhir.jpa.searchparam; import ca.uhn.fhir.model.api.IQueryParameterAnd; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.param.*; -import org.hl7.fhir.r4.model.BaseResource; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenAndListParam; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.param.UriAndListParam; +import ca.uhn.fhir.rest.param.UriParam; +import org.hl7.fhir.instance.model.api.IAnyResource; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; public class ResourceMetaParams { /** @@ -40,12 +49,12 @@ public class ResourceMetaParams { public static final Set EXCLUDE_ELEMENTS_IN_ENCODED; static { - Map> resourceMetaParams = new HashMap>(); - Map>> resourceMetaAndParams = new HashMap>>(); - resourceMetaParams.put(BaseResource.SP_RES_ID, StringParam.class); - resourceMetaAndParams.put(BaseResource.SP_RES_ID, StringAndListParam.class); - resourceMetaParams.put(BaseResource.SP_RES_LANGUAGE, StringParam.class); - resourceMetaAndParams.put(BaseResource.SP_RES_LANGUAGE, StringAndListParam.class); + Map> resourceMetaParams = new HashMap<>(); + Map>> resourceMetaAndParams = new HashMap<>(); + resourceMetaParams.put(IAnyResource.SP_RES_ID, StringParam.class); + resourceMetaAndParams.put(IAnyResource.SP_RES_ID, StringAndListParam.class); + resourceMetaParams.put(IAnyResource.SP_RES_LANGUAGE, StringParam.class); + resourceMetaAndParams.put(IAnyResource.SP_RES_LANGUAGE, StringAndListParam.class); resourceMetaParams.put(Constants.PARAM_TAG, TokenParam.class); resourceMetaAndParams.put(Constants.PARAM_TAG, TokenAndListParam.class); resourceMetaParams.put(Constants.PARAM_PROFILE, UriParam.class); @@ -55,7 +64,7 @@ public class ResourceMetaParams { RESOURCE_META_PARAMS = Collections.unmodifiableMap(resourceMetaParams); RESOURCE_META_AND_PARAMS = Collections.unmodifiableMap(resourceMetaAndParams); - HashSet excludeElementsInEncoded = new HashSet(); + HashSet excludeElementsInEncoded = new HashSet<>(); excludeElementsInEncoded.add("id"); excludeElementsInEncoded.add("*.meta"); EXCLUDE_ELEMENTS_IN_ENCODED = Collections.unmodifiableSet(excludeElementsInEncoded); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/BaseSearchParamConfig.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/BaseSearchParamConfig.java index ce9050770a2..f03d494e7cf 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/BaseSearchParamConfig.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/BaseSearchParamConfig.java @@ -20,6 +20,9 @@ package ca.uhn.fhir.jpa.searchparam.config; * #L% */ +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; @@ -29,4 +32,9 @@ import org.springframework.scheduling.annotation.EnableScheduling; @ComponentScan(basePackages = {"ca.uhn.fhir.jpa.searchparam"}) abstract public class BaseSearchParamConfig { + @Bean + public ISearchParamRegistry searchParamRegistry() { + return new SearchParamRegistryImpl(); + } + } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu2Config.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu2Config.java index 5c986497a0a..d9b2dd9fcdc 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu2Config.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu2Config.java @@ -23,8 +23,6 @@ package ca.uhn.fhir.jpa.searchparam.config; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.ParserOptions; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu2; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryDstu2; import org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport; import org.hl7.fhir.instance.hapi.validation.IValidationSupport; import org.springframework.beans.factory.annotation.Autowire; @@ -44,11 +42,6 @@ public class SearchParamDstu2Config extends BaseSearchParamConfig { return retVal; } - @Bean - public ISearchParamRegistry searchParamRegistry() { - return new SearchParamRegistryDstu2(); - } - @Bean(autowire = Autowire.BY_TYPE) public SearchParamExtractorDstu2 searchParamExtractor() { return new SearchParamExtractorDstu2(); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu3Config.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu3Config.java index 220634276a2..14a5a82f8a7 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu3Config.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu3Config.java @@ -23,8 +23,6 @@ package ca.uhn.fhir.jpa.searchparam.config; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.ParserOptions; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu3; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryDstu3; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; @@ -42,11 +40,6 @@ public class SearchParamDstu3Config extends BaseSearchParamConfig { return retVal; } - @Bean - public ISearchParamRegistry searchParamRegistry() { - return new SearchParamRegistryDstu3(); - } - @Bean(autowire = Autowire.BY_TYPE) public SearchParamExtractorDstu3 searchParamExtractor() { return new SearchParamExtractorDstu3(); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamR4Config.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamR4Config.java index 922ad50ab54..15017429447 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamR4Config.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamR4Config.java @@ -23,10 +23,6 @@ package ca.uhn.fhir.jpa.searchparam.config; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.ParserOptions; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR4; -import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; -import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; @@ -44,11 +40,6 @@ public class SearchParamR4Config extends BaseSearchParamConfig { return retVal; } - @Bean - public ISearchParamRegistry searchParamRegistry() { - return new SearchParamRegistryR4(); - } - @Bean(autowire = Autowire.BY_TYPE) public SearchParamExtractorR4 searchParamExtractor() { return new SearchParamExtractorR4(); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java index 8180d77e867..0ea7ade6d02 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java @@ -20,8 +20,22 @@ package ca.uhn.fhir.jpa.searchparam.extractor; * #L% */ -import ca.uhn.fhir.context.*; -import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; +import ca.uhn.fhir.jpa.model.entity.ModelConfig; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; import ca.uhn.fhir.jpa.model.util.StringNormalizer; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; @@ -30,21 +44,36 @@ import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import org.apache.commons.lang3.ObjectUtils; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.instance.model.api.*; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseEnumeration; +import org.hl7.fhir.instance.model.api.IBaseExtension; +import org.hl7.fhir.instance.model.api.IBaseReference; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; -import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.measure.quantity.Quantity; import javax.measure.unit.NonSI; import javax.measure.unit.Unit; import java.math.BigDecimal; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; import java.util.regex.Pattern; import java.util.stream.Collectors; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.trim; public abstract class BaseSearchParamExtractor implements ISearchParamExtractor { private static final Pattern SPLIT = Pattern.compile("\\||( or )"); @@ -113,6 +142,47 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor mySearchParamRegistry = theSearchParamRegistry; } + @Override + public SearchParamSet extractResourceLinks(IBaseResource theResource) { + IExtractor extractor = (params, searchParam, value, path) -> { + if (value instanceof IBaseResource) { + return; + } + + String nextType = toRootTypeName(value); + switch (nextType) { + case "canonical": + params.addWarning("Ignoring canonical reference (indexing canonical is not yet supported)"); + break; + case "reference": + case "Reference": + IBaseReference valueRef = (IBaseReference) value; + + IIdType nextId = valueRef.getReferenceElement(); + if (nextId.isEmpty() && valueRef.getResource() != null) { + nextId = valueRef.getResource().getIdElement(); + } + + if (nextId == null || + nextId.isEmpty() || + nextId.getValue().startsWith("#") || + nextId.getValue().startsWith("urn:")) { + return; + } + + PathAndRef ref = new PathAndRef(searchParam.getName(), path, valueRef); + params.add(ref); + break; + default: + addUnexpectedDatatypeWarning(params, searchParam, value); + break; + } + }; + + return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.REFERENCE); + } + + @Override public SearchParamSet extractSearchParamTokens(IBaseResource theResource) { @@ -214,8 +284,8 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN); } - private void addUnexpectedDatatypeWarning(SearchParamSet params, RuntimeSearchParam searchParam, IBase value) { - params.addWarning("Search param " + searchParam.getName() + " is of unexpected datatype: " + value.getClass()); + private void addUnexpectedDatatypeWarning(SearchParamSet theParams, RuntimeSearchParam theSearchParam, IBase theValue) { + theParams.addWarning("Search param " + theSearchParam.getName() + " is of unexpected datatype: " + theValue.getClass()); } @Override @@ -372,24 +442,6 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.STRING); } - @Override - public List extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) { - ArrayList retVal = new ArrayList<>(); - - String[] nextPathsSplit = split(theNextSpDef.getPath()); - for (String path : nextPathsSplit) { - path = path.trim(); - if (isNotBlank(path)) { - - for (Object next : extractValues(path, theResource)) { - retVal.add(new PathAndRef(path, next)); - } - } - } - - return retVal; - } - /** * Override parent because we're using FHIRPath here */ @@ -701,7 +753,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } - private SearchParamSet extractSearchParams(IBaseResource theResource, IExtractor theExtractor, RestSearchParameterTypeEnum theSearchParamType) { + private SearchParamSet extractSearchParams(IBaseResource theResource, IExtractor theExtractor, RestSearchParameterTypeEnum theSearchParamType) { SearchParamSet retVal = new SearchParamSet<>(); Collection searchParams = getSearchParams(theResource); @@ -710,16 +762,20 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor continue; } - String nextPath = nextSpDef.getPath(); - if (isBlank(nextPath)) { + String nextPathUnsplit = nextSpDef.getPath(); + if (isBlank(nextPathUnsplit)) { continue; } - for (IBase nextObject : extractValues(nextPath, theResource)) { - if (nextObject != null) { - String typeName = toRootTypeName(nextObject); - if (!myIgnoredForSearchDatatypes.contains(typeName)) { - theExtractor.extract(retVal, nextSpDef, nextObject, nextPath); + String[] splitPaths = split(nextPathUnsplit); + for (String nextPath : splitPaths) { + nextPath = trim(nextPath); + for (IBase nextObject : extractValues(nextPath, theResource)) { + if (nextObject != null) { + String typeName = toRootTypeName(nextObject); + if (!myIgnoredForSearchDatatypes.contains(typeName)) { + theExtractor.extract(retVal, nextSpDef, nextObject, nextPath); + } } } } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java index 7d929d53ef5..15796017006 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java @@ -42,7 +42,7 @@ public interface ISearchParamExtractor { SearchParamSet extractSearchParamUri(IBaseResource theResource); - List extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef); + SearchParamSet extractResourceLinks(IBaseResource theResource); String[] split(String theExpression); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/PathAndRef.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/PathAndRef.java index 5254416858b..69909e52bac 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/PathAndRef.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/PathAndRef.java @@ -20,20 +20,34 @@ package ca.uhn.fhir.jpa.searchparam.extractor; * #L% */ +import org.hl7.fhir.instance.model.api.IBaseReference; + public class PathAndRef { private final String myPath; - public String getPath() { - return myPath; - } - public PathAndRef(String thePath, Object theRef) { + private final IBaseReference myRef; + private final String mySearchParamName; + + /** + * Constructor + */ + public PathAndRef(String theSearchParamName, String thePath, IBaseReference theRef) { super(); + mySearchParamName = theSearchParamName; myPath = thePath; myRef = theRef; } - public Object getRef() { + + public String getSearchParamName() { + return mySearchParamName; + } + + public String getPath() { + return myPath; + } + + public IBaseReference getRef() { return myRef; } - private final Object myRef; - + } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java index cd3600da0f3..5a63817030f 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java @@ -20,36 +20,25 @@ package ca.uhn.fhir.jpa.searchparam.extractor; * #L% */ -import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; -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.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceLink; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import org.apache.commons.lang3.StringUtils; -import org.hl7.fhir.instance.model.api.IBase; -import org.hl7.fhir.instance.model.api.IBaseExtension; import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.CanonicalType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Date; -import java.util.List; import java.util.Map; import static org.apache.commons.lang3.StringUtils.isBlank; @@ -71,112 +60,33 @@ public class ResourceLinkExtractor { private IInterceptorBroadcaster myInterceptorBroadcaster; public void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver, boolean theFailOnInvalidReference, RequestDetails theRequest) { - String resourceType = theEntity.getResourceType(); + String resourceName = myContext.getResourceDefinition(theResource).getName(); - Map searchParams = mySearchParamRegistry.getActiveSearchParams(toResourceName(theResource.getClass())); - - for (RuntimeSearchParam nextSpDef : searchParams.values()) { - extractResourceLinks(theParams, theEntity, theResource, theUpdateTime, theResourceLinkResolver, resourceType, nextSpDef, theFailOnInvalidReference, theRequest); + ISearchParamExtractor.SearchParamSet refs = mySearchParamExtractor.extractResourceLinks(theResource); + SearchParamExtractorService.handleWarnings(theRequest, myInterceptorBroadcaster, refs); + for (PathAndRef nextPathAndRef : refs) { + RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(resourceName, nextPathAndRef.getSearchParamName()); + extractResourceLinks(theParams, theEntity, theUpdateTime, theResourceLinkResolver, searchParam, nextPathAndRef, theFailOnInvalidReference, theRequest); } theEntity.setHasLinks(theParams.myLinks.size() > 0); } - private void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver, String theResourceType, RuntimeSearchParam nextSpDef, boolean theFailOnInvalidReference, RequestDetails theRequest) { - if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.REFERENCE) { - return; - } - - String nextPathsUnsplit = nextSpDef.getPath(); - if (isBlank(nextPathsUnsplit)) { - return; - } - - boolean multiType = false; - if (nextPathsUnsplit.endsWith("[x]")) { - multiType = true; - } - - List refs = mySearchParamExtractor.extractResourceLinks(theResource, nextSpDef); - for (PathAndRef nextPathAndRef : refs) { - extractResourceLinks(theParams, theEntity, theUpdateTime, theResourceLinkResolver, theResourceType, nextSpDef, nextPathsUnsplit, multiType, nextPathAndRef, theFailOnInvalidReference, theRequest); - } - } - - private void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver, String theResourceType, RuntimeSearchParam theRuntimeSearchParam, String thePath, boolean theMultiType, PathAndRef thePathAndRef, boolean theFailOnInvalidReference, RequestDetails theRequest) { - Object nextObject = thePathAndRef.getRef(); + private void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver, RuntimeSearchParam theRuntimeSearchParam, PathAndRef thePathAndRef, boolean theFailOnInvalidReference, RequestDetails theRequest) { + IBaseReference nextObject = thePathAndRef.getRef(); + IIdType nextId = nextObject.getReferenceElement(); + String path = thePathAndRef.getPath(); /* - * A search parameter on an extension field that contains - * references should index those references + * This can only really happen if the DAO is being called + * programmatically with a Bundle (not through the FHIR REST API) + * but Smile does this */ - if (nextObject instanceof IBaseExtension) { - nextObject = ((IBaseExtension) nextObject).getValue(); + if (nextId.isEmpty() && nextObject.getResource() != null) { + nextId = nextObject.getResource().getIdElement(); } - if (nextObject instanceof CanonicalType) { - StorageProcessingMessage msg = new StorageProcessingMessage(); - msg.setMessage("Ignoring canonical reference (indexing canonidcal is not yet supported): " + ((CanonicalType) nextObject).getValueAsString()); - HookParams params = new HookParams() - .add(RequestDetails.class, theRequest) - .addIfMatchesType(ServletRequestDetails.class, theRequest) - .add(StorageProcessingMessage.class, msg); - JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_WARNING, params); - -// nextObject = new Reference(((CanonicalType) nextObject).getValueAsString()); -// wasCanonical = true; - return; - } - - IIdType nextId; - if (nextObject instanceof IBaseReference) { - IBaseReference nextValue = (IBaseReference) nextObject; - if (nextValue.isEmpty()) { - return; - } - nextId = nextValue.getReferenceElement(); - - /* - * This can only really happen if the DAO is being called - * programatically with a Bundle (not through the FHIR REST API) - * but Smile does this - */ - if (nextId.isEmpty() && nextValue.getResource() != null) { - nextId = nextValue.getResource().getIdElement(); - } - - } else if (nextObject instanceof IBaseResource) { -// nextId = ((IBaseResource) nextObject).getIdElement().toUnqualified(); - return; - } else { - @SuppressWarnings("unchecked") - Class clazz = (Class) nextObject.getClass(); - if (myContext.getElementDefinition(clazz).getName().equals("uri")) { - return; - } else if (theResourceType.equals("Consent") && thePathAndRef.getPath().equals("Consent.source")) { - // Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that - return; - } else { - if (!theMultiType) { - if (theRuntimeSearchParam.getName().equals("sourceuri")) { - return; - } - throw new ConfigurationException("Search param " + theRuntimeSearchParam.getName() + " is of unexpected datatype: " + nextObject.getClass()); - } else { - return; - } - } - } - - if (nextId == null || - nextId.isEmpty() || -// nextId.hasIdPart() == false || - nextId.getValue().startsWith("#") || - nextId.getValue().startsWith("urn:")) { - return; - } - - theParams.myPopulatedResourceLinkParameters.add(theRuntimeSearchParam.getName()); + theParams.myPopulatedResourceLinkParameters.add(thePathAndRef.getSearchParamName()); if (LogicalReferenceHelper.isLogicalReference(myModelConfig, nextId)) { ResourceLink resourceLink = new ResourceLink(thePathAndRef.getPath(), theEntity, nextId, theUpdateTime); @@ -189,7 +99,7 @@ public class ResourceLinkExtractor { String baseUrl = nextId.getBaseUrl(); String typeString = nextId.getResourceType(); if (isBlank(typeString)) { - String msg = "Invalid resource reference found at path[" + thePath + "] - Does not contain resource type - " + nextId.getValue(); + String msg = "Invalid resource reference found at path[" + path + "] - Does not contain resource type - " + nextId.getValue(); if (theFailOnInvalidReference) { throw new InvalidRequestException(msg); } else { @@ -201,7 +111,7 @@ public class ResourceLinkExtractor { try { resourceDefinition = myContext.getResourceDefinition(typeString); } catch (DataFormatException e) { - String msg = "Invalid resource reference found at path[" + thePath + "] - Resource type is unknown or not supported on this server - " + nextId.getValue(); + String msg = "Invalid resource reference found at path[" + path + "] - Resource type is unknown or not supported on this server - " + nextId.getValue(); if (theFailOnInvalidReference) { throw new InvalidRequestException(msg); } else { @@ -232,7 +142,7 @@ public class ResourceLinkExtractor { Class type = resourceDefinition.getImplementingClass(); String id = nextId.getIdPart(); if (StringUtils.isBlank(id)) { - String msg = "Invalid resource reference found at path[" + thePath + "] - Does not contain resource ID - " + nextId.getValue(); + String msg = "Invalid resource reference found at path[" + path + "] - Does not contain resource ID - " + nextId.getValue(); if (theFailOnInvalidReference) { throw new InvalidRequestException(msg); } else { @@ -242,7 +152,7 @@ public class ResourceLinkExtractor { } theResourceLinkResolver.validateTypeOrThrowException(type); - ResourceLink resourceLink = createResourceLink(theEntity, theUpdateTime, theResourceLinkResolver, theRuntimeSearchParam, thePath, thePathAndRef, nextId, typeString, type, id, theRequest); + ResourceLink resourceLink = createResourceLink(theEntity, theUpdateTime, theResourceLinkResolver, theRuntimeSearchParam, path, thePathAndRef, nextId, typeString, type, id, theRequest); if (resourceLink == null) { return; } @@ -255,11 +165,11 @@ public class ResourceLinkExtractor { if (targetResource == null) { return null; } - ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, targetResource, theUpdateTime); - return resourceLink; + + return new ResourceLink(nextPathAndRef.getPath(), theEntity, targetResource, theUpdateTime); } - public String toResourceName(Class theResourceType) { + private String toResourceName(Class theResourceType) { return myContext.getResourceDefinition(theResourceType).getName(); } } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java index 463a30bcd10..b0fc5901d98 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java @@ -48,27 +48,27 @@ public class SearchParamExtractorService { public void extractFromResource(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource) { ISearchParamExtractor.SearchParamSet strings = extractSearchParamStrings(theResource); - handleWarnings(theRequestDetails, strings); + handleWarnings(theRequestDetails, myInterceptorBroadcaster, strings); theParams.myStringParams.addAll(strings); ISearchParamExtractor.SearchParamSet numbers = extractSearchParamNumber(theResource); - handleWarnings(theRequestDetails, numbers); + handleWarnings(theRequestDetails, myInterceptorBroadcaster, numbers); theParams.myNumberParams.addAll(numbers); ISearchParamExtractor.SearchParamSet quantities = extractSearchParamQuantity(theResource); - handleWarnings(theRequestDetails, quantities); + handleWarnings(theRequestDetails, myInterceptorBroadcaster, quantities); theParams.myQuantityParams.addAll(quantities); ISearchParamExtractor.SearchParamSet dates = extractSearchParamDates(theResource); - handleWarnings(theRequestDetails, dates); + handleWarnings(theRequestDetails, myInterceptorBroadcaster, dates); theParams.myDateParams.addAll(dates); ISearchParamExtractor.SearchParamSet uris = extractSearchParamUri(theResource); - handleWarnings(theRequestDetails, uris); + handleWarnings(theRequestDetails, myInterceptorBroadcaster, uris); theParams.myUriParams.addAll(uris); ISearchParamExtractor.SearchParamSet coords = extractSearchParamCoords(theResource); - handleWarnings(theRequestDetails, coords); + handleWarnings(theRequestDetails, myInterceptorBroadcaster, coords); theParams.myCoordsParams.addAll(coords); ourLog.trace("Storing date indexes: {}", theParams.myDateParams); @@ -90,7 +90,7 @@ public class SearchParamExtractorService { populateResourceTable(theParams.myTokenParams, theEntity); } - void handleWarnings(RequestDetails theRequestDetails, ISearchParamExtractor.SearchParamSet theSearchParamSet) { + static void handleWarnings(RequestDetails theRequestDetails, IInterceptorBroadcaster theInterceptorBroadcaster, ISearchParamExtractor.SearchParamSet theSearchParamSet) { if (theSearchParamSet.getWarnings().isEmpty()) { return; } @@ -103,7 +103,7 @@ public class SearchParamExtractorService { .add(RequestDetails.class, theRequestDetails) .addIfMatchesType(ServletRequestDetails.class, theRequestDetails) .add(StorageProcessingMessage.class, messageHolder); - JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.JPA_PERFTRACE_WARNING, params); + JpaInterceptorBroadcaster.doCallHooks(theInterceptorBroadcaster, theRequestDetails, Pointcut.JPA_PERFTRACE_WARNING, params); } } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/ISearchParamProvider.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/ISearchParamProvider.java index b2c220980da..40d9faae51e 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/ISearchParamProvider.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/ISearchParamProvider.java @@ -27,5 +27,5 @@ import org.hl7.fhir.instance.model.api.IBaseResource; public interface ISearchParamProvider { IBundleProvider search(SearchParameterMap theParams); - int refreshCache(BaseSearchParamRegistry theSPBaseSearchParamRegistry, long theRefreshInterval); + int refreshCache(SearchParamRegistryImpl theSearchParamRegistry, long theRefreshInterval); } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu2.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu2.java deleted file mode 100644 index feda6a3d702..00000000000 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu2.java +++ /dev/null @@ -1,116 +0,0 @@ -package ca.uhn.fhir.jpa.searchparam.registry; - -/* - * #%L - * HAPI FHIR Search Parameters - * %% - * Copyright (C) 2014 - 2019 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; -import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; -import ca.uhn.fhir.model.api.ExtensionDt; -import ca.uhn.fhir.model.dstu2.resource.SearchParameter; -import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; -import ca.uhn.fhir.util.DatatypeUtil; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import static org.apache.commons.lang3.StringUtils.isBlank; - -public class SearchParamRegistryDstu2 extends BaseSearchParamRegistry { - - @Override - protected JpaRuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) { - String name = theNextSp.getCode(); - String description = theNextSp.getDescription(); - String path = theNextSp.getXpath(); - RestSearchParameterTypeEnum paramType = null; - RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null; - switch (theNextSp.getTypeElement().getValueAsEnum()) { - case COMPOSITE: - paramType = RestSearchParameterTypeEnum.COMPOSITE; - break; - case DATE_DATETIME: - paramType = RestSearchParameterTypeEnum.DATE; - break; - case NUMBER: - paramType = RestSearchParameterTypeEnum.NUMBER; - break; - case QUANTITY: - paramType = RestSearchParameterTypeEnum.QUANTITY; - break; - case REFERENCE: - paramType = RestSearchParameterTypeEnum.REFERENCE; - break; - case STRING: - paramType = RestSearchParameterTypeEnum.STRING; - break; - case TOKEN: - paramType = RestSearchParameterTypeEnum.TOKEN; - break; - case URI: - paramType = RestSearchParameterTypeEnum.URI; - break; - } - if (theNextSp.getStatus() != null) { - switch (theNextSp.getStatusElement().getValueAsEnum()) { - case ACTIVE: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE; - break; - case DRAFT: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.DRAFT; - break; - case RETIRED: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.RETIRED; - break; - } - } - Set providesMembershipInCompartments = Collections.emptySet(); - Set targets = DatatypeUtil.toStringSet(theNextSp.getTarget()); - - if (isBlank(name) || isBlank(path)) { - if (paramType != RestSearchParameterTypeEnum.COMPOSITE) { - return null; - } - } - - IIdType id = theNextSp.getIdElement(); - String uri = ""; - boolean unique = false; - - List uniqueExts = theNextSp.getUndeclaredExtensionsByUrl(SearchParamConstants.EXT_SP_UNIQUE); - if (uniqueExts.size() > 0) { - IPrimitiveType uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive(); - if (uniqueExtsValuePrimitive != null) { - if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) { - unique = true; - } - } - } - - List components = Collections.emptyList(); - Collection> base = Collections.singletonList(theNextSp.getBaseElement()); - return new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, base); - } - -} diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu3.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu3.java deleted file mode 100644 index 0a0f782ded6..00000000000 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu3.java +++ /dev/null @@ -1,126 +0,0 @@ -package ca.uhn.fhir.jpa.searchparam.registry; - -/* - * #%L - * HAPI FHIR Search Parameters - * %% - * Copyright (C) 2014 - 2019 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.RuntimeSearchParam.RuntimeSearchParamStatusEnum; -import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; -import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; -import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; -import ca.uhn.fhir.util.DatatypeUtil; -import org.hl7.fhir.dstu3.model.Extension; -import org.hl7.fhir.dstu3.model.SearchParameter; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import static org.apache.commons.lang3.StringUtils.isBlank; - -public class SearchParamRegistryDstu3 extends BaseSearchParamRegistry { - - @Override - protected JpaRuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) { - String name = theNextSp.getCode(); - String description = theNextSp.getDescription(); - String path = theNextSp.getExpression(); - RestSearchParameterTypeEnum paramType = null; - RuntimeSearchParamStatusEnum status = null; - switch (theNextSp.getType()) { - case COMPOSITE: - paramType = RestSearchParameterTypeEnum.COMPOSITE; - break; - case DATE: - paramType = RestSearchParameterTypeEnum.DATE; - break; - case NUMBER: - paramType = RestSearchParameterTypeEnum.NUMBER; - break; - case QUANTITY: - paramType = RestSearchParameterTypeEnum.QUANTITY; - break; - case REFERENCE: - paramType = RestSearchParameterTypeEnum.REFERENCE; - break; - case STRING: - paramType = RestSearchParameterTypeEnum.STRING; - break; - case TOKEN: - paramType = RestSearchParameterTypeEnum.TOKEN; - break; - case URI: - paramType = RestSearchParameterTypeEnum.URI; - break; - case NULL: - break; - } - if (theNextSp.getStatus() != null) { - switch (theNextSp.getStatus()) { - case ACTIVE: - status = RuntimeSearchParamStatusEnum.ACTIVE; - break; - case DRAFT: - status = RuntimeSearchParamStatusEnum.DRAFT; - break; - case RETIRED: - status = RuntimeSearchParamStatusEnum.RETIRED; - break; - case UNKNOWN: - status = RuntimeSearchParamStatusEnum.UNKNOWN; - break; - case NULL: - break; - } - } - Set providesMembershipInCompartments = Collections.emptySet(); - Set targets = DatatypeUtil.toStringSet(theNextSp.getTarget()); - - if (isBlank(name) || isBlank(path) || paramType == null) { - if (paramType != RestSearchParameterTypeEnum.COMPOSITE) { - return null; - } - } - - IIdType id = theNextSp.getIdElement(); - String uri = ""; - boolean unique = false; - - List uniqueExts = theNextSp.getExtensionsByUrl(SearchParamConstants.EXT_SP_UNIQUE); - if (uniqueExts.size() > 0) { - IPrimitiveType uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive(); - if (uniqueExtsValuePrimitive != null) { - if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) { - unique = true; - } - } - } - - List components = new ArrayList<>(); - for (SearchParameter.SearchParameterComponentComponent next : theNextSp.getComponent()) { - components.add(new JpaRuntimeSearchParam.Component(next.getExpression(), next.getDefinition())); - } - - return new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase()); - } - -} diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java similarity index 51% rename from hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java rename to hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java index fcb10f805d4..d9875583e24 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java @@ -31,17 +31,28 @@ import ca.uhn.fhir.jpa.model.sched.ISchedulerService; import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; +import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.retry.Retrier; +import ca.uhn.fhir.model.api.ExtensionDt; +import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.util.DatatypeUtil; import ca.uhn.fhir.util.SearchParameterUtil; import ca.uhn.fhir.util.StopWatch; import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; +import org.hl7.fhir.dstu3.model.Extension; +import org.hl7.fhir.dstu3.model.SearchParameter; +import org.hl7.fhir.instance.model.api.IBaseExtension; +import org.hl7.fhir.instance.model.api.IBaseHasExtensions; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.r4.model.Reference; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.slf4j.Logger; @@ -49,15 +60,23 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.PostConstruct; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; -public abstract class BaseSearchParamRegistry implements ISearchParamRegistry { +public class SearchParamRegistryImpl implements ISearchParamRegistry { private static final int MAX_MANAGED_PARAM_COUNT = 10000; - private static final Logger ourLog = LoggerFactory.getLogger(BaseSearchParamRegistry.class); + private static final Logger ourLog = LoggerFactory.getLogger(SearchParamRegistryImpl.class); private static final int MAX_RETRIES = 60; // 5 minutes private static long REFRESH_INTERVAL = 60 * DateUtils.MILLIS_PER_MINUTE; @Autowired @@ -264,12 +283,12 @@ public abstract class BaseSearchParamRegistry implemen int overriddenCount = 0; List allSearchParams = allSearchParamsBp.getResources(0, size); for (IBaseResource nextResource : allSearchParams) { - SP nextSp = (SP) nextResource; + IBaseResource nextSp = (IBaseResource) nextResource; if (nextSp == null) { continue; } - RuntimeSearchParam runtimeSp = toRuntimeSp(nextSp); + RuntimeSearchParam runtimeSp = canonicalizeSearchParameter(nextSp); if (runtimeSp == null) { continue; } @@ -281,7 +300,7 @@ public abstract class BaseSearchParamRegistry implemen Map searchParamMap = getSearchParamMap(searchParams, nextBaseName); String name = runtimeSp.getName(); - if (myModelConfig.isDefaultSearchParamsCanBeOverridden() || !searchParamMap.containsKey(name)) { + if (!searchParamMap.containsKey(name) || myModelConfig.isDefaultSearchParamsCanBeOverridden()) { searchParamMap.put(name, runtimeSp); overriddenCount++; } @@ -326,7 +345,361 @@ public abstract class BaseSearchParamRegistry implemen } } - protected abstract RuntimeSearchParam toRuntimeSp(SP theNextSp); + protected RuntimeSearchParam canonicalizeSearchParameter(IBaseResource theSearchParameter) { + switch (myFhirContext.getVersion().getVersion()) { + case DSTU2: + return canonicalizeSearchParameterDstu2((ca.uhn.fhir.model.dstu2.resource.SearchParameter) theSearchParameter); + case DSTU3: + return canonicalizeSearchParameterDstu3((org.hl7.fhir.dstu3.model.SearchParameter) theSearchParameter); + case R4: + return canonicalizeSearchParameterR4((org.hl7.fhir.r4.model.SearchParameter) theSearchParameter); + case DSTU2_HL7ORG: + case DSTU2_1: + // Non-supported - these won't happen so just fall through + case R5: + default: + return canonicalizeSearchParameterR5((org.hl7.fhir.r5.model.SearchParameter) theSearchParameter); + } + } + + private RuntimeSearchParam canonicalizeSearchParameterDstu2(ca.uhn.fhir.model.dstu2.resource.SearchParameter theNextSp) { + String name = theNextSp.getCode(); + String description = theNextSp.getDescription(); + String path = theNextSp.getXpath(); + RestSearchParameterTypeEnum paramType = null; + RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null; + switch (theNextSp.getTypeElement().getValueAsEnum()) { + case COMPOSITE: + paramType = RestSearchParameterTypeEnum.COMPOSITE; + break; + case DATE_DATETIME: + paramType = RestSearchParameterTypeEnum.DATE; + break; + case NUMBER: + paramType = RestSearchParameterTypeEnum.NUMBER; + break; + case QUANTITY: + paramType = RestSearchParameterTypeEnum.QUANTITY; + break; + case REFERENCE: + paramType = RestSearchParameterTypeEnum.REFERENCE; + break; + case STRING: + paramType = RestSearchParameterTypeEnum.STRING; + break; + case TOKEN: + paramType = RestSearchParameterTypeEnum.TOKEN; + break; + case URI: + paramType = RestSearchParameterTypeEnum.URI; + break; + } + if (theNextSp.getStatus() != null) { + switch (theNextSp.getStatusElement().getValueAsEnum()) { + case ACTIVE: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE; + break; + case DRAFT: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.DRAFT; + break; + case RETIRED: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.RETIRED; + break; + } + } + Set providesMembershipInCompartments = Collections.emptySet(); + Set targets = DatatypeUtil.toStringSet(theNextSp.getTarget()); + + if (isBlank(name) || isBlank(path)) { + if (paramType != RestSearchParameterTypeEnum.COMPOSITE) { + return null; + } + } + + IIdType id = theNextSp.getIdElement(); + String uri = ""; + boolean unique = false; + + List uniqueExts = theNextSp.getUndeclaredExtensionsByUrl(SearchParamConstants.EXT_SP_UNIQUE); + if (uniqueExts.size() > 0) { + IPrimitiveType uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive(); + if (uniqueExtsValuePrimitive != null) { + if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) { + unique = true; + } + } + } + + List components = Collections.emptyList(); + Collection> base = Collections.singletonList(theNextSp.getBaseElement()); + JpaRuntimeSearchParam retVal = new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, base); + extractExtensions(theNextSp, retVal); + return retVal; + } + + private RuntimeSearchParam canonicalizeSearchParameterDstu3(org.hl7.fhir.dstu3.model.SearchParameter theNextSp) { + String name = theNextSp.getCode(); + String description = theNextSp.getDescription(); + String path = theNextSp.getExpression(); + RestSearchParameterTypeEnum paramType = null; + RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null; + switch (theNextSp.getType()) { + case COMPOSITE: + paramType = RestSearchParameterTypeEnum.COMPOSITE; + break; + case DATE: + paramType = RestSearchParameterTypeEnum.DATE; + break; + case NUMBER: + paramType = RestSearchParameterTypeEnum.NUMBER; + break; + case QUANTITY: + paramType = RestSearchParameterTypeEnum.QUANTITY; + break; + case REFERENCE: + paramType = RestSearchParameterTypeEnum.REFERENCE; + break; + case STRING: + paramType = RestSearchParameterTypeEnum.STRING; + break; + case TOKEN: + paramType = RestSearchParameterTypeEnum.TOKEN; + break; + case URI: + paramType = RestSearchParameterTypeEnum.URI; + break; + case NULL: + break; + } + if (theNextSp.getStatus() != null) { + switch (theNextSp.getStatus()) { + case ACTIVE: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE; + break; + case DRAFT: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.DRAFT; + break; + case RETIRED: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.RETIRED; + break; + case UNKNOWN: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.UNKNOWN; + break; + case NULL: + break; + } + } + Set providesMembershipInCompartments = Collections.emptySet(); + Set targets = DatatypeUtil.toStringSet(theNextSp.getTarget()); + + if (isBlank(name) || isBlank(path) || paramType == null) { + if (paramType != RestSearchParameterTypeEnum.COMPOSITE) { + return null; + } + } + + IIdType id = theNextSp.getIdElement(); + String uri = ""; + boolean unique = false; + + List uniqueExts = theNextSp.getExtensionsByUrl(SearchParamConstants.EXT_SP_UNIQUE); + if (uniqueExts.size() > 0) { + IPrimitiveType uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive(); + if (uniqueExtsValuePrimitive != null) { + if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) { + unique = true; + } + } + } + + List components = new ArrayList<>(); + for (SearchParameter.SearchParameterComponentComponent next : theNextSp.getComponent()) { + components.add(new JpaRuntimeSearchParam.Component(next.getExpression(), next.getDefinition())); + } + + JpaRuntimeSearchParam retVal = new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase()); + extractExtensions(theNextSp, retVal); + return retVal; + } + + private RuntimeSearchParam canonicalizeSearchParameterR4(org.hl7.fhir.r4.model.SearchParameter theNextSp) { + String name = theNextSp.getCode(); + String description = theNextSp.getDescription(); + String path = theNextSp.getExpression(); + RestSearchParameterTypeEnum paramType = null; + RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null; + switch (theNextSp.getType()) { + case COMPOSITE: + paramType = RestSearchParameterTypeEnum.COMPOSITE; + break; + case DATE: + paramType = RestSearchParameterTypeEnum.DATE; + break; + case NUMBER: + paramType = RestSearchParameterTypeEnum.NUMBER; + break; + case QUANTITY: + paramType = RestSearchParameterTypeEnum.QUANTITY; + break; + case REFERENCE: + paramType = RestSearchParameterTypeEnum.REFERENCE; + break; + case STRING: + paramType = RestSearchParameterTypeEnum.STRING; + break; + case TOKEN: + paramType = RestSearchParameterTypeEnum.TOKEN; + break; + case URI: + paramType = RestSearchParameterTypeEnum.URI; + break; + case SPECIAL: + paramType = RestSearchParameterTypeEnum.SPECIAL; + break; + case NULL: + break; + } + if (theNextSp.getStatus() != null) { + switch (theNextSp.getStatus()) { + case ACTIVE: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE; + break; + case DRAFT: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.DRAFT; + break; + case RETIRED: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.RETIRED; + break; + case UNKNOWN: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.UNKNOWN; + break; + case NULL: + break; + } + } + Set providesMembershipInCompartments = Collections.emptySet(); + Set targets = DatatypeUtil.toStringSet(theNextSp.getTarget()); + + if (isBlank(name) || isBlank(path) || paramType == null) { + if (paramType != RestSearchParameterTypeEnum.COMPOSITE) { + return null; + } + } + + IIdType id = theNextSp.getIdElement(); + String uri = ""; + boolean unique = false; + + List uniqueExts = theNextSp.getExtensionsByUrl(SearchParamConstants.EXT_SP_UNIQUE); + if (uniqueExts.size() > 0) { + IPrimitiveType uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive(); + if (uniqueExtsValuePrimitive != null) { + if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) { + unique = true; + } + } + } + + List components = new ArrayList<>(); + for (org.hl7.fhir.r4.model.SearchParameter.SearchParameterComponentComponent next : theNextSp.getComponent()) { + components.add(new JpaRuntimeSearchParam.Component(next.getExpression(), new Reference(next.getDefinition()))); + } + + JpaRuntimeSearchParam retVal = new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase()); + extractExtensions(theNextSp, retVal); + return retVal; + + } + + + private RuntimeSearchParam canonicalizeSearchParameterR5(org.hl7.fhir.r5.model.SearchParameter theNextSp) { + String name = theNextSp.getCode(); + String description = theNextSp.getDescription(); + String path = theNextSp.getExpression(); + RestSearchParameterTypeEnum paramType = null; + RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null; + switch (theNextSp.getType()) { + case COMPOSITE: + paramType = RestSearchParameterTypeEnum.COMPOSITE; + break; + case DATE: + paramType = RestSearchParameterTypeEnum.DATE; + break; + case NUMBER: + paramType = RestSearchParameterTypeEnum.NUMBER; + break; + case QUANTITY: + paramType = RestSearchParameterTypeEnum.QUANTITY; + break; + case REFERENCE: + paramType = RestSearchParameterTypeEnum.REFERENCE; + break; + case STRING: + paramType = RestSearchParameterTypeEnum.STRING; + break; + case TOKEN: + paramType = RestSearchParameterTypeEnum.TOKEN; + break; + case URI: + paramType = RestSearchParameterTypeEnum.URI; + break; + case SPECIAL: + paramType = RestSearchParameterTypeEnum.SPECIAL; + break; + case NULL: + break; + } + if (theNextSp.getStatus() != null) { + switch (theNextSp.getStatus()) { + case ACTIVE: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE; + break; + case DRAFT: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.DRAFT; + break; + case RETIRED: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.RETIRED; + break; + case UNKNOWN: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.UNKNOWN; + break; + case NULL: + break; + } + } + Set providesMembershipInCompartments = Collections.emptySet(); + Set targets = DatatypeUtil.toStringSet(theNextSp.getTarget()); + + if (isBlank(name) || isBlank(path) || paramType == null) { + if (paramType != RestSearchParameterTypeEnum.COMPOSITE) { + return null; + } + } + + IIdType id = theNextSp.getIdElement(); + String uri = ""; + boolean unique = false; + + List uniqueExts = theNextSp.getExtensionsByUrl(SearchParamConstants.EXT_SP_UNIQUE); + if (uniqueExts.size() > 0) { + IPrimitiveType uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive(); + if (uniqueExtsValuePrimitive != null) { + if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) { + unique = true; + } + } + } + + List components = new ArrayList<>(); + for (org.hl7.fhir.r5.model.SearchParameter.SearchParameterComponentComponent next : theNextSp.getComponent()) { + components.add(new JpaRuntimeSearchParam.Component(next.getExpression(), new org.hl7.fhir.r5.model.Reference(next.getDefinition()))); + } + + JpaRuntimeSearchParam retVal = new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase()); + extractExtensions(theNextSp, retVal); + return retVal; + } + @Override public RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName) { @@ -354,7 +727,7 @@ public abstract class BaseSearchParamRegistry implemen int refreshCacheWithRetry() { Retrier refreshCacheRetrier = new Retrier<>(() -> { - synchronized (BaseSearchParamRegistry.this) { + synchronized (SearchParamRegistryImpl.this) { return mySearchParamProvider.refreshCache(this, REFRESH_INTERVAL); } }, MAX_RETRIES); @@ -364,7 +737,7 @@ public abstract class BaseSearchParamRegistry implemen @PostConstruct public void registerScheduledJob() { ScheduledJobDefinition jobDetail = new ScheduledJobDefinition(); - jobDetail.setId(BaseSearchParamRegistry.class.getName()); + jobDetail.setId(SearchParamRegistryImpl.class.getName()); jobDetail.setJobClass(SubmitJob.class); mySchedulerService.scheduleFixedDelay(10 * DateUtils.MILLIS_PER_SECOND, false, jobDetail); } @@ -390,6 +763,22 @@ public abstract class BaseSearchParamRegistry implemen mySchedulerService = theSchedulerService; } + /** + * Extracts any extensions from the resource and populates an extension field in the + */ + protected void extractExtensions(IBaseResource theSearchParamResource, JpaRuntimeSearchParam theRuntimeSearchParam) { + if (theSearchParamResource instanceof IBaseHasExtensions) { + List> extensions = ((IBaseHasExtensions) theSearchParamResource).getExtension(); + for (IBaseExtension next : extensions) { + String nextUrl = next.getUrl(); + if (isNotBlank(nextUrl)) { + theRuntimeSearchParam.addExtension(nextUrl, next); + } + } + } + } + + public static class SubmitJob implements Job { @Autowired private ISearchParamRegistry myTarget; diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryR4.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryR4.java deleted file mode 100644 index c736a8520c6..00000000000 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryR4.java +++ /dev/null @@ -1,130 +0,0 @@ -package ca.uhn.fhir.jpa.searchparam.registry; - -/* - * #%L - * HAPI FHIR Search Parameters - * %% - * Copyright (C) 2014 - 2019 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.context.RuntimeSearchParam.RuntimeSearchParamStatusEnum; -import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; -import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; -import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; -import ca.uhn.fhir.util.DatatypeUtil; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.hl7.fhir.r4.model.Extension; -import org.hl7.fhir.r4.model.Reference; -import org.hl7.fhir.r4.model.SearchParameter; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import static org.apache.commons.lang3.StringUtils.isBlank; - -public class SearchParamRegistryR4 extends BaseSearchParamRegistry { - - @Override - protected RuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) { - String name = theNextSp.getCode(); - String description = theNextSp.getDescription(); - String path = theNextSp.getExpression(); - RestSearchParameterTypeEnum paramType = null; - RuntimeSearchParamStatusEnum status = null; - switch (theNextSp.getType()) { - case COMPOSITE: - paramType = RestSearchParameterTypeEnum.COMPOSITE; - break; - case DATE: - paramType = RestSearchParameterTypeEnum.DATE; - break; - case NUMBER: - paramType = RestSearchParameterTypeEnum.NUMBER; - break; - case QUANTITY: - paramType = RestSearchParameterTypeEnum.QUANTITY; - break; - case REFERENCE: - paramType = RestSearchParameterTypeEnum.REFERENCE; - break; - case STRING: - paramType = RestSearchParameterTypeEnum.STRING; - break; - case TOKEN: - paramType = RestSearchParameterTypeEnum.TOKEN; - break; - case URI: - paramType = RestSearchParameterTypeEnum.URI; - break; - case SPECIAL: - paramType = RestSearchParameterTypeEnum.SPECIAL; - break; - case NULL: - break; - } - if (theNextSp.getStatus() != null) { - switch (theNextSp.getStatus()) { - case ACTIVE: - status = RuntimeSearchParamStatusEnum.ACTIVE; - break; - case DRAFT: - status = RuntimeSearchParamStatusEnum.DRAFT; - break; - case RETIRED: - status = RuntimeSearchParamStatusEnum.RETIRED; - break; - case UNKNOWN: - status = RuntimeSearchParamStatusEnum.UNKNOWN; - break; - case NULL: - break; - } - } - Set providesMembershipInCompartments = Collections.emptySet(); - Set targets = DatatypeUtil.toStringSet(theNextSp.getTarget()); - - if (isBlank(name) || isBlank(path) || paramType == null) { - if (paramType != RestSearchParameterTypeEnum.COMPOSITE) { - return null; - } - } - - IIdType id = theNextSp.getIdElement(); - String uri = ""; - boolean unique = false; - - List uniqueExts = theNextSp.getExtensionsByUrl(SearchParamConstants.EXT_SP_UNIQUE); - if (uniqueExts.size() > 0) { - IPrimitiveType uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive(); - if (uniqueExtsValuePrimitive != null) { - if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) { - unique = true; - } - } - } - - List components = new ArrayList<>(); - for (org.hl7.fhir.r4.model.SearchParameter.SearchParameterComponentComponent next : theNextSp.getComponent()) { - components.add(new JpaRuntimeSearchParam.Component(next.getExpression(), new Reference(next.getDefinition()))); - } - - return new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase()); - } -} diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryR5.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryR5.java deleted file mode 100644 index 3cba20c6456..00000000000 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryR5.java +++ /dev/null @@ -1,130 +0,0 @@ -package ca.uhn.fhir.jpa.searchparam.registry; - -/* - * #%L - * HAPI FHIR Search Parameters - * %% - * Copyright (C) 2014 - 2019 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.context.RuntimeSearchParam.RuntimeSearchParamStatusEnum; -import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; -import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; -import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; -import ca.uhn.fhir.util.DatatypeUtil; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.hl7.fhir.r5.model.Extension; -import org.hl7.fhir.r5.model.Reference; -import org.hl7.fhir.r5.model.SearchParameter; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import static org.apache.commons.lang3.StringUtils.isBlank; - -public class SearchParamRegistryR5 extends BaseSearchParamRegistry { - - @Override - protected RuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) { - String name = theNextSp.getCode(); - String description = theNextSp.getDescription(); - String path = theNextSp.getExpression(); - RestSearchParameterTypeEnum paramType = null; - RuntimeSearchParamStatusEnum status = null; - switch (theNextSp.getType()) { - case COMPOSITE: - paramType = RestSearchParameterTypeEnum.COMPOSITE; - break; - case DATE: - paramType = RestSearchParameterTypeEnum.DATE; - break; - case NUMBER: - paramType = RestSearchParameterTypeEnum.NUMBER; - break; - case QUANTITY: - paramType = RestSearchParameterTypeEnum.QUANTITY; - break; - case REFERENCE: - paramType = RestSearchParameterTypeEnum.REFERENCE; - break; - case STRING: - paramType = RestSearchParameterTypeEnum.STRING; - break; - case TOKEN: - paramType = RestSearchParameterTypeEnum.TOKEN; - break; - case URI: - paramType = RestSearchParameterTypeEnum.URI; - break; - case SPECIAL: - paramType = RestSearchParameterTypeEnum.SPECIAL; - break; - case NULL: - break; - } - if (theNextSp.getStatus() != null) { - switch (theNextSp.getStatus()) { - case ACTIVE: - status = RuntimeSearchParamStatusEnum.ACTIVE; - break; - case DRAFT: - status = RuntimeSearchParamStatusEnum.DRAFT; - break; - case RETIRED: - status = RuntimeSearchParamStatusEnum.RETIRED; - break; - case UNKNOWN: - status = RuntimeSearchParamStatusEnum.UNKNOWN; - break; - case NULL: - break; - } - } - Set providesMembershipInCompartments = Collections.emptySet(); - Set targets = DatatypeUtil.toStringSet(theNextSp.getTarget()); - - if (isBlank(name) || isBlank(path) || paramType == null) { - if (paramType != RestSearchParameterTypeEnum.COMPOSITE) { - return null; - } - } - - IIdType id = theNextSp.getIdElement(); - String uri = ""; - boolean unique = false; - - List uniqueExts = theNextSp.getExtensionsByUrl(SearchParamConstants.EXT_SP_UNIQUE); - if (uniqueExts.size() > 0) { - IPrimitiveType uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive(); - if (uniqueExtsValuePrimitive != null) { - if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) { - unique = true; - } - } - } - - List components = new ArrayList<>(); - for (SearchParameter.SearchParameterComponentComponent next : theNextSp.getComponent()) { - components.add(new JpaRuntimeSearchParam.Component(next.getExpression(), new Reference(next.getDefinition()))); - } - - return new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase()); - } -} diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorServiceTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorServiceTest.java index e724954a7a4..afd7408cf36 100644 --- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorServiceTest.java +++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorServiceTest.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.searchparam.extractor; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import org.junit.Before; import org.junit.Test; @@ -37,7 +38,7 @@ public class SearchParamExtractorServiceTest { when(myJpaInterceptorBroadcaster.callHooks(any(), any())).thenReturn(true); - mySvc.handleWarnings(new ServletRequestDetails(myRequestInterceptorBroadcaster), searchParamSet); + SearchParamExtractorService.handleWarnings(new ServletRequestDetails(myRequestInterceptorBroadcaster), myJpaInterceptorBroadcaster, searchParamSet); verify(myJpaInterceptorBroadcaster, times(2)).callHooks(eq(Pointcut.JPA_PERFTRACE_WARNING), any()); verify(myRequestInterceptorBroadcaster, times(2)).callHooks(eq(Pointcut.JPA_PERFTRACE_WARNING), any()); diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistryTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImplTest.java similarity index 58% rename from hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistryTest.java rename to hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImplTest.java index 87c04555963..4cb71863129 100644 --- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistryTest.java +++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImplTest.java @@ -6,6 +6,12 @@ import ca.uhn.fhir.jpa.model.sched.ISchedulerService; import ca.uhn.fhir.rest.server.SimpleBundleProvider; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import org.hamcrest.Matchers; +import org.hl7.fhir.instance.model.api.IBaseDatatype; +import org.hl7.fhir.instance.model.api.IBaseExtension; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.SearchParameter; +import org.hl7.fhir.r4.model.StringType; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -19,7 +25,7 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) -public class BaseSearchParamRegistryTest { +public class SearchParamRegistryImplTest { @Mock private ISchedulerService mySchedulerService; @@ -37,7 +43,7 @@ public class BaseSearchParamRegistryTest { public void testRefreshAfterExpiry() { when(mySearchParamProvider.search(any())).thenReturn(new SimpleBundleProvider()); - SearchParamRegistryR4 registry = new SearchParamRegistryR4(); + SearchParamRegistryImpl registry = new SearchParamRegistryImpl(); registry.setSchedulerServiceForUnitTest(mySchedulerService); registry.setFhirContextForUnitTest(FhirContext.forR4()); registry.setSearchParamProviderForUnitTest(mySearchParamProvider); @@ -54,7 +60,7 @@ public class BaseSearchParamRegistryTest { @Test public void testRefreshCacheIfNecessary() { - SearchParamRegistryR4 registry = new SearchParamRegistryR4(); + SearchParamRegistryImpl registry = new SearchParamRegistryImpl(); when(mySearchParamProvider.search(any())).thenReturn(new SimpleBundleProvider()); when(mySearchParamProvider.refreshCache(any(), anyLong())).thenAnswer(t -> { @@ -77,13 +83,13 @@ public class BaseSearchParamRegistryTest { @Test public void testGetActiveUniqueSearchParams_Empty() { - SearchParamRegistryR4 registry = new SearchParamRegistryR4(); + SearchParamRegistryImpl registry = new SearchParamRegistryImpl(); assertThat(registry.getActiveUniqueSearchParams("Patient"), Matchers.empty()); } @Test public void testGetActiveSearchParams() { - SearchParamRegistryR4 registry = new SearchParamRegistryR4(); + SearchParamRegistryImpl registry = new SearchParamRegistryImpl(); registry.setFhirContextForUnitTest(FhirContext.forR4()); registry.postConstruct(); @@ -103,4 +109,41 @@ public class BaseSearchParamRegistryTest { Map outcome = registry.getActiveSearchParams("Patient"); } + @Test + public void testExtractExtensions() { + SearchParamRegistryImpl registry = new SearchParamRegistryImpl(); + registry.setFhirContextForUnitTest(FhirContext.forR4()); + registry.postConstruct(); + + SearchParameter searchParameter = new SearchParameter(); + searchParameter.setCode("foo"); + searchParameter.setStatus(Enumerations.PublicationStatus.ACTIVE); + searchParameter.setType(Enumerations.SearchParamType.TOKEN); + searchParameter.setExpression("Patient.name"); + searchParameter.addBase("Patient"); + searchParameter.addExtension("http://foo", new StringType("FOO")); + searchParameter.addExtension("http://bar", new StringType("BAR")); + + // Invalid entries + searchParameter.addExtension("http://bar", null); + searchParameter.addExtension(null, new StringType("BAR")); + + when(mySearchParamProvider.search(any())).thenReturn(new SimpleBundleProvider(searchParameter)); + when(mySearchParamProvider.refreshCache(any(), anyLong())).thenAnswer(t -> { + registry.doRefresh(0); + return 0; + }); + + registry.setSearchParamProviderForUnitTest(mySearchParamProvider); + Map outcome = registry.getActiveSearchParams("Patient"); + + RuntimeSearchParam converted = outcome.get("foo"); + assertNotNull(converted); + + assertEquals(1, converted.getExtensions("http://foo").size()); + IPrimitiveType value = (IPrimitiveType) converted.getExtensions("http://foo").get(0).getValue(); + assertEquals("FOO", value.getValueAsString()); + + } + } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSearchParamProvider.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSearchParamProvider.java index 1cca7903a11..5e8b5a76964 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSearchParamProvider.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSearchParamProvider.java @@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.subscription.module.standalone; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.searchparam.registry.BaseSearchParamRegistry; +import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.rest.api.CacheControlDirective; @@ -62,7 +62,7 @@ public class FhirClientSearchParamProvider implements ISearchParamProvider { } @Override - public int refreshCache(BaseSearchParamRegistry theSearchParamRegistry, long theRefreshInterval) { + public int refreshCache(SearchParamRegistryImpl theSearchParamRegistry, long theRefreshInterval) { return theSearchParamRegistry.doRefresh(theRefreshInterval); } } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SearchParamLoaderTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SearchParamLoaderTest.java index 5ec3694bdbc..24c58bc8e5b 100755 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SearchParamLoaderTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SearchParamLoaderTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.subscription.module.standalone; -import ca.uhn.fhir.jpa.searchparam.registry.BaseSearchParamRegistry; +import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; import ca.uhn.fhir.jpa.subscription.module.config.MockFhirClientSearchParamProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.server.SimpleBundleProvider; @@ -20,7 +20,7 @@ public class SearchParamLoaderTest extends BaseBlockingQueueSubscribableChannelD @Autowired private MockFhirClientSearchParamProvider myMockFhirClientSearchParamProvider; @Autowired - private BaseSearchParamRegistry mySearchParamRegistry; + private SearchParamRegistryImpl mySearchParamRegistry; @Before public void setFailCount() { diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-head.html b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-head.html index 791b6955aee..923e77a235f 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-head.html +++ b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-head.html @@ -22,8 +22,8 @@ - - + + diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/js/RestfulTester.js b/hapi-fhir-testpage-overlay/src/main/webapp/js/RestfulTester.js index fab2692265c..560bc3ce100 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/js/RestfulTester.js +++ b/hapi-fhir-testpage-overlay/src/main/webapp/js/RestfulTester.js @@ -479,9 +479,9 @@ function addSearchControlQuantity(theSearchParamName, theContainerRowNum, theRow } function handleSearchParamTypeChange(select, params, theContainerRowNum, theParamRowNum) { - var oldVal = select.prevVal; - var newVal = select.val(); - if (oldVal === newVal) { + let oldVal = select.prevVal; + let newVal = select.val(); + if (oldVal == newVal || !(newVal)) { return; } $('#search-param-rowopts-' + theContainerRowNum).empty(); From 3173af31aeaa3132915febcfd3aae3283368a6e1 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sun, 17 Nov 2019 15:28:33 -0500 Subject: [PATCH 2/2] Try to avoid intermittent test failure --- .../fhir/jpa/provider/r4/StaleSearchDeletingSvcR4Test.java | 7 +++++++ .../fhir/test/utilities/UnregisterScheduledProcessor.java | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/StaleSearchDeletingSvcR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/StaleSearchDeletingSvcR4Test.java index f20f42fe527..c3ff40c8ea2 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/StaleSearchDeletingSvcR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/StaleSearchDeletingSvcR4Test.java @@ -14,6 +14,7 @@ import ca.uhn.fhir.jpa.search.cache.DatabaseSearchCacheSvcImpl; import ca.uhn.fhir.rest.gclient.IClientExecutable; import ca.uhn.fhir.rest.gclient.IQuery; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.test.utilities.UnregisterScheduledProcessor; import ca.uhn.fhir.util.TestUtil; import org.apache.commons.lang3.time.DateUtils; import org.hl7.fhir.r4.model.Bundle; @@ -24,6 +25,7 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.util.AopTestUtils; import java.util.Date; @@ -33,6 +35,11 @@ import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +@TestPropertySource(properties = { + // Since scheduled tasks can cause searches, which messes up the + // value returned by SearchBuilder.getLastHandlerMechanismForUnitTest() + UnregisterScheduledProcessor.SCHEDULING_DISABLED_EQUALS_TRUE +}) public class StaleSearchDeletingSvcR4Test extends BaseResourceProviderR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StaleSearchDeletingSvcR4Test.class); diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/UnregisterScheduledProcessor.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/UnregisterScheduledProcessor.java index d292b4ef6ec..fd943024dce 100644 --- a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/UnregisterScheduledProcessor.java +++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/UnregisterScheduledProcessor.java @@ -53,7 +53,7 @@ public class UnregisterScheduledProcessor implements BeanFactoryPostProcessor { } for (String beanName : beanFactory.getBeanNamesForType(ExecutorConfigurationSupport.class)) { - ExecutorConfigurationSupport executorConfigSupport = ((DefaultListableBeanFactory) beanFactory).getBean(beanName, ExecutorConfigurationSupport.class); + ExecutorConfigurationSupport executorConfigSupport = beanFactory.getBean(beanName, ExecutorConfigurationSupport.class); executorConfigSupport.shutdown(); } }