Add support for Resource.meta.source (#1438)
* Work on indexing source * Work on tests * Refactor query count tests * Unit test fixes * Add some tests * DAO fix * Fix compile error * Unit test fix * Cleanup * Test fix * Fix compile error * One more test fix
This commit is contained in:
parent
ce44115152
commit
9428430822
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.api;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class Constants {
|
public class Constants {
|
||||||
|
@ -168,6 +169,7 @@ public class Constants {
|
||||||
public static final String PARAM_SORT = "_sort";
|
public static final String PARAM_SORT = "_sort";
|
||||||
public static final String PARAM_SORT_ASC = "_sort:asc";
|
public static final String PARAM_SORT_ASC = "_sort:asc";
|
||||||
public static final String PARAM_SORT_DESC = "_sort:desc";
|
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_SUMMARY = "_summary";
|
||||||
public static final String PARAM_TAG = "_tag";
|
public static final String PARAM_TAG = "_tag";
|
||||||
public static final String PARAM_TAGS = "_tags";
|
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 String CACHE_CONTROL_PRIVATE = "private";
|
||||||
public static final int STATUS_HTTP_412_PAYLOAD_TOO_LARGE = 413;
|
public static final int STATUS_HTTP_412_PAYLOAD_TOO_LARGE = 413;
|
||||||
public static final String OPERATION_NAME_GRAPHQL = "$graphql";
|
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 {
|
static {
|
||||||
CHARSET_UTF8 = Charset.forName(CHARSET_NAME_UTF8);
|
CHARSET_UTF8 = StandardCharsets.UTF_8;
|
||||||
CHARSET_US_ASCII = Charset.forName("ISO-8859-1");
|
CHARSET_US_ASCII = StandardCharsets.ISO_8859_1;
|
||||||
|
|
||||||
HashMap<Integer, String> statusNames = new HashMap<>();
|
HashMap<Integer, String> statusNames = new HashMap<>();
|
||||||
statusNames.put(200, "OK");
|
statusNames.put(200, "OK");
|
||||||
|
@ -239,7 +245,6 @@ public class Constants {
|
||||||
statusNames.put(300, "Multiple Choices");
|
statusNames.put(300, "Multiple Choices");
|
||||||
statusNames.put(301, "Moved Permanently");
|
statusNames.put(301, "Moved Permanently");
|
||||||
statusNames.put(302, "Found");
|
statusNames.put(302, "Found");
|
||||||
statusNames.put(302, "Moved Temporarily");
|
|
||||||
statusNames.put(303, "See Other");
|
statusNames.put(303, "See Other");
|
||||||
statusNames.put(304, "Not Modified");
|
statusNames.put(304, "Not Modified");
|
||||||
statusNames.put(305, "Use Proxy");
|
statusNames.put(305, "Use Proxy");
|
||||||
|
@ -259,9 +264,7 @@ public class Constants {
|
||||||
statusNames.put(411, "Length Required");
|
statusNames.put(411, "Length Required");
|
||||||
statusNames.put(412, "Precondition Failed");
|
statusNames.put(412, "Precondition Failed");
|
||||||
statusNames.put(413, "Payload Too Large");
|
statusNames.put(413, "Payload Too Large");
|
||||||
statusNames.put(413, "Request Entity Too Large");
|
|
||||||
statusNames.put(414, "URI Too Long");
|
statusNames.put(414, "URI Too Long");
|
||||||
statusNames.put(414, "Request-URI Too Long");
|
|
||||||
statusNames.put(415, "Unsupported Media Type");
|
statusNames.put(415, "Unsupported Media Type");
|
||||||
statusNames.put(416, "Requested range not satisfiable");
|
statusNames.put(416, "Requested range not satisfiable");
|
||||||
statusNames.put(417, "Expectation Failed");
|
statusNames.put(417, "Expectation Failed");
|
||||||
|
|
|
@ -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<IBase> 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<IBase> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -57,4 +57,5 @@ public interface IBaseMetaType extends ICompositeType {
|
||||||
*/
|
*/
|
||||||
IBaseCoding getSecurity(String theSystem, String theCode);
|
IBaseCoding getSecurity(String theSystem, String theCode);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.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.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.matchesFound=Matches found!
|
||||||
ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoConceptMapDstu3.noMatchesFound=No matches found!
|
ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoConceptMapDstu3.noMatchesFound=No matches found!
|
||||||
|
|
|
@ -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.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
import ca.uhn.fhir.util.CoverageIgnore;
|
import ca.uhn.fhir.util.CoverageIgnore;
|
||||||
|
import ca.uhn.fhir.util.MetaUtil;
|
||||||
import ca.uhn.fhir.util.StopWatch;
|
import ca.uhn.fhir.util.StopWatch;
|
||||||
import ca.uhn.fhir.util.XmlUtil;
|
import ca.uhn.fhir.util.XmlUtil;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
@ -130,6 +131,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
@Autowired
|
@Autowired
|
||||||
protected IForcedIdDao myForcedIdDao;
|
protected IForcedIdDao myForcedIdDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
protected IResourceProvenanceDao myResourceProvenanceDao;
|
||||||
|
@Autowired
|
||||||
protected ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
protected ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
protected ISearchParamRegistry mySerarchParamRegistry;
|
protected ISearchParamRegistry mySerarchParamRegistry;
|
||||||
|
@ -282,6 +285,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void findMatchingTagIds(RequestDetails theRequest, String theResourceName, IIdType theResourceId, Set<Long> tagIds, Class<? extends BaseTag> entityClass) {
|
private void findMatchingTagIds(RequestDetails theRequest, String theResourceName, IIdType theResourceId, Set<Long> tagIds, Class<? extends BaseTag> entityClass) {
|
||||||
|
@ -611,7 +615,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
if (theEntity.getId() == null) {
|
if (theEntity.getId() == null) {
|
||||||
changed = true;
|
changed = true;
|
||||||
} else {
|
} 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) {
|
if (currentHistoryVersion == null || currentHistoryVersion.getResource() == null) {
|
||||||
changed = true;
|
changed = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -832,11 +839,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
metaSnapshotModeTokens = Collections.singleton(TagTypeEnum.PROFILE);
|
metaSnapshotModeTokens = Collections.singleton(TagTypeEnum.PROFILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metaSnapshotModeTokens.contains(theTag.getTag().getTagType())) {
|
return metaSnapshotModeTokens.contains(theTag.getTag().getTagType());
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -851,10 +854,12 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
public <R extends IBaseResource> R toResource(Class<R> theResourceType, IBaseResourceEntity theEntity, Collection<ResourceTag> theTagList, boolean theForHistoryOperation) {
|
public <R extends IBaseResource> R toResource(Class<R> theResourceType, IBaseResourceEntity theEntity, Collection<ResourceTag> theTagList, boolean theForHistoryOperation) {
|
||||||
|
|
||||||
// 1. get resource, it's encoding and the tags if any
|
// 1. get resource, it's encoding and the tags if any
|
||||||
byte[] resourceBytes = null;
|
byte[] resourceBytes;
|
||||||
ResourceEncodingEnum resourceEncoding = null;
|
ResourceEncodingEnum resourceEncoding;
|
||||||
Collection<? extends BaseTag> myTagList = null;
|
Collection<? extends BaseTag> myTagList;
|
||||||
Long version = null;
|
Long version;
|
||||||
|
String provenanceSourceUri = null;
|
||||||
|
String provenanceRequestId = null;
|
||||||
|
|
||||||
if (theEntity instanceof ResourceHistoryTable) {
|
if (theEntity instanceof ResourceHistoryTable) {
|
||||||
ResourceHistoryTable history = (ResourceHistoryTable) theEntity;
|
ResourceHistoryTable history = (ResourceHistoryTable) theEntity;
|
||||||
|
@ -862,14 +867,20 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
resourceEncoding = history.getEncoding();
|
resourceEncoding = history.getEncoding();
|
||||||
myTagList = history.getTags();
|
myTagList = history.getTags();
|
||||||
version = history.getVersion();
|
version = history.getVersion();
|
||||||
|
if (history.getProvenance() != null) {
|
||||||
|
provenanceRequestId = history.getProvenance().getRequestId();
|
||||||
|
provenanceSourceUri = history.getProvenance().getSourceUri();
|
||||||
|
}
|
||||||
} else if (theEntity instanceof ResourceTable) {
|
} else if (theEntity instanceof ResourceTable) {
|
||||||
ResourceTable resource = (ResourceTable) theEntity;
|
ResourceTable resource = (ResourceTable) theEntity;
|
||||||
version = theEntity.getVersion();
|
version = theEntity.getVersion();
|
||||||
ResourceHistoryTable history = myResourceHistoryTableDao.findForIdAndVersion(theEntity.getId(), version);
|
ResourceHistoryTable history = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(theEntity.getId(), version);
|
||||||
|
((ResourceTable)theEntity).setCurrentVersionEntity(history);
|
||||||
|
|
||||||
while (history == null) {
|
while (history == null) {
|
||||||
if (version > 1L) {
|
if (version > 1L) {
|
||||||
version--;
|
version--;
|
||||||
history = myResourceHistoryTableDao.findForIdAndVersion(theEntity.getId(), version);
|
history = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(theEntity.getId(), version);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -878,12 +889,18 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
resourceEncoding = history.getEncoding();
|
resourceEncoding = history.getEncoding();
|
||||||
myTagList = resource.getTags();
|
myTagList = resource.getTags();
|
||||||
version = history.getVersion();
|
version = history.getVersion();
|
||||||
|
if (history.getProvenance() != null) {
|
||||||
|
provenanceRequestId = history.getProvenance().getRequestId();
|
||||||
|
provenanceSourceUri = history.getProvenance().getSourceUri();
|
||||||
|
}
|
||||||
} else if (theEntity instanceof ResourceSearchView) {
|
} else if (theEntity instanceof ResourceSearchView) {
|
||||||
// This is the search View
|
// This is the search View
|
||||||
ResourceSearchView myView = (ResourceSearchView) theEntity;
|
ResourceSearchView view = (ResourceSearchView) theEntity;
|
||||||
resourceBytes = myView.getResource();
|
resourceBytes = view.getResource();
|
||||||
resourceEncoding = myView.getEncoding();
|
resourceEncoding = view.getEncoding();
|
||||||
version = myView.getVersion();
|
version = view.getVersion();
|
||||||
|
provenanceRequestId = view.getProvenanceRequestId();
|
||||||
|
provenanceSourceUri = view.getProvenanceSourceUri();
|
||||||
if (theTagList == null)
|
if (theTagList == null)
|
||||||
myTagList = new HashSet<>();
|
myTagList = new HashSet<>();
|
||||||
else
|
else
|
||||||
|
@ -954,6 +971,23 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
retVal = populateResourceMetadataRi(resourceType, theEntity, myTagList, theForHistoryOperation, res, version);
|
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<String> value = (IPrimitiveType<String>) 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;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1097,6 +1131,40 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
|
|
||||||
ourLog.debug("Saving history entry {}", historyEntry.getIdDt());
|
ourLog.debug("Saving history entry {}", historyEntry.getIdDt());
|
||||||
myResourceHistoryTableDao.save(historyEntry);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -269,7 +269,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
@Override
|
@Override
|
||||||
public DaoMethodOutcome delete(IIdType theId, RequestDetails theRequestDetails) {
|
public DaoMethodOutcome delete(IIdType theId, RequestDetails theRequestDetails) {
|
||||||
DeleteConflictList deleteConflicts = new DeleteConflictList();
|
DeleteConflictList deleteConflicts = new DeleteConflictList();
|
||||||
deleteConflicts.setResourceIdMarkedForDeletion(theId);
|
if (theId != null && isNotBlank(theId.getValue())) {
|
||||||
|
deleteConflicts.setResourceIdMarkedForDeletion(theId);
|
||||||
|
}
|
||||||
|
|
||||||
StopWatch w = new StopWatch();
|
StopWatch w = new StopWatch();
|
||||||
|
|
||||||
DaoMethodOutcome retVal = delete(theId, deleteConflicts, theRequestDetails);
|
DaoMethodOutcome retVal = delete(theId, deleteConflicts, theRequestDetails);
|
||||||
|
@ -673,7 +676,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
doMetaAdd(theMetaAdd, latestVersion);
|
doMetaAdd(theMetaAdd, latestVersion);
|
||||||
|
|
||||||
// Also update history entry
|
// Also update history entry
|
||||||
ResourceHistoryTable history = myResourceHistoryTableDao.findForIdAndVersion(entity.getId(), entity.getVersion());
|
ResourceHistoryTable history = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(entity.getId(), entity.getVersion());
|
||||||
doMetaAdd(theMetaAdd, history);
|
doMetaAdd(theMetaAdd, history);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -705,7 +708,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
doMetaDelete(theMetaDel, latestVersion);
|
doMetaDelete(theMetaDel, latestVersion);
|
||||||
|
|
||||||
// Also update history entry
|
// Also update history entry
|
||||||
ResourceHistoryTable history = myResourceHistoryTableDao.findForIdAndVersion(entity.getId(), entity.getVersion());
|
ResourceHistoryTable history = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(entity.getId(), entity.getVersion());
|
||||||
doMetaDelete(theMetaDel, history);
|
doMetaDelete(theMetaDel, history);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -151,6 +151,7 @@ public class DaoConfig {
|
||||||
*/
|
*/
|
||||||
private boolean myPreExpandValueSetsExperimental = false;
|
private boolean myPreExpandValueSetsExperimental = false;
|
||||||
private boolean myFilterParameterEnabled = false;
|
private boolean myFilterParameterEnabled = false;
|
||||||
|
private StoreMetaSourceInformation myStoreMetaSourceInformation = StoreMetaSourceInformation.SOURCE_URI_AND_REQUEST_ID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -974,6 +975,7 @@ public class DaoConfig {
|
||||||
* and other FHIR features may not behave as expected when referential integrity is not
|
* and other FHIR features may not behave as expected when referential integrity is not
|
||||||
* preserved. Use this feature with caution.
|
* preserved. Use this feature with caution.
|
||||||
* </p>
|
* </p>
|
||||||
|
*
|
||||||
* @see CascadingDeleteInterceptor
|
* @see CascadingDeleteInterceptor
|
||||||
*/
|
*/
|
||||||
public boolean isEnforceReferentialIntegrityOnDelete() {
|
public boolean isEnforceReferentialIntegrityOnDelete() {
|
||||||
|
@ -988,6 +990,7 @@ public class DaoConfig {
|
||||||
* and other FHIR features may not behave as expected when referential integrity is not
|
* and other FHIR features may not behave as expected when referential integrity is not
|
||||||
* preserved. Use this feature with caution.
|
* preserved. Use this feature with caution.
|
||||||
* </p>
|
* </p>
|
||||||
|
*
|
||||||
* @see CascadingDeleteInterceptor
|
* @see CascadingDeleteInterceptor
|
||||||
*/
|
*/
|
||||||
public void setEnforceReferentialIntegrityOnDelete(boolean theEnforceReferentialIntegrityOnDelete) {
|
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
|
* The expunge batch size (default 800) determines the number of records deleted within a single transaction by the
|
||||||
* expunge operation.
|
* expunge operation.
|
||||||
*/
|
*/
|
||||||
public void setExpungeBatchSize(int theExpungeBatchSize) {
|
public int getExpungeBatchSize() {
|
||||||
myExpungeBatchSize = theExpungeBatchSize;
|
return myExpungeBatchSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The expunge batch size (default 800) determines the number of records deleted within a single transaction by the
|
* The expunge batch size (default 800) determines the number of records deleted within a single transaction by the
|
||||||
* expunge operation.
|
* expunge operation.
|
||||||
*/
|
*/
|
||||||
public int getExpungeBatchSize() {
|
public void setExpungeBatchSize(int theExpungeBatchSize) {
|
||||||
return myExpungeBatchSize;
|
myExpungeBatchSize = theExpungeBatchSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1656,6 +1659,54 @@ public class DaoConfig {
|
||||||
myFilterParameterEnabled = theFilterParameterEnabled;
|
myFilterParameterEnabled = theFilterParameterEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If enabled, resource source information (<code>Resource.meta.source</code>) 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.
|
||||||
|
* <p>
|
||||||
|
* Default is {@link StoreMetaSourceInformation#SOURCE_URI_AND_REQUEST_ID}
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public StoreMetaSourceInformation getStoreMetaSourceInformation() {
|
||||||
|
return myStoreMetaSourceInformation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If enabled, resource source information (<code>Resource.meta.source</code>) 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.
|
||||||
|
* <p>
|
||||||
|
* Default is {@link StoreMetaSourceInformation#SOURCE_URI_AND_REQUEST_ID}
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
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 {
|
public enum IndexEnabledEnum {
|
||||||
ENABLED,
|
ENABLED,
|
||||||
DISABLED
|
DISABLED
|
||||||
|
|
|
@ -823,6 +823,48 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
return null;
|
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<ResourceTable, ResourceHistoryProvenanceEntity> join = myResourceTableRoot.join("myProvenance", JoinType.LEFT);
|
||||||
|
|
||||||
|
List<Predicate> 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,
|
private Predicate addPredicateString(String theResourceName,
|
||||||
String theParamName,
|
String theParamName,
|
||||||
List<? extends IQueryParameterType> theList) {
|
List<? extends IQueryParameterType> theList) {
|
||||||
|
@ -2680,6 +2722,14 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidRequestException("Unexpected search parameter type encountered, expected string type for language search");
|
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)) ||
|
// else if ((searchParam.getName().equals(Constants.PARAM_TAG)) ||
|
||||||
// (searchParam.equals(Constants.PARAM_SECURITY))) {
|
// (searchParam.equals(Constants.PARAM_SECURITY))) {
|
||||||
|
@ -2798,6 +2848,12 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
addPredicateTag(theAndOrParams, theParamName);
|
addPredicateTag(theAndOrParams, theParamName);
|
||||||
|
|
||||||
|
} else if (theParamName.equals(Constants.PARAM_SOURCE)) {
|
||||||
|
|
||||||
|
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
|
||||||
|
addPredicateSource(nextAnd, SearchFilterParser.CompareOperation.eq, theRequest);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName);
|
RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName);
|
||||||
|
|
|
@ -66,8 +66,8 @@ public interface IResourceHistoryTableDao extends JpaRepository<ResourceHistoryT
|
||||||
@Param("type") String theType
|
@Param("type") String theType
|
||||||
);
|
);
|
||||||
|
|
||||||
@Query("SELECT t FROM ResourceHistoryTable t WHERE t.myResourceId = :id AND t.myResourceVersion = :version")
|
@Query("SELECT t FROM ResourceHistoryTable t LEFT OUTER JOIN FETCH t.myProvenance WHERE t.myResourceId = :id AND t.myResourceVersion = :version")
|
||||||
ResourceHistoryTable findForIdAndVersion(@Param("id") long theId, @Param("version") long theVersion);
|
ResourceHistoryTable findForIdAndVersionAndFetchProvenance(@Param("id") long theId, @Param("version") long theVersion);
|
||||||
|
|
||||||
@Query("SELECT t.myId FROM ResourceHistoryTable t WHERE t.myResourceId = :resId AND t.myResourceVersion != :dontWantVersion")
|
@Query("SELECT t.myId FROM ResourceHistoryTable t WHERE t.myResourceId = :resId AND t.myResourceVersion != :dontWantVersion")
|
||||||
Slice<Long> findForResourceId(Pageable thePage, @Param("resId") Long theId, @Param("dontWantVersion") Long theDontWantVersion);
|
Slice<Long> findForResourceId(Pageable thePage, @Param("resId") Long theId, @Param("dontWantVersion") Long theDontWantVersion);
|
||||||
|
|
|
@ -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<ResourceHistoryProvenanceEntity, Long> {
|
||||||
|
|
||||||
|
}
|
|
@ -120,6 +120,7 @@ public class ExpungeEverythingService {
|
||||||
counter.addAndGet(expungeEverythingByType(ResourceHistoryTag.class));
|
counter.addAndGet(expungeEverythingByType(ResourceHistoryTag.class));
|
||||||
counter.addAndGet(expungeEverythingByType(ResourceTag.class));
|
counter.addAndGet(expungeEverythingByType(ResourceTag.class));
|
||||||
counter.addAndGet(expungeEverythingByType(TagDefinition.class));
|
counter.addAndGet(expungeEverythingByType(TagDefinition.class));
|
||||||
|
counter.addAndGet(expungeEverythingByType(ResourceHistoryProvenanceEntity.class));
|
||||||
counter.addAndGet(expungeEverythingByType(ResourceHistoryTable.class));
|
counter.addAndGet(expungeEverythingByType(ResourceHistoryTable.class));
|
||||||
counter.addAndGet(expungeEverythingByType(ResourceTable.class));
|
counter.addAndGet(expungeEverythingByType(ResourceTable.class));
|
||||||
myTxTemplate.execute(t -> {
|
myTxTemplate.execute(t -> {
|
||||||
|
|
|
@ -87,6 +87,8 @@ class ResourceExpungeService implements IResourceExpungeService {
|
||||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||||
@Autowired
|
@Autowired
|
||||||
private DaoRegistry myDaoRegistry;
|
private DaoRegistry myDaoRegistry;
|
||||||
|
@Autowired
|
||||||
|
private IResourceProvenanceDao myResourceHistoryProvenanceTableDao;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
|
@ -94,7 +96,7 @@ class ResourceExpungeService implements IResourceExpungeService {
|
||||||
Pageable page = PageRequest.of(0, theRemainingCount);
|
Pageable page = PageRequest.of(0, theRemainingCount);
|
||||||
if (theResourceId != null) {
|
if (theResourceId != null) {
|
||||||
if (theVersion != null) {
|
if (theVersion != null) {
|
||||||
return toSlice(myResourceHistoryTableDao.findForIdAndVersion(theResourceId, theVersion));
|
return toSlice(myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(theResourceId, theVersion));
|
||||||
} else {
|
} else {
|
||||||
return myResourceHistoryTableDao.findIdsOfPreviousVersionsOfResourceId(page, theResourceId);
|
return myResourceHistoryTableDao.findIdsOfPreviousVersionsOfResourceId(page, theResourceId);
|
||||||
}
|
}
|
||||||
|
@ -146,6 +148,10 @@ class ResourceExpungeService implements IResourceExpungeService {
|
||||||
|
|
||||||
callHooks(theRequestDetails, theRemainingCount, version, id);
|
callHooks(theRequestDetails, theRemainingCount, version, id);
|
||||||
|
|
||||||
|
if (version.getProvenance() != null) {
|
||||||
|
myResourceHistoryProvenanceTableDao.delete(version.getProvenance());
|
||||||
|
}
|
||||||
|
|
||||||
myResourceHistoryTagDao.deleteAll(version.getTags());
|
myResourceHistoryTagDao.deleteAll(version.getTags());
|
||||||
myResourceHistoryTableDao.delete(version);
|
myResourceHistoryTableDao.delete(version);
|
||||||
|
|
||||||
|
@ -194,7 +200,7 @@ class ResourceExpungeService implements IResourceExpungeService {
|
||||||
private void expungeCurrentVersionOfResource(RequestDetails theRequestDetails, Long theResourceId, AtomicInteger theRemainingCount) {
|
private void expungeCurrentVersionOfResource(RequestDetails theRequestDetails, Long theResourceId, AtomicInteger theRemainingCount) {
|
||||||
ResourceTable resource = myResourceTableDao.findById(theResourceId).orElseThrow(IllegalStateException::new);
|
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) {
|
if (currentVersion != null) {
|
||||||
expungeHistoricalVersion(theRequestDetails, currentVersion.getId(), theRemainingCount);
|
expungeHistoricalVersion(theRequestDetails, currentVersion.getId(), theRemainingCount);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.ForcedId;
|
||||||
import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity;
|
import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
|
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.IdDt;
|
||||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
@ -34,25 +35,26 @@ import javax.persistence.*;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
//@formatter:off
|
|
||||||
@Entity
|
@Entity
|
||||||
@Immutable
|
@Immutable
|
||||||
@Subselect("SELECT h.pid as pid " +
|
@Subselect("SELECT h.pid as pid, " +
|
||||||
", h.res_id as res_id " +
|
" h.res_id as res_id, " +
|
||||||
", h.res_type as res_type " +
|
" h.res_type as res_type, " +
|
||||||
", h.res_version as res_version " + // FHIR version
|
" h.res_version as res_version, " + // FHIR version
|
||||||
", h.res_ver as res_ver " + // resource version
|
" h.res_ver as res_ver, " + // resource version
|
||||||
", h.has_tags as has_tags " +
|
" h.has_tags as has_tags, " +
|
||||||
", h.res_deleted_at as res_deleted_at " +
|
" h.res_deleted_at as res_deleted_at, " +
|
||||||
", h.res_published as res_published " +
|
" h.res_published as res_published, " +
|
||||||
", h.res_updated as res_updated " +
|
" h.res_updated as res_updated, " +
|
||||||
", h.res_text as res_text " +
|
" h.res_text as res_text, " +
|
||||||
", h.res_encoding as res_encoding " +
|
" h.res_encoding as res_encoding, " +
|
||||||
", f.forced_id as FORCED_PID " +
|
" 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 "
|
"FROM HFJ_RES_VER h "
|
||||||
+ " LEFT OUTER JOIN HFJ_FORCED_ID f ON f.resource_pid = h.res_id "
|
+ " 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")
|
+ " 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 {
|
public class ResourceSearchView implements IBaseResourceEntity, Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
@ -73,36 +75,41 @@ public class ResourceSearchView implements IBaseResourceEntity, Serializable {
|
||||||
|
|
||||||
@Column(name = "RES_VER")
|
@Column(name = "RES_VER")
|
||||||
private Long myResourceVersion;
|
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")
|
@Column(name = "HAS_TAGS")
|
||||||
private boolean myHasTags;
|
private boolean myHasTags;
|
||||||
|
|
||||||
@Column(name = "RES_DELETED_AT")
|
@Column(name = "RES_DELETED_AT")
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
private Date myDeleted;
|
private Date myDeleted;
|
||||||
|
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
@Column(name = "RES_PUBLISHED")
|
@Column(name = "RES_PUBLISHED")
|
||||||
private Date myPublished;
|
private Date myPublished;
|
||||||
|
|
||||||
@Temporal(TemporalType.TIMESTAMP)
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
@Column(name = "RES_UPDATED")
|
@Column(name = "RES_UPDATED")
|
||||||
private Date myUpdated;
|
private Date myUpdated;
|
||||||
|
|
||||||
@Column(name = "RES_TEXT")
|
@Column(name = "RES_TEXT")
|
||||||
@Lob()
|
@Lob()
|
||||||
private byte[] myResource;
|
private byte[] myResource;
|
||||||
|
|
||||||
@Column(name = "RES_ENCODING")
|
@Column(name = "RES_ENCODING")
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
private ResourceEncodingEnum myEncoding;
|
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;
|
private String myForcedPid;
|
||||||
|
|
||||||
public ResourceSearchView() {
|
public ResourceSearchView() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getProvenanceRequestId() {
|
||||||
|
return myProvenanceRequestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProvenanceSourceUri() {
|
||||||
|
return myProvenanceSourceUri;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Date getDeleted() {
|
public Date getDeleted() {
|
||||||
return myDeleted;
|
return myDeleted;
|
||||||
|
|
|
@ -159,7 +159,7 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe
|
||||||
.stream()
|
.stream()
|
||||||
.map(CircularQueueCaptureQueriesListener::formatQueryAsSql)
|
.map(CircularQueueCaptureQueriesListener::formatQueryAsSql)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
ourLog.info("Select Queries:\n{}", String.join("\n", queries));
|
ourLog.info("Update Queries:\n{}", String.join("\n", queries));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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]+"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.ResourceHistoryTable;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
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.ResourceGoneException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
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.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.*;
|
import org.hl7.fhir.r4.model.*;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
|
@ -19,7 +14,6 @@ import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
|
public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
|
||||||
|
@ -42,11 +36,11 @@ public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
|
||||||
|
|
||||||
// Current version should be marked as deleted
|
// Current version should be marked as deleted
|
||||||
runInTransaction(()->{
|
runInTransaction(()->{
|
||||||
ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersion(id.getIdPartAsLong(), 1);
|
ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 1);
|
||||||
assertNull(resourceTable.getDeleted());
|
assertNull(resourceTable.getDeleted());
|
||||||
});
|
});
|
||||||
runInTransaction(()->{
|
runInTransaction(()->{
|
||||||
ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersion(id.getIdPartAsLong(), 2);
|
ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 2);
|
||||||
assertNotNull(resourceTable.getDeleted());
|
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
|
// Mark the current history version as not-deleted even though the actual resource
|
||||||
// table entry is marked deleted
|
// table entry is marked deleted
|
||||||
runInTransaction(()->{
|
runInTransaction(()->{
|
||||||
ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersion(id.getIdPartAsLong(), 2);
|
ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 2);
|
||||||
resourceTable.setDeleted(null);
|
resourceTable.setDeleted(null);
|
||||||
myResourceHistoryTableDao.save(resourceTable);
|
myResourceHistoryTableDao.save(resourceTable);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
@AfterClass
|
||||||
public static void afterClassClearContext() {
|
public static void afterClassClearContext() {
|
||||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
|
|
|
@ -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]+"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -187,7 +187,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
|
||||||
table.setDeleted(new Date());
|
table.setDeleted(new Date());
|
||||||
table = myResourceTableDao.saveAndFlush(table);
|
table = myResourceTableDao.saveAndFlush(table);
|
||||||
ResourceHistoryTable newHistory = table.toHistory();
|
ResourceHistoryTable newHistory = table.toHistory();
|
||||||
ResourceHistoryTable currentHistory = myResourceHistoryTableDao.findForIdAndVersion(table.getId(), 1L);
|
ResourceHistoryTable currentHistory = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(table.getId(), 1L);
|
||||||
newHistory.setEncoding(currentHistory.getEncoding());
|
newHistory.setEncoding(currentHistory.getEncoding());
|
||||||
newHistory.setResource(currentHistory.getResource());
|
newHistory.setResource(currentHistory.getResource());
|
||||||
myResourceHistoryTableDao.save(newHistory);
|
myResourceHistoryTableDao.save(newHistory);
|
||||||
|
@ -2724,7 +2724,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
|
||||||
tx.execute(new TransactionCallbackWithoutResult() {
|
tx.execute(new TransactionCallbackWithoutResult() {
|
||||||
@Override
|
@Override
|
||||||
protected void doInTransactionWithoutResult(TransactionStatus theStatus) {
|
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);
|
String newContent = myFhirCtx.newJsonParser().encodeResourceToString(p);
|
||||||
newContent = newContent.replace("male", "foo");
|
newContent = newContent.replace("male", "foo");
|
||||||
table.setResource(newContent.getBytes(Charsets.UTF_8));
|
table.setResource(newContent.getBytes(Charsets.UTF_8));
|
||||||
|
|
|
@ -108,20 +108,12 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
||||||
return resourceTable.getUpdated().getValueAsString();
|
return resourceTable.getUpdated().getValueAsString();
|
||||||
});
|
});
|
||||||
|
|
||||||
myCaptureQueriesListener.clear();
|
|
||||||
runInTransaction(() -> {
|
runInTransaction(() -> {
|
||||||
Patient p = new Patient();
|
Patient p = new Patient();
|
||||||
p.setId(id.getIdPart());
|
p.setId(id.getIdPart());
|
||||||
p.addIdentifier().setSystem("urn:system").setValue("2");
|
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(() -> {
|
runInTransaction(() -> {
|
||||||
List<ResourceTable> allResources = myResourceTableDao.findAll();
|
List<ResourceTable> allResources = myResourceTableDao.findAll();
|
||||||
|
@ -157,7 +149,9 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
||||||
|
|
||||||
// Do a read
|
// Do a read
|
||||||
{
|
{
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
Patient patient = myPatientDao.read(id, mySrd);
|
Patient patient = myPatientDao.read(id, mySrd);
|
||||||
|
myCaptureQueriesListener.logAllQueriesForCurrentThread();
|
||||||
List<CanonicalType> tl = patient.getMeta().getProfile();
|
List<CanonicalType> tl = patient.getMeta().getProfile();
|
||||||
assertEquals(1, tl.size());
|
assertEquals(1, tl.size());
|
||||||
assertEquals("http://foo/bar", tl.get(0).getValue());
|
assertEquals("http://foo/bar", tl.get(0).getValue());
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
package ca.uhn.fhir.jpa.dao.r4;
|
package ca.uhn.fhir.jpa.dao.r4;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||||
import static org.hamcrest.Matchers.empty;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import static org.junit.Assert.assertThat;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import ca.uhn.fhir.rest.param.StringAndListParam;
|
||||||
import java.util.List;
|
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.Organization;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
import java.util.List;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
import ca.uhn.fhir.rest.param.*;
|
import static org.hamcrest.Matchers.empty;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
public class FhirSearchDaoR4Test extends BaseJpaR4Test {
|
public class FhirSearchDaoR4Test extends BaseJpaR4Test {
|
||||||
|
|
||||||
|
|
|
@ -14,13 +14,9 @@ import ca.uhn.fhir.rest.param.StringParam;
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
import ca.uhn.fhir.rest.param.TokenParam;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.*;
|
import ca.uhn.fhir.rest.server.exceptions.*;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
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.apache.commons.io.IOUtils;
|
||||||
import org.hamcrest.Matchers;
|
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.IAnyResource;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.*;
|
import org.hl7.fhir.r4.model.*;
|
||||||
import org.hl7.fhir.r4.model.Bundle.*;
|
import org.hl7.fhir.r4.model.Bundle.*;
|
||||||
|
@ -520,7 +516,7 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
|
||||||
TransactionTemplate template = new TransactionTemplate(myTxManager);
|
TransactionTemplate template = new TransactionTemplate(myTxManager);
|
||||||
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||||
template.execute((TransactionCallback<ResourceTable>) t -> {
|
template.execute((TransactionCallback<ResourceTable>) t -> {
|
||||||
ResourceHistoryTable resourceHistoryTable = myResourceHistoryTableDao.findForIdAndVersion(id.getIdPartAsLong(), id.getVersionIdPartAsLong());
|
ResourceHistoryTable resourceHistoryTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), id.getVersionIdPartAsLong());
|
||||||
resourceHistoryTable.setEncoding(ResourceEncodingEnum.JSON);
|
resourceHistoryTable.setEncoding(ResourceEncodingEnum.JSON);
|
||||||
try {
|
try {
|
||||||
resourceHistoryTable.setResource("{\"resourceType\":\"FOO\"}".getBytes("UTF-8"));
|
resourceHistoryTable.setResource("{\"resourceType\":\"FOO\"}".getBytes("UTF-8"));
|
||||||
|
@ -569,7 +565,7 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
|
||||||
assertEquals(1, myPatientDao.search(searchParamMap).size().intValue());
|
assertEquals(1, myPatientDao.search(searchParamMap).size().intValue());
|
||||||
|
|
||||||
runInTransaction(()->{
|
runInTransaction(()->{
|
||||||
ResourceHistoryTable historyEntry = myResourceHistoryTableDao.findForIdAndVersion(id.getIdPartAsLong(), 3);
|
ResourceHistoryTable historyEntry = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 3);
|
||||||
assertNotNull(historyEntry);
|
assertNotNull(historyEntry);
|
||||||
myResourceHistoryTableDao.delete(historyEntry);
|
myResourceHistoryTableDao.delete(historyEntry);
|
||||||
});
|
});
|
||||||
|
|
|
@ -64,7 +64,6 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
|
||||||
protected static String ourServerBase;
|
protected static String ourServerBase;
|
||||||
protected static SearchParamRegistryR4 ourSearchParamRegistry;
|
protected static SearchParamRegistryR4 ourSearchParamRegistry;
|
||||||
private static DatabaseBackedPagingProvider ourPagingProvider;
|
private static DatabaseBackedPagingProvider ourPagingProvider;
|
||||||
protected static ISearchDao mySearchEntityDao;
|
|
||||||
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
||||||
private static GenericWebApplicationContext ourWebApplicationContext;
|
private static GenericWebApplicationContext ourWebApplicationContext;
|
||||||
private static SubscriptionMatcherInterceptor ourSubscriptionMatcherInterceptor;
|
private static SubscriptionMatcherInterceptor ourSubscriptionMatcherInterceptor;
|
||||||
|
@ -165,7 +164,6 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
|
||||||
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(subsServletHolder.getServlet().getServletConfig().getServletContext());
|
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(subsServletHolder.getServlet().getServletConfig().getServletContext());
|
||||||
myValidationSupport = wac.getBean(JpaValidationSupportChainR4.class);
|
myValidationSupport = wac.getBean(JpaValidationSupportChainR4.class);
|
||||||
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
|
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
|
||||||
mySearchEntityDao = wac.getBean(ISearchDao.class);
|
|
||||||
ourSearchParamRegistry = wac.getBean(SearchParamRegistryR4.class);
|
ourSearchParamRegistry = wac.getBean(SearchParamRegistryR4.class);
|
||||||
ourSubscriptionMatcherInterceptor = wac.getBean(SubscriptionMatcherInterceptor.class);
|
ourSubscriptionMatcherInterceptor = wac.getBean(SubscriptionMatcherInterceptor.class);
|
||||||
ourSubscriptionMatcherInterceptor.start();
|
ourSubscriptionMatcherInterceptor.start();
|
||||||
|
|
|
@ -75,6 +75,7 @@ public class ExpungeR4Test extends BaseResourceProviderR4Test {
|
||||||
Patient p = new Patient();
|
Patient p = new Patient();
|
||||||
p.setId("PT-ONEVERSION");
|
p.setId("PT-ONEVERSION");
|
||||||
p.getMeta().addTag().setSystem("http://foo").setCode("bar");
|
p.getMeta().addTag().setSystem("http://foo").setCode("bar");
|
||||||
|
p.getMeta().setSource("http://foo_source");
|
||||||
p.setActive(true);
|
p.setActive(true);
|
||||||
p.addIdentifier().setSystem("foo").setValue("bar");
|
p.addIdentifier().setSystem("foo").setValue("bar");
|
||||||
p.addName().setFamily("FAM");
|
p.addName().setFamily("FAM");
|
||||||
|
|
|
@ -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.IHttpRequest;
|
||||||
import ca.uhn.fhir.rest.client.api.IHttpResponse;
|
import ca.uhn.fhir.rest.client.api.IHttpResponse;
|
||||||
import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor;
|
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.gclient.StringClientParam;
|
||||||
import ca.uhn.fhir.rest.param.*;
|
import ca.uhn.fhir.rest.param.*;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
@ -3017,7 +3016,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
||||||
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
|
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
|
||||||
@Override
|
@Override
|
||||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||||
ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersion(id1.getIdPartAsLong(), 1);
|
ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id1.getIdPartAsLong(), 1);
|
||||||
myResourceHistoryTableDao.delete(version);
|
myResourceHistoryTableDao.delete(version);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -3038,15 +3037,18 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
||||||
p2.setActive(false);
|
p2.setActive(false);
|
||||||
IIdType id2 = ourClient.create().resource(p2).execute().getId();
|
IIdType id2 = ourClient.create().resource(p2).execute().getId();
|
||||||
|
|
||||||
|
myCaptureQueriesListener.clear();
|
||||||
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
|
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
|
||||||
@Override
|
@Override
|
||||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||||
ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersion(id1.getIdPartAsLong(), 1);
|
ResourceHistoryTable version = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id1.getIdPartAsLong(), 1);
|
||||||
myResourceHistoryTableDao.delete(version);
|
myResourceHistoryTableDao.delete(version);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
myCaptureQueriesListener.logAllQueriesForCurrentThread();
|
||||||
|
|
||||||
Bundle bundle = ourClient.search().forResource("Patient").returnBundle(Bundle.class).execute();
|
Bundle bundle = ourClient.search().forResource("Patient").returnBundle(Bundle.class).execute();
|
||||||
|
ourLog.info("Result: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
|
||||||
assertEquals(2, bundle.getTotal());
|
assertEquals(2, bundle.getTotal());
|
||||||
assertEquals(1, bundle.getEntry().size());
|
assertEquals(1, bundle.getEntry().size());
|
||||||
assertEquals(id2.getIdPart(), bundle.getEntry().get(0).getResource().getIdElement().getIdPart());
|
assertEquals(id2.getIdPart(), bundle.getEntry().get(0).getResource().getIdElement().getIdPart());
|
||||||
|
|
|
@ -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.taskdef.CalculateHashesTask;
|
||||||
import ca.uhn.fhir.jpa.migrate.tasks.api.BaseMigrationTasks;
|
import ca.uhn.fhir.jpa.migrate.tasks.api.BaseMigrationTasks;
|
||||||
import ca.uhn.fhir.jpa.model.entity.*;
|
import ca.uhn.fhir.jpa.model.entity.*;
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.util.VersionEnum;
|
import ca.uhn.fhir.util.VersionEnum;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -55,6 +56,29 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
||||||
init350();
|
init350();
|
||||||
init360();
|
init360();
|
||||||
init400();
|
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() {
|
protected void init400() {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -20,8 +20,6 @@ package ca.uhn.fhir.jpa.model.entity;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
|
||||||
import org.hibernate.annotations.OptimisticLock;
|
import org.hibernate.annotations.OptimisticLock;
|
||||||
|
|
||||||
import javax.persistence.*;
|
import javax.persistence.*;
|
||||||
|
@ -29,7 +27,6 @@ import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
//@formatter:off
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "HFJ_RES_VER", uniqueConstraints = {
|
@Table(name = "HFJ_RES_VER", uniqueConstraints = {
|
||||||
@UniqueConstraint(name = ResourceHistoryTable.IDX_RESVER_ID_VER, columnNames = {"RES_ID", "RES_VER"})
|
@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_ID_DATE", columnList = "RES_ID,RES_UPDATED"),
|
||||||
@Index(name = "IDX_RESVER_DATE", columnList = "RES_UPDATED")
|
@Index(name = "IDX_RESVER_DATE", columnList = "RES_UPDATED")
|
||||||
})
|
})
|
||||||
//@formatter:on
|
|
||||||
public class ResourceHistoryTable extends BaseHasResource implements Serializable {
|
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";
|
public static final String IDX_RESVER_ID_VER = "IDX_RESVER_ID_VER";
|
||||||
/**
|
/**
|
||||||
* @see ResourceEncodingEnum
|
* @see ResourceEncodingEnum
|
||||||
*/
|
*/
|
||||||
public static final int ENCODING_COL_LENGTH = 5;
|
public static final int ENCODING_COL_LENGTH = 5;
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
@Id
|
@Id
|
||||||
@SequenceGenerator(name = "SEQ_RESOURCE_HISTORY_ID", sequenceName = "SEQ_RESOURCE_HISTORY_ID")
|
@SequenceGenerator(name = "SEQ_RESOURCE_HISTORY_ID", sequenceName = "SEQ_RESOURCE_HISTORY_ID")
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO, generator = "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)
|
@OptimisticLock(excluded = true)
|
||||||
private ResourceEncodingEnum myEncoding;
|
private ResourceEncodingEnum myEncoding;
|
||||||
|
|
||||||
|
@OneToOne(mappedBy = "myResourceHistoryTable", cascade = {CascadeType.REMOVE})
|
||||||
|
private ResourceHistoryProvenanceEntity myProvenance;
|
||||||
|
|
||||||
public ResourceHistoryTable() {
|
public ResourceHistoryTable() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResourceHistoryProvenanceEntity getProvenance() {
|
||||||
|
return myProvenance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProvenance(ResourceHistoryProvenanceEntity theProvenance) {
|
||||||
|
myProvenance = theProvenance;
|
||||||
|
}
|
||||||
|
|
||||||
public void addTag(ResourceHistoryTag theTag) {
|
public void addTag(ResourceHistoryTag theTag) {
|
||||||
for (ResourceHistoryTag next : getTags()) {
|
for (ResourceHistoryTag next : getTags()) {
|
||||||
if (next.equals(theTag)) {
|
if (next.equals(theTag)) {
|
||||||
|
|
|
@ -214,6 +214,10 @@ public class ResourceTable extends BaseHasResource implements Serializable {
|
||||||
@Version
|
@Version
|
||||||
@Column(name = "RES_VER")
|
@Column(name = "RES_VER")
|
||||||
private long myVersion;
|
private long myVersion;
|
||||||
|
@OneToMany(mappedBy = "myResourceTable", fetch = FetchType.LAZY)
|
||||||
|
private Collection<ResourceHistoryProvenanceEntity> myProvenance;
|
||||||
|
@Transient
|
||||||
|
private transient ResourceHistoryTable myCurrentVersionEntity;
|
||||||
|
|
||||||
public Collection<ResourceLink> getResourceLinksAsTarget() {
|
public Collection<ResourceLink> getResourceLinksAsTarget() {
|
||||||
if (myResourceLinksAsTarget == null) {
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,5 +32,5 @@ public enum TagTypeEnum {
|
||||||
PROFILE,
|
PROFILE,
|
||||||
|
|
||||||
SECURITY_LABEL
|
SECURITY_LABEL
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -228,4 +228,15 @@ public class JpaConstants {
|
||||||
*/
|
*/
|
||||||
public static final String EXT_EXTERNALIZED_BINARY_ID = "http://hapifhir.io/fhir/StructureDefinition/externalized-binary-id";
|
public static final String EXT_EXTERNALIZED_BINARY_ID = "http://hapifhir.io/fhir/StructureDefinition/externalized-binary-id";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* This extension represents the equivalent of the
|
||||||
|
* <code>Resource.meta.source</code> field within R4+ resources, and is for
|
||||||
|
* use in DSTU3 resources. It should contain a value of type <code>uri</code>
|
||||||
|
* and will be located on the Resource.meta
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public static final String EXT_META_SOURCE = "http://hapifhir.io/fhir/StructureDefinition/resource-meta-source";
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package ca.uhn.fhir.jpa.model.entity;
|
package ca.uhn.fhir.jpa.model.entity;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
|
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
|
@ -58,7 +58,6 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.servlet.Servlet;
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.UnavailableException;
|
import javax.servlet.UnavailableException;
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
|
@ -1124,7 +1123,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||||
|
|
||||||
if (isBlank(requestId)) {
|
if (isBlank(requestId)) {
|
||||||
requestId = Long.toHexString(RANDOM.nextLong());
|
requestId = Long.toHexString(RANDOM.nextLong());
|
||||||
requestId = leftPad(requestId, 16, '0');
|
requestId = leftPad(requestId, Constants.REQUEST_ID_LENGTH, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
return requestId;
|
return requestId;
|
||||||
|
|
|
@ -69,6 +69,10 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
* otherwise</td>
|
* otherwise</td>
|
||||||
* </tr>
|
* </tr>
|
||||||
* <tr>
|
* <tr>
|
||||||
|
* <td>${requestId}</td>
|
||||||
|
* <td>The request ID assigned to this request (either automatically, or via the <code>X-Request-ID</code> header in the request)</td>
|
||||||
|
* </tr>
|
||||||
|
* <tr>
|
||||||
* <td>${operationType}</td>
|
* <td>${operationType}</td>
|
||||||
* <td>A code indicating the operation type for this request, e.g. "read", "history-instance",
|
* <td>A code indicating the operation type for this request, e.g. "read", "history-instance",
|
||||||
* "extended-operation-instance", etc.)</td>
|
* "extended-operation-instance", etc.)</td>
|
||||||
|
@ -323,6 +327,8 @@ public class LoggingInterceptor {
|
||||||
long time = System.currentTimeMillis() - startTime.getTime();
|
long time = System.currentTimeMillis() - startTime.getTime();
|
||||||
return Long.toString(time);
|
return Long.toString(time);
|
||||||
}
|
}
|
||||||
|
} else if ("requestId".equals(theKey)) {
|
||||||
|
return myRequestDetails.getRequestId();
|
||||||
}
|
}
|
||||||
|
|
||||||
return "!VAL!";
|
return "!VAL!";
|
||||||
|
|
|
@ -98,6 +98,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
|
||||||
return myContained;
|
return myContained;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public IdDt getId() {
|
public IdDt getId() {
|
||||||
if (myId == null) {
|
if (myId == null) {
|
||||||
myId = new IdDt();
|
myId = new IdDt();
|
||||||
|
@ -121,7 +122,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
|
||||||
@Override
|
@Override
|
||||||
public IBaseMetaType getMeta() {
|
public IBaseMetaType getMeta() {
|
||||||
return new IBaseMetaType() {
|
return new IBaseMetaType() {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -132,12 +133,12 @@ public abstract class BaseResource extends BaseElement implements IResource {
|
||||||
newTagList.addAll(existingTagList);
|
newTagList.addAll(existingTagList);
|
||||||
}
|
}
|
||||||
ResourceMetadataKeyEnum.PROFILES.put(BaseResource.this, newTagList);
|
ResourceMetadataKeyEnum.PROFILES.put(BaseResource.this, newTagList);
|
||||||
|
|
||||||
IdDt tag = new IdDt(theProfile);
|
IdDt tag = new IdDt(theProfile);
|
||||||
newTagList.add(tag);
|
newTagList.add(tag);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBaseCoding addSecurity() {
|
public IBaseCoding addSecurity() {
|
||||||
List<BaseCodingDt> tagList = ResourceMetadataKeyEnum.SECURITY_LABELS.get(BaseResource.this);
|
List<BaseCodingDt> tagList = ResourceMetadataKeyEnum.SECURITY_LABELS.get(BaseResource.this);
|
||||||
|
@ -149,7 +150,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
|
||||||
tagList.add(tag);
|
tagList.add(tag);
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBaseCoding addTag() {
|
public IBaseCoding addTag() {
|
||||||
TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(BaseResource.this);
|
TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(BaseResource.this);
|
||||||
|
@ -161,17 +162,17 @@ public abstract class BaseResource extends BaseElement implements IResource {
|
||||||
tagList.add(tag);
|
tagList.add(tag);
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getFormatCommentsPost() {
|
public List<String> getFormatCommentsPost() {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getFormatCommentsPre() {
|
public List<String> getFormatCommentsPre() {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Date getLastUpdated() {
|
public Date getLastUpdated() {
|
||||||
InstantDt lu = ResourceMetadataKeyEnum.UPDATED.get(BaseResource.this);
|
InstantDt lu = ResourceMetadataKeyEnum.UPDATED.get(BaseResource.this);
|
||||||
|
@ -180,7 +181,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<? extends IPrimitiveType<String>> getProfile() {
|
public List<? extends IPrimitiveType<String>> getProfile() {
|
||||||
ArrayList<IPrimitiveType<String>> retVal = new ArrayList<IPrimitiveType<String>>();
|
ArrayList<IPrimitiveType<String>> retVal = new ArrayList<IPrimitiveType<String>>();
|
||||||
|
@ -193,7 +194,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
|
||||||
}
|
}
|
||||||
return Collections.unmodifiableList(retVal);
|
return Collections.unmodifiableList(retVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<? extends IBaseCoding> getSecurity() {
|
public List<? extends IBaseCoding> getSecurity() {
|
||||||
ArrayList<CodingDt> retVal = new ArrayList<CodingDt>();
|
ArrayList<CodingDt> retVal = new ArrayList<CodingDt>();
|
||||||
|
@ -206,7 +207,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
|
||||||
}
|
}
|
||||||
return Collections.unmodifiableList(retVal);
|
return Collections.unmodifiableList(retVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBaseCoding getSecurity(String theSystem, String theCode) {
|
public IBaseCoding getSecurity(String theSystem, String theCode) {
|
||||||
for (IBaseCoding next : getSecurity()) {
|
for (IBaseCoding next : getSecurity()) {
|
||||||
|
@ -216,7 +217,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<? extends IBaseCoding> getTag() {
|
public List<? extends IBaseCoding> getTag() {
|
||||||
ArrayList<IBaseCoding> retVal = new ArrayList<IBaseCoding>();
|
ArrayList<IBaseCoding> retVal = new ArrayList<IBaseCoding>();
|
||||||
|
@ -229,7 +230,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
|
||||||
}
|
}
|
||||||
return Collections.unmodifiableList(retVal);
|
return Collections.unmodifiableList(retVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBaseCoding getTag(String theSystem, String theCode) {
|
public IBaseCoding getTag(String theSystem, String theCode) {
|
||||||
for (IBaseCoding next : getTag()) {
|
for (IBaseCoding next : getTag()) {
|
||||||
|
@ -239,22 +240,22 @@ public abstract class BaseResource extends BaseElement implements IResource {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getVersionId() {
|
public String getVersionId() {
|
||||||
return getId().getVersionIdPart();
|
return getId().getVersionIdPart();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasFormatComment() {
|
public boolean hasFormatComment() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return getResourceMetadata().isEmpty();
|
return getResourceMetadata().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBaseMetaType setLastUpdated(Date theHeaderDateValue) {
|
public IBaseMetaType setLastUpdated(Date theHeaderDateValue) {
|
||||||
if (theHeaderDateValue == null) {
|
if (theHeaderDateValue == null) {
|
||||||
|
@ -264,7 +265,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBaseMetaType setVersionId(String theVersionId) {
|
public IBaseMetaType setVersionId(String theVersionId) {
|
||||||
setId(getId().withVersion(theVersionId));
|
setId(getId().withVersion(theVersionId));
|
||||||
|
|
|
@ -152,6 +152,26 @@ public class LoggingInterceptorDstu2Test {
|
||||||
assertEquals("read - - Patient/1 - ", captor.getValue());
|
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<String> captor = ArgumentCaptor.forClass(String.class);
|
||||||
|
verify(logger, timeout(1000).times(1)).info(captor.capture());
|
||||||
|
assertEquals(Constants.REQUEST_ID_LENGTH, captor.getValue().length());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRequestProcessingTime() throws Exception {
|
public void testRequestProcessingTime() throws Exception {
|
||||||
|
|
||||||
|
|
|
@ -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(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(IBaseResource.class), nullable(HttpServletRequest.class), nullable(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myInterceptor1.outgoingResponse(nullable(RequestDetails.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();
|
String input = createInput();
|
||||||
|
|
||||||
|
|
4
pom.xml
4
pom.xml
|
@ -594,7 +594,7 @@
|
||||||
<jetty_version>9.4.14.v20181114</jetty_version>
|
<jetty_version>9.4.14.v20181114</jetty_version>
|
||||||
<jsr305_version>3.0.2</jsr305_version>
|
<jsr305_version>3.0.2</jsr305_version>
|
||||||
<!--<hibernate_version>5.2.10.Final</hibernate_version>-->
|
<!--<hibernate_version>5.2.10.Final</hibernate_version>-->
|
||||||
<hibernate_version>5.4.2.Final</hibernate_version>
|
<hibernate_version>5.4.4.Final</hibernate_version>
|
||||||
<!-- Update lucene version when you update hibernate-search version -->
|
<!-- Update lucene version when you update hibernate-search version -->
|
||||||
<hibernate_search_version>5.11.1.Final</hibernate_search_version>
|
<hibernate_search_version>5.11.1.Final</hibernate_search_version>
|
||||||
<lucene_version>5.5.5</lucene_version>
|
<lucene_version>5.5.5</lucene_version>
|
||||||
|
@ -1281,7 +1281,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mockito</groupId>
|
<groupId>org.mockito</groupId>
|
||||||
<artifactId>mockito-core</artifactId>
|
<artifactId>mockito-core</artifactId>
|
||||||
<version>2.28.2</version>
|
<version>3.0.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.postgresql</groupId>
|
<groupId>org.postgresql</groupId>
|
||||||
|
|
|
@ -7,6 +7,15 @@
|
||||||
</properties>
|
</properties>
|
||||||
<body>
|
<body>
|
||||||
<release version="4.1.0" date="TBD" description="Igloo">
|
<release version="4.1.0" date="TBD" description="Igloo">
|
||||||
|
<action type="add">
|
||||||
|
The version of a few dependencies have been bumped to the
|
||||||
|
latest versions (dependent HAPI modules listed in brackets):
|
||||||
|
<![CDATA[
|
||||||
|
<ul>
|
||||||
|
<li>Hibernate Core (Core): 5.4.2.Final -> 5.4.4.Final</li>
|
||||||
|
</ul>
|
||||||
|
]]>
|
||||||
|
</action>
|
||||||
<!--
|
<!--
|
||||||
<action type="add" issue="1321">
|
<action type="add" issue="1321">
|
||||||
Support has been added for RDF encoding and parsing in the
|
Support has been added for RDF encoding and parsing in the
|
||||||
|
@ -14,7 +23,15 @@
|
||||||
format. Thanks to Raul Estrada for the pull request!
|
format. Thanks to Raul Estrada for the pull request!
|
||||||
</action>
|
</action>
|
||||||
-->
|
-->
|
||||||
<action type="add" issye="1357">
|
<action type="add">
|
||||||
|
<![CDATA[
|
||||||
|
<b>New Feature</b>:
|
||||||
|
The JPA server now saves and supports searching on <code>Resource.meta.source</code>. 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.
|
||||||
|
]]>
|
||||||
|
</action>
|
||||||
|
<action type="add" issue="1357">
|
||||||
The email Subscription deliverer now respects the payload property of the subscription
|
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
|
when deciding how to encode the resource being sent. Thanks to Sean McIlvenna for the
|
||||||
pull request!
|
pull request!
|
||||||
|
@ -543,7 +560,7 @@
|
||||||
not any values contained within. This has been corrected.
|
not any values contained within. This has been corrected.
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
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
|
is not enabled, and will perform simple ValueSet expansions without relying
|
||||||
on Hibenrate Search in such cases.
|
on Hibenrate Search in such cases.
|
||||||
</action>
|
</action>
|
||||||
|
|
Loading…
Reference in New Issue