diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
index 97feca2e281..e82661f5ce4 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.api;
*/
import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.util.*;
public class Constants {
@@ -168,6 +169,7 @@ public class Constants {
public static final String PARAM_SORT = "_sort";
public static final String PARAM_SORT_ASC = "_sort:asc";
public static final String PARAM_SORT_DESC = "_sort:desc";
+ public static final String PARAM_SOURCE = "_source";
public static final String PARAM_SUMMARY = "_summary";
public static final String PARAM_TAG = "_tag";
public static final String PARAM_TAGS = "_tags";
@@ -220,10 +222,14 @@ public class Constants {
public static final String CACHE_CONTROL_PRIVATE = "private";
public static final int STATUS_HTTP_412_PAYLOAD_TOO_LARGE = 413;
public static final String OPERATION_NAME_GRAPHQL = "$graphql";
+ /**
+ * Note that this constant is used in a number of places including DB column lengths! Be careful if you decide to change it.
+ */
+ public static final int REQUEST_ID_LENGTH = 16;
static {
- CHARSET_UTF8 = Charset.forName(CHARSET_NAME_UTF8);
- CHARSET_US_ASCII = Charset.forName("ISO-8859-1");
+ CHARSET_UTF8 = StandardCharsets.UTF_8;
+ CHARSET_US_ASCII = StandardCharsets.ISO_8859_1;
HashMap statusNames = new HashMap<>();
statusNames.put(200, "OK");
@@ -239,7 +245,6 @@ public class Constants {
statusNames.put(300, "Multiple Choices");
statusNames.put(301, "Moved Permanently");
statusNames.put(302, "Found");
- statusNames.put(302, "Moved Temporarily");
statusNames.put(303, "See Other");
statusNames.put(304, "Not Modified");
statusNames.put(305, "Use Proxy");
@@ -259,9 +264,7 @@ public class Constants {
statusNames.put(411, "Length Required");
statusNames.put(412, "Precondition Failed");
statusNames.put(413, "Payload Too Large");
- statusNames.put(413, "Request Entity Too Large");
statusNames.put(414, "URI Too Long");
- statusNames.put(414, "Request-URI Too Long");
statusNames.put(415, "Unsupported Media Type");
statusNames.put(416, "Requested range not satisfiable");
statusNames.put(417, "Expectation Failed");
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/MetaUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/MetaUtil.java
new file mode 100644
index 00000000000..2fd2d20bdad
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/MetaUtil.java
@@ -0,0 +1,43 @@
+package ca.uhn.fhir.util;
+
+import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
+import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
+import ca.uhn.fhir.context.FhirContext;
+import org.hl7.fhir.instance.model.api.IBase;
+import org.hl7.fhir.instance.model.api.IBaseMetaType;
+import org.hl7.fhir.instance.model.api.IPrimitiveType;
+
+import java.util.List;
+
+public class MetaUtil {
+
+ private MetaUtil() {
+ // non-instantiable
+ }
+
+ public static String getSource(FhirContext theContext, IBaseMetaType theMeta) {
+ BaseRuntimeElementCompositeDefinition> elementDef = (BaseRuntimeElementCompositeDefinition>) theContext.getElementDefinition(theMeta.getClass());
+ BaseRuntimeChildDefinition sourceChild = elementDef.getChildByName("source");
+ List sourceValues = sourceChild.getAccessor().getValues(theMeta);
+ String retVal = null;
+ if (sourceValues.size() > 0) {
+ retVal = ((IPrimitiveType>) sourceValues.get(0)).getValueAsString();
+ }
+ return retVal;
+ }
+
+ public static void setSource(FhirContext theContext, IBaseMetaType theMeta, String theValue) {
+ BaseRuntimeElementCompositeDefinition> elementDef = (BaseRuntimeElementCompositeDefinition>) theContext.getElementDefinition(theMeta.getClass());
+ BaseRuntimeChildDefinition sourceChild = elementDef.getChildByName("source");
+ List sourceValues = sourceChild.getAccessor().getValues(theMeta);
+ IPrimitiveType> sourceElement;
+ if (sourceValues.size() > 0) {
+ sourceElement = ((IPrimitiveType>) sourceValues.get(0));
+ } else {
+ sourceElement = (IPrimitiveType>) theContext.getElementDefinition("uri").newInstance();
+ sourceChild.getMutator().setValue(theMeta, sourceElement);
+ }
+ sourceElement.setValueAsString(theValue);
+ }
+
+}
diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseMetaType.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseMetaType.java
index a41862a044c..8ddb333e125 100644
--- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseMetaType.java
+++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseMetaType.java
@@ -57,4 +57,5 @@ public interface IBaseMetaType extends ICompositeType {
*/
IBaseCoding getSecurity(String theSystem, String theCode);
+
}
diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
index 542cc62522e..ed76533ec3b 100644
--- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
+++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
@@ -105,6 +105,7 @@ ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.failedToExtractPa
ca.uhn.fhir.jpa.dao.SearchBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1}
ca.uhn.fhir.jpa.dao.SearchBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1}
+ca.uhn.fhir.jpa.dao.SearchBuilder.sourceParamDisabled=The _source parameter is disabled on this server
ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoConceptMapDstu3.matchesFound=Matches found!
ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoConceptMapDstu3.noMatchesFound=No matches found!
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 dd76b67e210..602daaeb653 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
@@ -51,6 +51,7 @@ 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.MetaUtil;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.XmlUtil;
import com.google.common.annotations.VisibleForTesting;
@@ -130,6 +131,8 @@ public abstract class BaseHapiFhirDao implements IDao,
@Autowired
protected IForcedIdDao myForcedIdDao;
@Autowired
+ protected IResourceProvenanceDao myResourceProvenanceDao;
+ @Autowired
protected ISearchCoordinatorSvc mySearchCoordinatorSvc;
@Autowired
protected ISearchParamRegistry mySerarchParamRegistry;
@@ -282,6 +285,7 @@ public abstract class BaseHapiFhirDao implements IDao,
}
}
}
+
}
private void findMatchingTagIds(RequestDetails theRequest, String theResourceName, IIdType theResourceId, Set tagIds, Class extends BaseTag> entityClass) {
@@ -611,7 +615,10 @@ public abstract class BaseHapiFhirDao implements IDao,
if (theEntity.getId() == null) {
changed = true;
} else {
- ResourceHistoryTable currentHistoryVersion = myResourceHistoryTableDao.findForIdAndVersion(theEntity.getId(), theEntity.getVersion());
+ ResourceHistoryTable currentHistoryVersion = theEntity.getCurrentVersionEntity();
+ if (currentHistoryVersion == null) {
+ currentHistoryVersion = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(theEntity.getId(), theEntity.getVersion());
+ }
if (currentHistoryVersion == null || currentHistoryVersion.getResource() == null) {
changed = true;
} else {
@@ -832,11 +839,7 @@ public abstract class BaseHapiFhirDao implements IDao,
metaSnapshotModeTokens = Collections.singleton(TagTypeEnum.PROFILE);
}
- if (metaSnapshotModeTokens.contains(theTag.getTag().getTagType())) {
- return true;
- }
-
- return false;
+ return metaSnapshotModeTokens.contains(theTag.getTag().getTagType());
}
@Override
@@ -851,10 +854,12 @@ public abstract class BaseHapiFhirDao implements IDao,
public R toResource(Class theResourceType, IBaseResourceEntity theEntity, Collection theTagList, boolean theForHistoryOperation) {
// 1. get resource, it's encoding and the tags if any
- byte[] resourceBytes = null;
- ResourceEncodingEnum resourceEncoding = null;
- Collection extends BaseTag> myTagList = null;
- Long version = null;
+ byte[] resourceBytes;
+ ResourceEncodingEnum resourceEncoding;
+ Collection extends BaseTag> myTagList;
+ Long version;
+ String provenanceSourceUri = null;
+ String provenanceRequestId = null;
if (theEntity instanceof ResourceHistoryTable) {
ResourceHistoryTable history = (ResourceHistoryTable) theEntity;
@@ -862,14 +867,20 @@ public abstract class BaseHapiFhirDao implements IDao,
resourceEncoding = history.getEncoding();
myTagList = history.getTags();
version = history.getVersion();
+ if (history.getProvenance() != null) {
+ provenanceRequestId = history.getProvenance().getRequestId();
+ provenanceSourceUri = history.getProvenance().getSourceUri();
+ }
} else if (theEntity instanceof ResourceTable) {
ResourceTable resource = (ResourceTable) theEntity;
version = theEntity.getVersion();
- ResourceHistoryTable history = myResourceHistoryTableDao.findForIdAndVersion(theEntity.getId(), version);
+ ResourceHistoryTable history = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(theEntity.getId(), version);
+ ((ResourceTable)theEntity).setCurrentVersionEntity(history);
+
while (history == null) {
if (version > 1L) {
version--;
- history = myResourceHistoryTableDao.findForIdAndVersion(theEntity.getId(), version);
+ history = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(theEntity.getId(), version);
} else {
return null;
}
@@ -878,12 +889,18 @@ public abstract class BaseHapiFhirDao implements IDao,
resourceEncoding = history.getEncoding();
myTagList = resource.getTags();
version = history.getVersion();
+ if (history.getProvenance() != null) {
+ provenanceRequestId = history.getProvenance().getRequestId();
+ provenanceSourceUri = history.getProvenance().getSourceUri();
+ }
} else if (theEntity instanceof ResourceSearchView) {
// This is the search View
- ResourceSearchView myView = (ResourceSearchView) theEntity;
- resourceBytes = myView.getResource();
- resourceEncoding = myView.getEncoding();
- version = myView.getVersion();
+ ResourceSearchView view = (ResourceSearchView) theEntity;
+ resourceBytes = view.getResource();
+ resourceEncoding = view.getEncoding();
+ version = view.getVersion();
+ provenanceRequestId = view.getProvenanceRequestId();
+ provenanceSourceUri = view.getProvenanceSourceUri();
if (theTagList == null)
myTagList = new HashSet<>();
else
@@ -954,6 +971,23 @@ public abstract class BaseHapiFhirDao implements IDao,
retVal = populateResourceMetadataRi(resourceType, theEntity, myTagList, theForHistoryOperation, res, version);
}
+ // 6. Handle source (provenance)
+ if (isNotBlank(provenanceRequestId) || isNotBlank(provenanceSourceUri)) {
+ String sourceString = defaultString(provenanceSourceUri)
+ + (isNotBlank(provenanceRequestId) ? "#" : "")
+ + defaultString(provenanceRequestId);
+
+ if (myContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) {
+ IBaseExtension, ?> sourceExtension = ((IBaseHasExtensions) retVal.getMeta()).addExtension();
+ sourceExtension.setUrl(JpaConstants.EXT_META_SOURCE);
+ IPrimitiveType value = (IPrimitiveType) myContext.getElementDefinition("uri").newInstance();
+ value.setValue(sourceString);
+ sourceExtension.setValue(value);
+ } else if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
+ MetaUtil.setSource(myContext, retVal.getMeta(), sourceString);
+ }
+ }
+
return retVal;
}
@@ -1097,6 +1131,40 @@ public abstract class BaseHapiFhirDao implements IDao,
ourLog.debug("Saving history entry {}", historyEntry.getIdDt());
myResourceHistoryTableDao.save(historyEntry);
+
+ // Save resource source
+ String source = null;
+ String requestId = theRequest != null ? theRequest.getRequestId() : null;
+ if (theResource != null) {
+ if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
+ IBaseMetaType meta = theResource.getMeta();
+ source = MetaUtil.getSource(myContext, meta);
+ }
+ if (myContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) {
+ source = ((IBaseHasExtensions) theResource.getMeta())
+ .getExtension()
+ .stream()
+ .filter(t -> JpaConstants.EXT_META_SOURCE.equals(t.getUrl()))
+ .filter(t -> t.getValue() instanceof IPrimitiveType)
+ .map(t -> ((IPrimitiveType) t.getValue()).getValueAsString())
+ .findFirst()
+ .orElse(null);
+ }
+ }
+ boolean haveSource = isNotBlank(source) && myConfig.getStoreMetaSourceInformation().isStoreSourceUri();
+ boolean haveRequestId = isNotBlank(requestId) && myConfig.getStoreMetaSourceInformation().isStoreRequestId();
+ if (haveSource || haveRequestId) {
+ ResourceHistoryProvenanceEntity provenance = new ResourceHistoryProvenanceEntity();
+ provenance.setResourceHistoryTable(historyEntry);
+ provenance.setResourceTable(theEntity);
+ if (haveRequestId) {
+ provenance.setRequestId(left(requestId, Constants.REQUEST_ID_LENGTH));
+ }
+ if (haveSource) {
+ provenance.setSourceUri(source);
+ }
+ myEntityManager.persist(provenance);
+ }
}
/*
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 443b9587808..f638c26be59 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
@@ -269,7 +269,10 @@ public abstract class BaseHapiFhirResourceDao extends B
@Override
public DaoMethodOutcome delete(IIdType theId, RequestDetails theRequestDetails) {
DeleteConflictList deleteConflicts = new DeleteConflictList();
- deleteConflicts.setResourceIdMarkedForDeletion(theId);
+ if (theId != null && isNotBlank(theId.getValue())) {
+ deleteConflicts.setResourceIdMarkedForDeletion(theId);
+ }
+
StopWatch w = new StopWatch();
DaoMethodOutcome retVal = delete(theId, deleteConflicts, theRequestDetails);
@@ -673,7 +676,7 @@ public abstract class BaseHapiFhirResourceDao extends B
doMetaAdd(theMetaAdd, latestVersion);
// Also update history entry
- ResourceHistoryTable history = myResourceHistoryTableDao.findForIdAndVersion(entity.getId(), entity.getVersion());
+ ResourceHistoryTable history = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(entity.getId(), entity.getVersion());
doMetaAdd(theMetaAdd, history);
}
@@ -705,7 +708,7 @@ public abstract class BaseHapiFhirResourceDao extends B
doMetaDelete(theMetaDel, latestVersion);
// Also update history entry
- ResourceHistoryTable history = myResourceHistoryTableDao.findForIdAndVersion(entity.getId(), entity.getVersion());
+ ResourceHistoryTable history = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(entity.getId(), entity.getVersion());
doMetaDelete(theMetaDel, history);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
index 53f4506efff..18ae898014a 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
@@ -151,6 +151,7 @@ public class DaoConfig {
*/
private boolean myPreExpandValueSetsExperimental = false;
private boolean myFilterParameterEnabled = false;
+ private StoreMetaSourceInformation myStoreMetaSourceInformation = StoreMetaSourceInformation.SOURCE_URI_AND_REQUEST_ID;
/**
* Constructor
@@ -974,6 +975,7 @@ public class DaoConfig {
* and other FHIR features may not behave as expected when referential integrity is not
* preserved. Use this feature with caution.
*
+ *
* @see CascadingDeleteInterceptor
*/
public boolean isEnforceReferentialIntegrityOnDelete() {
@@ -988,6 +990,7 @@ public class DaoConfig {
* and other FHIR features may not behave as expected when referential integrity is not
* preserved. Use this feature with caution.
*
+ *
* @see CascadingDeleteInterceptor
*/
public void setEnforceReferentialIntegrityOnDelete(boolean theEnforceReferentialIntegrityOnDelete) {
@@ -1088,16 +1091,16 @@ public class DaoConfig {
* The expunge batch size (default 800) determines the number of records deleted within a single transaction by the
* expunge operation.
*/
- public void setExpungeBatchSize(int theExpungeBatchSize) {
- myExpungeBatchSize = theExpungeBatchSize;
+ public int getExpungeBatchSize() {
+ return myExpungeBatchSize;
}
/**
* The expunge batch size (default 800) determines the number of records deleted within a single transaction by the
* expunge operation.
*/
- public int getExpungeBatchSize() {
- return myExpungeBatchSize;
+ public void setExpungeBatchSize(int theExpungeBatchSize) {
+ myExpungeBatchSize = theExpungeBatchSize;
}
/**
@@ -1656,6 +1659,54 @@ public class DaoConfig {
myFilterParameterEnabled = theFilterParameterEnabled;
}
+ /**
+ * If enabled, resource source information (Resource.meta.source
) will be persisted along with
+ * each resource. This adds extra table and index space so it should be disabled if it is not being
+ * used.
+ *
+ * Default is {@link StoreMetaSourceInformation#SOURCE_URI_AND_REQUEST_ID}
+ *
+ */
+ public StoreMetaSourceInformation getStoreMetaSourceInformation() {
+ return myStoreMetaSourceInformation;
+ }
+
+ /**
+ * If enabled, resource source information (Resource.meta.source
) will be persisted along with
+ * each resource. This adds extra table and index space so it should be disabled if it is not being
+ * used.
+ *
+ * Default is {@link StoreMetaSourceInformation#SOURCE_URI_AND_REQUEST_ID}
+ *
+ */
+ public void setStoreMetaSourceInformation(StoreMetaSourceInformation theStoreMetaSourceInformation) {
+ Validate.notNull(theStoreMetaSourceInformation, "theStoreMetaSourceInformation must not be null");
+ myStoreMetaSourceInformation = theStoreMetaSourceInformation;
+ }
+
+ public enum StoreMetaSourceInformation {
+ NONE(false, false),
+ SOURCE_URI(true, false),
+ REQUEST_ID(false, true),
+ SOURCE_URI_AND_REQUEST_ID(true, true);
+
+ private final boolean myStoreSourceUri;
+ private final boolean myStoreRequestId;
+
+ StoreMetaSourceInformation(boolean theStoreSourceUri, boolean theStoreRequestId) {
+ myStoreSourceUri = theStoreSourceUri;
+ myStoreRequestId = theStoreRequestId;
+ }
+
+ public boolean isStoreSourceUri() {
+ return myStoreSourceUri;
+ }
+
+ public boolean isStoreRequestId() {
+ return myStoreRequestId;
+ }
+ }
+
public enum IndexEnabledEnum {
ENABLED,
DISABLED
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
index 7d17c84922d..1e16b8d7ea2 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java
@@ -823,6 +823,48 @@ public class SearchBuilder implements ISearchBuilder {
return null;
}
+
+ private Predicate addPredicateSource(List extends IQueryParameterType> theList, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) {
+ if (myDaoConfig.getStoreMetaSourceInformation() == DaoConfig.StoreMetaSourceInformation.NONE) {
+ String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, "sourceParamDisabled");
+ throw new InvalidRequestException(msg);
+ }
+
+ Join join = myResourceTableRoot.join("myProvenance", JoinType.LEFT);
+
+ List codePredicates = new ArrayList<>();
+
+ for (IQueryParameterType nextParameter : theList) {
+ String nextParamValue = nextParameter.getValueAsQueryToken(myContext);
+ int lastHashValueIndex = nextParamValue.lastIndexOf('#');
+ String sourceUri;
+ String requestId;
+ if (lastHashValueIndex == -1) {
+ sourceUri = nextParamValue;
+ requestId = null;
+ } else {
+ sourceUri = nextParamValue.substring(0, lastHashValueIndex);
+ requestId = nextParamValue.substring(lastHashValueIndex + 1);
+ }
+ requestId = left(requestId, Constants.REQUEST_ID_LENGTH);
+
+ Predicate sourceUriPredicate = myBuilder.equal(join.get("mySourceUri"), sourceUri);
+ Predicate requestIdPredicate = myBuilder.equal(join.get("myRequestId"), requestId);
+ if (isNotBlank(sourceUri) && isNotBlank(requestId)) {
+ codePredicates.add(myBuilder.and(sourceUriPredicate, requestIdPredicate));
+ } else if (isNotBlank(sourceUri)) {
+ codePredicates.add(sourceUriPredicate);
+ } else if (isNotBlank(requestId)) {
+ codePredicates.add(requestIdPredicate);
+ }
+ }
+
+ Predicate retVal = myBuilder.or(toArray(codePredicates));
+ myPredicates.add(retVal);
+ return retVal;
+ }
+
+
private Predicate addPredicateString(String theResourceName,
String theParamName,
List extends IQueryParameterType> theList) {
@@ -2680,6 +2722,14 @@ public class SearchBuilder implements ISearchBuilder {
} else {
throw new InvalidRequestException("Unexpected search parameter type encountered, expected string type for language search");
}
+ } else if (searchParam.getName().equals(Constants.PARAM_SOURCE)) {
+ if (searchParam.getParamType() == RestSearchParameterTypeEnum.TOKEN) {
+ TokenParam param = new TokenParam();
+ param.setValueAsQueryToken(null, null, null, theFilter.getValue());
+ return addPredicateSource(Collections.singletonList(param), theFilter.getOperation(), theRequest);
+ } else {
+ throw new InvalidRequestException("Unexpected search parameter type encountered, expected token type for _id search");
+ }
}
// else if ((searchParam.getName().equals(Constants.PARAM_TAG)) ||
// (searchParam.equals(Constants.PARAM_SECURITY))) {
@@ -2798,6 +2848,12 @@ public class SearchBuilder implements ISearchBuilder {
addPredicateTag(theAndOrParams, theParamName);
+ } else if (theParamName.equals(Constants.PARAM_SOURCE)) {
+
+ for (List extends IQueryParameterType> nextAnd : theAndOrParams) {
+ addPredicateSource(nextAnd, SearchFilterParser.CompareOperation.eq, theRequest);
+ }
+
} else {
RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceHistoryTableDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceHistoryTableDao.java
index 9ecaa1480ab..d4dda01f835 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceHistoryTableDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceHistoryTableDao.java
@@ -66,8 +66,8 @@ public interface IResourceHistoryTableDao extends JpaRepository findForResourceId(Pageable thePage, @Param("resId") Long theId, @Param("dontWantVersion") Long theDontWantVersion);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceProvenanceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceProvenanceDao.java
new file mode 100644
index 00000000000..3b301516ea5
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceProvenanceDao.java
@@ -0,0 +1,38 @@
+package ca.uhn.fhir.jpa.dao.data;
+
+import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity;
+import ca.uhn.fhir.jpa.model.entity.ResourceTable;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/*
+ * #%L
+ * HAPI FHIR JPA Server
+ * %%
+ * Copyright (C) 2014 - 2019 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+public interface IResourceProvenanceDao extends JpaRepository {
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java
index 083f7f25ba3..673ef3a9f49 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java
@@ -120,6 +120,7 @@ public class ExpungeEverythingService {
counter.addAndGet(expungeEverythingByType(ResourceHistoryTag.class));
counter.addAndGet(expungeEverythingByType(ResourceTag.class));
counter.addAndGet(expungeEverythingByType(TagDefinition.class));
+ counter.addAndGet(expungeEverythingByType(ResourceHistoryProvenanceEntity.class));
counter.addAndGet(expungeEverythingByType(ResourceHistoryTable.class));
counter.addAndGet(expungeEverythingByType(ResourceTable.class));
myTxTemplate.execute(t -> {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ResourceExpungeService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ResourceExpungeService.java
index 051e171704b..5f5203a055f 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ResourceExpungeService.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ResourceExpungeService.java
@@ -87,6 +87,8 @@ class ResourceExpungeService implements IResourceExpungeService {
private IInterceptorBroadcaster myInterceptorBroadcaster;
@Autowired
private DaoRegistry myDaoRegistry;
+ @Autowired
+ private IResourceProvenanceDao myResourceHistoryProvenanceTableDao;
@Override
@Transactional
@@ -94,7 +96,7 @@ class ResourceExpungeService implements IResourceExpungeService {
Pageable page = PageRequest.of(0, theRemainingCount);
if (theResourceId != null) {
if (theVersion != null) {
- return toSlice(myResourceHistoryTableDao.findForIdAndVersion(theResourceId, theVersion));
+ return toSlice(myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(theResourceId, theVersion));
} else {
return myResourceHistoryTableDao.findIdsOfPreviousVersionsOfResourceId(page, theResourceId);
}
@@ -146,6 +148,10 @@ class ResourceExpungeService implements IResourceExpungeService {
callHooks(theRequestDetails, theRemainingCount, version, id);
+ if (version.getProvenance() != null) {
+ myResourceHistoryProvenanceTableDao.delete(version.getProvenance());
+ }
+
myResourceHistoryTagDao.deleteAll(version.getTags());
myResourceHistoryTableDao.delete(version);
@@ -194,7 +200,7 @@ class ResourceExpungeService implements IResourceExpungeService {
private void expungeCurrentVersionOfResource(RequestDetails theRequestDetails, Long theResourceId, AtomicInteger theRemainingCount) {
ResourceTable resource = myResourceTableDao.findById(theResourceId).orElseThrow(IllegalStateException::new);
- ResourceHistoryTable currentVersion = myResourceHistoryTableDao.findForIdAndVersion(resource.getId(), resource.getVersion());
+ ResourceHistoryTable currentVersion = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(resource.getId(), resource.getVersion());
if (currentVersion != null) {
expungeHistoricalVersion(theRequestDetails, currentVersion.getId(), theRemainingCount);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceSearchView.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceSearchView.java
index 60515aaa4bf..3d94ee48daf 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceSearchView.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceSearchView.java
@@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
+import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.api.Constants;
@@ -34,25 +35,26 @@ import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
-//@formatter:off
@Entity
@Immutable
-@Subselect("SELECT h.pid as pid " +
- ", h.res_id as res_id " +
- ", h.res_type as res_type " +
- ", h.res_version as res_version " + // FHIR version
- ", h.res_ver as res_ver " + // resource version
- ", h.has_tags as has_tags " +
- ", h.res_deleted_at as res_deleted_at " +
- ", h.res_published as res_published " +
- ", h.res_updated as res_updated " +
- ", h.res_text as res_text " +
- ", h.res_encoding as res_encoding " +
- ", f.forced_id as FORCED_PID " +
+@Subselect("SELECT h.pid as pid, " +
+ " h.res_id as res_id, " +
+ " h.res_type as res_type, " +
+ " h.res_version as res_version, " + // FHIR version
+ " h.res_ver as res_ver, " + // resource version
+ " h.has_tags as has_tags, " +
+ " h.res_deleted_at as res_deleted_at, " +
+ " h.res_published as res_published, " +
+ " h.res_updated as res_updated, " +
+ " h.res_text as res_text, " +
+ " h.res_encoding as res_encoding, " +
+ " p.SOURCE_URI as PROV_SOURCE_URI," +
+ " p.REQUEST_ID as PROV_REQUEST_ID," +
+ " f.forced_id as FORCED_PID " +
"FROM HFJ_RES_VER h "
+ " LEFT OUTER JOIN HFJ_FORCED_ID f ON f.resource_pid = h.res_id "
+ + " LEFT OUTER JOIN HFJ_RES_VER_PROV p ON p.res_ver_pid = h.pid "
+ " INNER JOIN HFJ_RESOURCE r ON r.res_id = h.res_id and r.res_ver = h.res_ver")
-// @formatter:on
public class ResourceSearchView implements IBaseResourceEntity, Serializable {
private static final long serialVersionUID = 1L;
@@ -73,36 +75,41 @@ public class ResourceSearchView implements IBaseResourceEntity, Serializable {
@Column(name = "RES_VER")
private Long myResourceVersion;
-
+ @Column(name = "PROV_REQUEST_ID", length = Constants.REQUEST_ID_LENGTH)
+ private String myProvenanceRequestId;
+ @Column(name = "PROV_SOURCE_URI", length = ResourceHistoryProvenanceEntity.SOURCE_URI_LENGTH)
+ private String myProvenanceSourceUri;
@Column(name = "HAS_TAGS")
private boolean myHasTags;
-
@Column(name = "RES_DELETED_AT")
@Temporal(TemporalType.TIMESTAMP)
private Date myDeleted;
-
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "RES_PUBLISHED")
private Date myPublished;
-
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "RES_UPDATED")
private Date myUpdated;
-
@Column(name = "RES_TEXT")
@Lob()
private byte[] myResource;
-
@Column(name = "RES_ENCODING")
@Enumerated(EnumType.STRING)
private ResourceEncodingEnum myEncoding;
-
- @Column(name = "FORCED_PID", length= ForcedId.MAX_FORCED_ID_LENGTH)
+ @Column(name = "FORCED_PID", length = ForcedId.MAX_FORCED_ID_LENGTH)
private String myForcedPid;
public ResourceSearchView() {
}
+ public String getProvenanceRequestId() {
+ return myProvenanceRequestId;
+ }
+
+ public String getProvenanceSourceUri() {
+ return myProvenanceSourceUri;
+ }
+
@Override
public Date getDeleted() {
return myDeleted;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/CircularQueueCaptureQueriesListener.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/CircularQueueCaptureQueriesListener.java
index b7787adcd4d..6a05a26dae7 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/CircularQueueCaptureQueriesListener.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/CircularQueueCaptureQueriesListener.java
@@ -159,7 +159,7 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe
.stream()
.map(CircularQueueCaptureQueriesListener::formatQueryAsSql)
.collect(Collectors.toList());
- ourLog.info("Select Queries:\n{}", String.join("\n", queries));
+ ourLog.info("Update Queries:\n{}", String.join("\n", queries));
}
/**
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SourceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SourceTest.java
new file mode 100644
index 00000000000..0e9a32c0b58
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SourceTest.java
@@ -0,0 +1,234 @@
+package ca.uhn.fhir.jpa.dao.dstu3;
+
+import ca.uhn.fhir.jpa.dao.DaoConfig;
+import ca.uhn.fhir.jpa.model.util.JpaConstants;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.jpa.util.TestUtil;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.param.TokenAndListParam;
+import ca.uhn.fhir.rest.param.TokenOrListParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
+import org.apache.commons.text.RandomStringGenerator;
+import org.hl7.fhir.dstu3.model.Patient;
+import org.hl7.fhir.dstu3.model.StringType;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.matchesPattern;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+@SuppressWarnings({"Duplicates"})
+public class FhirResourceDaoDstu3SourceTest extends BaseJpaDstu3Test {
+
+ @After
+ public final void after() {
+ when(mySrd.getRequestId()).thenReturn(null);
+ myDaoConfig.setStoreMetaSourceInformation(new DaoConfig().getStoreMetaSourceInformation());
+ }
+
+ @Before
+ public void before() {
+ myDaoConfig.setStoreMetaSourceInformation(DaoConfig.StoreMetaSourceInformation.SOURCE_URI_AND_REQUEST_ID);
+ }
+
+ @Test
+ public void testSourceStoreAndSearch() {
+ String requestId = "a_request_id";
+
+ when(mySrd.getRequestId()).thenReturn(requestId);
+ Patient pt0 = new Patient();
+ pt0.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:0"));
+ pt0.setActive(true);
+ IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
+
+ Patient pt1 = new Patient();
+ pt1.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:1"));
+ pt1.setActive(true);
+ IIdType pt1id = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless();
+
+ // Search by source URI
+ SearchParameterMap params = new SearchParameterMap();
+ params.setLoadSynchronous(true);
+ params.add(Constants.PARAM_SOURCE, new TokenParam("urn:source:0"));
+ IBundleProvider result = myPatientDao.search(params);
+ assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(pt0id.getValue()));
+ pt0 = (Patient) result.getResources(0, 1).get(0);
+ assertEquals("urn:source:0#a_request_id", pt0.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE));
+
+ // Search by request ID
+ params = new SearchParameterMap();
+ params.setLoadSynchronous(true);
+ params.add(Constants.PARAM_SOURCE, new TokenParam("#a_request_id"));
+ result = myPatientDao.search(params);
+ assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(pt0id.getValue(), pt1id.getValue()));
+
+ // Search by source URI and request ID
+ params = new SearchParameterMap();
+ params.setLoadSynchronous(true);
+ params.add(Constants.PARAM_SOURCE, new TokenParam("urn:source:0#a_request_id"));
+ result = myPatientDao.search(params);
+ assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(pt0id.getValue()));
+
+ }
+
+
+ @Test
+ public void testSearchWithOr() {
+ String requestId = "a_request_id";
+
+ when(mySrd.getRequestId()).thenReturn(requestId);
+ Patient pt0 = new Patient();
+ pt0.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:0"));
+ pt0.setActive(true);
+ IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
+
+ Patient pt1 = new Patient();
+ pt1.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:1"));
+ pt1.setActive(true);
+ IIdType pt1id = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless();
+
+ Patient pt2 = new Patient();
+ pt2.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:2"));
+ pt2.setActive(true);
+ myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless();
+
+ // Search
+ SearchParameterMap params = new SearchParameterMap();
+ params.setLoadSynchronous(true);
+ params.add(Constants.PARAM_SOURCE, new TokenOrListParam()
+ .addOr(new TokenParam("urn:source:0"))
+ .addOr(new TokenParam("urn:source:1")));
+ IBundleProvider result = myPatientDao.search(params);
+ assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(pt0id.getValue(), pt1id.getValue()));
+
+ }
+
+ @Test
+ public void testSearchWithAnd() {
+ String requestId = "a_request_id";
+
+ when(mySrd.getRequestId()).thenReturn(requestId);
+ Patient pt0 = new Patient();
+ pt0.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:0"));
+ pt0.setActive(true);
+ IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
+
+ Patient pt1 = new Patient();
+ pt1.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:1"));
+ pt1.setActive(true);
+ IIdType pt1id = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless();
+
+ Patient pt2 = new Patient();
+ pt2.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:2"));
+ pt2.setActive(true);
+ myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless();
+
+ // Search
+ SearchParameterMap params = new SearchParameterMap();
+ params.setLoadSynchronous(true);
+ params.add(Constants.PARAM_SOURCE, new TokenAndListParam()
+ .addAnd(new TokenParam("urn:source:0"), new TokenParam("@a_request_id")));
+ IBundleProvider result = myPatientDao.search(params);
+ assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(pt0id.getValue()));
+
+ }
+
+ @Test
+ public void testSearchLongRequestId() {
+ String requestId = new RandomStringGenerator.Builder().build().generate(5000);
+ when(mySrd.getRequestId()).thenReturn(requestId);
+
+ Patient pt0 = new Patient();
+ pt0.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:0"));
+ pt0.setActive(true);
+ IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
+
+ // Search
+ SearchParameterMap params = new SearchParameterMap();
+ params.setLoadSynchronous(true);
+ params.add(Constants.PARAM_SOURCE, new TokenAndListParam()
+ .addAnd(new TokenParam("urn:source:0"), new TokenParam("#" + requestId)));
+ IBundleProvider result = myPatientDao.search(params);
+ assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(pt0id.getValue()));
+
+ }
+
+ @Test
+ public void testSourceNotPreservedAcrossUpdate() {
+
+ Patient pt0 = new Patient();
+ pt0.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:0"));
+ pt0.setActive(true);
+ IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
+
+ pt0 = myPatientDao.read(pt0id);
+ assertEquals("urn:source:0", pt0.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE));
+
+ pt0.getMeta().getExtension().clear();
+ pt0.setActive(false);
+ myPatientDao.update(pt0);
+
+ pt0 = myPatientDao.read(pt0id.withVersion("2"));
+ assertEquals(null, pt0.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE));
+
+ }
+
+ @Test
+ public void testSourceDisabled() {
+ myDaoConfig.setStoreMetaSourceInformation(DaoConfig.StoreMetaSourceInformation.NONE);
+ when(mySrd.getRequestId()).thenReturn("0000000000000000");
+
+ Patient pt0 = new Patient();
+ pt0.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:0"));
+ pt0.setActive(true);
+ IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
+
+ pt0 = myPatientDao.read(pt0id);
+ assertEquals(null, pt0.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE));
+
+ pt0.getMeta().addExtension(JpaConstants.EXT_META_SOURCE, new StringType("urn:source:1"));
+ pt0.setActive(false);
+ myPatientDao.update(pt0);
+
+ pt0 = myPatientDao.read(pt0id.withVersion("2"));
+ assertEquals(null, pt0.getMeta().getExtensionString(JpaConstants.EXT_META_SOURCE));
+
+ // Search without source param
+ SearchParameterMap params = new SearchParameterMap();
+ params.setLoadSynchronous(true);
+ IBundleProvider result = myPatientDao.search(params);
+ assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(pt0id.getValue()));
+
+ // Search with source param
+ params = new SearchParameterMap();
+ params.setLoadSynchronous(true);
+ params.add(Constants.PARAM_SOURCE, new TokenAndListParam()
+ .addAnd(new TokenParam("urn:source:0"), new TokenParam("@a_request_id")));
+ try {
+ myPatientDao.search(params);
+ } catch (InvalidRequestException e) {
+ assertEquals(e.getMessage(), "The _source parameter is disabled on this server");
+ }
+ }
+
+ @AfterClass
+ public static void afterClassClearContext() {
+ TestUtil.clearAllStaticFieldsForUnitTest();
+ }
+
+ public static void assertConflictException(String theResourceType, ResourceVersionConflictException e) {
+ assertThat(e.getMessage(), matchesPattern(
+ "Unable to delete [a-zA-Z]+/[0-9]+ because at least one resource has a reference to this resource. First reference found was resource " + theResourceType + "/[0-9]+ in path [a-zA-Z]+.[a-zA-Z]+"));
+
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4DeleteTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4DeleteTest.java
index 72f0d32ee7e..f0a1c5acee8 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4DeleteTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4DeleteTest.java
@@ -2,14 +2,9 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
-import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.util.TestUtil;
-import com.google.common.base.Charsets;
-import org.apache.commons.io.IOUtils;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpDelete;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
import org.junit.AfterClass;
@@ -19,7 +14,6 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.*;
public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
@@ -42,11 +36,11 @@ public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
// Current version should be marked as deleted
runInTransaction(()->{
- ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersion(id.getIdPartAsLong(), 1);
+ ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 1);
assertNull(resourceTable.getDeleted());
});
runInTransaction(()->{
- ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersion(id.getIdPartAsLong(), 2);
+ ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 2);
assertNotNull(resourceTable.getDeleted());
});
@@ -169,7 +163,7 @@ public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
// Mark the current history version as not-deleted even though the actual resource
// table entry is marked deleted
runInTransaction(()->{
- ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersion(id.getIdPartAsLong(), 2);
+ ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 2);
resourceTable.setDeleted(null);
myResourceHistoryTableDao.save(resourceTable);
});
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java
new file mode 100644
index 00000000000..dbfb0052895
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java
@@ -0,0 +1,144 @@
+package ca.uhn.fhir.jpa.dao.r4;
+
+import ca.uhn.fhir.jpa.dao.DaoConfig;
+import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
+import ca.uhn.fhir.jpa.model.entity.ResourceTable;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.jpa.util.TestUtil;
+import ca.uhn.fhir.model.primitive.InstantDt;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.param.StringParam;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
+import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
+import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.r4.model.*;
+import org.junit.*;
+import org.springframework.test.context.TestPropertySource;
+
+import java.util.*;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.reset;
+
+@TestPropertySource(properties = {
+ "scheduling_disabled=true"
+})
+public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4QueryCountTest.class);
+
+ @After
+ public void afterResetDao() {
+ myDaoConfig.setResourceMetaCountHardLimit(new DaoConfig().getResourceMetaCountHardLimit());
+ myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
+ }
+
+ @Before
+ public void before() {
+ myInterceptorRegistry.registerInterceptor(myInterceptor);
+ }
+
+
+ @Test
+ public void testUpdateWithNoChanges() {
+ IIdType id = runInTransaction(() -> {
+ Patient p = new Patient();
+ p.addIdentifier().setSystem("urn:system").setValue("2");
+ return myPatientDao.create(p).getId().toUnqualified();
+ });
+
+ myCaptureQueriesListener.clear();
+ runInTransaction(() -> {
+ Patient p = new Patient();
+ p.setId(id.getIdPart());
+ p.addIdentifier().setSystem("urn:system").setValue("2");
+ myPatientDao.update(p).getResource();
+ });
+ myCaptureQueriesListener.logSelectQueriesForCurrentThread();
+ assertEquals(5, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
+ myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
+ assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
+ assertThat(myCaptureQueriesListener.getInsertQueriesForCurrentThread(), empty());
+ assertThat(myCaptureQueriesListener.getDeleteQueriesForCurrentThread(), empty());
+ }
+
+
+ @Test
+ public void testUpdateWithChanges() {
+ IIdType id = runInTransaction(() -> {
+ Patient p = new Patient();
+ p.addIdentifier().setSystem("urn:system").setValue("2");
+ return myPatientDao.create(p).getId().toUnqualified();
+ });
+
+ myCaptureQueriesListener.clear();
+ runInTransaction(() -> {
+ Patient p = new Patient();
+ p.setId(id.getIdPart());
+ p.addIdentifier().setSystem("urn:system").setValue("3");
+ myPatientDao.update(p).getResource();
+ });
+ myCaptureQueriesListener.logSelectQueriesForCurrentThread();
+ assertEquals(6, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
+ myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
+ assertEquals(2, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
+ myCaptureQueriesListener.logInsertQueriesForCurrentThread();
+ assertEquals(1, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
+ myCaptureQueriesListener.logDeleteQueriesForCurrentThread();
+ assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
+ }
+
+ @Test
+ public void testRead() {
+ IIdType id = runInTransaction(() -> {
+ Patient p = new Patient();
+ p.addIdentifier().setSystem("urn:system").setValue("2");
+ return myPatientDao.create(p).getId().toUnqualified();
+ });
+
+ myCaptureQueriesListener.clear();
+ runInTransaction(() -> {
+ myPatientDao.read(id.toVersionless());
+ });
+ myCaptureQueriesListener.logSelectQueriesForCurrentThread();
+ assertEquals(2, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
+ myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
+ assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
+ myCaptureQueriesListener.logInsertQueriesForCurrentThread();
+ assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
+ myCaptureQueriesListener.logDeleteQueriesForCurrentThread();
+ assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
+ }
+
+ @Test
+ public void testVRead() {
+ IIdType id = runInTransaction(() -> {
+ Patient p = new Patient();
+ p.addIdentifier().setSystem("urn:system").setValue("2");
+ return myPatientDao.create(p).getId().toUnqualified();
+ });
+
+ myCaptureQueriesListener.clear();
+ runInTransaction(() -> {
+ myPatientDao.read(id.withVersion("1"));
+ });
+ myCaptureQueriesListener.logSelectQueriesForCurrentThread();
+ assertEquals(2, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
+ myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
+ assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
+ myCaptureQueriesListener.logInsertQueriesForCurrentThread();
+ assertEquals(0, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size());
+ myCaptureQueriesListener.logDeleteQueriesForCurrentThread();
+ assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
+ }
+
+ @AfterClass
+ public static void afterClassClearContext() {
+ TestUtil.clearAllStaticFieldsForUnitTest();
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java
index d83377face9..1b4c239f66c 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java
@@ -1298,6 +1298,34 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
}
+ @Test
+ public void testCustomCodeableConcept() {
+ SearchParameter fooSp = new SearchParameter();
+ fooSp.addBase("ChargeItem");
+ fooSp.setName("Product");
+ fooSp.setCode("product");
+ fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.TOKEN);
+ fooSp.setTitle("Product within a ChargeItem");
+ fooSp.setExpression("ChargeItem.product.as(CodeableConcept)");
+ fooSp.setStatus(Enumerations.PublicationStatus.ACTIVE);
+ mySearchParameterDao.create(fooSp, mySrd);
+
+ mySearchParamRegistry.forceRefresh();
+
+ ChargeItem ci = new ChargeItem();
+ ci.setProduct(new CodeableConcept());
+ ci.getProductCodeableConcept().addCoding().setCode("1");
+ myChargeItemDao.create(ci);
+
+ SearchParameterMap map = new SearchParameterMap();
+ map.setLoadSynchronous(true);
+ map.add("product", new TokenParam(null, "1"));
+ IBundleProvider results = myChargeItemDao.search(map);
+ assertEquals(1, results.size().intValue());
+
+
+ }
+
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SourceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SourceTest.java
new file mode 100644
index 00000000000..10e51c05432
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SourceTest.java
@@ -0,0 +1,234 @@
+package ca.uhn.fhir.jpa.dao.r4;
+
+import ca.uhn.fhir.jpa.dao.DaoConfig;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.jpa.util.TestUtil;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.param.TokenAndListParam;
+import ca.uhn.fhir.rest.param.TokenOrListParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
+import org.apache.commons.text.RandomStringGenerator;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.r4.model.Patient;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.UUID;
+
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.matchesPattern;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+@SuppressWarnings({"Duplicates"})
+public class FhirResourceDaoR4SourceTest extends BaseJpaR4Test {
+
+ @After
+ public final void after() {
+ when(mySrd.getRequestId()).thenReturn(null);
+ myDaoConfig.setStoreMetaSourceInformation(new DaoConfig().getStoreMetaSourceInformation());
+ }
+
+ @Before
+ public void before() {
+ myDaoConfig.setStoreMetaSourceInformation(DaoConfig.StoreMetaSourceInformation.SOURCE_URI_AND_REQUEST_ID);
+ }
+
+ @Test
+ public void testSourceStoreAndSearch() {
+ String requestId = "a_request_id";
+
+ when(mySrd.getRequestId()).thenReturn(requestId);
+ Patient pt0 = new Patient();
+ pt0.getMeta().setSource("urn:source:0");
+ pt0.setActive(true);
+ IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
+
+ Patient pt1 = new Patient();
+ pt1.getMeta().setSource("urn:source:1");
+ pt1.setActive(true);
+ IIdType pt1id = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless();
+
+ // Search by source URI
+ SearchParameterMap params = new SearchParameterMap();
+ params.setLoadSynchronous(true);
+ params.add(Constants.PARAM_SOURCE, new TokenParam("urn:source:0"));
+ IBundleProvider result = myPatientDao.search(params);
+ assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(pt0id.getValue()));
+ pt0 = (Patient) result.getResources(0, 1).get(0);
+ assertEquals("urn:source:0#a_request_id", pt0.getMeta().getSource());
+
+ // Search by request ID
+ params = new SearchParameterMap();
+ params.setLoadSynchronous(true);
+ params.add(Constants.PARAM_SOURCE, new TokenParam("#a_request_id"));
+ result = myPatientDao.search(params);
+ assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(pt0id.getValue(), pt1id.getValue()));
+
+ // Search by source URI and request ID
+ params = new SearchParameterMap();
+ params.setLoadSynchronous(true);
+ params.add(Constants.PARAM_SOURCE, new TokenParam("urn:source:0#a_request_id"));
+ result = myPatientDao.search(params);
+ assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(pt0id.getValue()));
+
+ }
+
+
+ @Test
+ public void testSearchWithOr() {
+ String requestId = "a_request_id";
+
+ when(mySrd.getRequestId()).thenReturn(requestId);
+ Patient pt0 = new Patient();
+ pt0.getMeta().setSource("urn:source:0");
+ pt0.setActive(true);
+ IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
+
+ Patient pt1 = new Patient();
+ pt1.getMeta().setSource("urn:source:1");
+ pt1.setActive(true);
+ IIdType pt1id = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless();
+
+ Patient pt2 = new Patient();
+ pt2.getMeta().setSource("urn:source:2");
+ pt2.setActive(true);
+ myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless();
+
+ // Search
+ SearchParameterMap params = new SearchParameterMap();
+ params.setLoadSynchronous(true);
+ params.add(Constants.PARAM_SOURCE, new TokenOrListParam()
+ .addOr(new TokenParam("urn:source:0"))
+ .addOr(new TokenParam("urn:source:1")));
+ IBundleProvider result = myPatientDao.search(params);
+ assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(pt0id.getValue(), pt1id.getValue()));
+
+ }
+
+ @Test
+ public void testSearchWithAnd() {
+ String requestId = "a_request_id";
+
+ when(mySrd.getRequestId()).thenReturn(requestId);
+ Patient pt0 = new Patient();
+ pt0.getMeta().setSource("urn:source:0");
+ pt0.setActive(true);
+ IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
+
+ Patient pt1 = new Patient();
+ pt1.getMeta().setSource("urn:source:1");
+ pt1.setActive(true);
+ IIdType pt1id = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless();
+
+ Patient pt2 = new Patient();
+ pt2.getMeta().setSource("urn:source:2");
+ pt2.setActive(true);
+ myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless();
+
+ // Search
+ SearchParameterMap params = new SearchParameterMap();
+ params.setLoadSynchronous(true);
+ params.add(Constants.PARAM_SOURCE, new TokenAndListParam()
+ .addAnd(new TokenParam("urn:source:0"), new TokenParam("@a_request_id")));
+ IBundleProvider result = myPatientDao.search(params);
+ assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(pt0id.getValue()));
+
+ }
+
+ @Test
+ public void testSearchLongRequestId() {
+ String requestId = new RandomStringGenerator.Builder().build().generate(5000);
+ when(mySrd.getRequestId()).thenReturn(requestId);
+
+ Patient pt0 = new Patient();
+ pt0.getMeta().setSource("urn:source:0");
+ pt0.setActive(true);
+ IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
+
+ // Search
+ SearchParameterMap params = new SearchParameterMap();
+ params.setLoadSynchronous(true);
+ params.add(Constants.PARAM_SOURCE, new TokenAndListParam()
+ .addAnd(new TokenParam("urn:source:0"), new TokenParam("#" + requestId)));
+ IBundleProvider result = myPatientDao.search(params);
+ assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(pt0id.getValue()));
+
+ }
+
+ @Test
+ public void testSourceNotPreservedAcrossUpdate() {
+
+ Patient pt0 = new Patient();
+ pt0.getMeta().setSource("urn:source:0");
+ pt0.setActive(true);
+ IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
+
+ pt0 = myPatientDao.read(pt0id);
+ assertEquals("urn:source:0", pt0.getMeta().getSource());
+
+ pt0.getMeta().setSource(null);
+ pt0.setActive(false);
+ myPatientDao.update(pt0);
+
+ pt0 = myPatientDao.read(pt0id.withVersion("2"));
+ assertEquals(null, pt0.getMeta().getSource());
+
+ }
+
+ @Test
+ public void testSourceDisabled() {
+ myDaoConfig.setStoreMetaSourceInformation(DaoConfig.StoreMetaSourceInformation.NONE);
+ when(mySrd.getRequestId()).thenReturn("0000000000000000");
+
+ Patient pt0 = new Patient();
+ pt0.getMeta().setSource("urn:source:0");
+ pt0.setActive(true);
+ IIdType pt0id = myPatientDao.create(pt0, mySrd).getId().toUnqualifiedVersionless();
+
+ pt0 = myPatientDao.read(pt0id);
+ assertEquals(null, pt0.getMeta().getSource());
+
+ pt0.getMeta().setSource("urn:source:1");
+ pt0.setActive(false);
+ myPatientDao.update(pt0);
+
+ pt0 = myPatientDao.read(pt0id.withVersion("2"));
+ assertEquals(null, pt0.getMeta().getSource());
+
+ // Search without source param
+ SearchParameterMap params = new SearchParameterMap();
+ params.setLoadSynchronous(true);
+ IBundleProvider result = myPatientDao.search(params);
+ assertThat(toUnqualifiedVersionlessIdValues(result), containsInAnyOrder(pt0id.getValue()));
+
+ // Search with source param
+ params = new SearchParameterMap();
+ params.setLoadSynchronous(true);
+ params.add(Constants.PARAM_SOURCE, new TokenAndListParam()
+ .addAnd(new TokenParam("urn:source:0"), new TokenParam("@a_request_id")));
+ try {
+ myPatientDao.search(params);
+ } catch (InvalidRequestException e) {
+ assertEquals(e.getMessage(), "The _source parameter is disabled on this server");
+ }
+ }
+
+ @AfterClass
+ public static void afterClassClearContext() {
+ TestUtil.clearAllStaticFieldsForUnitTest();
+ }
+
+ public static void assertConflictException(String theResourceType, ResourceVersionConflictException e) {
+ assertThat(e.getMessage(), matchesPattern(
+ "Unable to delete [a-zA-Z]+/[0-9]+ because at least one resource has a reference to this resource. First reference found was resource " + theResourceType + "/[0-9]+ in path [a-zA-Z]+.[a-zA-Z]+"));
+
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java
index 980bb11d103..f709df0beb8 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java
@@ -187,7 +187,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
table.setDeleted(new Date());
table = myResourceTableDao.saveAndFlush(table);
ResourceHistoryTable newHistory = table.toHistory();
- ResourceHistoryTable currentHistory = myResourceHistoryTableDao.findForIdAndVersion(table.getId(), 1L);
+ ResourceHistoryTable currentHistory = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(table.getId(), 1L);
newHistory.setEncoding(currentHistory.getEncoding());
newHistory.setResource(currentHistory.getResource());
myResourceHistoryTableDao.save(newHistory);
@@ -2724,7 +2724,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
tx.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus theStatus) {
- ResourceHistoryTable table = myResourceHistoryTableDao.findForIdAndVersion(id.getIdPartAsLong(), 1L);
+ ResourceHistoryTable table = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 1L);
String newContent = myFhirCtx.newJsonParser().encodeResourceToString(p);
newContent = newContent.replace("male", "foo");
table.setResource(newContent.getBytes(Charsets.UTF_8));
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java
index 2059b333a50..22208bacc05 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java
@@ -108,20 +108,12 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
return resourceTable.getUpdated().getValueAsString();
});
- myCaptureQueriesListener.clear();
runInTransaction(() -> {
Patient p = new Patient();
p.setId(id.getIdPart());
p.addIdentifier().setSystem("urn:system").setValue("2");
- myPatientDao.update(p);
+ myPatientDao.update(p).getResource();
});
- myCaptureQueriesListener.logSelectQueriesForCurrentThread();
- // TODO: it'd be nice if this was lower
- assertEquals(6, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
- myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
- assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size());
- assertThat(myCaptureQueriesListener.getInsertQueriesForCurrentThread(), empty());
- assertThat(myCaptureQueriesListener.getDeleteQueriesForCurrentThread(), empty());
runInTransaction(() -> {
List allResources = myResourceTableDao.findAll();
@@ -157,7 +149,9 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
// Do a read
{
+ myCaptureQueriesListener.clear();
Patient patient = myPatientDao.read(id, mySrd);
+ myCaptureQueriesListener.logAllQueriesForCurrentThread();
List tl = patient.getMeta().getProfile();
assertEquals(1, tl.size());
assertEquals("http://foo/bar", tl.get(0).getValue());
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSearchDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSearchDaoR4Test.java
index 68886499c8f..996c0dd7b32 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSearchDaoR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSearchDaoR4Test.java
@@ -1,22 +1,23 @@
package ca.uhn.fhir.jpa.dao.r4;
-import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.hamcrest.Matchers.empty;
-import static org.junit.Assert.assertThat;
-
-import java.util.List;
-
+import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.param.StringAndListParam;
+import ca.uhn.fhir.rest.param.StringOrListParam;
+import ca.uhn.fhir.rest.param.StringParam;
+import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.junit.AfterClass;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
-import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
-import ca.uhn.fhir.rest.api.Constants;
-import ca.uhn.fhir.rest.param.*;
-import ca.uhn.fhir.util.TestUtil;
+import java.util.List;
+
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.empty;
+import static org.junit.Assert.assertThat;
public class FhirSearchDaoR4Test extends BaseJpaR4Test {
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java
index c90ddea289e..1d2212bf910 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java
@@ -14,13 +14,9 @@ import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.*;
import ca.uhn.fhir.util.TestUtil;
-import ca.uhn.fhir.validation.FhirValidator;
-import ca.uhn.fhir.validation.ValidationResult;
import org.apache.commons.io.IOUtils;
import org.hamcrest.Matchers;
-import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IAnyResource;
-import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.Bundle.*;
@@ -520,7 +516,7 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
TransactionTemplate template = new TransactionTemplate(myTxManager);
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
template.execute((TransactionCallback) t -> {
- ResourceHistoryTable resourceHistoryTable = myResourceHistoryTableDao.findForIdAndVersion(id.getIdPartAsLong(), id.getVersionIdPartAsLong());
+ ResourceHistoryTable resourceHistoryTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), id.getVersionIdPartAsLong());
resourceHistoryTable.setEncoding(ResourceEncodingEnum.JSON);
try {
resourceHistoryTable.setResource("{\"resourceType\":\"FOO\"}".getBytes("UTF-8"));
@@ -569,7 +565,7 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
assertEquals(1, myPatientDao.search(searchParamMap).size().intValue());
runInTransaction(()->{
- ResourceHistoryTable historyEntry = myResourceHistoryTableDao.findForIdAndVersion(id.getIdPartAsLong(), 3);
+ ResourceHistoryTable historyEntry = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 3);
assertNotNull(historyEntry);
myResourceHistoryTableDao.delete(historyEntry);
});
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java
index 0a068832f70..abdd8dffb15 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java
@@ -64,7 +64,6 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
protected static String ourServerBase;
protected static SearchParamRegistryR4 ourSearchParamRegistry;
private static DatabaseBackedPagingProvider ourPagingProvider;
- protected static ISearchDao mySearchEntityDao;
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
private static GenericWebApplicationContext ourWebApplicationContext;
private static SubscriptionMatcherInterceptor ourSubscriptionMatcherInterceptor;
@@ -165,7 +164,6 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(subsServletHolder.getServlet().getServletConfig().getServletContext());
myValidationSupport = wac.getBean(JpaValidationSupportChainR4.class);
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
- mySearchEntityDao = wac.getBean(ISearchDao.class);
ourSearchParamRegistry = wac.getBean(SearchParamRegistryR4.class);
ourSubscriptionMatcherInterceptor = wac.getBean(SubscriptionMatcherInterceptor.class);
ourSubscriptionMatcherInterceptor.start();
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java
index 6b98ee2a39b..9bc035957ab 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java
@@ -75,6 +75,7 @@ public class ExpungeR4Test extends BaseResourceProviderR4Test {
Patient p = new Patient();
p.setId("PT-ONEVERSION");
p.getMeta().addTag().setSystem("http://foo").setCode("bar");
+ p.getMeta().setSource("http://foo_source");
p.setActive(true);
p.addIdentifier().setSystem("foo").setValue("bar");
p.addName().setFamily("FAM");
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java
index 5649ddded6b..06b8eca19b9 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java
@@ -19,7 +19,6 @@ import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor;
-import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.gclient.StringClientParam;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@@ -3017,7 +3016,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
- ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersion(id1.getIdPartAsLong(), 1);
+ ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id1.getIdPartAsLong(), 1);
myResourceHistoryTableDao.delete(version);
}
});
@@ -3038,15 +3037,18 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
p2.setActive(false);
IIdType id2 = ourClient.create().resource(p2).execute().getId();
+ myCaptureQueriesListener.clear();
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
- ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersion(id1.getIdPartAsLong(), 1);
+ ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id1.getIdPartAsLong(), 1);
myResourceHistoryTableDao.delete(version);
}
});
+ myCaptureQueriesListener.logAllQueriesForCurrentThread();
Bundle bundle = ourClient.search().forResource("Patient").returnBundle(Bundle.class).execute();
+ ourLog.info("Result: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
assertEquals(2, bundle.getTotal());
assertEquals(1, bundle.getEntry().size());
assertEquals(id2.getIdPart(), bundle.getEntry().get(0).getResource().getIdElement().getIdPart());
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
index f647d6b84a5..8dae8204771 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
@@ -28,6 +28,7 @@ import ca.uhn.fhir.jpa.migrate.taskdef.BaseTableColumnTypeTask;
import ca.uhn.fhir.jpa.migrate.taskdef.CalculateHashesTask;
import ca.uhn.fhir.jpa.migrate.tasks.api.BaseMigrationTasks;
import ca.uhn.fhir.jpa.model.entity.*;
+import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.util.VersionEnum;
import java.util.Arrays;
@@ -55,6 +56,29 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks {
init350();
init360();
init400();
+ init410();
+ }
+
+ protected void init410() {
+ Builder version = forVersion(VersionEnum.V4_1_0);
+
+ version.startSectionWithMessage("Processing table: HFJ_RES_VER_PROV");
+ Builder.BuilderAddTableByColumns resVerProv = version.addTableByColumns("HFJ_RES_VER_PROV", "RES_VER_PID");
+ resVerProv.addColumn("RES_VER_PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
+ resVerProv
+ .addForeignKey("FK_RESVERPROV_RESVER_PID")
+ .toColumn("RES_VER_PID")
+ .references("HFJ_RES_VER", "PID");
+ resVerProv.addColumn("RES_PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
+ resVerProv
+ .addForeignKey("FK_RESVERPROV_RES_PID")
+ .toColumn("RES_PID")
+ .references("HFJ_RESOURCE", "RES_ID");
+ resVerProv.addColumn("SOURCE_URI").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, ResourceHistoryProvenanceEntity.SOURCE_URI_LENGTH);
+ resVerProv.addColumn("REQUEST_ID").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, Constants.REQUEST_ID_LENGTH);
+ resVerProv.addIndex("IDX_RESVERPROV_SOURCEURI").unique(false).withColumns("SOURCE_URI");
+ resVerProv.addIndex("IDX_RESVERPROV_REQUESTID").unique(false).withColumns("REQUEST_ID");
+
}
protected void init400() {
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryProvenanceEntity.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryProvenanceEntity.java
new file mode 100644
index 00000000000..19527c96c18
--- /dev/null
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryProvenanceEntity.java
@@ -0,0 +1,63 @@
+package ca.uhn.fhir.jpa.model.entity;
+
+import ca.uhn.fhir.rest.api.Constants;
+
+import javax.persistence.*;
+
+@Table(name = "HFJ_RES_VER_PROV", indexes = {
+ @Index(name = "IDX_RESVERPROV_SOURCEURI", columnList = "SOURCE_URI"),
+ @Index(name = "IDX_RESVERPROV_REQUESTID", columnList = "REQUEST_ID")
+})
+@Entity
+public class ResourceHistoryProvenanceEntity {
+
+ public static final int SOURCE_URI_LENGTH = 100;
+
+ @Id
+ @Column(name = "RES_VER_PID")
+ private Long myId;
+ @OneToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "RES_VER_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_RESVERPROV_RESVER_PID"), nullable = false, insertable = false, updatable = false)
+ @MapsId
+ private ResourceHistoryTable myResourceHistoryTable;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "RES_PID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_RESVERPROV_RES_PID"), nullable = false)
+ private ResourceTable myResourceTable;
+ @Column(name = "SOURCE_URI", length = SOURCE_URI_LENGTH, nullable = true)
+ private String mySourceUri;
+ @Column(name = "REQUEST_ID", length = Constants.REQUEST_ID_LENGTH, nullable = true)
+ private String myRequestId;
+
+ public ResourceTable getResourceTable() {
+ return myResourceTable;
+ }
+
+ public void setResourceTable(ResourceTable theResourceTable) {
+ myResourceTable = theResourceTable;
+ }
+
+ public ResourceHistoryTable getResourceHistoryTable() {
+ return myResourceHistoryTable;
+ }
+
+ public void setResourceHistoryTable(ResourceHistoryTable theResourceHistoryTable) {
+ myResourceHistoryTable = theResourceHistoryTable;
+ }
+
+ public String getSourceUri() {
+ return mySourceUri;
+ }
+
+ public void setSourceUri(String theSourceUri) {
+ mySourceUri = theSourceUri;
+ }
+
+ public String getRequestId() {
+ return myRequestId;
+ }
+
+ public void setRequestId(String theRequestId) {
+ myRequestId = theRequestId;
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java
index 7411582608a..42c3de0250b 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java
@@ -20,8 +20,6 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
-import ca.uhn.fhir.model.primitive.IdDt;
-import ca.uhn.fhir.rest.api.Constants;
import org.hibernate.annotations.OptimisticLock;
import javax.persistence.*;
@@ -29,7 +27,6 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
-//@formatter:off
@Entity
@Table(name = "HFJ_RES_VER", uniqueConstraints = {
@UniqueConstraint(name = ResourceHistoryTable.IDX_RESVER_ID_VER, columnNames = {"RES_ID", "RES_VER"})
@@ -38,16 +35,14 @@ import java.util.Collection;
@Index(name = "IDX_RESVER_ID_DATE", columnList = "RES_ID,RES_UPDATED"),
@Index(name = "IDX_RESVER_DATE", columnList = "RES_UPDATED")
})
-//@formatter:on
public class ResourceHistoryTable extends BaseHasResource implements Serializable {
- private static final long serialVersionUID = 1L;
public static final String IDX_RESVER_ID_VER = "IDX_RESVER_ID_VER";
/**
* @see ResourceEncodingEnum
*/
public static final int ENCODING_COL_LENGTH = 5;
-
+ private static final long serialVersionUID = 1L;
@Id
@SequenceGenerator(name = "SEQ_RESOURCE_HISTORY_ID", sequenceName = "SEQ_RESOURCE_HISTORY_ID")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_RESOURCE_HISTORY_ID")
@@ -76,10 +71,21 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
@OptimisticLock(excluded = true)
private ResourceEncodingEnum myEncoding;
+ @OneToOne(mappedBy = "myResourceHistoryTable", cascade = {CascadeType.REMOVE})
+ private ResourceHistoryProvenanceEntity myProvenance;
+
public ResourceHistoryTable() {
super();
}
+ public ResourceHistoryProvenanceEntity getProvenance() {
+ return myProvenance;
+ }
+
+ public void setProvenance(ResourceHistoryProvenanceEntity theProvenance) {
+ myProvenance = theProvenance;
+ }
+
public void addTag(ResourceHistoryTag theTag) {
for (ResourceHistoryTag next : getTags()) {
if (next.equals(theTag)) {
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
index e27ae857298..ff61a5849b0 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
@@ -214,6 +214,10 @@ public class ResourceTable extends BaseHasResource implements Serializable {
@Version
@Column(name = "RES_VER")
private long myVersion;
+ @OneToMany(mappedBy = "myResourceTable", fetch = FetchType.LAZY)
+ private Collection myProvenance;
+ @Transient
+ private transient ResourceHistoryTable myCurrentVersionEntity;
public Collection getResourceLinksAsTarget() {
if (myResourceLinksAsTarget == null) {
@@ -610,4 +614,19 @@ public class ResourceTable extends BaseHasResource implements Serializable {
}
}
+ /**
+ * This is a convenience to avoid loading the version a second time within a single transaction. It is
+ * not persisted.
+ */
+ public void setCurrentVersionEntity(ResourceHistoryTable theCurrentVersionEntity) {
+ myCurrentVersionEntity = theCurrentVersionEntity;
+ }
+
+ /**
+ * This is a convenience to avoid loading the version a second time within a single transaction. It is
+ * not persisted.
+ */
+ public ResourceHistoryTable getCurrentVersionEntity() {
+ return myCurrentVersionEntity;
+ }
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/TagTypeEnum.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/TagTypeEnum.java
index 22dba8eda89..c7301b16f60 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/TagTypeEnum.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/TagTypeEnum.java
@@ -32,5 +32,5 @@ public enum TagTypeEnum {
PROFILE,
SECURITY_LABEL
-
+
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java
index e97c14313d5..a85e709bd36 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java
@@ -228,4 +228,15 @@ public class JpaConstants {
*/
public static final String EXT_EXTERNALIZED_BINARY_ID = "http://hapifhir.io/fhir/StructureDefinition/externalized-binary-id";
+ /**
+ *
+ * This extension represents the equivalent of the
+ * Resource.meta.source
field within R4+ resources, and is for
+ * use in DSTU3 resources. It should contain a value of type uri
+ * and will be located on the Resource.meta
+ *
+ */
+ public static final String EXT_META_SOURCE = "http://hapifhir.io/fhir/StructureDefinition/resource-meta-source";
+
+
}
diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/TagTypeEnumTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/TagTypeEnumTest.java
index fa03d66c522..1c8c701b926 100644
--- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/TagTypeEnumTest.java
+++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/TagTypeEnumTest.java
@@ -1,6 +1,5 @@
package ca.uhn.fhir.jpa.model.entity;
-import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import ca.uhn.fhir.util.TestUtil;
import org.junit.AfterClass;
import org.junit.Test;
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
index c43860e4296..7a8b442e753 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
@@ -58,7 +58,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
-import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServlet;
@@ -1124,7 +1123,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer
*
*
+ * ${requestId} |
+ * The request ID assigned to this request (either automatically, or via the X-Request-ID header in the request) |
+ *
+ *
* ${operationType} |
* A code indicating the operation type for this request, e.g. "read", "history-instance",
* "extended-operation-instance", etc.) |
@@ -323,6 +327,8 @@ public class LoggingInterceptor {
long time = System.currentTimeMillis() - startTime.getTime();
return Long.toString(time);
}
+ } else if ("requestId".equals(theKey)) {
+ return myRequestDetails.getRequestId();
}
return "!VAL!";
diff --git a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/model/dstu2/resource/BaseResource.java b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/model/dstu2/resource/BaseResource.java
index 6235ee5deba..1834c456688 100644
--- a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/model/dstu2/resource/BaseResource.java
+++ b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/model/dstu2/resource/BaseResource.java
@@ -98,6 +98,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
return myContained;
}
+ @Override
public IdDt getId() {
if (myId == null) {
myId = new IdDt();
@@ -121,7 +122,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
@Override
public IBaseMetaType getMeta() {
return new IBaseMetaType() {
-
+
private static final long serialVersionUID = 1L;
@Override
@@ -132,12 +133,12 @@ public abstract class BaseResource extends BaseElement implements IResource {
newTagList.addAll(existingTagList);
}
ResourceMetadataKeyEnum.PROFILES.put(BaseResource.this, newTagList);
-
+
IdDt tag = new IdDt(theProfile);
newTagList.add(tag);
return this;
}
-
+
@Override
public IBaseCoding addSecurity() {
List tagList = ResourceMetadataKeyEnum.SECURITY_LABELS.get(BaseResource.this);
@@ -149,7 +150,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
tagList.add(tag);
return tag;
}
-
+
@Override
public IBaseCoding addTag() {
TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(BaseResource.this);
@@ -161,17 +162,17 @@ public abstract class BaseResource extends BaseElement implements IResource {
tagList.add(tag);
return tag;
}
-
+
@Override
public List getFormatCommentsPost() {
return Collections.emptyList();
}
-
+
@Override
public List getFormatCommentsPre() {
return Collections.emptyList();
}
-
+
@Override
public Date getLastUpdated() {
InstantDt lu = ResourceMetadataKeyEnum.UPDATED.get(BaseResource.this);
@@ -180,7 +181,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
}
return null;
}
-
+
@Override
public List extends IPrimitiveType> getProfile() {
ArrayList> retVal = new ArrayList>();
@@ -193,7 +194,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
}
return Collections.unmodifiableList(retVal);
}
-
+
@Override
public List extends IBaseCoding> getSecurity() {
ArrayList retVal = new ArrayList();
@@ -206,7 +207,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
}
return Collections.unmodifiableList(retVal);
}
-
+
@Override
public IBaseCoding getSecurity(String theSystem, String theCode) {
for (IBaseCoding next : getSecurity()) {
@@ -216,7 +217,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
}
return null;
}
-
+
@Override
public List extends IBaseCoding> getTag() {
ArrayList retVal = new ArrayList();
@@ -229,7 +230,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
}
return Collections.unmodifiableList(retVal);
}
-
+
@Override
public IBaseCoding getTag(String theSystem, String theCode) {
for (IBaseCoding next : getTag()) {
@@ -239,22 +240,22 @@ public abstract class BaseResource extends BaseElement implements IResource {
}
return null;
}
-
+
@Override
public String getVersionId() {
return getId().getVersionIdPart();
}
-
+
@Override
public boolean hasFormatComment() {
return false;
}
-
+
@Override
public boolean isEmpty() {
return getResourceMetadata().isEmpty();
}
-
+
@Override
public IBaseMetaType setLastUpdated(Date theHeaderDateValue) {
if (theHeaderDateValue == null) {
@@ -264,7 +265,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
}
return this;
}
-
+
@Override
public IBaseMetaType setVersionId(String theVersionId) {
setId(getId().withVersion(theVersionId));
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptorDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptorDstu2Test.java
index 034f46d31a5..28e5d557339 100644
--- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptorDstu2Test.java
+++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptorDstu2Test.java
@@ -152,6 +152,26 @@ public class LoggingInterceptorDstu2Test {
assertEquals("read - - Patient/1 - ", captor.getValue());
}
+ @Test
+ public void testRequestId() throws Exception {
+
+ LoggingInterceptor interceptor = new LoggingInterceptor();
+ interceptor.setMessageFormat("${requestId}");
+ servlet.getInterceptorService().registerInterceptor(interceptor);
+
+ Logger logger = mock(Logger.class);
+ interceptor.setLogger(logger);
+
+ HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1");
+
+ HttpResponse status = ourClient.execute(httpGet);
+ IOUtils.closeQuietly(status.getEntity().getContent());
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(String.class);
+ verify(logger, timeout(1000).times(1)).info(captor.capture());
+ assertEquals(Constants.REQUEST_ID_LENGTH, captor.getValue().length());
+ }
+
@Test
public void testRequestProcessingTime() throws Exception {
diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/InterceptorDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/InterceptorDstu3Test.java
index 8c0408fd1b2..5c3e5bc9426 100644
--- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/InterceptorDstu3Test.java
+++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/InterceptorDstu3Test.java
@@ -200,7 +200,6 @@ public class InterceptorDstu3Test {
when(myInterceptor1.outgoingResponse(nullable(RequestDetails.class), nullable(ResponseDetails.class), nullable(HttpServletRequest.class), nullable(HttpServletResponse.class))).thenReturn(true);
when(myInterceptor1.outgoingResponse(nullable(RequestDetails.class), nullable(IBaseResource.class), nullable(HttpServletRequest.class), nullable(HttpServletResponse.class))).thenReturn(true);
when(myInterceptor1.outgoingResponse(nullable(RequestDetails.class), nullable(HttpServletRequest.class), nullable(HttpServletResponse.class))).thenReturn(true);
- doThrow(new NullPointerException("FOO")).when(myInterceptor1).processingCompletedNormally(any());
String input = createInput();
diff --git a/pom.xml b/pom.xml
index ea8ef24303c..1bb1c3b3ff9 100755
--- a/pom.xml
+++ b/pom.xml
@@ -594,7 +594,7 @@
9.4.14.v20181114
3.0.2
- 5.4.2.Final
+ 5.4.4.Final
5.11.1.Final
5.5.5
@@ -1281,7 +1281,7 @@
org.mockito
mockito-core
- 2.28.2
+ 3.0.0
org.postgresql
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 78e16239557..fc505a69c21 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -7,6 +7,15 @@
+
+ The version of a few dependencies have been bumped to the
+ latest versions (dependent HAPI modules listed in brackets):
+
+ Hibernate Core (Core): 5.4.2.Final -> 5.4.4.Final
+
+ ]]>
+
-
+
+ New Feature:
+ The JPA server now saves and supports searching on Resource.meta.source
. The server automatically
+ appends the Request ID as a hash value on the URI as well in order to provide request level tracking. Searches
+ can use either the source URI, the request ID, or both.
+ ]]>
+
+
The email Subscription deliverer now respects the payload property of the subscription
when deciding how to encode the resource being sent. Thanks to Sean McIlvenna for the
pull request!
@@ -543,7 +560,7 @@
not any values contained within. This has been corrected.
- The JPA terminology service can now detect when Hibvernate Search (Lucene)
+ The JPA terminology service can now detect when Hibernate Search (Lucene)
is not enabled, and will perform simple ValueSet expansions without relying
on Hibenrate Search in such cases.