From 2bf680509bbd64c6a408fcbf69274f24d1edbd66 Mon Sep 17 00:00:00 2001 From: Ken Stevens Date: Thu, 13 Jun 2019 13:59:51 -0400 Subject: [PATCH] DeleteConflict hook, SearchableHashMapResourceProvider, and in-memory searching (#1341) * Make IBundle list interface more flexible * Make matchResourceLinks visible * Make matchResourceLinks visible * Pull in-memory matching functionality out of subscription module into dedicated classes in searchparam module * Added more accessors to version independent resource classes * improved forcedid matching (needs to be verified by James) filter out nulls from HashMapProvider.searchAll() (hopefully no tests depend on this) * add AnyComposition * cleanup of Any classes * create searchparam config so people can use that module without using subscriptions * create searchparam config so people can use that module without using subscriptions * fix broken test * added derivedFrom * renamed daoRegistry.getResourceDaoIfExists to getResourceDaoOrNull also added method to add a new dao to the registry and retrieve registered types * TokenParam.equals * add error check to searchable hashmap resource provider * added support for R4 * add successor to measure * delete conflict hook start with failing test * test passes. hook is called. * added delete conflict hook all delete conflict tests pass * added javadoc * Performance test * test bad interceptor case * typo * rename criteria matcher * added support for multiple topics * fix method name * add effective period * Temporarily excluding StressTest from build to try to get Travis under time limit * change log * review feedback * review feedback * review feedback --- .../ca/uhn/fhir/interceptor/api/Pointcut.java | 26 +- .../ca/uhn/fhir/rest/param/TokenParam.java | 27 ++ .../uhn/fhir/rest/param/TokenParamTest.java | 14 + hapi-fhir-jpaserver-base/pom.xml | 5 +- .../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 35 +- .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 45 +- .../java/ca/uhn/fhir/jpa/dao/DaoRegistry.java | 34 +- .../fhir/jpa/dao/FhirResourceDaoDstu2.java | 44 +- .../uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java | 8 +- .../ca/uhn/fhir/jpa/dao/IFhirResourceDao.java | 6 +- .../fhir/jpa/dao/TransactionProcessor.java | 8 +- .../jpa/dao/dstu3/FhirResourceDaoDstu3.java | 47 +- .../fhir/jpa/dao/r4/FhirResourceDaoR4.java | 11 +- .../delete/DeleteConflictFinderService.java | 24 + .../fhir/jpa/delete/DeleteConflictList.java | 36 ++ .../jpa/delete/DeleteConflictService.java | 120 +++++ ...mpositeInMemoryDaoSubscriptionMatcher.java | 6 +- .../dbmatcher/DaoSubscriptionMatcher.java | 6 +- ...ResourceDaoR4ReferentialIntegrityTest.java | 37 +- .../delete/DeleteConflictServiceR4Test.java | 186 ++++++++ .../jpa/delete/DeleteConflictServiceTest.java | 64 +++ .../MatchUrlServiceTest.java} | 19 +- .../InMemorySubscriptionMatcherR4Test.java | 18 +- .../fhir/jpa/model/any/AnyComposition.java | 215 +++++++++ .../fhir/jpa/model/any/AnyListResource.java | 252 ++++++++++ .../ca/uhn/fhir/jpa/model/any/AnyMeasure.java | 431 ++++++++++++++++++ .../config/BaseSearchParamConfig.java | 12 + .../config/SearchParamDstu2Config.java | 42 ++ .../config/SearchParamDstu3Config.java | 34 ++ .../config/SearchParamR4Config.java | 42 ++ .../ResourceIndexedSearchParams.java | 6 +- .../matcher/InMemoryMatchResult.java | 24 +- .../matcher/InMemoryResourceMatcher.java | 38 +- .../matcher/IndexedSearchParamExtractor.java | 32 ++ .../matcher/InlineResourceLinkResolver.java | 2 +- .../matcher/SearchParamMatcher.java | 19 + .../SearchableHashMapResourceProvider.java | 52 +++ .../module/config/BaseSubscriptionConfig.java | 7 +- .../config/SubscriptionDstu2Config.java | 45 +- .../config/SubscriptionDstu3Config.java | 42 +- .../module/config/SubscriptionR4Config.java | 45 +- .../SubscriptionDebugLogInterceptor.java | 4 +- .../module/matcher/ISubscriptionMatcher.java | 3 +- .../matcher/InMemorySubscriptionMatcher.java | 28 +- .../SubscriptionStrategyEvaluator.java | 6 +- .../SubscriptionMatchingSubscriber.java | 6 +- .../InMemorySubscriptionMatcherR3Test.java | 10 +- .../standalone/SearchParamLoaderTest.java | 1 - .../server/BundleProviderWithNamedPages.java | 2 +- .../rest/server/SimpleBundleProvider.java | 10 +- .../provider/HashMapResourceProvider.java | 7 +- pom.xml | 5 + src/changes/changes.xml | 22 +- 53 files changed, 1887 insertions(+), 383 deletions(-) create mode 100644 hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/param/TokenParamTest.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictFinderService.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictList.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictService.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceR4Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceTest.java rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/{dao/BaseHapiFhirDaoTest.java => searchparam/MatchUrlServiceTest.java} (77%) create mode 100644 hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyComposition.java create mode 100644 hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyListResource.java create mode 100644 hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyMeasure.java create mode 100644 hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/BaseSearchParamConfig.java create mode 100644 hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu2Config.java create mode 100644 hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu3Config.java create mode 100644 hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamR4Config.java rename hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionMatchResult.java => hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryMatchResult.java (71%) rename hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/CriteriaResourceMatcher.java => hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java (74%) create mode 100644 hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/IndexedSearchParamExtractor.java rename {hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module => hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam}/matcher/InlineResourceLinkResolver.java (97%) create mode 100644 hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/SearchParamMatcher.java create mode 100644 hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/provider/SearchableHashMapResourceProvider.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java index feaa88c9bfc..c07a235b7e4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java @@ -429,7 +429,7 @@ public enum Pointcut { * *

* Hooks may return void or may return a boolean. If the method returns @@ -437,7 +437,7 @@ public enum Pointcut { * returns false, delivery will be aborted. *

*/ - SUBSCRIPTION_RESOURCE_MATCHED(boolean.class, "ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage", "ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult"), + SUBSCRIPTION_RESOURCE_MATCHED(boolean.class, "ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage", "ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult"), /** @@ -865,6 +865,27 @@ public enum Pointcut { "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails" ), + /** + * Invoked when a resource delete operation is about to fail due to referential integrity conflicts. + *

+ * Hooks will have access to the list of resources that have references to the resource being deleted. + *

+ * Hooks may accept the following parameters: + * + *

+ * Hooks should return boolean. If the method returns true then the caller + * will retry checking for delete conflicts. If there are still conflicts, then the hook will be invoked again, + * repeatedly up to a maximum of {@value ca.uhn.fhir.jpa.delete.DeleteConflictService#MAX_RETRIES} retries. + * The first time the hook is invoked, there will be a maximum of {@value ca.uhn.fhir.jpa.delete.DeleteConflictService#MIN_QUERY_RESULT_COUNT} + * conflicts passed to the method. Subsequent hook invocations will pass a maximum of + * {@value ca.uhn.fhir.jpa.delete.DeleteConflictService#MAX_RETRY_COUNT} conflicts to the hook. + *

+ */ + STORAGE_PRESTORAGE_DELETE_CONFLICTS(boolean.class, "ca.uhn.fhir.jpa.delete.DeleteConflictList"), + + /** * Note that this is a performance tracing hook. Use with caution in production * systems, since calling it may (or may not) carry a cost. @@ -994,7 +1015,6 @@ public enum Pointcut { */ JPA_PERFTRACE_SEARCH_COMPLETE(void.class, "ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails"), - /** * This pointcut is used only for unit tests. Do not use in production code as it may be changed or * removed at any time. diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenParam.java index 708e227d362..3e15709e2bb 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenParam.java @@ -25,6 +25,8 @@ import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.model.base.composite.BaseIdentifierDt; import ca.uhn.fhir.model.primitive.UriDt; import org.apache.commons.lang3.StringUtils; +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; @@ -218,6 +220,7 @@ public class TokenParam extends BaseParam /*implements IQueryParameterType*/ { return this; } + @Override public String toString() { ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); @@ -232,6 +235,30 @@ public class TokenParam extends BaseParam /*implements IQueryParameterType*/ { return builder.toString(); } + @Override + public boolean equals(Object theO) { + if (this == theO) return true; + + if (theO == null || getClass() != theO.getClass()) return false; + + TokenParam that = (TokenParam) theO; + + return new EqualsBuilder() + .append(myModifier, that.myModifier) + .append(mySystem, that.mySystem) + .append(myValue, that.myValue) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(myModifier) + .append(mySystem) + .append(myValue) + .toHashCode(); + } + private static String toSystemValue(UriDt theSystem) { return theSystem.getValueAsString(); } diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/param/TokenParamTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/param/TokenParamTest.java new file mode 100644 index 00000000000..640a150e5ec --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/param/TokenParamTest.java @@ -0,0 +1,14 @@ +package ca.uhn.fhir.rest.param; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class TokenParamTest { + @Test + public void testEquals() { + TokenParam tokenParam1 = new TokenParam("foo", "bar"); + TokenParam tokenParam2 = new TokenParam("foo", "bar"); + assertEquals(tokenParam1, tokenParam2); + } +} diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index e8db546587a..01369bec620 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -517,8 +517,8 @@ test - org.springframework - spring-test + org.springframework.boot + spring-boot-test test @@ -655,6 +655,7 @@ alphabetical @{argLine} -Dfile.encoding=UTF-8 -Xmx20484M -Xss128M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=2048M -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC 0.6C + StressTest* 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 df3ecce6b0e..5ddfca1f3ba 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 @@ -9,6 +9,7 @@ import ca.uhn.fhir.jpa.dao.expunge.ExpungeService; import ca.uhn.fhir.jpa.dao.index.DaoSearchParamSynchronizer; import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.dao.index.SearchParamWithInlineReferencesExtractor; +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; @@ -22,7 +23,6 @@ import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; -import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.jpa.util.JpaConstants; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; @@ -44,12 +44,10 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 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.servlet.ServletRequestDetails; import ca.uhn.fhir.util.CoverageIgnore; -import ca.uhn.fhir.util.OperationOutcomeUtil; import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.XmlUtil; import com.google.common.annotations.VisibleForTesting; @@ -129,8 +127,6 @@ public abstract class BaseHapiFhirDao implements IDao, @Autowired protected IForcedIdDao myForcedIdDao; @Autowired - protected IResourceLinkDao myResourceLinkDao; - @Autowired protected ISearchCoordinatorSvc mySearchCoordinatorSvc; @Autowired protected ISearchParamRegistry mySerarchParamRegistry; @@ -163,6 +159,8 @@ public abstract class BaseHapiFhirDao implements IDao, private SearchBuilderFactory mySearchBuilderFactory; @Autowired ExpungeService myExpungeService; + @Autowired + protected DeleteConflictService myDeleteConflictService; private FhirContext myContext; private ApplicationContext myApplicationContext; @@ -353,7 +351,7 @@ public abstract class BaseHapiFhirDao implements IDao, @SuppressWarnings("unchecked") public IFhirResourceDao getDao(Class theType) { - return myDaoRegistry.getResourceDaoIfExists(theType); + return myDaoRegistry.getResourceDaoOrNull(theType); } protected TagDefinition getTagOrNull(TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) { @@ -1282,31 +1280,6 @@ public abstract class BaseHapiFhirDao implements IDao, } } - public void validateDeleteConflictsEmptyOrThrowException(List theDeleteConflicts) { - if (theDeleteConflicts.isEmpty()) { - return; - } - - IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext()); - String firstMsg = null; - for (DeleteConflict next : theDeleteConflicts) { - StringBuilder b = new StringBuilder(); - b.append("Unable to delete "); - b.append(next.getTargetId().toUnqualifiedVersionless().getValue()); - b.append(" because at least one resource has a reference to this resource. First reference found was resource "); - b.append(next.getSourceId().toUnqualifiedVersionless().getValue()); - b.append(" in path "); - b.append(next.getSourcePath()); - String msg = b.toString(); - if (firstMsg == null) { - firstMsg = msg; - } - OperationOutcomeUtil.addIssue(getContext(), oo, OO_SEVERITY_ERROR, msg, null, "processing"); - } - - throw new ResourceVersionConflictException(firstMsg, oo); - } - protected void validateMetaCount(int theMetaCount) { if (myConfig.getResourceMetaCountHardLimit() != null) { if (theMetaCount > myConfig.getResourceMetaCountHardLimit()) { 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 c49f40ed442..11762da8b81 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 @@ -27,13 +27,13 @@ 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.dao.r4.MatchResourceUrlService; +import ca.uhn.fhir.jpa.delete.DeleteConflictList; import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.jpa.util.ExpungeOptions; import ca.uhn.fhir.jpa.util.ExpungeOutcome; import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils; @@ -48,15 +48,15 @@ import ca.uhn.fhir.rest.api.server.RequestDetails; 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.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; -import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor; import ca.uhn.fhir.rest.server.method.SearchMethodBinding; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.*; import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.r4.model.InstantType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; import org.springframework.transaction.PlatformTransactionManager; @@ -80,7 +80,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; @Transactional(propagation = Propagation.REQUIRED) public abstract class BaseHapiFhirResourceDao extends BaseHapiFhirDao implements IFhirResourceDao { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirResourceDao.class); + private static final Logger ourLog = LoggerFactory.getLogger(BaseHapiFhirResourceDao.class); @Autowired protected PlatformTransactionManager myPlatformTransactionManager; @@ -187,7 +187,7 @@ public abstract class BaseHapiFhirResourceDao extends B } @Override - public DaoMethodOutcome delete(IIdType theId, List theDeleteConflicts, RequestDetails theRequest) { + public DaoMethodOutcome delete(IIdType theId, DeleteConflictList theDeleteConflicts, RequestDetails theRequest) { if (theId == null || !theId.hasIdPart()) { throw new InvalidRequestException("Can not perform delete, no ID provided"); } @@ -226,7 +226,7 @@ public abstract class BaseHapiFhirResourceDao extends B .addIfMatchesType(ServletRequestDetails.class, theRequest); myInterceptorBroadcaster.callHooks(Pointcut.STORAGE_PRESTORAGE_RESOURCE_DELETED, hook); - validateOkToDelete(theDeleteConflicts, entity, false); + myDeleteConflictService.validateOkToDelete(theDeleteConflicts, entity, false); preDelete(resourceToDelete, entity); @@ -265,12 +265,12 @@ public abstract class BaseHapiFhirResourceDao extends B @Override public DaoMethodOutcome delete(IIdType theId, RequestDetails theRequestDetails) { - List deleteConflicts = new ArrayList(); + DeleteConflictList deleteConflicts = new DeleteConflictList(); StopWatch w = new StopWatch(); DaoMethodOutcome retVal = delete(theId, deleteConflicts, theRequestDetails); - validateDeleteConflictsEmptyOrThrowException(deleteConflicts); + myDeleteConflictService.validateDeleteConflictsEmptyOrThrowException(deleteConflicts); ourLog.debug("Processed delete on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); return retVal; @@ -281,7 +281,7 @@ public abstract class BaseHapiFhirResourceDao extends B * transaction processors */ @Override - public DeleteMethodOutcome deleteByUrl(String theUrl, List deleteConflicts, RequestDetails theRequest) { + public DeleteMethodOutcome deleteByUrl(String theUrl, DeleteConflictList deleteConflicts, RequestDetails theRequest) { StopWatch w = new StopWatch(); Set resourceIds = myMatchResourceUrlService.processMatchUrl(theUrl, myResourceType); @@ -305,7 +305,7 @@ public abstract class BaseHapiFhirResourceDao extends B .addIfMatchesType(ServletRequestDetails.class, theRequest); myInterceptorBroadcaster.callHooks(Pointcut.STORAGE_PRESTORAGE_RESOURCE_DELETED, hooks); - validateOkToDelete(deleteConflicts, entity, false); + myDeleteConflictService.validateOkToDelete(deleteConflicts, entity, false); // Notify interceptors IdDt idToDelete = entity.getIdDt(); @@ -357,11 +357,11 @@ public abstract class BaseHapiFhirResourceDao extends B @Override public DeleteMethodOutcome deleteByUrl(String theUrl, RequestDetails theRequestDetails) { - List deleteConflicts = new ArrayList<>(); + DeleteConflictList deleteConflicts = new DeleteConflictList(); DeleteMethodOutcome outcome = deleteByUrl(theUrl, deleteConflicts, theRequestDetails); - validateDeleteConflictsEmptyOrThrowException(deleteConflicts); + myDeleteConflictService.validateDeleteConflictsEmptyOrThrowException(deleteConflicts); return outcome; } @@ -1392,28 +1392,7 @@ public abstract class BaseHapiFhirResourceDao extends B } } - protected void validateOkToDelete(List theDeleteConflicts, ResourceTable theEntity, boolean theForValidate) { - TypedQuery query = myEntityManager.createQuery("SELECT l FROM ResourceLink l WHERE l.myTargetResourcePid = :target_pid", ResourceLink.class); - query.setParameter("target_pid", theEntity.getId()); - query.setMaxResults(1); - List resultList = query.getResultList(); - if (resultList.isEmpty()) { - return; - } - if (myDaoConfig.isEnforceReferentialIntegrityOnDelete() == false && !theForValidate) { - ourLog.debug("Deleting {} resource dependencies which can no longer be satisfied", resultList.size()); - myResourceLinkDao.deleteAll(resultList); - return; - } - - ResourceLink link = resultList.get(0); - IdDt targetId = theEntity.getIdDt(); - IdDt sourceId = link.getSourceResource().getIdDt(); - String sourcePath = link.getSourcePath(); - - theDeleteConflicts.add(new DeleteConflict(sourceId, sourcePath, targetId)); - } private void validateResourceType(BaseHasResource entity) { validateResourceType(entity, myResourceName); 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 f9ec72a902b..19379782db1 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 @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao; * 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. @@ -30,8 +30,8 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; -import org.springframework.stereotype.Component; +import javax.annotation.Nullable; import java.util.*; import java.util.stream.Collectors; @@ -100,7 +100,16 @@ public class DaoRegistry implements ApplicationContextAware { return retVal; } + /** + * Use getResourceDaoOrNull + */ + @Deprecated public IFhirResourceDao getResourceDaoIfExists(Class theResourceType) { + return getResourceDaoOrNull(theResourceType); + } + + @Nullable + public IFhirResourceDao getResourceDaoOrNull(Class theResourceType) { String resourceName = myContext.getResourceDefinition(theResourceType).getName(); try { return (IFhirResourceDao) getResourceDao(resourceName); @@ -109,7 +118,16 @@ public class DaoRegistry implements ApplicationContextAware { } } + /** + * Use getResourceDaoOrNull + */ + @Deprecated public IFhirResourceDao getResourceDaoIfExists(String theResourceType) { + return getResourceDaoOrNull(theResourceType); + } + + @Nullable + public IFhirResourceDao getResourceDaoOrNull(String theResourceType) { try { return (IFhirResourceDao) getResourceDao(theResourceType); } catch (InvalidRequestException e) { @@ -139,6 +157,12 @@ public class DaoRegistry implements ApplicationContextAware { } } + public void register(IFhirResourceDao theResourceDao) { + RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(theResourceDao.getResourceType()); + String resourceName = resourceDef.getName(); + myResourceNameToResourceDao.put(resourceName, theResourceDao); + } + public IFhirResourceDao getDaoOrThrowException(Class theClass) { IFhirResourceDao retVal = getResourceDao(theClass); if (retVal == null) { @@ -172,4 +196,8 @@ public class DaoRegistry implements ApplicationContextAware { } return retVal; } + + public Set getRegisteredDaoTypes() { + return Collections.unmodifiableSet(myResourceNameToResourceDao.keySet()); + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2.java index 21b958824fa..10f0b6b6bf0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2.java @@ -1,30 +1,32 @@ package ca.uhn.fhir.jpa.dao; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import java.util.*; - -import org.hl7.fhir.instance.hapi.validation.IValidationSupport; -import org.hl7.fhir.instance.model.api.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; - -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.jpa.delete.DeleteConflictList; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.api.Include; -import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; import ca.uhn.fhir.model.dstu2.resource.OperationOutcome; import ca.uhn.fhir.model.dstu2.valueset.IssueSeverityEnum; import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.api.*; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.api.ValidationModeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.exceptions.*; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; -import ca.uhn.fhir.util.FhirTerser; -import ca.uhn.fhir.validation.*; +import ca.uhn.fhir.validation.FhirValidator; +import ca.uhn.fhir.validation.IValidationContext; +import ca.uhn.fhir.validation.IValidatorModule; +import ca.uhn.fhir.validation.ValidationResult; +import org.hl7.fhir.instance.hapi.validation.IValidationSupport; +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.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; /* * #%L @@ -79,11 +81,11 @@ public class FhirResourceDaoDstu2 extends BaseHapiFhirResou // Validate that there are no resources pointing to the candidate that // would prevent deletion - List deleteConflicts = new ArrayList(); + DeleteConflictList deleteConflicts = new DeleteConflictList(); if (myDaoConfig.isEnforceReferentialIntegrityOnDelete()) { - validateOkToDelete(deleteConflicts, entity, true); + myDeleteConflictService.validateOkToDelete(deleteConflicts, entity, true); } - validateDeleteConflictsEmptyOrThrowException(deleteConflicts); + myDeleteConflictService.validateDeleteConflictsEmptyOrThrowException(deleteConflicts); OperationOutcome oo = new OperationOutcome(); oo.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDiagnostics("Ok to delete"); 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 ba2456f61d2..d80e320ea66 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 @@ -21,11 +21,11 @@ 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.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.TagDefinition; import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails; import ca.uhn.fhir.jpa.searchparam.MatchUrlService; -import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; @@ -212,7 +212,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { Collections.sort(theRequest.getEntry(), new TransactionSorter()); List deletedResources = new ArrayList<>(); - List deleteConflicts = new ArrayList<>(); + DeleteConflictList deleteConflicts = new DeleteConflictList(); Map entriesToProcess = new IdentityHashMap<>(); Set nonUpdatedEntities = new HashSet(); Set updatedEntities = new HashSet<>(); @@ -307,7 +307,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { 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, List 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 @@ -444,7 +444,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { */ theDeleteConflicts.removeIf(next -> theDeletedResources.contains(next.getTargetId().toVersionless())); - validateDeleteConflictsEmptyOrThrowException(theDeleteConflicts); + myDeleteConflictService.validateDeleteConflictsEmptyOrThrowException(theDeleteConflicts); /* * Perform ID substitutions and then index each resource we have saved 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 ad4d4842977..16dc505728f 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 @@ -21,11 +21,11 @@ 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.entity.BaseHasResource; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.jpa.util.ExpungeOptions; import ca.uhn.fhir.jpa.util.ExpungeOutcome; import ca.uhn.fhir.model.api.IQueryParameterType; @@ -90,7 +90,7 @@ public interface IFhirResourceDao extends IDao { * * @param theRequestDetails TODO */ - DaoMethodOutcome delete(IIdType theResource, List theDeleteConflictsListToPopulate, RequestDetails theRequestDetails); + DaoMethodOutcome delete(IIdType theResource, DeleteConflictList theDeleteConflictsListToPopulate, RequestDetails theRequestDetails); /** * This method throws an exception if there are delete conflicts @@ -101,7 +101,7 @@ public interface IFhirResourceDao extends IDao { * This method does not throw an exception if there are delete conflicts, but populates them * in the provided list */ - DeleteMethodOutcome deleteByUrl(String theUrl, List theDeleteConflictsListToPopulate, RequestDetails theRequestDetails); + DeleteMethodOutcome deleteByUrl(String theUrl, DeleteConflictList theDeleteConflictsListToPopulate, RequestDetails theRequestDetails); /** * This method throws an exception if there are delete conflicts 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 fa8c964e2b3..7ca31f96380 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 @@ -23,6 +23,8 @@ package ca.uhn.fhir.jpa.dao; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; 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.provider.ServletSubRequestDetails; import ca.uhn.fhir.jpa.searchparam.MatchUrlService; @@ -90,6 +92,8 @@ public class TransactionProcessor { private DaoRegistry myDaoRegistry; @Autowired(required = false) private HapiFhirHibernateJpaDialect myHapiFhirHibernateJpaDialect; + @Autowired + private DeleteConflictService myDeleteConflictService; public BUNDLE transaction(RequestDetails theRequestDetails, BUNDLE theRequest) { if (theRequestDetails != null) { @@ -504,7 +508,7 @@ public class TransactionProcessor { try { Set deletedResources = new HashSet<>(); - List deleteConflicts = new ArrayList<>(); + DeleteConflictList deleteConflicts = new DeleteConflictList(); Map entriesToProcess = new IdentityHashMap<>(); Set nonUpdatedEntities = new HashSet<>(); Set updatedEntities = new HashSet<>(); @@ -783,7 +787,7 @@ public class TransactionProcessor { } } } - myDao.validateDeleteConflictsEmptyOrThrowException(deleteConflicts); + myDeleteConflictService.validateDeleteConflictsEmptyOrThrowException(deleteConflicts); /* * Perform ID substitutions and then index each resource we have saved diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3.java index 6f662aebe11..57fe0a4b8df 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3.java @@ -20,33 +20,36 @@ package ca.uhn.fhir.jpa.dao.dstu3; * #L% */ -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import java.util.*; - +import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.delete.DeleteConflictList; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.api.ValidationModeEnum; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; +import ca.uhn.fhir.validation.FhirValidator; +import ca.uhn.fhir.validation.IValidationContext; +import ca.uhn.fhir.validation.IValidatorModule; +import ca.uhn.fhir.validation.ValidationResult; import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity; import org.hl7.fhir.dstu3.model.OperationOutcome.OperationOutcomeIssueComponent; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.instance.model.api.*; +import org.hl7.fhir.instance.model.api.IAnyResource; +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.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.util.DeleteConflict; -import ca.uhn.fhir.model.api.Include; -import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; -import ca.uhn.fhir.rest.api.*; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.exceptions.*; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; -import ca.uhn.fhir.util.FhirTerser; -import ca.uhn.fhir.validation.*; +import static org.apache.commons.lang3.StringUtils.isNotBlank; public class FhirResourceDaoDstu3 extends BaseHapiFhirResourceDao { @@ -86,11 +89,11 @@ public class FhirResourceDaoDstu3 extends BaseHapiFhirRe // Validate that there are no resources pointing to the candidate that // would prevent deletion - List deleteConflicts = new ArrayList(); + DeleteConflictList deleteConflicts = new DeleteConflictList(); if (myDaoConfig.isEnforceReferentialIntegrityOnDelete()) { - validateOkToDelete(deleteConflicts, entity, true); + myDeleteConflictService.validateOkToDelete(deleteConflicts, entity, true); } - validateDeleteConflictsEmptyOrThrowException(deleteConflicts); + myDeleteConflictService.validateDeleteConflictsEmptyOrThrowException(deleteConflicts); OperationOutcome oo = new OperationOutcome(); oo.addIssue().setSeverity(IssueSeverity.INFORMATION).setDiagnostics("Ok to delete"); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4.java index f74d44b2000..a080d378d7c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4.java @@ -22,8 +22,8 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.delete.DeleteConflictList; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; @@ -49,9 +49,6 @@ import org.hl7.fhir.r4.model.OperationOutcome.OperationOutcomeIssueComponent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import java.util.ArrayList; -import java.util.List; - import static org.apache.commons.lang3.StringUtils.isNotBlank; public class FhirResourceDaoR4 extends BaseHapiFhirResourceDao { @@ -90,11 +87,11 @@ public class FhirResourceDaoR4 extends BaseHapiFhirResou // Validate that there are no resources pointing to the candidate that // would prevent deletion - List deleteConflicts = new ArrayList(); + DeleteConflictList deleteConflicts = new DeleteConflictList(); if (myDaoConfig.isEnforceReferentialIntegrityOnDelete()) { - validateOkToDelete(deleteConflicts, entity, true); + myDeleteConflictService.validateOkToDelete(deleteConflicts, entity, true); } - validateDeleteConflictsEmptyOrThrowException(deleteConflicts); + myDeleteConflictService.validateDeleteConflictsEmptyOrThrowException(deleteConflicts); OperationOutcome oo = new OperationOutcome(); oo.addIssue().setSeverity(IssueSeverity.INFORMATION).setDiagnostics("Ok to delete"); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictFinderService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictFinderService.java new file mode 100644 index 00000000000..1316e84cf1c --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictFinderService.java @@ -0,0 +1,24 @@ +package ca.uhn.fhir.jpa.delete; + +import ca.uhn.fhir.jpa.model.entity.ResourceLink; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import org.springframework.stereotype.Service; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.PersistenceContextType; +import javax.persistence.TypedQuery; +import java.util.List; + +@Service +class DeleteConflictFinderService { + @PersistenceContext(type = PersistenceContextType.TRANSACTION) + protected EntityManager myEntityManager; + + List findConflicts(ResourceTable theEntity, int maxResults) { + TypedQuery query = myEntityManager.createQuery("SELECT l FROM ResourceLink l WHERE l.myTargetResourcePid = :target_pid", ResourceLink.class); + query.setParameter("target_pid", theEntity.getId()); + query.setMaxResults(maxResults); + return query.getResultList(); + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictList.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictList.java new file mode 100644 index 00000000000..709223a2f80 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictList.java @@ -0,0 +1,36 @@ +package ca.uhn.fhir.jpa.delete; + +import ca.uhn.fhir.jpa.util.DeleteConflict; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; + +public class DeleteConflictList { + private final List myList = new ArrayList<>(); + + public void add(DeleteConflict theDeleteConflict) { + myList.add(theDeleteConflict); + } + + public boolean isEmpty() { + return myList.isEmpty(); + } + + public Iterator iterator() { + return myList.iterator(); + } + + public boolean removeIf(Predicate theFilter) { + return myList.removeIf(theFilter); + } + + public void addAll(DeleteConflictList theNewConflicts) { + myList.addAll(theNewConflicts.myList); + } + + public int size() { + return myList.size(); + } +} 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 new file mode 100644 index 00000000000..a9e9cbb4eee --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictService.java @@ -0,0 +1,120 @@ +package ca.uhn.fhir.jpa.delete; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.interceptor.api.HookParams; +import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao; +import ca.uhn.fhir.jpa.model.entity.ResourceLink; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.util.DeleteConflict; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.util.OperationOutcomeUtil; +import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Iterator; +import java.util.List; + +@Service +public class DeleteConflictService { + private static final Logger ourLog = LoggerFactory.getLogger(DeleteConflictService.class); + public static final int FIRST_QUERY_RESULT_COUNT = 1; + public static final int RETRY_QUERY_RESULT_COUNT = 60; + public static final int MAX_RETRY_ATTEMPTS = 10; + + @Autowired + DeleteConflictFinderService myDeleteConflictFinderService; + @Autowired + DaoConfig myDaoConfig; + @Autowired + protected IResourceLinkDao myResourceLinkDao; + @Autowired + private FhirContext myFhirContext; + @Autowired + protected IInterceptorBroadcaster myInterceptorBroadcaster; + + public int validateOkToDelete(DeleteConflictList theDeleteConflicts, ResourceTable theEntity, boolean theForValidate) { + DeleteConflictList newConflicts = new DeleteConflictList(); + + // In most cases, there will be no hooks, and so we only need to check if there is at least FIRST_QUERY_RESULT_COUNT conflict and populate that. + // Only in the case where there is a hook do we need to go back and collect larger batches of conflicts for processing. + + boolean tryAgain = findAndHandleConflicts(newConflicts, theEntity, theForValidate, FIRST_QUERY_RESULT_COUNT); + + int retryCount = 0; + while (tryAgain && retryCount < MAX_RETRY_ATTEMPTS) { + newConflicts = new DeleteConflictList(); + tryAgain = findAndHandleConflicts(newConflicts, theEntity, theForValidate, RETRY_QUERY_RESULT_COUNT); + ++retryCount; + } + theDeleteConflicts.addAll(newConflicts); + return retryCount; + } + + private boolean findAndHandleConflicts(DeleteConflictList theDeleteConflicts, ResourceTable theEntity, boolean theForValidate, int theMinQueryResultCount) { + List resultList = myDeleteConflictFinderService.findConflicts(theEntity, theMinQueryResultCount); + if (resultList.isEmpty()) { + return false; + } + return handleConflicts(theDeleteConflicts, theEntity, theForValidate, resultList); + } + + private boolean handleConflicts(DeleteConflictList theDeleteConflicts, ResourceTable theEntity, boolean theForValidate, List theResultList) { + if (!myDaoConfig.isEnforceReferentialIntegrityOnDelete() && !theForValidate) { + ourLog.debug("Deleting {} resource dependencies which can no longer be satisfied", theResultList.size()); + myResourceLinkDao.deleteAll(theResultList); + return false; + } + + addConflictsToList(theDeleteConflicts, theEntity, theResultList); + + // Notify Interceptors about pre-action call + HookParams hooks = new HookParams() + .add(DeleteConflictList.class, theDeleteConflicts); + return myInterceptorBroadcaster.callHooks(Pointcut.STORAGE_PRESTORAGE_DELETE_CONFLICTS, hooks); + } + + private void addConflictsToList(DeleteConflictList theDeleteConflicts, ResourceTable theEntity, List theResultList) { + for (ResourceLink link : theResultList) { + IdDt targetId = theEntity.getIdDt(); + IdDt sourceId = link.getSourceResource().getIdDt(); + String sourcePath = link.getSourcePath(); + theDeleteConflicts.add(new DeleteConflict(sourceId, sourcePath, targetId)); + } + } + + public void validateDeleteConflictsEmptyOrThrowException(DeleteConflictList theDeleteConflicts) { + if (theDeleteConflicts.isEmpty()) { + return; + } + + IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(myFhirContext); + String firstMsg = null; + + Iterator iterator = theDeleteConflicts.iterator(); + while (iterator.hasNext()) { + DeleteConflict next = iterator.next(); + StringBuilder b = new StringBuilder(); + b.append("Unable to delete "); + b.append(next.getTargetId().toUnqualifiedVersionless().getValue()); + b.append(" because at least one resource has a reference to this resource. First reference found was resource "); + b.append(next.getSourceId().toUnqualifiedVersionless().getValue()); + b.append(" in path "); + b.append(next.getSourcePath()); + String msg = b.toString(); + if (firstMsg == null) { + firstMsg = msg; + } + OperationOutcomeUtil.addIssue(myFhirContext, 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/subscription/dbmatcher/CompositeInMemoryDaoSubscriptionMatcher.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/CompositeInMemoryDaoSubscriptionMatcher.java index 4e084ba642b..26836f40c9e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/CompositeInMemoryDaoSubscriptionMatcher.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/CompositeInMemoryDaoSubscriptionMatcher.java @@ -21,11 +21,11 @@ package ca.uhn.fhir.jpa.subscription.dbmatcher; */ import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher; import ca.uhn.fhir.jpa.subscription.module.matcher.InMemorySubscriptionMatcher; -import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -44,8 +44,8 @@ public class CompositeInMemoryDaoSubscriptionMatcher implements ISubscriptionMat } @Override - public SubscriptionMatchResult match(CanonicalSubscription theSubscription, ResourceModifiedMessage theMsg) { - SubscriptionMatchResult result; + public InMemoryMatchResult match(CanonicalSubscription theSubscription, ResourceModifiedMessage theMsg) { + InMemoryMatchResult result; if (myDaoConfig.isEnableInMemorySubscriptionMatching()) { result = myInMemorySubscriptionMatcher.match(theSubscription, theMsg); if (result.supported()) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/DaoSubscriptionMatcher.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/DaoSubscriptionMatcher.java index 98aef307da7..720e77c0113 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/DaoSubscriptionMatcher.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/DaoSubscriptionMatcher.java @@ -26,10 +26,10 @@ import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher; -import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult; import ca.uhn.fhir.rest.api.server.IBundleProvider; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -52,7 +52,7 @@ public class DaoSubscriptionMatcher implements ISubscriptionMatcher { private PlatformTransactionManager myTxManager; @Override - public SubscriptionMatchResult match(CanonicalSubscription theSubscription, ResourceModifiedMessage theMsg) { + public InMemoryMatchResult match(CanonicalSubscription theSubscription, ResourceModifiedMessage theMsg) { IIdType id = theMsg.getId(myCtx); String resourceType = id.getResourceType(); String resourceId = id.getIdPart(); @@ -65,7 +65,7 @@ public class DaoSubscriptionMatcher implements ISubscriptionMatcher { ourLog.debug("Subscription check found {} results for query: {}", results.size(), criteria); - return SubscriptionMatchResult.fromBoolean(results.size() > 0); + return InMemoryMatchResult.fromBoolean(results.size() > 0); } /** diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ReferentialIntegrityTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ReferentialIntegrityTest.java index 51a5bc74b06..447d749fc8f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ReferentialIntegrityTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ReferentialIntegrityTest.java @@ -1,20 +1,19 @@ package ca.uhn.fhir.jpa.dao.r4; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -import org.hl7.fhir.r4.model.Organization; -import org.hl7.fhir.r4.model.Patient; -import org.hl7.fhir.r4.model.Reference; -import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Test; - import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.util.TestUtil; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Reference; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; public class FhirResourceDaoR4ReferentialIntegrityTest extends BaseJpaR4Test { @@ -46,11 +45,11 @@ public class FhirResourceDaoR4ReferentialIntegrityTest extends BaseJpaR4Test { @Test public void testCreateUnknownReferenceAllow() throws Exception { myDaoConfig.setEnforceReferentialIntegrityOnWrite(false); - + Patient p = new Patient(); p.setManagingOrganization(new Reference("Organization/AAA")); IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless(); - + p = myPatientDao.read(id); assertEquals("Organization/AAA", p.getManagingOrganization().getReference()); @@ -61,11 +60,11 @@ public class FhirResourceDaoR4ReferentialIntegrityTest extends BaseJpaR4Test { Organization o = new Organization(); o.setName("FOO"); IIdType oid = myOrganizationDao.create(o).getId().toUnqualifiedVersionless(); - + Patient p = new Patient(); p.setManagingOrganization(new Reference(oid)); IIdType pid = myPatientDao.create(p).getId().toUnqualifiedVersionless(); - + try { myOrganizationDao.delete(oid); fail(); @@ -81,19 +80,19 @@ public class FhirResourceDaoR4ReferentialIntegrityTest extends BaseJpaR4Test { @Test public void testDeleteAllow() throws Exception { myDaoConfig.setEnforceReferentialIntegrityOnDelete(false); - + Organization o = new Organization(); o.setName("FOO"); IIdType oid = myOrganizationDao.create(o).getId().toUnqualifiedVersionless(); - + Patient p = new Patient(); p.setManagingOrganization(new Reference(oid)); IIdType pid = myPatientDao.create(p).getId().toUnqualifiedVersionless(); - + myOrganizationDao.delete(oid); myPatientDao.delete(pid); } - + } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceR4Test.java new file mode 100644 index 00000000000..31a6287179b --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceR4Test.java @@ -0,0 +1,186 @@ +package ca.uhn.fhir.jpa.delete; + +import ca.uhn.fhir.interceptor.api.Hook; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; +import ca.uhn.fhir.jpa.util.DeleteConflict; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Reference; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Iterator; +import java.util.function.Function; + +import static org.junit.Assert.*; + +public class DeleteConflictServiceR4Test extends BaseJpaR4Test { + private static final Logger ourLog = LoggerFactory.getLogger(DeleteConflictServiceR4Test.class); + + private DeleteConflictInterceptor myDeleteInterceptor = new DeleteConflictInterceptor(); + private int myInterceptorDeleteCount; + + @Before + public void beforeRegisterInterceptor() { + myInterceptorRegistry.registerInterceptor(myDeleteInterceptor); + myInterceptorDeleteCount = 0; + myDeleteInterceptor.clear(); + } + + @After + public void afterUnregisterInterceptor() { + myInterceptorRegistry.unregisterAllInterceptors(); + } + + @Test + public void testDeleteFailCallsHook() throws Exception { + Organization organization = new Organization(); + organization.setName("FOO"); + IIdType organizationId = myOrganizationDao.create(organization).getId().toUnqualifiedVersionless(); + + Patient patient = new Patient(); + patient.setManagingOrganization(new Reference(organizationId)); + IIdType patientId = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + myDeleteInterceptor.deleteConflictFunction = list -> false; + try { + myOrganizationDao.delete(organizationId); + fail(); + } catch (ResourceVersionConflictException e) { + assertEquals("Unable to delete Organization/" + organizationId.getIdPart() + " because at least one resource has a reference to this resource. First reference found was resource Patient/" + patientId.getIdPart() + " in path Patient.managingOrganization", e.getMessage()); + } + assertEquals(1, myDeleteInterceptor.myDeleteConflictList.size()); + assertEquals(1, myDeleteInterceptor.myCallCount); + assertEquals(0, myInterceptorDeleteCount); + myPatientDao.delete(patientId); + myOrganizationDao.delete(organizationId); + } + + @Test + public void testDeleteHookDeletesConflict() throws Exception { + Organization organization = new Organization(); + organization.setName("FOO"); + IIdType organizationId = myOrganizationDao.create(organization).getId().toUnqualifiedVersionless(); + + Patient patient = new Patient(); + patient.setManagingOrganization(new Reference(organizationId)); + myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + myDeleteInterceptor.deleteConflictFunction = this::deleteConflicts; + myOrganizationDao.delete(organizationId); + + assertNotNull(myDeleteInterceptor.myDeleteConflictList); + assertEquals(1, myDeleteInterceptor.myCallCount); + assertEquals(1, myInterceptorDeleteCount); + } + + @Test + public void testDeleteHookDeletesTwoConflicts() throws Exception { + Organization organization = new Organization(); + organization.setName("FOO"); + IIdType organizationId = myOrganizationDao.create(organization).getId().toUnqualifiedVersionless(); + + Patient patient = new Patient(); + patient.setManagingOrganization(new Reference(organizationId)); + myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + patient = new Patient(); + patient.setManagingOrganization(new Reference(organizationId)); + myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + myDeleteInterceptor.deleteConflictFunction = this::deleteConflicts; + myOrganizationDao.delete(organizationId); + + assertNotNull(myDeleteInterceptor.myDeleteConflictList); + assertEquals(2, myDeleteInterceptor.myCallCount); + assertEquals(2, myInterceptorDeleteCount); + } + + @Test + public void testDeleteHookDeletesThreeConflicts() throws Exception { + Organization organization = new Organization(); + organization.setName("FOO"); + IIdType organizationId = myOrganizationDao.create(organization).getId().toUnqualifiedVersionless(); + + Patient patient = new Patient(); + patient.setManagingOrganization(new Reference(organizationId)); + myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + patient = new Patient(); + patient.setManagingOrganization(new Reference(organizationId)); + myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + patient = new Patient(); + patient.setManagingOrganization(new Reference(organizationId)); + myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + myDeleteInterceptor.deleteConflictFunction = this::deleteConflicts; + myOrganizationDao.delete(organizationId); + + assertNotNull(myDeleteInterceptor.myDeleteConflictList); + assertEquals(2, myDeleteInterceptor.myCallCount); + assertEquals(3, myInterceptorDeleteCount); + } + + @Test + public void testBadInterceptorNoInfiniteLoop() throws Exception { + Organization organization = new Organization(); + organization.setName("FOO"); + IIdType organizationId = myOrganizationDao.create(organization).getId().toUnqualifiedVersionless(); + + Patient patient = new Patient(); + patient.setManagingOrganization(new Reference(organizationId)); + IIdType patientId = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + + // Always returning true is bad behaviour. Our infinite loop checker should halt it + myDeleteInterceptor.deleteConflictFunction = list -> true; + + try { + myOrganizationDao.delete(organizationId); + fail(); + } catch (ResourceVersionConflictException e) { + assertEquals("Unable to delete Organization/" + organizationId.getIdPart() + " because at least one resource has a reference to this resource. First reference found was resource Patient/" + patientId.getIdPart() + " in path Patient.managingOrganization", e.getMessage()); + } + assertEquals(1 + DeleteConflictService.MAX_RETRY_ATTEMPTS, myDeleteInterceptor.myCallCount); + } + + private boolean deleteConflicts(DeleteConflictList theList) { + Iterator iterator = theList.iterator(); + while (iterator.hasNext()) { + DeleteConflict next = iterator.next(); + IdDt source = next.getSourceId(); + if ("Patient".equals(source.getResourceType())) { + ourLog.info("Deleting {}", source); + myPatientDao.delete(source); + ++myInterceptorDeleteCount; + } + } + return myInterceptorDeleteCount > 0; + } + + private static class DeleteConflictInterceptor { + int myCallCount; + DeleteConflictList myDeleteConflictList; + Function deleteConflictFunction; + + @Hook(Pointcut.STORAGE_PRESTORAGE_DELETE_CONFLICTS) + public boolean deleteConflicts(DeleteConflictList theDeleteConflictList) { + ++myCallCount; + myDeleteConflictList = theDeleteConflictList; + return deleteConflictFunction.apply(theDeleteConflictList); + } + + public void clear() { + myDeleteConflictList = null; + myCallCount = 0; + } + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceTest.java new file mode 100644 index 00000000000..5dd81d63dbc --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceTest.java @@ -0,0 +1,64 @@ +package ca.uhn.fhir.jpa.delete; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao; +import ca.uhn.fhir.jpa.model.entity.ResourceLink; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; + +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = {DeleteConflictServiceTest.SpringConfig.class}) +public class DeleteConflictServiceTest { + private static final Logger ourLog = LoggerFactory.getLogger(DeleteConflictServiceTest.class); + + @MockBean + private DeleteConflictFinderService myDeleteConflictFinderService; + @MockBean + private IResourceLinkDao myResourceLinkDao; + @MockBean + private FhirContext myFhirContext; + @MockBean + private IInterceptorBroadcaster myInterceptorBroadcaster; + + @Autowired + private DeleteConflictService myDeleteConflictService; + + static class SpringConfig { + @Bean + DeleteConflictService myDeleteConflictService() { return new DeleteConflictService(); } + @Bean + DaoConfig myDaoConfig() { return new DaoConfig(); } + } + + @Test + public void noInterceptorTwoConflictsDoesntRetry() { + ResourceTable entity = new ResourceTable(); + DeleteConflictList deleteConflicts = new DeleteConflictList(); + + List list = new ArrayList<>(); + ResourceLink link = new ResourceLink(); + link.setSourceResource(entity); + list.add(link); + when(myDeleteConflictFinderService.findConflicts(any(), anyInt())).thenReturn(list); + int retryCount = myDeleteConflictService.validateOkToDelete(deleteConflicts, entity, false); + assertEquals(0, retryCount); + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDaoTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/searchparam/MatchUrlServiceTest.java similarity index 77% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDaoTest.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/searchparam/MatchUrlServiceTest.java index 9415641cca3..803c47e11e6 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDaoTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/searchparam/MatchUrlServiceTest.java @@ -1,16 +1,11 @@ -package ca.uhn.fhir.jpa.dao; +package ca.uhn.fhir.jpa.searchparam; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.jpa.config.TestR4Config; -import ca.uhn.fhir.jpa.model.util.StringNormalizer; -import ca.uhn.fhir.jpa.searchparam.MatchUrlService; +import ca.uhn.fhir.jpa.dao.BaseJpaTest; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.model.dstu2.composite.PeriodDt; import ca.uhn.fhir.model.dstu2.resource.Condition; -import ca.uhn.fhir.model.dstu2.resource.Observation; -import ca.uhn.fhir.model.primitive.DateTimeDt; import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.util.TestUtil; import org.junit.AfterClass; @@ -29,7 +24,7 @@ import static org.mockito.Mockito.when; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {TestR4Config.class}) -public class BaseHapiFhirDaoTest extends BaseJpaTest { +public class MatchUrlServiceTest extends BaseJpaTest { private static FhirContext ourCtx = FhirContext.forDstu2(); @@ -50,14 +45,6 @@ public class BaseHapiFhirDaoTest extends BaseJpaTest { assertEquals("2011-01-01T11:12:21.0000Z", match.getLastUpdated().getLowerBound().getValueAsString()); assertEquals(ReferenceParam.class, match.get("patient").get(0).get(0).getClass()); assertEquals("304", ((ReferenceParam)match.get("patient").get(0).get(0)).getIdPart()); - - Observation observation = new Observation(); - - PeriodDt period = new PeriodDt(); - period.setStart(new DateTimeDt("2011-01-02T11:22:33Z")); - period.setEnd(new DateTimeDt("2011-01-02T11:33:33Z")); - observation.setEffective(period); - } @Override diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java index 5cf57f91adb..1d5c6249339 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java @@ -4,6 +4,8 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.config.TestR4Config; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; +import ca.uhn.fhir.jpa.searchparam.matcher.SearchParamMatcher; import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; @@ -31,6 +33,8 @@ import static org.junit.Assert.*; public class InMemorySubscriptionMatcherR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(InMemorySubscriptionMatcherR4Test.class); + @Autowired + SearchParamMatcher mySearchParamMatcher; @Autowired InMemorySubscriptionMatcher myInMemorySubscriptionMatcher; @Autowired @@ -39,20 +43,20 @@ public class InMemorySubscriptionMatcherR4Test { FhirContext myContext; private void assertMatched(Resource resource, SearchParameterMap params) { - SubscriptionMatchResult result = match(resource, params); + InMemoryMatchResult result = match(resource, params); assertTrue(result.getUnsupportedReason(), result.supported()); assertTrue(result.matched()); assertEquals(SubscriptionMatchingStrategy.IN_MEMORY, mySubscriptionStrategyEvaluator.determineStrategy(getCriteria(resource, params))); } private void assertNotMatched(Resource resource, SearchParameterMap params) { - SubscriptionMatchResult result = match(resource, params); + InMemoryMatchResult result = match(resource, params); assertTrue(result.getUnsupportedReason(), result.supported()); assertFalse(result.matched()); assertEquals(SubscriptionMatchingStrategy.IN_MEMORY, mySubscriptionStrategyEvaluator.determineStrategy(getCriteria(resource, params))); } - private SubscriptionMatchResult match(Resource theResource, SearchParameterMap theParams) { + private InMemoryMatchResult match(Resource theResource, SearchParameterMap theParams) { return match(getCriteria(theResource, theParams), theResource); } @@ -60,13 +64,13 @@ public class InMemorySubscriptionMatcherR4Test { return theResource.getResourceType().name() + theParams.toNormalizedQueryString(myContext); } - private SubscriptionMatchResult match(String criteria, Resource theResource) { + private InMemoryMatchResult match(String criteria, Resource theResource) { ourLog.info("Criteria: <{}>", criteria); - return myInMemorySubscriptionMatcher.match(criteria, theResource); + return mySearchParamMatcher.match(criteria, theResource); } private void assertUnsupported(Resource resource, SearchParameterMap theParams) { - SubscriptionMatchResult result = match(resource, theParams); + InMemoryMatchResult result = match(resource, theParams); assertFalse(result.supported()); assertEquals(SubscriptionMatchingStrategy.DATABASE, mySubscriptionStrategyEvaluator.determineStrategy(getCriteria(resource, theParams))); } @@ -397,7 +401,7 @@ public class InMemorySubscriptionMatcherR4Test { ResourceModifiedMessage msg = new ResourceModifiedMessage(myContext, patient, ResourceModifiedMessage.OperationTypeEnum.CREATE); msg.setSubscriptionId("Subscription/123"); msg.setId(new IdType("Patient/ABC")); - SubscriptionMatchResult result = myInMemorySubscriptionMatcher.match(subscription, msg); + InMemoryMatchResult result = myInMemorySubscriptionMatcher.match(subscription, msg); fail(); } catch (AssertionError e){ assertEquals("Reference at managingOrganization is invalid: urn:uuid:13720262-b392-465f-913e-54fb198ff954", e.getMessage()); diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyComposition.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyComposition.java new file mode 100644 index 00000000000..b588e7dbe1a --- /dev/null +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyComposition.java @@ -0,0 +1,215 @@ +package ca.uhn.fhir.jpa.model.any; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import java.util.List; + +public class AnyComposition { + private final FhirVersionEnum myFhirVersion; + private final IBaseResource myComposition; + + public static AnyComposition fromFhirContext(FhirContext theFhirContext) { + FhirVersionEnum version = theFhirContext.getVersion().getVersion(); + switch (version) { + case DSTU3: + return new AnyComposition(new org.hl7.fhir.dstu3.model.Composition()); + case R4: + return new AnyComposition(new org.hl7.fhir.r4.model.Composition()); + default: + throw new UnsupportedOperationException(version + " not supported"); + } + } + + public AnyComposition(org.hl7.fhir.dstu3.model.Composition theCompositionR3) { + myFhirVersion = FhirVersionEnum.DSTU3; + myComposition = theCompositionR3; + } + + public AnyComposition(org.hl7.fhir.r4.model.Composition theCompositionR4) { + myFhirVersion = FhirVersionEnum.R4; + myComposition = theCompositionR4; + } + + public static AnyComposition fromResource(IBaseResource theComposition) { + if (theComposition instanceof org.hl7.fhir.dstu3.model.Composition) { + return new AnyComposition((org.hl7.fhir.dstu3.model.Composition) theComposition); + } else if (theComposition instanceof org.hl7.fhir.r4.model.Composition) { + return new AnyComposition((org.hl7.fhir.r4.model.Composition) theComposition); + } else { + throw new UnsupportedOperationException("Cannot convert " + theComposition.getClass().getName() + " to AnyList"); + } + } + + public IBaseResource get() { + return myComposition; + } + + public org.hl7.fhir.dstu3.model.Composition getDstu3() { + Validate.isTrue(myFhirVersion == FhirVersionEnum.DSTU3); + return (org.hl7.fhir.dstu3.model.Composition) get(); + } + + public org.hl7.fhir.r4.model.Composition getR4() { + Validate.isTrue(myFhirVersion == FhirVersionEnum.R4); + return (org.hl7.fhir.r4.model.Composition) get(); + } + + public void setIdentifier(String theSystem, String theValue) { + switch (myFhirVersion) { + case DSTU3: + getDstu3().setIdentifier(new org.hl7.fhir.dstu3.model.Identifier().setSystem(theSystem).setValue(theValue)); + break; + case R4: + getR4().setIdentifier(new org.hl7.fhir.r4.model.Identifier().setSystem(theSystem).setValue(theValue)); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public String getIdentifier() { + switch (myFhirVersion) { + case DSTU3: + return getDstu3().getIdentifier().getValue(); + case R4: + return getR4().getIdentifier().getValue(); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public void setClass(String theSystem, String theCode) { + switch (myFhirVersion) { + case DSTU3: + setClassDstu3(theSystem, theCode); + break; + case R4: + setClassR4(theSystem, theCode); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + private void setClassDstu3(String theSystem, String theCode) { + org.hl7.fhir.dstu3.model.CodeableConcept codeableConcept = new org.hl7.fhir.dstu3.model.CodeableConcept(); + codeableConcept.addCoding().setSystem(theSystem).setCode(theCode); + getDstu3().setClass_(codeableConcept); + } + + private void setClassR4(String theSystem, String theCode) { + org.hl7.fhir.r4.model.CodeableConcept codeableConcept = new org.hl7.fhir.r4.model.CodeableConcept(); + codeableConcept.addCoding().setSystem(theSystem).setCode(theCode); + getR4().addCategory(codeableConcept); + } + + public void addStringExtension(String theUrl, String theValue) { + switch (myFhirVersion) { + case DSTU3: + getDstu3().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.dstu3.model.StringType(theValue)); + break; + case R4: + getR4().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.r4.model.StringType(theValue)); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + // TODO KHS Consolidate with other classes in this package + public String getStringExtensionValueOrNull(String theUrl) { + switch (myFhirVersion) { + case DSTU3: + return getStringExtensionValueOrNullDstu3(theUrl); + case R4: + return getStringExtensionValueOrNullR4(theUrl); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + private String getStringExtensionValueOrNullDstu3(String theUrl) { + List targetTypes = getDstu3().getExtensionsByUrl(theUrl); + if (targetTypes.size() < 1) { + return null; + } + org.hl7.fhir.dstu3.model.StringType targetType = (org.hl7.fhir.dstu3.model.StringType) targetTypes.get(0).getValue(); + return targetType.getValue(); + } + + private String getStringExtensionValueOrNullR4(String theUrl) { + List targetTypes = getR4().getExtensionsByUrl(theUrl); + if (targetTypes.size() < 1) { + return null; + } + org.hl7.fhir.r4.model.StringType targetType = (org.hl7.fhir.r4.model.StringType) targetTypes.get(0).getValue(); + return targetType.getValue(); + } + + public void setSubject(String theReferenceId) { + switch (myFhirVersion) { + case DSTU3: + getDstu3().setSubject(new org.hl7.fhir.dstu3.model.Reference(theReferenceId)); + break; + case R4: + getR4().setSubject(new org.hl7.fhir.r4.model.Reference(theReferenceId)); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public void setTitle(String theTitle) { + switch (myFhirVersion) { + case DSTU3: + getDstu3().setTitle(theTitle); + break; + case R4: + getR4().setTitle(theTitle); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public String getTitle() { + switch (myFhirVersion) { + case DSTU3: + return getDstu3().getTitle(); + case R4: + return getR4().getTitle(); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public void addEntry(String theReferenceId) { + switch (myFhirVersion) { + case DSTU3: + getDstu3().getSectionFirstRep().addEntry(new org.hl7.fhir.dstu3.model.Reference(theReferenceId)); + break; + case R4: + getR4().getSectionFirstRep().addEntry(new org.hl7.fhir.r4.model.Reference(theReferenceId)); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public void setRandomUuid() { + switch (myFhirVersion) { + case DSTU3: + getDstu3().setId(org.hl7.fhir.dstu3.model.IdType.newRandomUuid()); + break; + case R4: + getR4().setId(org.hl7.fhir.r4.model.IdType.newRandomUuid()); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + + } +} diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyListResource.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyListResource.java new file mode 100644 index 00000000000..f33279747bc --- /dev/null +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyListResource.java @@ -0,0 +1,252 @@ +package ca.uhn.fhir.jpa.model.any; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.rest.param.TokenParam; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.IBaseReference; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import java.util.List; +import java.util.stream.Stream; + +public class AnyListResource { + private final FhirVersionEnum myFhirVersion; + private final IBaseResource myListResource; + + public static AnyListResource fromFhirContext(FhirContext theFhirContext) { + FhirVersionEnum version = theFhirContext.getVersion().getVersion(); + switch (version) { + case DSTU2: + return new AnyListResource(new ca.uhn.fhir.model.dstu2.resource.ListResource()); + case DSTU3: + return new AnyListResource(new org.hl7.fhir.dstu3.model.ListResource()); + case R4: + return new AnyListResource(new org.hl7.fhir.r4.model.ListResource()); + default: + throw new UnsupportedOperationException(version + " not supported"); + } + } + + public AnyListResource(ca.uhn.fhir.model.dstu2.resource.ListResource theListResourceR2) { + myFhirVersion = FhirVersionEnum.DSTU2; + myListResource = theListResourceR2; + } + + public AnyListResource(org.hl7.fhir.dstu3.model.ListResource theListResourceR3) { + myFhirVersion = FhirVersionEnum.DSTU3; + myListResource = theListResourceR3; + } + + public AnyListResource(org.hl7.fhir.r4.model.ListResource theListResourceR4) { + myFhirVersion = FhirVersionEnum.R4; + myListResource = theListResourceR4; + } + + public static AnyListResource fromResource(IBaseResource theListResource) { + if (theListResource instanceof ca.uhn.fhir.model.dstu2.resource.ListResource) { + return new AnyListResource((ca.uhn.fhir.model.dstu2.resource.ListResource) theListResource); + } else if (theListResource instanceof org.hl7.fhir.dstu3.model.ListResource) { + return new AnyListResource((org.hl7.fhir.dstu3.model.ListResource) theListResource); + } else if (theListResource instanceof org.hl7.fhir.r4.model.ListResource) { + return new AnyListResource((org.hl7.fhir.r4.model.ListResource) theListResource); + } else { + throw new UnsupportedOperationException("Cannot convert " + theListResource.getClass().getName() + " to AnyList"); + } + } + + public IBaseResource get() { + return myListResource; + } + + public ca.uhn.fhir.model.dstu2.resource.ListResource getDstu2() { + Validate.isTrue(myFhirVersion == FhirVersionEnum.DSTU2); + return (ca.uhn.fhir.model.dstu2.resource.ListResource) get(); + } + + public org.hl7.fhir.dstu3.model.ListResource getDstu3() { + Validate.isTrue(myFhirVersion == FhirVersionEnum.DSTU3); + return (org.hl7.fhir.dstu3.model.ListResource) get(); + } + + public org.hl7.fhir.r4.model.ListResource getR4() { + Validate.isTrue(myFhirVersion == FhirVersionEnum.R4); + return (org.hl7.fhir.r4.model.ListResource) get(); + } + + public void addCode(String theSystem, String theCode) { + switch (myFhirVersion) { + case DSTU3: + getDstu3().getCode().addCoding().setSystem(theSystem).setCode(theCode); + break; + case R4: + getR4().getCode().addCoding().setSystem(theSystem).setCode(theCode); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public void addIdentifier(String theSystem, String theValue) { + switch (myFhirVersion) { + case DSTU3: + getDstu3().getIdentifier().add(new org.hl7.fhir.dstu3.model.Identifier().setSystem(theSystem).setValue(theValue)); + break; + case R4: + getR4().getIdentifier().add(new org.hl7.fhir.r4.model.Identifier().setSystem(theSystem).setValue(theValue)); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public void addStringExtension(String theUrl, String theValue) { + switch (myFhirVersion) { + case DSTU3: + getDstu3().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.dstu3.model.StringType(theValue)); + break; + case R4: + getR4().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.r4.model.StringType(theValue)); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public String getStringExtensionValueOrNull(String theUrl) { + switch (myFhirVersion) { + case DSTU3: + return getStringExtensionValueOrNullDstu3(theUrl); + case R4: + return getStringExtensionValueOrNullR4(theUrl); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + private String getStringExtensionValueOrNullDstu3(String theUrl) { + List targetTypes = getDstu3().getExtensionsByUrl(theUrl); + if (targetTypes.size() < 1) { + return null; + } + org.hl7.fhir.dstu3.model.StringType targetType = (org.hl7.fhir.dstu3.model.StringType) targetTypes.get(0).getValue(); + return targetType.getValue(); + } + + private String getStringExtensionValueOrNullR4(String theUrl) { + List targetTypes = getR4().getExtensionsByUrl(theUrl); + if (targetTypes.size() < 1) { + return null; + } + org.hl7.fhir.r4.model.StringType targetType = (org.hl7.fhir.r4.model.StringType) targetTypes.get(0).getValue(); + return targetType.getValue(); + } + + public void addReference(IBaseReference theReference) { + switch (myFhirVersion) { + case DSTU3: + getDstu3().addEntry().setItem((org.hl7.fhir.dstu3.model.Reference) theReference); + break; + case R4: + getR4().addEntry().setItem((org.hl7.fhir.r4.model.Reference) theReference); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public void addReference(String theReferenceId) { + switch (myFhirVersion) { + case DSTU3: + getDstu3().addEntry().setItem(new org.hl7.fhir.dstu3.model.Reference(theReferenceId)); + break; + case R4: + getR4().addEntry().setItem(new org.hl7.fhir.r4.model.Reference(theReferenceId)); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public Stream getReferenceStream() { + switch (myFhirVersion) { + case DSTU3: + return getDstu3().getEntry().stream() + .map(entry -> entry.getItem().getReference()) + .map(reference -> new org.hl7.fhir.dstu3.model.IdType(reference).toUnqualifiedVersionless().getValue()); + case R4: + return getR4().getEntry().stream() + .map(entry -> entry.getItem().getReference()) + .map(reference -> new org.hl7.fhir.r4.model.IdType(reference).toUnqualifiedVersionless().getValue()); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public boolean removeItem(String theReferenceId) { + switch (myFhirVersion) { + case DSTU3: + return removeItemDstu3(theReferenceId); + case R4: + return removeItemR4(theReferenceId); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + private boolean removeItemDstu3(String theReferenceId) { + boolean removed = false; + for (org.hl7.fhir.dstu3.model.ListResource.ListEntryComponent entry : getDstu3().getEntry()) { + if (theReferenceId.equals(entry.getItem().getReference()) && !entry.getDeleted()) { + entry.setDeleted(true); + removed = true; + break; + } + } + + if (removed) { + getDstu3().getEntry().removeIf(entry -> entry.getDeleted()); + } + return removed; + } + + private boolean removeItemR4(String theReferenceId) { + boolean removed = false; + for (org.hl7.fhir.r4.model.ListResource.ListEntryComponent entry : getR4().getEntry()) { + if (theReferenceId.equals(entry.getItem().getReference()) && !entry.getDeleted()) { + entry.setDeleted(true); + removed = true; + break; + } + } + + if (removed) { + getR4().getEntry().removeIf(entry -> entry.getDeleted()); + } + return removed; + } + + public TokenParam getCodeFirstRep() { + switch (myFhirVersion) { + case DSTU3: + org.hl7.fhir.dstu3.model.Coding codingDstu3 = getDstu3().getCode().getCodingFirstRep(); + return new TokenParam(codingDstu3.getSystem(), codingDstu3.getCode()); + case R4: + org.hl7.fhir.r4.model.Coding codingR4 = getR4().getCode().getCodingFirstRep(); + return new TokenParam(codingR4.getSystem(), codingR4.getCode()); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public boolean isEmpty() { + switch (myFhirVersion) { + case DSTU3: + return getDstu3().getEntry().isEmpty(); + case R4: + return getR4().getEntry().isEmpty(); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } +} diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyMeasure.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyMeasure.java new file mode 100644 index 00000000000..79fd65d4162 --- /dev/null +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/any/AnyMeasure.java @@ -0,0 +1,431 @@ +package ca.uhn.fhir.jpa.model.any; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.rest.param.TokenParam; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.IBaseReference; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class AnyMeasure { + private final FhirVersionEnum myFhirVersion; + private final IBaseResource myMeasure; + + public static AnyMeasure fromFhirContext(FhirContext theFhirContext) { + FhirVersionEnum version = theFhirContext.getVersion().getVersion(); + switch (version) { + case DSTU3: + return new AnyMeasure(new org.hl7.fhir.dstu3.model.Measure()); + case R4: + return new AnyMeasure(new org.hl7.fhir.r4.model.Measure()); + default: + throw new UnsupportedOperationException(version + " not supported"); + } + } + + public AnyMeasure(org.hl7.fhir.dstu3.model.Measure theMeasureR3) { + myFhirVersion = FhirVersionEnum.DSTU3; + myMeasure = theMeasureR3; + } + + public AnyMeasure(org.hl7.fhir.r4.model.Measure theMeasureR4) { + myFhirVersion = FhirVersionEnum.R4; + myMeasure = theMeasureR4; + } + + public static AnyMeasure fromResource(IBaseResource theMeasure) { + if (theMeasure instanceof org.hl7.fhir.dstu3.model.Measure) { + return new AnyMeasure((org.hl7.fhir.dstu3.model.Measure) theMeasure); + } else if (theMeasure instanceof org.hl7.fhir.r4.model.Measure) { + return new AnyMeasure((org.hl7.fhir.r4.model.Measure) theMeasure); + } else { + throw new UnsupportedOperationException("Cannot convert " + theMeasure.getClass().getName() + " to AnyList"); + } + } + + public IBaseResource get() { + return myMeasure; + } + + public org.hl7.fhir.dstu3.model.Measure getDstu3() { + Validate.isTrue(myFhirVersion == FhirVersionEnum.DSTU3); + return (org.hl7.fhir.dstu3.model.Measure) get(); + } + + public org.hl7.fhir.r4.model.Measure getR4() { + Validate.isTrue(myFhirVersion == FhirVersionEnum.R4); + return (org.hl7.fhir.r4.model.Measure) get(); + } + + public void addIdentifier(String theSystem, String theValue) { + switch (myFhirVersion) { + case DSTU3: + getDstu3().getIdentifier().add(new org.hl7.fhir.dstu3.model.Identifier().setSystem(theSystem).setValue(theValue)); + break; + case R4: + getR4().getIdentifier().add(new org.hl7.fhir.r4.model.Identifier().setSystem(theSystem).setValue(theValue)); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public void addType(String theSystem, String theCode) { + switch (myFhirVersion) { + case DSTU3: + org.hl7.fhir.dstu3.model.CodeableConcept codeableConcept = new org.hl7.fhir.dstu3.model.CodeableConcept(); + codeableConcept.addCoding().setSystem(theSystem).setCode(theCode); + getDstu3().getType().add(codeableConcept); + break; + case R4: + org.hl7.fhir.r4.model.CodeableConcept codeableConceptR4 = new org.hl7.fhir.r4.model.CodeableConcept(); + codeableConceptR4.addCoding().setSystem(theSystem).setCode(theCode); + getR4().getType().add(codeableConceptR4); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public void addStringExtension(String theUrl, String theValue) { + switch (myFhirVersion) { + case DSTU3: + getDstu3().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.dstu3.model.StringType(theValue)); + break; + case R4: + getR4().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.r4.model.StringType(theValue)); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public String getStringExtensionValueOrNull(String theUrl) { + switch (myFhirVersion) { + case DSTU3: + return getStringExtensionValueOrNullDstu3(theUrl); + case R4: + return getStringExtensionValueOrNullR4(theUrl); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + private String getStringExtensionValueOrNullDstu3(String theUrl) { + List targetTypes = getDstu3().getExtensionsByUrl(theUrl); + if (targetTypes.size() < 1) { + return null; + } + org.hl7.fhir.dstu3.model.StringType targetType = (org.hl7.fhir.dstu3.model.StringType) targetTypes.get(0).getValue(); + return targetType.getValue(); + } + + private String getStringExtensionValueOrNullR4(String theUrl) { + List targetTypes = getR4().getExtensionsByUrl(theUrl); + if (targetTypes.size() < 1) { + return null; + } + org.hl7.fhir.r4.model.StringType targetType = (org.hl7.fhir.r4.model.StringType) targetTypes.get(0).getValue(); + return targetType.getValue(); + } + + public String getIdentifierFirstRep() { + switch (myFhirVersion) { + case DSTU3: + return getDstu3().getIdentifierFirstRep().getValue(); + case R4: + return getR4().getIdentifierFirstRep().getValue(); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public void setComposedOf(String theReferenceId) { + switch (myFhirVersion) { + case DSTU3: + getRelatedArtifactDstu3(theReferenceId, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.COMPOSEDOF); + break; + case R4: + getRelatedArtifactR4(theReferenceId, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.COMPOSEDOF); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + private void getRelatedArtifactDstu3(String theReferenceId, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType theArtifactType) { + org.hl7.fhir.dstu3.model.RelatedArtifact artifact = new org.hl7.fhir.dstu3.model.RelatedArtifact(); + artifact.setType(theArtifactType); + artifact.setResource(new org.hl7.fhir.dstu3.model.Reference(theReferenceId)); + getDstu3().getRelatedArtifact().add(artifact); + } + + private void getRelatedArtifactR4(String theReferenceId, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType theArtifactType) { + org.hl7.fhir.r4.model.RelatedArtifact artifact = new org.hl7.fhir.r4.model.RelatedArtifact(); + artifact.setType(theArtifactType); + artifact.setResource(theReferenceId); + getR4().getRelatedArtifact().add(artifact); + } + + public IBaseReference getComposedOf() { + switch (myFhirVersion) { + case DSTU3: + return getArtifactOfTypeDstu3(getDstu3(), org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.COMPOSEDOF); + case R4: + return getArtifactOfTypeR4(getR4(), org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.COMPOSEDOF); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public void setPredecessor(String theReferenceId) { + switch (myFhirVersion) { + case DSTU3: + getRelatedArtifactDstu3(theReferenceId, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.PREDECESSOR); + break; + case R4: + getRelatedArtifactR4(theReferenceId, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.PREDECESSOR); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + + public IBaseReference getPredecessor() { + switch (myFhirVersion) { + case DSTU3: + return getArtifactOfTypeDstu3(getDstu3(), org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.PREDECESSOR); + case R4: + return getArtifactOfTypeR4(getR4(), org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.PREDECESSOR); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public IBaseReference getDerivedFrom() { + switch (myFhirVersion) { + case DSTU3: + return getArtifactOfTypeDstu3(getDstu3(), org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.DERIVEDFROM); + case R4: + return getArtifactOfTypeR4(getR4(), org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.DERIVEDFROM); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public void setDerivedFrom(String theReferenceId) { + switch (myFhirVersion) { + case DSTU3: + getRelatedArtifactDstu3(theReferenceId, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.DERIVEDFROM); + break; + case R4: + getRelatedArtifactR4(theReferenceId, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.DERIVEDFROM); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public IBaseReference getSuccessor() { + switch (myFhirVersion) { + case DSTU3: + return getArtifactOfTypeDstu3(getDstu3(), org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.SUCCESSOR); + case R4: + return getArtifactOfTypeR4(getR4(), org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.SUCCESSOR); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public void setSuccessor(String theReferenceId) { + switch (myFhirVersion) { + case DSTU3: + getRelatedArtifactDstu3(theReferenceId, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.SUCCESSOR); + break; + case R4: + getRelatedArtifactR4(theReferenceId, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.SUCCESSOR); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + private IBaseReference getArtifactOfTypeDstu3(org.hl7.fhir.dstu3.model.Measure theMeasure, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType theType) { + return theMeasure.getRelatedArtifact() + .stream() + .filter(artifact -> theType == artifact.getType()) + .map(org.hl7.fhir.dstu3.model.RelatedArtifact::getResource) + .findFirst() + .get(); + } + + private IBaseReference getArtifactOfTypeR4(org.hl7.fhir.r4.model.Measure theMeasure, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType theType) { + return new org.hl7.fhir.r4.model.Reference(theMeasure.getRelatedArtifact() + .stream() + .filter(artifact -> theType == artifact.getType()) + .map(org.hl7.fhir.r4.model.RelatedArtifact::getResource) + .findFirst() + .get()); + } + + public void setPublisher(String thePublisher) { + switch (myFhirVersion) { + case DSTU3: + getDstu3().setPublisher(thePublisher); + break; + case R4: + getR4().setPublisher(thePublisher); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public String getPublisher() { + switch (myFhirVersion) { + case DSTU3: + return getDstu3().getPublisher(); + case R4: + return getR4().getPublisher(); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public void setName(String theName) { + switch (myFhirVersion) { + case DSTU3: + getDstu3().setName(theName); + break; + case R4: + getR4().setName(theName); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public String getName() { + switch (myFhirVersion) { + case DSTU3: + return getDstu3().getName(); + case R4: + return getR4().getName(); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public void setEffectivePeriod(Date start, Date end) { + switch (myFhirVersion) { + case DSTU3: + org.hl7.fhir.dstu3.model.Period effectivePeriod = new org.hl7.fhir.dstu3.model.Period(); + effectivePeriod.setStart(start); + effectivePeriod.setEnd(end); + getDstu3().setEffectivePeriod(effectivePeriod); + break; + case R4: + org.hl7.fhir.r4.model.Period effectivePeriodr4 = new org.hl7.fhir.r4.model.Period(); + effectivePeriodr4.setStart(start); + effectivePeriodr4.setEnd(end); + getR4().setEffectivePeriod(effectivePeriodr4); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public Date getEffectivePeriodStart() { + switch (myFhirVersion) { + case DSTU3: + return getDstu3().getEffectivePeriod().getStart(); + case R4: + return getR4().getEffectivePeriod().getStart(); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public Date getEffectivePeriodEnd() { + switch (myFhirVersion) { + case DSTU3: + return getDstu3().getEffectivePeriod().getEnd(); + case R4: + return getR4().getEffectivePeriod().getEnd(); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public void setTopics(List theTokenParamList) { + switch (myFhirVersion) { + case DSTU3: + setTopicsDstu3(theTokenParamList); + break; + case R4: + setTopicsR4(theTokenParamList); + break; + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + private void setTopicsDstu3(List theTokenParamList) { + List topicList = new ArrayList<>(); + + for (TokenParam tokenParam : theTokenParamList) { + org.hl7.fhir.dstu3.model.CodeableConcept codeableConcept = new org.hl7.fhir.dstu3.model.CodeableConcept(); + codeableConcept.addCoding().setSystem(tokenParam.getSystem()).setCode(tokenParam.getValue()); + topicList.add(codeableConcept); + } + getDstu3().setTopic(topicList); + } + + private void setTopicsR4(List theTokenParamList) { + List topicList = new ArrayList<>(); + + for (TokenParam tokenParam : theTokenParamList) { + org.hl7.fhir.r4.model.CodeableConcept codeableConcept = new org.hl7.fhir.r4.model.CodeableConcept(); + codeableConcept.addCoding().setSystem(tokenParam.getSystem()).setCode(tokenParam.getValue()); + topicList.add(codeableConcept); + } + getR4().setTopic(topicList); + } + + public TokenParam getTopicFirstRep() { + switch (myFhirVersion) { + case DSTU3: + org.hl7.fhir.dstu3.model.Coding codingDstu3 = getDstu3().getTopicFirstRep().getCodingFirstRep(); + return new TokenParam(codingDstu3.getSystem(), codingDstu3.getCode()); + case R4: + org.hl7.fhir.r4.model.Coding codingR4 = getR4().getTopicFirstRep().getCodingFirstRep(); + return new TokenParam(codingR4.getSystem(), codingR4.getCode()); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } + + public TokenParam getTopicSecondRepOrNull() { + switch (myFhirVersion) { + case DSTU3: + if (getDstu3().getTopic().size() < 2) { + return null; + } + org.hl7.fhir.dstu3.model.Coding codingDstu3 = getDstu3().getTopic().get(1).getCodingFirstRep(); + return new TokenParam(codingDstu3.getSystem(), codingDstu3.getCode()); + case R4: + if (getR4().getTopic().size() < 2) { + return null; + } + org.hl7.fhir.r4.model.Coding codingR4 = getR4().getTopic().get(1).getCodingFirstRep(); + return new TokenParam(codingR4.getSystem(), codingR4.getCode()); + default: + throw new UnsupportedOperationException(myFhirVersion + " not supported"); + } + } +} 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 new file mode 100644 index 00000000000..47411b73bc9 --- /dev/null +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/BaseSearchParamConfig.java @@ -0,0 +1,12 @@ +package ca.uhn.fhir.jpa.searchparam.config; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +@Configuration +@EnableScheduling +@ComponentScan(basePackages = {"ca.uhn.fhir.jpa.searchparam"}) +abstract public class BaseSearchParamConfig { + +} 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 new file mode 100644 index 00000000000..efed5389fd0 --- /dev/null +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu2Config.java @@ -0,0 +1,42 @@ +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; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +public class SearchParamDstu2Config extends BaseSearchParamConfig { + @Bean + @Primary + public FhirContext fhirContextDstu2() { + FhirContext retVal = FhirContext.forDstu2(); + + // Don't strip versions in some places + ParserOptions parserOptions = retVal.getParserOptions(); + parserOptions.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference"); + + return retVal; + } + + @Bean + public ISearchParamRegistry searchParamRegistry() { + return new SearchParamRegistryDstu2(); + } + + @Bean(autowire = Autowire.BY_TYPE) + public SearchParamExtractorDstu2 searchParamExtractor() { + return new SearchParamExtractorDstu2(); + } + + @Primary + @Bean(autowire = Autowire.BY_NAME, name = "myJpaValidationSupportChainDstu2") + public IValidationSupport validationSupportChainDstu2() { + return new DefaultProfileValidationSupport(); + } +} 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 new file mode 100644 index 00000000000..a3e9d7b6623 --- /dev/null +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamDstu3Config.java @@ -0,0 +1,34 @@ +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; + +public class SearchParamDstu3Config extends BaseSearchParamConfig { + @Bean + @Primary + public FhirContext fhirContextDstu3() { + FhirContext retVal = FhirContext.forDstu3(); + + // Don't strip versions in some places + ParserOptions parserOptions = retVal.getParserOptions(); + parserOptions.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference"); + + 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 new file mode 100644 index 00000000000..a0d75b74e2d --- /dev/null +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamR4Config.java @@ -0,0 +1,42 @@ +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; + +public class SearchParamR4Config extends BaseSearchParamConfig { + @Bean + @Primary + public FhirContext fhirContextR4() { + FhirContext retVal = FhirContext.forR4(); + + // Don't strip versions in some places + ParserOptions parserOptions = retVal.getParserOptions(); + parserOptions.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference"); + + return retVal; + } + + @Bean + public ISearchParamRegistry searchParamRegistry() { + return new SearchParamRegistryR4(); + } + + @Bean(autowire = Autowire.BY_TYPE) + public SearchParamExtractorR4 searchParamExtractor() { + return new SearchParamExtractorR4(); + } + + @Primary + @Bean(autowire = Autowire.BY_NAME, name = "myJpaValidationSupportChainR4") + public IValidationSupport validationSupportChainR4() { + return new DefaultProfileValidationSupport(); + } +} diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java index edda27656ba..5c622379c6f 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java @@ -260,7 +260,7 @@ public final class ResourceIndexedSearchParams { return resourceParams.stream().anyMatch(namedParamPredicate); } - private boolean matchResourceLinks(String theResourceName, String theParamName, IQueryParameterType theParam, String theParamPath) { + public boolean matchResourceLinks(String theResourceName, String theParamName, IQueryParameterType theParam, String theParamPath) { ReferenceParam reference = (ReferenceParam)theParam; Predicate namedParamPredicate = resourceLink -> @@ -278,7 +278,9 @@ public final class ResourceIndexedSearchParams { } else { ForcedId forcedId = target.getForcedId(); if (forcedId != null) { - return forcedId.getForcedId().equals(theReference.getValue()); + // TODO KHS is forcedId.getForcedId().equals(theReference.getIdPart() also valid? + return forcedId.getForcedId().equals(theReference.getValue()) || + forcedId.getForcedId().equals(theReference.getIdPart()); } else { return false; } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionMatchResult.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryMatchResult.java similarity index 71% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionMatchResult.java rename to hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryMatchResult.java index 52237826630..2056eab4a82 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionMatchResult.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryMatchResult.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.matcher; +package ca.uhn.fhir.jpa.searchparam.matcher; /*- * #%L @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.subscription.module.matcher; * #L% */ -public class SubscriptionMatchResult { +public class InMemoryMatchResult { public static final String PARSE_FAIL = "Failed to translate parse query string"; public static final String STANDARD_PARAMETER = "Standard parameters not supported"; public static final String PREFIX = "Prefixes not supported"; @@ -34,34 +34,34 @@ public class SubscriptionMatchResult { private boolean myInMemory = false; - private SubscriptionMatchResult(boolean theMatch) { + private InMemoryMatchResult(boolean theMatch) { this.myMatch = theMatch; this.mySupported = true; this.myUnsupportedParameter = null; this.myUnsupportedReason = null; } - private SubscriptionMatchResult(String theUnsupportedParameter, String theUnsupportedReason) { + private InMemoryMatchResult(String theUnsupportedParameter, String theUnsupportedReason) { this.myMatch = false; this.mySupported = false; this.myUnsupportedParameter = theUnsupportedParameter; this.myUnsupportedReason = theUnsupportedReason; } - public static SubscriptionMatchResult successfulMatch() { - return new SubscriptionMatchResult(true); + public static InMemoryMatchResult successfulMatch() { + return new InMemoryMatchResult(true); } - public static SubscriptionMatchResult fromBoolean(boolean theMatched) { - return new SubscriptionMatchResult(theMatched); + public static InMemoryMatchResult fromBoolean(boolean theMatched) { + return new InMemoryMatchResult(theMatched); } - public static SubscriptionMatchResult unsupportedFromReason(String theUnsupportedReason) { - return new SubscriptionMatchResult(null, theUnsupportedReason); + public static InMemoryMatchResult unsupportedFromReason(String theUnsupportedReason) { + return new InMemoryMatchResult(null, theUnsupportedReason); } - public static SubscriptionMatchResult unsupportedFromParameterAndReason(String theUnsupportedParameter, String theUnsupportedReason) { - return new SubscriptionMatchResult(theUnsupportedParameter, theUnsupportedReason); + public static InMemoryMatchResult unsupportedFromParameterAndReason(String theUnsupportedParameter, String theUnsupportedReason) { + return new InMemoryMatchResult(theUnsupportedParameter, theUnsupportedReason); } public boolean supported() { diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/CriteriaResourceMatcher.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java similarity index 74% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/CriteriaResourceMatcher.java rename to hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java index 1ab7cc72ce8..7314c6540a4 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/CriteriaResourceMatcher.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InMemoryResourceMatcher.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.matcher; +package ca.uhn.fhir.jpa.searchparam.matcher; /*- * #%L @@ -45,7 +45,7 @@ import java.util.Map; import java.util.function.Predicate; @Service -public class CriteriaResourceMatcher { +public class InMemoryResourceMatcher { @Autowired private MatchUrlService myMatchUrlService; @@ -64,7 +64,7 @@ public class CriteriaResourceMatcher { * */ - public SubscriptionMatchResult match(String theCriteria, IBaseResource theResource, ResourceIndexedSearchParams theSearchParams) { + public InMemoryMatchResult match(String theCriteria, IBaseResource theResource, ResourceIndexedSearchParams theSearchParams) { RuntimeResourceDefinition resourceDefinition; if (theResource == null) { resourceDefinition = UrlUtil.parseUrlResourceType(myFhirContext, theCriteria); @@ -75,45 +75,45 @@ public class CriteriaResourceMatcher { try { searchParameterMap = myMatchUrlService.translateMatchUrl(theCriteria, resourceDefinition); } catch (UnsupportedOperationException e) { - return SubscriptionMatchResult.unsupportedFromReason(SubscriptionMatchResult.PARSE_FAIL); + return InMemoryMatchResult.unsupportedFromReason(InMemoryMatchResult.PARSE_FAIL); } searchParameterMap.clean(); if (searchParameterMap.getLastUpdated() != null) { - return SubscriptionMatchResult.unsupportedFromParameterAndReason(Constants.PARAM_LASTUPDATED, SubscriptionMatchResult.STANDARD_PARAMETER); + return InMemoryMatchResult.unsupportedFromParameterAndReason(Constants.PARAM_LASTUPDATED, InMemoryMatchResult.STANDARD_PARAMETER); } for (Map.Entry>> entry : searchParameterMap.entrySet()) { String theParamName = entry.getKey(); List> theAndOrParams = entry.getValue(); - SubscriptionMatchResult result = matchIdsWithAndOr(theParamName, theAndOrParams, resourceDefinition, theResource, theSearchParams); + InMemoryMatchResult result = matchIdsWithAndOr(theParamName, theAndOrParams, resourceDefinition, theResource, theSearchParams); if (!result.matched()){ return result; } } - return SubscriptionMatchResult.successfulMatch(); + return InMemoryMatchResult.successfulMatch(); } // This method is modelled from SearchBuilder.searchForIdsWithAndOr() - private SubscriptionMatchResult matchIdsWithAndOr(String theParamName, List> theAndOrParams, RuntimeResourceDefinition theResourceDefinition, IBaseResource theResource, ResourceIndexedSearchParams theSearchParams) { + private InMemoryMatchResult matchIdsWithAndOr(String theParamName, List> theAndOrParams, RuntimeResourceDefinition theResourceDefinition, IBaseResource theResource, ResourceIndexedSearchParams theSearchParams) { if (theAndOrParams.isEmpty()) { - return SubscriptionMatchResult.successfulMatch(); + return InMemoryMatchResult.successfulMatch(); } if (hasQualifiers(theAndOrParams)) { - return SubscriptionMatchResult.unsupportedFromParameterAndReason(theParamName, SubscriptionMatchResult.STANDARD_PARAMETER); + return InMemoryMatchResult.unsupportedFromParameterAndReason(theParamName, InMemoryMatchResult.STANDARD_PARAMETER); } if (hasPrefixes(theAndOrParams)) { - return SubscriptionMatchResult.unsupportedFromParameterAndReason(theParamName, SubscriptionMatchResult.PREFIX); + return InMemoryMatchResult.unsupportedFromParameterAndReason(theParamName, InMemoryMatchResult.PREFIX); } if (hasChain(theAndOrParams)) { - return SubscriptionMatchResult.unsupportedFromParameterAndReason(theParamName, SubscriptionMatchResult.CHAIN); + return InMemoryMatchResult.unsupportedFromParameterAndReason(theParamName, InMemoryMatchResult.CHAIN); } switch (theParamName) { case IAnyResource.SP_RES_ID: - return SubscriptionMatchResult.fromBoolean(matchIdsAndOr(theAndOrParams, theResource)); + return InMemoryMatchResult.fromBoolean(matchIdsAndOr(theAndOrParams, theResource)); case IAnyResource.SP_RES_LANGUAGE: case Constants.PARAM_HAS: @@ -121,7 +121,7 @@ public class CriteriaResourceMatcher { case Constants.PARAM_PROFILE: case Constants.PARAM_SECURITY: - return SubscriptionMatchResult.unsupportedFromParameterAndReason(theParamName, SubscriptionMatchResult.PARAM); + return InMemoryMatchResult.unsupportedFromParameterAndReason(theParamName, InMemoryMatchResult.PARAM); default: @@ -148,7 +148,7 @@ public class CriteriaResourceMatcher { return theValue.equals(theId.getValue()) || theValue.equals(theId.getIdPart()); } - private SubscriptionMatchResult matchResourceParam(String theParamName, List> theAndOrParams, ResourceIndexedSearchParams theSearchParams, String theResourceName, RuntimeSearchParam theParamDef) { + private InMemoryMatchResult matchResourceParam(String theParamName, List> theAndOrParams, ResourceIndexedSearchParams theSearchParams, String theResourceName, RuntimeSearchParam theParamDef) { if (theParamDef != null) { switch (theParamDef.getParamType()) { case QUANTITY: @@ -159,19 +159,19 @@ public class CriteriaResourceMatcher { case DATE: case REFERENCE: if (theSearchParams == null) { - return SubscriptionMatchResult.successfulMatch(); + return InMemoryMatchResult.successfulMatch(); } else { - return SubscriptionMatchResult.fromBoolean(theAndOrParams.stream().anyMatch(nextAnd -> matchParams(theResourceName, theParamName, theParamDef, nextAnd, theSearchParams))); + return InMemoryMatchResult.fromBoolean(theAndOrParams.stream().anyMatch(nextAnd -> matchParams(theResourceName, theParamName, theParamDef, nextAnd, theSearchParams))); } case COMPOSITE: case HAS: case SPECIAL: default: - return SubscriptionMatchResult.unsupportedFromParameterAndReason(theParamName, SubscriptionMatchResult.PARAM); + return InMemoryMatchResult.unsupportedFromParameterAndReason(theParamName, InMemoryMatchResult.PARAM); } } else { if (Constants.PARAM_CONTENT.equals(theParamName) || Constants.PARAM_TEXT.equals(theParamName)) { - return SubscriptionMatchResult.unsupportedFromParameterAndReason(theParamName, SubscriptionMatchResult.PARAM); + return InMemoryMatchResult.unsupportedFromParameterAndReason(theParamName, InMemoryMatchResult.PARAM); } else { throw new InvalidRequestException("Unknown search parameter " + theParamName + " for resource type " + theResourceName); } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/IndexedSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/IndexedSearchParamExtractor.java new file mode 100644 index 00000000000..6b623faa7e5 --- /dev/null +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/IndexedSearchParamExtractor.java @@ -0,0 +1,32 @@ +package ca.uhn.fhir.jpa.searchparam.matcher; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; +import ca.uhn.fhir.jpa.searchparam.extractor.ResourceLinkExtractor; +import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class IndexedSearchParamExtractor { + @Autowired + private FhirContext myContext; + @Autowired + private SearchParamExtractorService mySearchParamExtractorService; + @Autowired + private ResourceLinkExtractor myResourceLinkExtractor; + @Autowired + private InlineResourceLinkResolver myInlineResourceLinkResolver; + + public ResourceIndexedSearchParams extractIndexedSearchParams(IBaseResource theResource) { + ResourceTable entity = new ResourceTable(); + String resourceType = myContext.getResourceDefinition(theResource).getName(); + entity.setResourceType(resourceType); + ResourceIndexedSearchParams resourceIndexedSearchParams = new ResourceIndexedSearchParams(); + mySearchParamExtractorService.extractFromResource(resourceIndexedSearchParams, entity, theResource); + myResourceLinkExtractor.extractResourceLinks(resourceIndexedSearchParams, entity, theResource, theResource.getMeta().getLastUpdated(), myInlineResourceLinkResolver, false); + return resourceIndexedSearchParams; + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InlineResourceLinkResolver.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InlineResourceLinkResolver.java similarity index 97% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InlineResourceLinkResolver.java rename to hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InlineResourceLinkResolver.java index 545e481a330..9dff05a8c35 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InlineResourceLinkResolver.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/InlineResourceLinkResolver.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.module.matcher; +package ca.uhn.fhir.jpa.searchparam.matcher; /*- * #%L diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/SearchParamMatcher.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/SearchParamMatcher.java new file mode 100644 index 00000000000..329eaf3e25b --- /dev/null +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/SearchParamMatcher.java @@ -0,0 +1,19 @@ +package ca.uhn.fhir.jpa.searchparam.matcher; + +import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class SearchParamMatcher { + @Autowired + private IndexedSearchParamExtractor myIndexedSearchParamExtractor; + @Autowired + private InMemoryResourceMatcher myInMemoryResourceMatcher; + + public InMemoryMatchResult match(String theCriteria, IBaseResource theResource) { + ResourceIndexedSearchParams resourceIndexedSearchParams = myIndexedSearchParamExtractor.extractIndexedSearchParams(theResource); + return myInMemoryResourceMatcher.match(theCriteria, theResource, resourceIndexedSearchParams); + } +} diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/provider/SearchableHashMapResourceProvider.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/provider/SearchableHashMapResourceProvider.java new file mode 100644 index 00000000000..d69e7bfac4d --- /dev/null +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/provider/SearchableHashMapResourceProvider.java @@ -0,0 +1,52 @@ +package ca.uhn.fhir.jpa.searchparam.provider; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; +import ca.uhn.fhir.jpa.searchparam.matcher.SearchParamMatcher; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.provider.HashMapResourceProvider; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class SearchableHashMapResourceProvider extends HashMapResourceProvider { + private final SearchParamMatcher mySearchParamMatcher; + + /** + * Constructor + * + * @param theFhirContext The FHIR context + * @param theResourceType The resource type to support + */ + public SearchableHashMapResourceProvider(FhirContext theFhirContext, Class theResourceType, SearchParamMatcher theSearchParamMatcher) { + super(theFhirContext, theResourceType); + mySearchParamMatcher = theSearchParamMatcher; + } + + public List searchByCriteria(String theCriteria) { + return searchBy(resource -> mySearchParamMatcher.match(theCriteria, resource)); + + } + + public List searchByParams(SearchParameterMap theSearchParams) { + return searchBy(resource -> mySearchParamMatcher.match(theSearchParams.toNormalizedQueryString(getFhirContext()), resource)); + } + + private List searchBy(Function theMatcher) { + List allEResources = searchAll(); + List matches = new ArrayList<>(); + for (T resource : allEResources) { + InMemoryMatchResult result = theMatcher.apply(resource); + if (!result.supported()) { + throw new InvalidRequestException("Search not supported by in-memory matcher: "+result.getUnsupportedReason()); + } + if (result.matched()) { + matches.add(resource); + } + } + return matches; + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/BaseSubscriptionConfig.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/BaseSubscriptionConfig.java index 8faa8321aeb..78869268e8e 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/BaseSubscriptionConfig.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/BaseSubscriptionConfig.java @@ -20,7 +20,6 @@ package ca.uhn.fhir.jpa.subscription.module.config; * #L% */ -import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.executor.InterceptorService; import ca.uhn.fhir.jpa.subscription.module.cache.ISubscribableChannelFactory; import ca.uhn.fhir.jpa.subscription.module.cache.LinkedBlockingQueueSubscribableChannelFactory; @@ -31,10 +30,8 @@ import org.springframework.scheduling.annotation.EnableScheduling; @Configuration @EnableScheduling -@ComponentScan(basePackages = {"ca.uhn.fhir.jpa.searchparam", "ca.uhn.fhir.jpa.subscription.module"}) +@ComponentScan(basePackages = {"ca.uhn.fhir.jpa.subscription.module"}) public abstract class BaseSubscriptionConfig { - public abstract FhirContext fhirContext(); - @Bean public ISubscribableChannelFactory blockingQueueSubscriptionDeliveryChannelFactory() { return new LinkedBlockingQueueSubscribableChannelFactory(); @@ -44,6 +41,4 @@ public abstract class BaseSubscriptionConfig { public InterceptorService interceptorRegistry() { return new InterceptorService("hapi-fhir-jpa-subscription"); } - - } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu2Config.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu2Config.java index c302868b9d0..6b07c4af7d6 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu2Config.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu2Config.java @@ -20,48 +20,9 @@ package ca.uhn.fhir.jpa.subscription.module.config; * #L% */ -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; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; +import ca.uhn.fhir.jpa.searchparam.config.SearchParamDstu3Config; +import org.springframework.context.annotation.Import; +@Import({SearchParamDstu3Config.class}) public class SubscriptionDstu2Config extends BaseSubscriptionConfig { - @Override - public FhirContext fhirContext() { - return fhirContextDstu2(); - } - - @Bean - @Primary - public FhirContext fhirContextDstu2() { - FhirContext retVal = FhirContext.forDstu2(); - - // Don't strip versions in some places - ParserOptions parserOptions = retVal.getParserOptions(); - parserOptions.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference"); - - return retVal; - } - - @Bean - public ISearchParamRegistry searchParamRegistry() { - return new SearchParamRegistryDstu2(); - } - - @Bean(autowire = Autowire.BY_TYPE) - public SearchParamExtractorDstu2 searchParamExtractor() { - return new SearchParamExtractorDstu2(); - } - - @Primary - @Bean(autowire = Autowire.BY_NAME, name = "myJpaValidationSupportChainDstu2") - public IValidationSupport validationSupportChainDstu2() { - return new DefaultProfileValidationSupport(); - } } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu3Config.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu3Config.java index c621a4eac61..893bcac5ab3 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu3Config.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu3Config.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.subscription.module.config; * 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. @@ -20,46 +20,16 @@ package ca.uhn.fhir.jpa.subscription.module.config; * #L% */ -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.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; +import ca.uhn.fhir.jpa.searchparam.config.SearchParamDstu3Config; import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport; +import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; -// From BaseDstu3Config +@Import({SearchParamDstu3Config.class}) public class SubscriptionDstu3Config extends BaseSubscriptionConfig { - @Override - public FhirContext fhirContext() { - return fhirContextDstu3(); - } - - @Bean - @Primary - public FhirContext fhirContextDstu3() { - FhirContext retVal = FhirContext.forDstu3(); - - // Don't strip versions in some places - ParserOptions parserOptions = retVal.getParserOptions(); - parserOptions.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference"); - - return retVal; - } - - @Bean - public ISearchParamRegistry searchParamRegistry() { - return new SearchParamRegistryDstu3(); - } - - @Bean(autowire = Autowire.BY_TYPE) - public SearchParamExtractorDstu3 searchParamExtractor() { - return new SearchParamExtractorDstu3(); - } - @Primary @Bean(autowire = Autowire.BY_NAME, name = "myJpaValidationSupportChainDstu3") public IValidationSupport validationSupportChainDstu3() { diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionR4Config.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionR4Config.java index 11efc5a13dc..5c758a15fd6 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionR4Config.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionR4Config.java @@ -20,48 +20,9 @@ package ca.uhn.fhir.jpa.subscription.module.config; * #L% */ -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; +import ca.uhn.fhir.jpa.searchparam.config.SearchParamR4Config; +import org.springframework.context.annotation.Import; +@Import({SearchParamR4Config.class}) public class SubscriptionR4Config extends BaseSubscriptionConfig { - @Override - public FhirContext fhirContext() { - return fhirContextR4(); - } - - @Bean - @Primary - public FhirContext fhirContextR4() { - FhirContext retVal = FhirContext.forR4(); - - // Don't strip versions in some places - ParserOptions parserOptions = retVal.getParserOptions(); - parserOptions.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference"); - - return retVal; - } - - @Bean - public ISearchParamRegistry searchParamRegistry() { - return new SearchParamRegistryR4(); - } - - @Bean(autowire = Autowire.BY_TYPE) - public SearchParamExtractorR4 searchParamExtractor() { - return new SearchParamExtractorR4(); - } - - @Primary - @Bean(autowire = Autowire.BY_NAME, name = "myJpaValidationSupportChainR4") - public IValidationSupport validationSupportChainR4() { - return new DefaultProfileValidationSupport(); - } } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/interceptor/SubscriptionDebugLogInterceptor.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/interceptor/SubscriptionDebugLogInterceptor.java index 344b342f050..ed940ba8404 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/interceptor/SubscriptionDebugLogInterceptor.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/interceptor/SubscriptionDebugLogInterceptor.java @@ -23,9 +23,9 @@ package ca.uhn.fhir.jpa.subscription.module.interceptor; import ca.uhn.fhir.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.Interceptor; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType; import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; -import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult; import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage; import ca.uhn.fhir.util.StopWatch; import org.slf4j.Logger; @@ -103,7 +103,7 @@ public class SubscriptionDebugLogInterceptor { } @Hook(Pointcut.SUBSCRIPTION_RESOURCE_MATCHED) - public void step30_subscriptionMatched(ResourceDeliveryMessage theMessage, SubscriptionMatchResult theResult) { + public void step30_subscriptionMatched(ResourceDeliveryMessage theMessage, InMemoryMatchResult theResult) { log(EventCodeEnum.SUBS3, "Resource {} matched by subscription {} (memory match={})", theMessage.getPayloadId(), theMessage.getSubscription().getIdElementString(), theResult.isInMemory()); } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/ISubscriptionMatcher.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/ISubscriptionMatcher.java index cf012682bed..409666289f3 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/ISubscriptionMatcher.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/ISubscriptionMatcher.java @@ -20,9 +20,10 @@ package ca.uhn.fhir.jpa.subscription.module.matcher; * #L% */ +import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; public interface ISubscriptionMatcher { - SubscriptionMatchResult match(CanonicalSubscription subscription, ResourceModifiedMessage msg); + InMemoryMatchResult match(CanonicalSubscription subscription, ResourceModifiedMessage msg); } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcher.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcher.java index 13ee5cbbaab..53428468092 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcher.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcher.java @@ -21,14 +21,11 @@ package ca.uhn.fhir.jpa.subscription.module.matcher; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; -import ca.uhn.fhir.jpa.searchparam.extractor.ResourceLinkExtractor; -import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService; +import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; +import ca.uhn.fhir.jpa.searchparam.matcher.SearchParamMatcher; import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -39,31 +36,16 @@ public class InMemorySubscriptionMatcher implements ISubscriptionMatcher { @Autowired private FhirContext myContext; @Autowired - private CriteriaResourceMatcher myCriteriaResourceMatcher; - @Autowired - private SearchParamExtractorService mySearchParamExtractorService; - @Autowired - private ResourceLinkExtractor myResourceLinkExtractor; - @Autowired - private InlineResourceLinkResolver myInlineResourceLinkResolver; + private SearchParamMatcher mySearchParamMatcher; @Override - public SubscriptionMatchResult match(CanonicalSubscription theSubscription, ResourceModifiedMessage theMsg) { + public InMemoryMatchResult match(CanonicalSubscription theSubscription, ResourceModifiedMessage theMsg) { try { - return match(theSubscription.getCriteriaString(), theMsg.getNewPayload(myContext)); + return mySearchParamMatcher.match(theSubscription.getCriteriaString(), theMsg.getNewPayload(myContext)); } catch (Exception e) { ourLog.error("Failure in in-memory matcher", e); throw new InternalErrorException("Failure performing memory-match for resource ID[" + theMsg.getId(myContext) + "] for subscription ID[" + theSubscription.getIdElementString() + "]: " + e.getMessage(), e); } } - SubscriptionMatchResult match(String criteria, IBaseResource resource) { - ResourceTable entity = new ResourceTable(); - String resourceType = myContext.getResourceDefinition(resource).getName(); - entity.setResourceType(resourceType); - ResourceIndexedSearchParams searchParams = new ResourceIndexedSearchParams(); - mySearchParamExtractorService.extractFromResource(searchParams, entity, resource); - myResourceLinkExtractor.extractResourceLinks(searchParams, entity, resource, resource.getMeta().getLastUpdated(), myInlineResourceLinkResolver, false); - return myCriteriaResourceMatcher.match(criteria, resource, searchParams); - } } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionStrategyEvaluator.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionStrategyEvaluator.java index 2e5152db1ae..4dd32f4c1aa 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionStrategyEvaluator.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionStrategyEvaluator.java @@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.subscription.module.matcher; * #L% */ +import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; +import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryResourceMatcher; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -27,10 +29,10 @@ import org.springframework.stereotype.Service; public class SubscriptionStrategyEvaluator { @Autowired - private CriteriaResourceMatcher myCriteriaResourceMatcher; + private InMemoryResourceMatcher myInMemoryResourceMatcher; public SubscriptionMatchingStrategy determineStrategy(String theCriteria) { - SubscriptionMatchResult result = myCriteriaResourceMatcher.match(theCriteria, null, null); + InMemoryMatchResult result = myInMemoryResourceMatcher.match(theCriteria, null, null); if (result.supported()) { return SubscriptionMatchingStrategy.IN_MEMORY; } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionMatchingSubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionMatchingSubscriber.java index 2578b1f66d7..b4fbbd4cccb 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionMatchingSubscriber.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionMatchingSubscriber.java @@ -4,12 +4,12 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher; -import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -124,7 +124,7 @@ public class SubscriptionMatchingSubscriber implements MessageHandler { continue; } - SubscriptionMatchResult matchResult = mySubscriptionMatcher.match(nextActiveSubscription.getSubscription(), theMsg); + InMemoryMatchResult matchResult = mySubscriptionMatcher.match(nextActiveSubscription.getSubscription(), theMsg); if (!matchResult.matched()) { continue; } @@ -148,7 +148,7 @@ public class SubscriptionMatchingSubscriber implements MessageHandler { HookParams params = new HookParams() .add(CanonicalSubscription.class, nextActiveSubscription.getSubscription()) .add(ResourceDeliveryMessage.class, deliveryMsg) - .add(SubscriptionMatchResult.class, matchResult); + .add(InMemoryMatchResult.class, matchResult); if (!myInterceptorBroadcaster.callHooks(Pointcut.SUBSCRIPTION_RESOURCE_MATCHED, params)) { return; } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR3Test.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR3Test.java index 16f9637b51e..a8206b4922b 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR3Test.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR3Test.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.jpa.subscription.module.matcher; +import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; +import ca.uhn.fhir.jpa.searchparam.matcher.SearchParamMatcher; import ca.uhn.fhir.jpa.subscription.module.BaseSubscriptionDstu3Test; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.server.SimpleBundleProvider; @@ -18,15 +20,15 @@ public class InMemorySubscriptionMatcherR3Test extends BaseSubscriptionDstu3Test @Autowired SubscriptionStrategyEvaluator mySubscriptionStrategyEvaluator; @Autowired - InMemorySubscriptionMatcher myInMemorySubscriptionMatcher; + SearchParamMatcher mySearchParamMatcher; private void assertUnsupported(IBaseResource resource, String criteria) { - assertFalse(myInMemorySubscriptionMatcher.match(criteria, resource).supported()); + assertFalse(mySearchParamMatcher.match(criteria, resource).supported()); assertEquals(SubscriptionMatchingStrategy.DATABASE, mySubscriptionStrategyEvaluator.determineStrategy(criteria)); } private void assertMatched(IBaseResource resource, String criteria) { - SubscriptionMatchResult result = myInMemorySubscriptionMatcher.match(criteria, resource); + InMemoryMatchResult result = mySearchParamMatcher.match(criteria, resource); assertTrue(result.supported()); assertTrue(result.matched()); @@ -38,7 +40,7 @@ public class InMemorySubscriptionMatcherR3Test extends BaseSubscriptionDstu3Test } private void assertNotMatched(IBaseResource resource, String criteria, SubscriptionMatchingStrategy theSubscriptionMatchingStrategy) { - SubscriptionMatchResult result = myInMemorySubscriptionMatcher.match(criteria, resource); + InMemoryMatchResult result = mySearchParamMatcher.match(criteria, resource); assertTrue(result.supported()); assertFalse(result.matched()); 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 62f54735eda..926bd1f59e8 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 @@ -11,7 +11,6 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import java.util.Arrays; import java.util.Collections; import static org.junit.Assert.assertEquals; diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/BundleProviderWithNamedPages.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/BundleProviderWithNamedPages.java index af31ece0713..1466f3d4327 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/BundleProviderWithNamedPages.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/BundleProviderWithNamedPages.java @@ -86,7 +86,7 @@ public class BundleProviderWithNamedPages extends SimpleBundleProvider { @Override public List getResources(int theFromIndex, int theToIndex) { - return getList(); // indexes are ignored for this provider type + return (List) getList(); // indexes are ignored for this provider type } @Override diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/SimpleBundleProvider.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/SimpleBundleProvider.java index 5e628df6b43..6f8a40a6359 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/SimpleBundleProvider.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/SimpleBundleProvider.java @@ -31,12 +31,12 @@ import java.util.List; public class SimpleBundleProvider implements IBundleProvider { - private final List myList; + private final List myList; private final String myUuid; private Integer myPreferredPageSize; private Integer mySize; private IPrimitiveType myPublished = InstantDt.withCurrentTime(); - public SimpleBundleProvider(List theList) { + public SimpleBundleProvider(List theList) { this(theList, null); } @@ -51,7 +51,7 @@ public class SimpleBundleProvider implements IBundleProvider { this(Collections.emptyList()); } - public SimpleBundleProvider(List theList, String theUuid) { + public SimpleBundleProvider(List theList, String theUuid) { myList = theList; myUuid = theUuid; setSize(theList.size()); @@ -60,7 +60,7 @@ public class SimpleBundleProvider implements IBundleProvider { /** * Returns the results stored in this provider */ - protected List getList() { + protected List getList() { return myList; } @@ -80,7 +80,7 @@ public class SimpleBundleProvider implements IBundleProvider { @Override public List getResources(int theFromIndex, int theToIndex) { - return myList.subList(theFromIndex, Math.min(theToIndex, myList.size())); + return (List) myList.subList(theFromIndex, Math.min(theToIndex, myList.size())); } @Override diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/HashMapResourceProvider.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/HashMapResourceProvider.java index 51b24d1b76a..ede21bf7fa4 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/HashMapResourceProvider.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/HashMapResourceProvider.java @@ -246,7 +246,9 @@ public class HashMapResourceProvider implements IResour for (TreeMap next : myIdToVersionToResourceMap.values()) { if (next.isEmpty() == false) { T nextResource = next.lastEntry().getValue(); - retVal.add(nextResource); + if (nextResource != null) { + retVal.add(nextResource); + } } } @@ -373,4 +375,7 @@ public class HashMapResourceProvider implements IResour .setId(id); } + public FhirContext getFhirContext() { + return myFhirContext; + } } diff --git a/pom.xml b/pom.xml index 328c216a185..5f52aa5d42e 100755 --- a/pom.xml +++ b/pom.xml @@ -1291,6 +1291,11 @@ spring-boot-starter-test ${spring_boot_version} + + org.springframework.boot + spring-boot-test + ${spring_boot_version} + org.springframework spring-tx diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 3f2ac07d697..874e92727c1 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -49,6 +49,26 @@ Improved stability of concurrency test framework. Thanks to Stig Døssing for the pull request! + + Moved in-memory matcher from Subscription module to SearchParam module and renamed the result type + from SubscriptionMatchResult to InMemoryMatchResult. + + + Added some experimental version-independent model classes to ca.uhn.fhir.jpa.model.any. They permit + writing code that is version independent. + + + Added new subclass of HashMapResourceProvider called SearchableHashMapResourceProvider that uses the + in-memory matcher to search the HashMap (using a full table scan). This allows rudimentary testing + without a database. + + + Added a new interceptor hook called STORAGE_PRESTORAGE_DELETE_CONFLICTS that is invoked when a + resource delete operation is about to fail due to referential integrity conflicts. + Hooks have access to the list of resources that have references to the resource being deleted and + can delete them. The boolean return value of the hook indicates whether the server should try + checking for conflicts again (true means try again). + @@ -3388,7 +3408,7 @@ Bundle bundle = client.search().forResource(Patient.class) In server, when returning a list of resources, the server sometimes failed to add _include]]> resources to the response bundle if they were - referred to by a contained reosurce. Thanks to Neal Acharya for reporting! + referred to by a contained resource. Thanks to Neal Acharya for reporting! Fix regression in web testing UI where "prev" and "next" buttons don't work