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 {
*
* - ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription
* - ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage
- * - ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult
+ * - ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult
*
*
* 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:
+ *
+ * - ca.uhn.fhir.jpa.delete.DeleteConflictList - The list of delete conflicts
+ *
+ *
+ * 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 extends IBaseResource> 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 extends IBaseResource> myList;
private final String myUuid;
private Integer myPreferredPageSize;
private Integer mySize;
private IPrimitiveType myPublished = InstantDt.withCurrentTime();
- public SimpleBundleProvider(List theList) {
+ public SimpleBundleProvider(List extends IBaseResource> 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 extends IBaseResource> 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 extends IBaseResource> 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