Impropve transaction Performance (#2717)
* Work on changes * Work on perf * Work on testing * Work on perf * Work on perf * Work on fix * Work on perf * Ongoing work * Add changelog * Additional docs * Test fixes * Address review comments * Test fix
This commit is contained in:
parent
48eea5a7cc
commit
b934abb297
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
type: perf
|
||||
issue: 2717
|
||||
title: "A new setting has been added to the DaoConfig called Tag Versioning Mode. This setting controls whether a single collection of
|
||||
tags/profiles/security labels is maintained across all versions of a single resource, or whether each version of the
|
||||
resource maintains its own independent collection. Previously each version always maintained an independent collection,
|
||||
which is useful sometimes, but is often not useful and can affect performance."
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: perf
|
||||
issue: 2717
|
||||
title: "FHIR transactions in the JPA server that perform writes will now aggressively pre-fetch as many entities
|
||||
as possible at the very start of transaction processing. This can drastically reduce the number of
|
||||
round-trips, especially as the number of resources in a transaction gets bigger."
|
|
@ -84,6 +84,10 @@ public class DaoConfig {
|
|||
*/
|
||||
public static final boolean DEFAULT_ENABLE_TASKS = true;
|
||||
public static final int DEFAULT_MAXIMUM_INCLUDES_TO_LOAD_PER_PAGE = 1000;
|
||||
/**
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public static final TagStorageModeEnum DEFAULT_TAG_STORAGE_MODE = TagStorageModeEnum.VERSIONED;
|
||||
/**
|
||||
* Default value for {@link #setMaximumSearchResultCountInTransaction(Integer)}
|
||||
*
|
||||
|
@ -129,6 +133,7 @@ public class DaoConfig {
|
|||
private SearchTotalModeEnum myDefaultTotalMode = null;
|
||||
private int myEverythingIncludesFetchPageSize = 50;
|
||||
private int myBulkImportMaxRetryCount = 10;
|
||||
private TagStorageModeEnum myTagStorageMode = DEFAULT_TAG_STORAGE_MODE;
|
||||
/**
|
||||
* update setter javadoc if default changes
|
||||
*/
|
||||
|
@ -219,7 +224,7 @@ public class DaoConfig {
|
|||
/**
|
||||
* @since 5.4.0
|
||||
*/
|
||||
private boolean myMatchUrlCache;
|
||||
private boolean myMatchUrlCacheEnabled;
|
||||
/**
|
||||
* @since 5.5.0
|
||||
*/
|
||||
|
@ -266,6 +271,26 @@ public class DaoConfig {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tag storage mode for the server. Default is {@link TagStorageModeEnum#VERSIONED}.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*/
|
||||
@Nonnull
|
||||
public TagStorageModeEnum getTagStorageMode() {
|
||||
return myTagStorageMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tag storage mode for the server. Default is {@link TagStorageModeEnum#VERSIONED}.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public void setTagStorageMode(@Nonnull TagStorageModeEnum theTagStorageMode) {
|
||||
Validate.notNull(theTagStorageMode, "theTagStorageMode must not be null");
|
||||
myTagStorageMode = theTagStorageMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the maximum number of times that a chunk will be retried during bulk import
|
||||
* processes before giving up.
|
||||
|
@ -421,9 +446,25 @@ public class DaoConfig {
|
|||
* Default is <code>false</code>
|
||||
*
|
||||
* @since 5.4.0
|
||||
* @deprecated Deprecated in 5.5.0. Use {@link #isMatchUrlCacheEnabled()} instead (the name of this method is misleading)
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean getMatchUrlCache() {
|
||||
return myMatchUrlCache;
|
||||
return myMatchUrlCacheEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* If enabled, resolutions for match URLs (e.g. conditional create URLs, conditional update URLs, etc) will be
|
||||
* cached in an in-memory cache. This cache can have a noticeable improvement on write performance on servers
|
||||
* where conditional operations are frequently performed, but note that this cache will not be
|
||||
* invalidated based on updates to resources so this may have detrimental effects.
|
||||
* <p>
|
||||
* Default is <code>false</code>
|
||||
*
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public boolean isMatchUrlCacheEnabled() {
|
||||
return getMatchUrlCache();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -435,9 +476,25 @@ public class DaoConfig {
|
|||
* Default is <code>false</code>
|
||||
*
|
||||
* @since 5.4.0
|
||||
* @deprecated Deprecated in 5.5.0. Use {@link #setMatchUrlCacheEnabled(boolean)} instead (the name of this method is misleading)
|
||||
*/
|
||||
@Deprecated
|
||||
public void setMatchUrlCache(boolean theMatchUrlCache) {
|
||||
myMatchUrlCache = theMatchUrlCache;
|
||||
myMatchUrlCacheEnabled = theMatchUrlCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* If enabled, resolutions for match URLs (e.g. conditional create URLs, conditional update URLs, etc) will be
|
||||
* cached in an in-memory cache. This cache can have a noticeable improvement on write performance on servers
|
||||
* where conditional operations are frequently performed, but note that this cache will not be
|
||||
* invalidated based on updates to resources so this may have detrimental effects.
|
||||
* <p>
|
||||
* Default is <code>false</code>
|
||||
*
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public void setMatchUrlCacheEnabled(boolean theMatchUrlCache) {
|
||||
setMatchUrlCache(theMatchUrlCache);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2548,4 +2605,17 @@ public class DaoConfig {
|
|||
ANY
|
||||
}
|
||||
|
||||
public enum TagStorageModeEnum {
|
||||
|
||||
/**
|
||||
* A separate set of tags is stored for each resource version
|
||||
*/
|
||||
VERSIONED,
|
||||
|
||||
/**
|
||||
* A single set of tags is shared by all resource versions
|
||||
*/
|
||||
NON_VERSIONED
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ca.uhn.fhir.jpa.api.dao;
|
||||
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
|
@ -26,6 +27,6 @@ import org.hl7.fhir.instance.model.api.IIdType;
|
|||
|
||||
public interface IFhirResourceDaoSubscription<T extends IBaseResource> extends IFhirResourceDao<T> {
|
||||
|
||||
Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest);
|
||||
Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest, TransactionDetails theTransactionDetails);
|
||||
|
||||
}
|
||||
|
|
|
@ -58,7 +58,6 @@ import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryResourceMatcher;
|
|||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
|
||||
import ca.uhn.fhir.jpa.util.AddRemoveCount;
|
||||
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
|
@ -84,6 +83,7 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
|||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.util.CoverageIgnore;
|
||||
import ca.uhn.fhir.util.HapiExtensions;
|
||||
import ca.uhn.fhir.util.MetaUtil;
|
||||
|
@ -137,6 +137,7 @@ import java.util.Collections;
|
|||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
@ -185,6 +186,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
private static final Logger ourLog = LoggerFactory.getLogger(BaseHapiFhirDao.class);
|
||||
private static final Map<FhirVersionEnum, FhirContext> ourRetrievalContexts = new HashMap<>();
|
||||
private static final String PROCESSING_SUB_REQUEST = "BaseHapiFhirDao.processingSubRequest";
|
||||
private static final String TRANSACTION_DETAILS_CACHE_KEY_EXISTING_SEARCH_PARAMS = BaseHapiFhirDao.class.getName() + "_EXISTING_SEARCH_PARAMS";
|
||||
private static boolean ourValidationDisabledForUnitTest;
|
||||
private static boolean ourDisableIncrementOnUpdateForUnitTest = false;
|
||||
|
||||
|
@ -394,6 +396,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
|
||||
@Autowired
|
||||
public void setContext(FhirContext theContext) {
|
||||
super.myFhirContext = theContext;
|
||||
myContext = theContext;
|
||||
}
|
||||
|
||||
|
@ -668,6 +671,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
|
||||
// Don't check existing - We'll rely on the SHA256 hash only
|
||||
|
||||
} else if (theEntity.getVersion() == 1L && theEntity.getCurrentVersionEntity() == null) {
|
||||
|
||||
// No previous version if this is the first version
|
||||
|
||||
} else {
|
||||
ResourceHistoryTable currentHistoryVersion = theEntity.getCurrentVersionEntity();
|
||||
if (currentHistoryVersion == null) {
|
||||
|
@ -791,27 +798,23 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
res.getMeta().setLastUpdated(theEntity.getUpdatedDate());
|
||||
IDao.RESOURCE_PID.put(res, theEntity.getResourceId());
|
||||
|
||||
Collection<? extends BaseTag> tags = theTagList;
|
||||
|
||||
if (theEntity.isHasTags()) {
|
||||
for (BaseTag next : tags) {
|
||||
switch (next.getTag().getTagType()) {
|
||||
case PROFILE:
|
||||
res.getMeta().addProfile(next.getTag().getCode());
|
||||
break;
|
||||
case SECURITY_LABEL:
|
||||
IBaseCoding sec = res.getMeta().addSecurity();
|
||||
sec.setSystem(next.getTag().getSystem());
|
||||
sec.setCode(next.getTag().getCode());
|
||||
sec.setDisplay(next.getTag().getDisplay());
|
||||
break;
|
||||
case TAG:
|
||||
IBaseCoding tag = res.getMeta().addTag();
|
||||
tag.setSystem(next.getTag().getSystem());
|
||||
tag.setCode(next.getTag().getCode());
|
||||
tag.setDisplay(next.getTag().getDisplay());
|
||||
break;
|
||||
}
|
||||
for (BaseTag next : theTagList) {
|
||||
switch (next.getTag().getTagType()) {
|
||||
case PROFILE:
|
||||
res.getMeta().addProfile(next.getTag().getCode());
|
||||
break;
|
||||
case SECURITY_LABEL:
|
||||
IBaseCoding sec = res.getMeta().addSecurity();
|
||||
sec.setSystem(next.getTag().getSystem());
|
||||
sec.setCode(next.getTag().getCode());
|
||||
sec.setDisplay(next.getTag().getDisplay());
|
||||
break;
|
||||
case TAG:
|
||||
IBaseCoding tag = res.getMeta().addTag();
|
||||
tag.setSystem(next.getTag().getSystem());
|
||||
tag.setCode(next.getTag().getCode());
|
||||
tag.setDisplay(next.getTag().getDisplay());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -912,7 +915,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
// 1. get resource, it's encoding and the tags if any
|
||||
byte[] resourceBytes;
|
||||
ResourceEncodingEnum resourceEncoding;
|
||||
Collection<? extends BaseTag> myTagList;
|
||||
Collection<? extends BaseTag> tagList = Collections.emptyList();
|
||||
long version;
|
||||
String provenanceSourceUri = null;
|
||||
String provenanceRequestId = null;
|
||||
|
@ -921,10 +924,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
ResourceHistoryTable history = (ResourceHistoryTable) theEntity;
|
||||
resourceBytes = history.getResource();
|
||||
resourceEncoding = history.getEncoding();
|
||||
if (history.isHasTags()) {
|
||||
myTagList = history.getTags();
|
||||
if (getConfig().getTagStorageMode() == DaoConfig.TagStorageModeEnum.VERSIONED) {
|
||||
if (history.isHasTags()) {
|
||||
tagList = history.getTags();
|
||||
}
|
||||
} else {
|
||||
myTagList = Collections.emptyList();
|
||||
if (history.getResourceTable().isHasTags()) {
|
||||
tagList = history.getResourceTable().getTags();
|
||||
}
|
||||
}
|
||||
version = history.getVersion();
|
||||
if (history.getProvenance() != null) {
|
||||
|
@ -948,9 +955,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
resourceBytes = history.getResource();
|
||||
resourceEncoding = history.getEncoding();
|
||||
if (resource.isHasTags()) {
|
||||
myTagList = resource.getTags();
|
||||
tagList = resource.getTags();
|
||||
} else {
|
||||
myTagList = Collections.emptyList();
|
||||
tagList = Collections.emptyList();
|
||||
}
|
||||
version = history.getVersion();
|
||||
if (history.getProvenance() != null) {
|
||||
|
@ -966,9 +973,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
provenanceRequestId = view.getProvenanceRequestId();
|
||||
provenanceSourceUri = view.getProvenanceSourceUri();
|
||||
if (theTagList == null)
|
||||
myTagList = new HashSet<>();
|
||||
tagList = new HashSet<>();
|
||||
else
|
||||
myTagList = theTagList;
|
||||
tagList = theTagList;
|
||||
} else {
|
||||
// something wrong
|
||||
return null;
|
||||
|
@ -980,7 +987,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
// 3. Use the appropriate custom type if one is specified in the context
|
||||
Class<R> resourceType = theResourceType;
|
||||
if (myContext.hasDefaultTypeForProfile()) {
|
||||
for (BaseTag nextTag : myTagList) {
|
||||
for (BaseTag nextTag : tagList) {
|
||||
if (nextTag.getTag().getTagType() == TagTypeEnum.PROFILE) {
|
||||
String profile = nextTag.getTag().getCode();
|
||||
if (isNotBlank(profile)) {
|
||||
|
@ -1030,10 +1037,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
// 5. fill MetaData
|
||||
if (retVal instanceof IResource) {
|
||||
IResource res = (IResource) retVal;
|
||||
retVal = populateResourceMetadataHapi(resourceType, theEntity, myTagList, theForHistoryOperation, res, version);
|
||||
retVal = populateResourceMetadataHapi(resourceType, theEntity, tagList, theForHistoryOperation, res, version);
|
||||
} else {
|
||||
IAnyResource res = (IAnyResource) retVal;
|
||||
retVal = populateResourceMetadataRi(resourceType, theEntity, myTagList, theForHistoryOperation, res, version);
|
||||
retVal = populateResourceMetadataRi(resourceType, theEntity, tagList, theForHistoryOperation, res, version);
|
||||
}
|
||||
|
||||
// 6. Handle source (provenance)
|
||||
|
@ -1152,14 +1159,22 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
changed = populateResourceIntoEntity(theTransactionDetails, theRequest, theResource, entity, true);
|
||||
|
||||
} else {
|
||||
|
||||
// CREATE or UPDATE
|
||||
existingParams = new ResourceIndexedSearchParams(entity);
|
||||
|
||||
IdentityHashMap<ResourceTable, ResourceIndexedSearchParams> existingSearchParams = theTransactionDetails.getOrCreateUserData(TRANSACTION_DETAILS_CACHE_KEY_EXISTING_SEARCH_PARAMS, () -> new IdentityHashMap<>());
|
||||
existingParams = existingSearchParams.get(entity);
|
||||
if (existingParams == null) {
|
||||
existingParams = new ResourceIndexedSearchParams(entity);
|
||||
existingSearchParams.put(entity, existingParams);
|
||||
}
|
||||
entity.setDeleted(null);
|
||||
|
||||
if (thePerformIndexing) {
|
||||
// TODO: is this IF statement always true? Try removing it
|
||||
if (thePerformIndexing || ((ResourceTable) theEntity).getVersion() == 1) {
|
||||
|
||||
newParams = new ResourceIndexedSearchParams();
|
||||
mySearchParamWithInlineReferencesExtractor.populateFromResource(newParams, theTransactionDetails, entity, theResource, existingParams, theRequest);
|
||||
mySearchParamWithInlineReferencesExtractor.populateFromResource(newParams, theTransactionDetails, entity, theResource, existingParams, theRequest, thePerformIndexing);
|
||||
|
||||
changed = populateResourceIntoEntity(theTransactionDetails, theRequest, theResource, entity, true);
|
||||
|
||||
|
@ -1175,7 +1190,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
// to match a resource and then update it in a way that it no longer
|
||||
// matches. We could certainly make this configurable though in the
|
||||
// future.
|
||||
if (entity.getVersion() <= 1L && entity.getCreatedByMatchUrl() != null) {
|
||||
if (entity.getVersion() <= 1L && entity.getCreatedByMatchUrl() != null && thePerformIndexing) {
|
||||
verifyMatchUrlForConditionalCreate(theResource, entity.getCreatedByMatchUrl(), entity, newParams);
|
||||
}
|
||||
|
||||
|
@ -1205,7 +1220,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
|
||||
}
|
||||
|
||||
if (thePerformIndexing && changed != null && !changed.isChanged() && !theForceUpdate && myConfig.isSuppressUpdatesWithNoChange()) {
|
||||
if (thePerformIndexing && changed != null && !changed.isChanged() && !theForceUpdate && myConfig.isSuppressUpdatesWithNoChange() && (entity.getVersion() > 1 || theUpdateVersion)) {
|
||||
ourLog.debug("Resource {} has not changed", entity.getIdDt().toUnqualified().getValue());
|
||||
if (theResource != null) {
|
||||
updateResourceMetadata(entity, theResource);
|
||||
|
@ -1245,7 +1260,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
* Create history entry
|
||||
*/
|
||||
if (theCreateNewHistoryEntry) {
|
||||
final ResourceHistoryTable historyEntry = entity.toHistory();
|
||||
boolean versionedTags = getConfig().getTagStorageMode() == DaoConfig.TagStorageModeEnum.VERSIONED;
|
||||
final ResourceHistoryTable historyEntry = entity.toHistory(versionedTags);
|
||||
historyEntry.setEncoding(changed.getEncoding());
|
||||
historyEntry.setResource(changed.getResource());
|
||||
|
||||
|
@ -1575,6 +1591,11 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
// nothing yet
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setDaoConfigForUnitTest(DaoConfig theDaoConfig) {
|
||||
myConfig = theDaoConfig;
|
||||
}
|
||||
|
||||
private class AddTagDefinitionToCacheAfterCommitSynchronization implements TransactionSynchronization {
|
||||
|
||||
private final TagDefinition myTagDefinition;
|
||||
|
@ -1726,11 +1747,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
ourDisableIncrementOnUpdateForUnitTest = theDisableIncrementOnUpdateForUnitTest;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setDaoConfigForUnitTest(DaoConfig theDaoConfig) {
|
||||
myConfig = theDaoConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not call this method outside of unit tests
|
||||
*/
|
||||
|
|
|
@ -35,6 +35,7 @@ import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
|
|||
import ca.uhn.fhir.jpa.api.model.ExpungeOutcome;
|
||||
import ca.uhn.fhir.jpa.api.model.LazyDaoMethodOutcome;
|
||||
import ca.uhn.fhir.jpa.dao.expunge.DeleteExpungeService;
|
||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
||||
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
||||
import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
|
||||
|
@ -57,7 +58,6 @@ import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
|
|||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
|
||||
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.model.dstu2.resource.ListResource;
|
||||
|
@ -91,6 +91,7 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
|||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.util.ObjectUtil;
|
||||
import ca.uhn.fhir.util.OperationOutcomeUtil;
|
||||
import ca.uhn.fhir.util.ReflectionUtil;
|
||||
|
@ -257,9 +258,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
entity.setResourceType(toResourceName(theResource));
|
||||
entity.setPartitionId(theRequestPartitionId);
|
||||
entity.setCreatedByMatchUrl(theIfNoneExist);
|
||||
entity.setVersion(1);
|
||||
|
||||
if (isNotBlank(theIfNoneExist)) {
|
||||
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theIfNoneExist, myResourceType, theRequest);
|
||||
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theIfNoneExist, myResourceType, theTransactionDetails, theRequest);
|
||||
if (match.size() > 1) {
|
||||
String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "CREATE", theIfNoneExist, match.size());
|
||||
throw new PreconditionFailedException(msg);
|
||||
|
@ -338,9 +340,17 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
doCallHooks(theTransactionDetails, theRequest, Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, hookParams);
|
||||
|
||||
// Perform actual DB update
|
||||
ResourceTable updatedEntity = updateEntity(theRequest, theResource, entity, null, thePerformIndexing, thePerformIndexing, theTransactionDetails, false, thePerformIndexing);
|
||||
ResourceTable updatedEntity = updateEntity(theRequest, theResource, entity, null, thePerformIndexing, false, theTransactionDetails, false, thePerformIndexing);
|
||||
|
||||
IIdType id = myFhirContext.getVersion().newIdType().setValue(updatedEntity.getIdDt().toUnqualifiedVersionless().getValue());
|
||||
ResourcePersistentId persistentId = new ResourcePersistentId(updatedEntity.getResourceId());
|
||||
theTransactionDetails.addResolvedResourceId(id, persistentId);
|
||||
if (entity.getForcedId() != null) {
|
||||
myIdHelperService.addResolvedPidToForcedId(persistentId, theRequestPartitionId, updatedEntity.getResourceType(), updatedEntity.getForcedId().getForcedId());
|
||||
}
|
||||
|
||||
theResource.setId(entity.getIdDt());
|
||||
|
||||
if (serverAssignedId) {
|
||||
switch (getConfig().getResourceClientIdStrategy()) {
|
||||
case NOT_ALLOWED:
|
||||
|
@ -357,16 +367,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
if (theIfNoneExist != null) {
|
||||
// Pre-cache the match URL
|
||||
myMatchResourceUrlService.matchUrlResolved(theIfNoneExist, new ResourcePersistentId(entity.getResourceId()));
|
||||
}
|
||||
|
||||
/*
|
||||
* If we aren't indexing (meaning we're probably executing a sub-operation within a transaction),
|
||||
* we'll manually increase the version. This is important because we want the updated version number
|
||||
* to be reflected in the resource shared with interceptors
|
||||
*/
|
||||
if (!thePerformIndexing) {
|
||||
incrementId(theResource, entity, theResource.getIdElement());
|
||||
myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, getResourceName(), theIfNoneExist, new ResourcePersistentId(entity.getResourceId()));
|
||||
}
|
||||
|
||||
// Update the version/last updated in the resource so that interceptors get
|
||||
|
@ -399,9 +400,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
if (updatedEntity.getForcedId() != null) {
|
||||
forcedId = updatedEntity.getForcedId().getForcedId();
|
||||
}
|
||||
if (myIdHelperService != null) {
|
||||
myIdHelperService.addResolvedPidToForcedId(new ResourcePersistentId(updatedEntity.getResourceId()), theRequestPartitionId, getResourceName(), forcedId);
|
||||
}
|
||||
myIdHelperService.addResolvedPidToForcedId(persistentId, theRequestPartitionId, getResourceName(), forcedId);
|
||||
|
||||
ourLog.debug(msg);
|
||||
return outcome;
|
||||
|
@ -443,7 +442,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
validateIdPresentForDelete(theId);
|
||||
validateDeleteEnabled();
|
||||
|
||||
final ResourceTable entity = readEntityLatestVersion(theId, theRequestDetails);
|
||||
final ResourceTable entity = readEntityLatestVersion(theId, theRequestDetails, theTransactionDetails);
|
||||
if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) {
|
||||
throw new ResourceVersionConflictException("Trying to delete " + theId + " but this is not the current version");
|
||||
}
|
||||
|
@ -913,7 +912,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
throw new ResourceNotFoundException(theResourceId);
|
||||
}
|
||||
|
||||
ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest);
|
||||
ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest, transactionDetails);
|
||||
if (latestVersion.getVersion() != entity.getVersion()) {
|
||||
doMetaAdd(theMetaAdd, entity, theRequest, transactionDetails);
|
||||
} else {
|
||||
|
@ -948,7 +947,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
throw new ResourceNotFoundException(theResourceId);
|
||||
}
|
||||
|
||||
ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest);
|
||||
ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest, transactionDetails);
|
||||
if (latestVersion.getVersion() != entity.getVersion()) {
|
||||
doMetaDelete(theMetaDel, entity, theRequest, transactionDetails);
|
||||
} else {
|
||||
|
@ -1007,14 +1006,14 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
@Override
|
||||
public DaoMethodOutcome patch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequest) {
|
||||
return myTransactionService.execute(theRequest, tx -> doPatch(theId, theConditionalUrl, thePatchType, thePatchBody, theFhirPatchBody, theRequest));
|
||||
return myTransactionService.execute(theRequest, tx -> doPatch(theId, theConditionalUrl, thePatchType, thePatchBody, theFhirPatchBody, theRequest, new TransactionDetails()));
|
||||
}
|
||||
|
||||
private DaoMethodOutcome doPatch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequest) {
|
||||
private DaoMethodOutcome doPatch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
|
||||
ResourceTable entityToUpdate;
|
||||
if (isNotBlank(theConditionalUrl)) {
|
||||
|
||||
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theConditionalUrl, myResourceType, theRequest);
|
||||
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theConditionalUrl, myResourceType, theTransactionDetails, theRequest);
|
||||
if (match.size() > 1) {
|
||||
String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "PATCH", theConditionalUrl, match.size());
|
||||
throw new PreconditionFailedException(msg);
|
||||
|
@ -1027,7 +1026,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
|
||||
} else {
|
||||
entityToUpdate = readEntityLatestVersion(theId, theRequest);
|
||||
entityToUpdate = readEntityLatestVersion(theId, theRequest, theTransactionDetails);
|
||||
if (theId.hasVersionIdPart()) {
|
||||
if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) {
|
||||
throw new ResourceVersionConflictException("Version " + theId.getVersionIdPart() + " is not the most recent version of this resource, unable to apply patch");
|
||||
|
@ -1064,7 +1063,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
@Override
|
||||
public void start() {
|
||||
assert getConfig() != null;
|
||||
|
||||
|
||||
ourLog.debug("Starting resource DAO for type: {}", getResourceName());
|
||||
myInstanceValidator = getApplicationContext().getBean(IInstanceValidatorModule.class);
|
||||
myTxTemplate = new TransactionTemplate(myPlatformTransactionManager);
|
||||
|
@ -1252,15 +1251,19 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
|
||||
@Nonnull
|
||||
protected ResourceTable readEntityLatestVersion(IIdType theId, RequestDetails theRequestDetails) {
|
||||
protected ResourceTable readEntityLatestVersion(IIdType theId, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails) {
|
||||
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, getResourceName());
|
||||
return readEntityLatestVersion(theId, requestPartitionId);
|
||||
return readEntityLatestVersion(theId, requestPartitionId, theTransactionDetails);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private ResourceTable readEntityLatestVersion(IIdType theId, @Nullable RequestPartitionId theRequestPartitionId) {
|
||||
private ResourceTable readEntityLatestVersion(IIdType theId, @Nullable RequestPartitionId theRequestPartitionId, TransactionDetails theTransactionDetails) {
|
||||
validateResourceTypeAndThrowInvalidRequestException(theId);
|
||||
|
||||
if (theTransactionDetails.isResolvedResourceIdEmpty(theId.toUnqualifiedVersionless())) {
|
||||
throw new ResourceNotFoundException(theId);
|
||||
}
|
||||
|
||||
ResourcePersistentId persistentId = myIdHelperService.resolveResourcePersistentIds(theRequestPartitionId, getResourceName(), theId.getIdPart());
|
||||
ResourceTable entity = myEntityManager.find(ResourceTable.class, persistentId.getId());
|
||||
if (entity == null) {
|
||||
|
@ -1569,7 +1572,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
IIdType resourceId;
|
||||
if (isNotBlank(theMatchUrl)) {
|
||||
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theMatchUrl, myResourceType, theRequest);
|
||||
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theMatchUrl, myResourceType, theTransactionDetails, theRequest);
|
||||
if (match.size() > 1) {
|
||||
String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "UPDATE", theMatchUrl, match.size());
|
||||
throw new PreconditionFailedException(msg);
|
||||
|
@ -1582,7 +1585,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
// Pre-cache the match URL
|
||||
if (outcome.getPersistentId() != null) {
|
||||
myMatchResourceUrlService.matchUrlResolved(theMatchUrl, outcome.getPersistentId());
|
||||
myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, getResourceName(), theMatchUrl, outcome.getPersistentId());
|
||||
}
|
||||
|
||||
return outcome;
|
||||
|
@ -1610,7 +1613,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
if (!create) {
|
||||
try {
|
||||
entity = readEntityLatestVersion(resourceId, requestPartitionId);
|
||||
entity = readEntityLatestVersion(resourceId, requestPartitionId, theTransactionDetails);
|
||||
} catch (ResourceNotFoundException e) {
|
||||
create = true;
|
||||
}
|
||||
|
@ -1692,6 +1695,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
@Override
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
public MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile, RequestDetails theRequest) {
|
||||
TransactionDetails transactionDetails = new TransactionDetails();
|
||||
|
||||
if (theRequest != null) {
|
||||
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, theResource, null, theId);
|
||||
notifyInterceptors(RestOperationTypeEnum.VALIDATE, requestDetails);
|
||||
|
@ -1701,7 +1706,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
if (theId == null || theId.hasIdPart() == false) {
|
||||
throw new InvalidRequestException("No ID supplied. ID is required when validating with mode=DELETE");
|
||||
}
|
||||
final ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||
final ResourceTable entity = readEntityLatestVersion(theId, theRequest, transactionDetails);
|
||||
|
||||
// Validate that there are no resources pointing to the candidate that
|
||||
// would prevent deletion
|
||||
|
@ -1799,6 +1804,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setIdHelperSvcForUnitTest(IdHelperService theIdHelperService) {
|
||||
myIdHelperService = theIdHelperService;
|
||||
}
|
||||
|
||||
private static class IdChecker implements IValidatorModule {
|
||||
|
||||
private final ValidationModeEnum myMode;
|
||||
|
|
|
@ -145,6 +145,20 @@ public abstract class BaseTransactionProcessor {
|
|||
@Autowired
|
||||
private InMemoryResourceMatcher myInMemoryResourceMatcher;
|
||||
|
||||
@VisibleForTesting
|
||||
public void setDaoConfig(DaoConfig theDaoConfig) {
|
||||
myDaoConfig = theDaoConfig;
|
||||
}
|
||||
|
||||
public ITransactionProcessorVersionAdapter getVersionAdapter() {
|
||||
return myVersionAdapter;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setVersionAdapter(ITransactionProcessorVersionAdapter theVersionAdapter) {
|
||||
myVersionAdapter = theVersionAdapter;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
ourLog.trace("Starting transaction processor");
|
||||
|
@ -287,11 +301,6 @@ public abstract class BaseTransactionProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setVersionAdapter(ITransactionProcessorVersionAdapter theVersionAdapter) {
|
||||
myVersionAdapter = theVersionAdapter;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setTxManager(PlatformTransactionManager theTxManager) {
|
||||
myTxManager = theTxManager;
|
||||
|
@ -582,8 +591,8 @@ public abstract class BaseTransactionProcessor {
|
|||
myModelConfig = theModelConfig;
|
||||
}
|
||||
|
||||
private Map<IBase, IIdType> doTransactionWriteOperations(final RequestDetails theRequest, String theActionName, TransactionDetails theTransactionDetails, Set<IIdType> theAllIds,
|
||||
Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome, IBaseBundle theResponse, IdentityHashMap<IBase, Integer> theOriginalRequestOrder, List<IBase> theEntries, StopWatch theTransactionStopWatch) {
|
||||
protected Map<IBase, IIdType> doTransactionWriteOperations(final RequestDetails theRequest, String theActionName, TransactionDetails theTransactionDetails, Set<IIdType> theAllIds,
|
||||
Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome, IBaseBundle theResponse, IdentityHashMap<IBase, Integer> theOriginalRequestOrder, List<IBase> theEntries, StopWatch theTransactionStopWatch) {
|
||||
|
||||
theTransactionDetails.beginAcceptingDeferredInterceptorBroadcasts(
|
||||
Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED,
|
||||
|
@ -1067,7 +1076,7 @@ public abstract class BaseTransactionProcessor {
|
|||
if (!nextId.hasIdPart()) {
|
||||
if (resourceReference.getResource() != null) {
|
||||
IIdType targetId = resourceReference.getResource().getIdElement();
|
||||
if (targetId.getValue() == null) {
|
||||
if (targetId.getValue() == null || targetId.getValue().startsWith("#")) {
|
||||
// This means it's a contained resource
|
||||
continue;
|
||||
} else if (theIdSubstitutions.containsValue(targetId)) {
|
||||
|
@ -1258,7 +1267,6 @@ public abstract class BaseTransactionProcessor {
|
|||
return dao;
|
||||
}
|
||||
|
||||
|
||||
private String toResourceName(Class<? extends IBaseResource> theResourceType) {
|
||||
return myContext.getResourceType(theResourceType);
|
||||
}
|
||||
|
@ -1318,11 +1326,6 @@ public abstract class BaseTransactionProcessor {
|
|||
return null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setDaoConfig(DaoConfig theDaoConfig) {
|
||||
myDaoConfig = theDaoConfig;
|
||||
}
|
||||
|
||||
public interface ITransactionProcessorVersionAdapter<BUNDLE extends IBaseBundle, BUNDLEENTRY extends IBase> {
|
||||
|
||||
void setResponseStatus(BUNDLEENTRY theBundleEntry, String theStatus);
|
||||
|
|
|
@ -51,8 +51,8 @@ public class FhirResourceDaoSubscriptionDstu2 extends BaseHapiFhirResourceDao<Su
|
|||
}
|
||||
|
||||
@Override
|
||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
||||
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
|
||||
ResourceTable entity = readEntityLatestVersion(theId, theRequest, theTransactionDetails);
|
||||
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
||||
if (table == null) {
|
||||
return null;
|
||||
|
|
|
@ -31,22 +31,26 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
|||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.util.StopWatch;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
@Service
|
||||
public class MatchResourceUrlService {
|
||||
@Autowired
|
||||
|
@ -65,31 +69,63 @@ public class MatchResourceUrlService {
|
|||
/**
|
||||
* Note that this will only return a maximum of 2 results!!
|
||||
*/
|
||||
public <R extends IBaseResource> Set<ResourcePersistentId> processMatchUrl(String theMatchUrl, Class<R> theResourceType, RequestDetails theRequest) {
|
||||
if (myDaoConfig.getMatchUrlCache()) {
|
||||
ResourcePersistentId existing = myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.MATCH_URL, theMatchUrl);
|
||||
if (existing != null) {
|
||||
return Collections.singleton(existing);
|
||||
public <R extends IBaseResource> Set<ResourcePersistentId> processMatchUrl(String theMatchUrl, Class<R> theResourceType, TransactionDetails theTransactionDetails, RequestDetails theRequest) {
|
||||
String resourceType = myContext.getResourceType(theResourceType);
|
||||
String matchUrl = massageForStorage(resourceType, theMatchUrl);
|
||||
|
||||
ResourcePersistentId resolvedInTransaction = theTransactionDetails.getResolvedMatchUrls().get(matchUrl);
|
||||
if (resolvedInTransaction != null) {
|
||||
if (resolvedInTransaction == TransactionDetails.NOT_FOUND) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
return Collections.singleton(resolvedInTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
ResourcePersistentId resolvedInCache = processMatchUrlUsingCacheOnly(resourceType, matchUrl);
|
||||
if (resolvedInCache != null) {
|
||||
return Collections.singleton(resolvedInCache);
|
||||
}
|
||||
|
||||
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(theResourceType);
|
||||
SearchParameterMap paramMap = myMatchUrlService.translateMatchUrl(theMatchUrl, resourceDef);
|
||||
SearchParameterMap paramMap = myMatchUrlService.translateMatchUrl(matchUrl, resourceDef);
|
||||
if (paramMap.isEmpty() && paramMap.getLastUpdated() == null) {
|
||||
throw new InvalidRequestException("Invalid match URL[" + theMatchUrl + "] - URL has no search parameters");
|
||||
throw new InvalidRequestException("Invalid match URL[" + matchUrl + "] - URL has no search parameters");
|
||||
}
|
||||
paramMap.setLoadSynchronousUpTo(2);
|
||||
|
||||
Set<ResourcePersistentId> retVal = search(paramMap, theResourceType, theRequest);
|
||||
|
||||
if (myDaoConfig.getMatchUrlCache() && retVal.size() == 1) {
|
||||
if (myDaoConfig.isMatchUrlCacheEnabled() && retVal.size() == 1) {
|
||||
ResourcePersistentId pid = retVal.iterator().next();
|
||||
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.MATCH_URL, theMatchUrl, pid);
|
||||
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.MATCH_URL, matchUrl, pid);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private String massageForStorage(String theResourceType, String theMatchUrl) {
|
||||
Validate.notBlank(theMatchUrl, "theMatchUrl must not be null or blank");
|
||||
int questionMarkIdx = theMatchUrl.indexOf("?");
|
||||
if (questionMarkIdx > 0) {
|
||||
return theMatchUrl;
|
||||
}
|
||||
if (questionMarkIdx == 0) {
|
||||
return theResourceType + theMatchUrl;
|
||||
}
|
||||
return theResourceType + "?" + theMatchUrl;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ResourcePersistentId processMatchUrlUsingCacheOnly(String theResourceType, String theMatchUrl) {
|
||||
ResourcePersistentId existing = null;
|
||||
if (myDaoConfig.getMatchUrlCache()) {
|
||||
String matchUrl = massageForStorage(theResourceType, theMatchUrl);
|
||||
existing = myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.MATCH_URL, matchUrl);
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
|
||||
public <R extends IBaseResource> Set<ResourcePersistentId> search(SearchParameterMap theParamMap, Class<R> theResourceType, RequestDetails theRequest) {
|
||||
StopWatch sw = new StopWatch();
|
||||
IFhirResourceDao<R> dao = myDaoRegistry.getResourceDao(theResourceType);
|
||||
|
@ -113,11 +149,14 @@ public class MatchResourceUrlService {
|
|||
}
|
||||
|
||||
|
||||
public void matchUrlResolved(String theMatchUrl, ResourcePersistentId theResourcePersistentId) {
|
||||
public void matchUrlResolved(TransactionDetails theTransactionDetails, String theResourceType, String theMatchUrl, ResourcePersistentId theResourcePersistentId) {
|
||||
Validate.notBlank(theMatchUrl);
|
||||
Validate.notNull(theResourcePersistentId);
|
||||
if (myDaoConfig.getMatchUrlCache()) {
|
||||
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.MATCH_URL, theMatchUrl, theResourcePersistentId);
|
||||
String matchUrl = massageForStorage(theResourceType, theMatchUrl);
|
||||
theTransactionDetails.addResolvedMatchUrl(matchUrl, theResourcePersistentId);
|
||||
if (myDaoConfig.isMatchUrlCacheEnabled()) {
|
||||
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.MATCH_URL, matchUrl, theResourcePersistentId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,12 +20,31 @@ package ca.uhn.fhir.jpa.dao;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
|
||||
import ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect;
|
||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.util.StopWatch;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.internal.SessionImpl;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -35,17 +54,48 @@ import javax.persistence.EntityManager;
|
|||
import javax.persistence.PersistenceContext;
|
||||
import javax.persistence.PersistenceContextType;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.persistence.TypedQuery;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static ca.uhn.fhir.jpa.dao.index.IdHelperService.EMPTY_PREDICATE_ARRAY;
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public class TransactionProcessor extends BaseTransactionProcessor {
|
||||
|
||||
public static final Pattern SINGLE_PARAMETER_MATCH_URL_PATTERN = Pattern.compile("^[^?]+[?][a-z0-9-]+=[^&,]+$");
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(TransactionProcessor.class);
|
||||
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
||||
private EntityManager myEntityManager;
|
||||
@Autowired(required = false)
|
||||
private HapiFhirHibernateJpaDialect myHapiFhirHibernateJpaDialect;
|
||||
@Autowired
|
||||
private IdHelperService myIdHelperService;
|
||||
@Autowired
|
||||
private PartitionSettings myPartitionSettings;
|
||||
@Autowired
|
||||
private DaoConfig myDaoConfig;
|
||||
@Autowired
|
||||
private FhirContext myFhirContext;
|
||||
@Autowired
|
||||
private MatchResourceUrlService myMatchResourceUrlService;
|
||||
@Autowired
|
||||
private MatchUrlService myMatchUrlService;
|
||||
@Autowired
|
||||
private IRequestPartitionHelperSvc myRequestPartitionSvc;
|
||||
|
||||
|
||||
public void setEntityManagerForUnitTest(EntityManager theEntityManager) {
|
||||
myEntityManager = theEntityManager;
|
||||
|
@ -58,6 +108,225 @@ public class TransactionProcessor extends BaseTransactionProcessor {
|
|||
Validate.notNull(myEntityManager);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setFhirContextForUnitTest(FhirContext theFhirContext) {
|
||||
myFhirContext = theFhirContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<IBase, IIdType> doTransactionWriteOperations(final RequestDetails theRequest, String theActionName, TransactionDetails theTransactionDetails, Set<IIdType> theAllIds,
|
||||
Map<IIdType, IIdType> theIdSubstitutions, Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome, IBaseBundle theResponse, IdentityHashMap<IBase, Integer> theOriginalRequestOrder, List<IBase> theEntries, StopWatch theTransactionStopWatch) {
|
||||
|
||||
ITransactionProcessorVersionAdapter versionAdapter = getVersionAdapter();
|
||||
RequestPartitionId requestPartitionId = null;
|
||||
if (!myPartitionSettings.isPartitioningEnabled()) {
|
||||
requestPartitionId = RequestPartitionId.allPartitions();
|
||||
} else {
|
||||
// If all entries in the transaction point to the exact same partition, we'll try and do a pre-fetch
|
||||
Set<RequestPartitionId> requestPartitionIdsForAllEntries = new HashSet<>();
|
||||
for (IBase nextEntry : theEntries) {
|
||||
IBaseResource resource = versionAdapter.getResource(nextEntry);
|
||||
if (resource != null) {
|
||||
RequestPartitionId requestPartition = myRequestPartitionSvc.determineReadPartitionForRequest(theRequest, myFhirContext.getResourceType(resource));
|
||||
requestPartitionIdsForAllEntries.add(requestPartition);
|
||||
}
|
||||
}
|
||||
if (requestPartitionIdsForAllEntries.size() == 1) {
|
||||
requestPartitionId = requestPartitionIdsForAllEntries.iterator().next();
|
||||
}
|
||||
}
|
||||
|
||||
if (requestPartitionId != null) {
|
||||
|
||||
Set<String> foundIds = new HashSet<>();
|
||||
List<Long> idsToPreFetch = new ArrayList<>();
|
||||
|
||||
/*
|
||||
* Pre-Fetch any resources that are referred to normally by ID, e.g.
|
||||
* regular FHIR updates within the transaction.
|
||||
*/
|
||||
List<IIdType> idsToPreResolve = new ArrayList<>();
|
||||
for (IBase nextEntry : theEntries) {
|
||||
IBaseResource resource = versionAdapter.getResource(nextEntry);
|
||||
if (resource != null) {
|
||||
String fullUrl = versionAdapter.getFullUrl(nextEntry);
|
||||
boolean isPlaceholder = defaultString(fullUrl).startsWith("urn:");
|
||||
if (!isPlaceholder) {
|
||||
if (resource.getIdElement().hasIdPart() && resource.getIdElement().hasResourceType()) {
|
||||
idsToPreResolve.add(resource.getIdElement());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
List<ResourcePersistentId> outcome = myIdHelperService.resolveResourcePersistentIdsWithCache(requestPartitionId, idsToPreResolve);
|
||||
for (ResourcePersistentId next : outcome) {
|
||||
foundIds.add(next.getAssociatedResourceId().toUnqualifiedVersionless().getValue());
|
||||
theTransactionDetails.addResolvedResourceId(next.getAssociatedResourceId(), next);
|
||||
if (myDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY || !next.getAssociatedResourceId().isIdPartValidLong()) {
|
||||
idsToPreFetch.add(next.getIdAsLong());
|
||||
}
|
||||
}
|
||||
for (IIdType next : idsToPreResolve) {
|
||||
if (!foundIds.contains(next.toUnqualifiedVersionless().getValue())) {
|
||||
theTransactionDetails.addResolvedResourceId(next.toUnqualifiedVersionless(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Pre-resolve any conditional URLs we can
|
||||
*/
|
||||
List<MatchUrlToResolve> searchParameterMapsToResolve = new ArrayList<>();
|
||||
for (IBase nextEntry : theEntries) {
|
||||
IBaseResource resource = versionAdapter.getResource(nextEntry);
|
||||
if (resource != null) {
|
||||
String verb = versionAdapter.getEntryRequestVerb(myFhirContext, nextEntry);
|
||||
String requestUrl = versionAdapter.getEntryRequestUrl(nextEntry);
|
||||
String requestIfNoneExist = versionAdapter.getEntryIfNoneExist(nextEntry);
|
||||
String resourceType = myFhirContext.getResourceType(resource);
|
||||
if ("PUT".equals(verb) && requestUrl != null && requestUrl.contains("?")) {
|
||||
ResourcePersistentId cachedId = myMatchResourceUrlService.processMatchUrlUsingCacheOnly(resourceType, requestUrl);
|
||||
if (cachedId != null) {
|
||||
idsToPreFetch.add(cachedId.getIdAsLong());
|
||||
} else if (SINGLE_PARAMETER_MATCH_URL_PATTERN.matcher(requestUrl).matches()) {
|
||||
RuntimeResourceDefinition resourceDefinition = myFhirContext.getResourceDefinition(resource);
|
||||
SearchParameterMap matchUrlSearchMap = myMatchUrlService.translateMatchUrl(requestUrl, resourceDefinition);
|
||||
searchParameterMapsToResolve.add(new MatchUrlToResolve(requestUrl, matchUrlSearchMap, resourceDefinition));
|
||||
}
|
||||
} else if ("POST".equals(verb) && requestIfNoneExist != null && requestIfNoneExist.contains("?")) {
|
||||
ResourcePersistentId cachedId = myMatchResourceUrlService.processMatchUrlUsingCacheOnly(resourceType, requestIfNoneExist);
|
||||
if (cachedId != null) {
|
||||
idsToPreFetch.add(cachedId.getIdAsLong());
|
||||
} else if (SINGLE_PARAMETER_MATCH_URL_PATTERN.matcher(requestIfNoneExist).matches()) {
|
||||
RuntimeResourceDefinition resourceDefinition = myFhirContext.getResourceDefinition(resource);
|
||||
SearchParameterMap matchUrlSearchMap = myMatchUrlService.translateMatchUrl(requestIfNoneExist, resourceDefinition);
|
||||
searchParameterMapsToResolve.add(new MatchUrlToResolve(requestIfNoneExist, matchUrlSearchMap, resourceDefinition));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (searchParameterMapsToResolve.size() > 0) {
|
||||
CriteriaBuilder cb = myEntityManager.getCriteriaBuilder();
|
||||
CriteriaQuery<ResourceIndexedSearchParamToken> cq = cb.createQuery(ResourceIndexedSearchParamToken.class);
|
||||
Root<ResourceIndexedSearchParamToken> from = cq.from(ResourceIndexedSearchParamToken.class);
|
||||
List<Predicate> orPredicates = new ArrayList<>();
|
||||
|
||||
for (MatchUrlToResolve next : searchParameterMapsToResolve) {
|
||||
Collection<List<List<IQueryParameterType>>> values = next.myMatchUrlSearchMap.values();
|
||||
if (values.size() == 1) {
|
||||
List<List<IQueryParameterType>> andList = values.iterator().next();
|
||||
IQueryParameterType param = andList.get(0).get(0);
|
||||
|
||||
if (param instanceof TokenParam) {
|
||||
TokenParam tokenParam = (TokenParam) param;
|
||||
Predicate hashPredicate = null;
|
||||
if (isNotBlank(tokenParam.getValue()) && isNotBlank(tokenParam.getSystem())) {
|
||||
next.myHashSystemAndValue = ResourceIndexedSearchParamToken.calculateHashSystemAndValue(myPartitionSettings, requestPartitionId, next.myResourceDefinition.getName(), next.myMatchUrlSearchMap.keySet().iterator().next(), tokenParam.getSystem(), tokenParam.getValue());
|
||||
hashPredicate = cb.equal(from.get("myHashSystemAndValue").as(Long.class), next.myHashSystemAndValue);
|
||||
} else if (isNotBlank(tokenParam.getValue())) {
|
||||
next.myHashValue = ResourceIndexedSearchParamToken.calculateHashValue(myPartitionSettings, requestPartitionId, next.myResourceDefinition.getName(), next.myMatchUrlSearchMap.keySet().iterator().next(), tokenParam.getValue());
|
||||
hashPredicate = cb.equal(from.get("myHashValue").as(Long.class), next.myHashValue);
|
||||
}
|
||||
|
||||
if (hashPredicate != null) {
|
||||
|
||||
if (myPartitionSettings.isPartitioningEnabled() && !myPartitionSettings.isIncludePartitionInSearchHashes()) {
|
||||
if (requestPartitionId.isDefaultPartition()) {
|
||||
Predicate partitionIdCriteria = cb.isNull(from.get("myPartitionIdValue").as(Integer.class));
|
||||
hashPredicate = cb.and(hashPredicate, partitionIdCriteria);
|
||||
} else if (!requestPartitionId.isAllPartitions()) {
|
||||
Predicate partitionIdCriteria = from.get("myPartitionIdValue").as(Integer.class).in(requestPartitionId.getPartitionIds());
|
||||
hashPredicate = cb.and(hashPredicate, partitionIdCriteria);
|
||||
}
|
||||
}
|
||||
|
||||
orPredicates.add(hashPredicate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (orPredicates.size() > 1) {
|
||||
cq.where(cb.or(orPredicates.toArray(EMPTY_PREDICATE_ARRAY)));
|
||||
|
||||
TypedQuery<ResourceIndexedSearchParamToken> query = myEntityManager.createQuery(cq);
|
||||
List<ResourceIndexedSearchParamToken> results = query.getResultList();
|
||||
for (ResourceIndexedSearchParamToken nextResult : results) {
|
||||
|
||||
for (MatchUrlToResolve nextSearchParameterMap : searchParameterMapsToResolve) {
|
||||
if (nextSearchParameterMap.myHashSystemAndValue != null && nextSearchParameterMap.myHashSystemAndValue.equals(nextResult.getHashSystemAndValue())) {
|
||||
idsToPreFetch.add(nextResult.getResourcePid());
|
||||
myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, nextSearchParameterMap.myResourceDefinition.getName(), nextSearchParameterMap.myRequestUrl, new ResourcePersistentId(nextResult.getResourcePid()));
|
||||
theTransactionDetails.addResolvedMatchUrl(nextSearchParameterMap.myRequestUrl, new ResourcePersistentId(nextResult.getResourcePid()));
|
||||
nextSearchParameterMap.myResolved = true;
|
||||
}
|
||||
if (nextSearchParameterMap.myHashValue != null && nextSearchParameterMap.myHashValue.equals(nextResult.getHashValue())) {
|
||||
idsToPreFetch.add(nextResult.getResourcePid());
|
||||
myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, nextSearchParameterMap.myResourceDefinition.getName(), nextSearchParameterMap.myRequestUrl, new ResourcePersistentId(nextResult.getResourcePid()));
|
||||
theTransactionDetails.addResolvedMatchUrl(nextSearchParameterMap.myRequestUrl, new ResourcePersistentId(nextResult.getResourcePid()));
|
||||
nextSearchParameterMap.myResolved = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for (MatchUrlToResolve nextSearchParameterMap : searchParameterMapsToResolve) {
|
||||
// No matches
|
||||
if (!nextSearchParameterMap.myResolved) {
|
||||
theTransactionDetails.addResolvedMatchUrl(nextSearchParameterMap.myRequestUrl, TransactionDetails.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Pre-fetch the resources we're touching in this transaction in mass - this reduced the
|
||||
* number of database round trips.
|
||||
*
|
||||
* The thresholds below are kind of arbitrary. It's not
|
||||
* actually guaranteed that this pre-fetching will help (e.g. if a Bundle contains
|
||||
* a bundle of NOP conditional creates for example, the pre-fetching is actually loading
|
||||
* more data than would otherwise be loaded).
|
||||
*
|
||||
* However, for realistic average workloads, this should reduce the number of round trips.
|
||||
*/
|
||||
if (idsToPreFetch.size() > 2) {
|
||||
List<ResourceTable> loadedResourceTableEntries = preFetchIndexes(idsToPreFetch, "forcedId", "myForcedId");
|
||||
|
||||
if (loadedResourceTableEntries.stream().filter(t -> t.isParamsStringPopulated()).count() > 1) {
|
||||
preFetchIndexes(idsToPreFetch, "string", "myParamsString");
|
||||
}
|
||||
if (loadedResourceTableEntries.stream().filter(t -> t.isParamsTokenPopulated()).count() > 1) {
|
||||
preFetchIndexes(idsToPreFetch, "token", "myParamsToken");
|
||||
}
|
||||
if (loadedResourceTableEntries.stream().filter(t -> t.isParamsDatePopulated()).count() > 1) {
|
||||
preFetchIndexes(idsToPreFetch, "date", "myParamsDate");
|
||||
}
|
||||
if (loadedResourceTableEntries.stream().filter(t -> t.isParamsDatePopulated()).count() > 1) {
|
||||
preFetchIndexes(idsToPreFetch, "quantity", "myParamsQuantity");
|
||||
}
|
||||
if (loadedResourceTableEntries.stream().filter(t -> t.isHasLinks()).count() > 1) {
|
||||
preFetchIndexes(idsToPreFetch, "resourceLinks", "myResourceLinks");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return super.doTransactionWriteOperations(theRequest, theActionName, theTransactionDetails, theAllIds, theIdSubstitutions, theIdToPersistedOutcome, theResponse, theOriginalRequestOrder, theEntries, theTransactionStopWatch);
|
||||
}
|
||||
|
||||
private List<ResourceTable> preFetchIndexes(List<Long> ids, String typeDesc, String fieldName) {
|
||||
TypedQuery<ResourceTable> query = myEntityManager.createQuery("FROM ResourceTable r LEFT JOIN FETCH r." + fieldName + " WHERE r.myId IN ( :IDS )", ResourceTable.class);
|
||||
query.setParameter("IDS", ids);
|
||||
List<ResourceTable> indexFetchOutcome = query.getResultList();
|
||||
ourLog.debug("Pre-fetched {} {}} indexes", indexFetchOutcome.size(), typeDesc);
|
||||
return indexFetchOutcome;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void flushSession(Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome) {
|
||||
|
@ -86,5 +355,29 @@ public class TransactionProcessor extends BaseTransactionProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setPartitionSettingsForUnitTest(PartitionSettings thePartitionSettings) {
|
||||
myPartitionSettings = thePartitionSettings;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setIdHelperServiceForUnitTest(IdHelperService theIdHelperService) {
|
||||
myIdHelperService = theIdHelperService;
|
||||
}
|
||||
|
||||
private static class MatchUrlToResolve {
|
||||
|
||||
private final String myRequestUrl;
|
||||
private final SearchParameterMap myMatchUrlSearchMap;
|
||||
private final RuntimeResourceDefinition myResourceDefinition;
|
||||
public boolean myResolved;
|
||||
private Long myHashValue;
|
||||
private Long myHashSystemAndValue;
|
||||
|
||||
public MatchUrlToResolve(String theRequestUrl, SearchParameterMap theMatchUrlSearchMap, RuntimeResourceDefinition theResourceDefinition) {
|
||||
myRequestUrl = theRequestUrl;
|
||||
myMatchUrlSearchMap = theMatchUrlSearchMap;
|
||||
myResourceDefinition = theResourceDefinition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,8 +48,8 @@ public class FhirResourceDaoSubscriptionDstu3 extends BaseHapiFhirResourceDao<Su
|
|||
}
|
||||
|
||||
@Override
|
||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
||||
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
|
||||
ResourceTable entity = readEntityLatestVersion(theId, theRequest, theTransactionDetails);
|
||||
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
||||
if (table == null) {
|
||||
return null;
|
||||
|
|
|
@ -28,6 +28,7 @@ import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
|
|||
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
||||
import ca.uhn.fhir.jpa.model.cross.ResourceLookup;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
||||
import ca.uhn.fhir.jpa.util.QueryChunker;
|
||||
|
@ -50,11 +51,20 @@ import org.springframework.stereotype.Service;
|
|||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.persistence.PersistenceContextType;
|
||||
import javax.persistence.TypedQuery;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -86,6 +96,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
public class IdHelperService {
|
||||
private static final String RESOURCE_PID = "RESOURCE_PID";
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(IdHelperService.class);
|
||||
public static final Predicate[] EMPTY_PREDICATE_ARRAY = new Predicate[0];
|
||||
@Autowired
|
||||
protected IForcedIdDao myForcedIdDao;
|
||||
@Autowired
|
||||
|
@ -167,6 +178,9 @@ public class IdHelperService {
|
|||
return RequestPartitionId.stringifyForKey(theRequestPartitionId) + "/" + theResourceType + "/" + theId;
|
||||
}
|
||||
|
||||
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
||||
private EntityManager myEntityManager;
|
||||
|
||||
/**
|
||||
* Given a collection of resource IDs (resource type + id), resolves the internal persistent IDs.
|
||||
* <p>
|
||||
|
@ -181,71 +195,80 @@ public class IdHelperService {
|
|||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<ResourcePersistentId> retVal = new ArrayList<>();
|
||||
List<ResourcePersistentId> retVal = new ArrayList<>(theIds.size());
|
||||
|
||||
if (myDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY) {
|
||||
theIds
|
||||
.stream()
|
||||
.filter(IdHelperService::isValidPid)
|
||||
.map(IIdType::getIdPartAsLong)
|
||||
.map(ResourcePersistentId::new)
|
||||
.forEach(retVal::add);
|
||||
Set<IIdType> idsToCheck = new HashSet<>(theIds.size());
|
||||
for (IIdType nextId : theIds) {
|
||||
if (myDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY) {
|
||||
if (nextId.isIdPartValidLong()) {
|
||||
retVal.add(new ResourcePersistentId(nextId.getIdPartAsLong()).setAssociatedResourceId(nextId));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
String key = toForcedIdToPidKey(theRequestPartitionId, nextId.getResourceType(), nextId.getIdPart());
|
||||
ResourcePersistentId cachedId = myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.FORCED_ID_TO_PID, key);
|
||||
if (cachedId != null) {
|
||||
retVal.add(cachedId);
|
||||
continue;
|
||||
}
|
||||
|
||||
idsToCheck.add(nextId);
|
||||
}
|
||||
|
||||
ListMultimap<String, String> typeToIds = organizeIdsByResourceType(theIds);
|
||||
if (idsToCheck.size() > 0) {
|
||||
CriteriaBuilder cb = myEntityManager.getCriteriaBuilder();
|
||||
CriteriaQuery<ForcedId> criteriaQuery = cb.createQuery(ForcedId.class);
|
||||
Root<ForcedId> from = criteriaQuery.from(ForcedId.class);
|
||||
|
||||
for (Map.Entry<String, Collection<String>> nextEntry : typeToIds.asMap().entrySet()) {
|
||||
String nextResourceType = nextEntry.getKey();
|
||||
Collection<String> nextIds = nextEntry.getValue();
|
||||
if (isBlank(nextResourceType)) {
|
||||
List<Predicate> predicates = new ArrayList<>(idsToCheck.size());
|
||||
for (IIdType next : idsToCheck) {
|
||||
|
||||
List<Long> views = myForcedIdDao.findByForcedId(nextIds);
|
||||
views.forEach(t -> retVal.add(new ResourcePersistentId(t)));
|
||||
List<Predicate> andPredicates = new ArrayList<>(3);
|
||||
|
||||
} else {
|
||||
|
||||
// String partitionIdStringForKey = RequestPartitionId.stringifyForKey(theRequestPartitionId);
|
||||
for (Iterator<String> idIterator = nextIds.iterator(); idIterator.hasNext(); ) {
|
||||
String nextId = idIterator.next();
|
||||
String key = toForcedIdToPidKey(theRequestPartitionId, nextResourceType, nextId);
|
||||
ResourcePersistentId nextCachedPid = myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.FORCED_ID_TO_PID, key);
|
||||
if (nextCachedPid != null) {
|
||||
idIterator.remove();
|
||||
retVal.add(nextCachedPid);
|
||||
}
|
||||
if (isNotBlank(next.getResourceType())) {
|
||||
Predicate typeCriteria = cb.equal(from.get("myResourceType").as(String.class), next.getResourceType());
|
||||
andPredicates.add(typeCriteria);
|
||||
}
|
||||
|
||||
if (nextIds.size() > 0) {
|
||||
Predicate idCriteria = cb.equal(from.get("myForcedId").as(String.class), next.getIdPart());
|
||||
andPredicates.add(idCriteria);
|
||||
|
||||
Collection<Object[]> views;
|
||||
if (theRequestPartitionId.isAllPartitions()) {
|
||||
views = myForcedIdDao.findByTypeAndForcedId(nextResourceType, nextIds);
|
||||
} else {
|
||||
if (theRequestPartitionId.isDefaultPartition()) {
|
||||
views = myForcedIdDao.findByTypeAndForcedIdInPartitionNull(nextResourceType, nextIds);
|
||||
} else if (theRequestPartitionId.hasDefaultPartitionId()) {
|
||||
views = myForcedIdDao.findByTypeAndForcedIdInPartitionIdsOrNullPartition(nextResourceType, nextIds, theRequestPartitionId.getPartitionIds());
|
||||
} else {
|
||||
views = myForcedIdDao.findByTypeAndForcedIdInPartitionIds(nextResourceType, nextIds, theRequestPartitionId.getPartitionIds());
|
||||
}
|
||||
}
|
||||
for (Object[] nextView : views) {
|
||||
String forcedId = (String) nextView[0];
|
||||
Long pid = (Long) nextView[1];
|
||||
ResourcePersistentId persistentId = new ResourcePersistentId(pid);
|
||||
retVal.add(persistentId);
|
||||
|
||||
String key = toForcedIdToPidKey(theRequestPartitionId, nextResourceType, forcedId);
|
||||
myMemoryCacheService.put(MemoryCacheService.CacheEnum.FORCED_ID_TO_PID, key, persistentId);
|
||||
}
|
||||
if (theRequestPartitionId.isDefaultPartition()) {
|
||||
Predicate partitionIdCriteria = cb.isNull(from.get("myPartitionIdValue").as(Integer.class));
|
||||
andPredicates.add(partitionIdCriteria);
|
||||
} else if (!theRequestPartitionId.isAllPartitions()) {
|
||||
Predicate partitionIdCriteria = from.get("myPartitionIdValue").as(Integer.class).in(theRequestPartitionId.getPartitionIds());
|
||||
andPredicates.add(partitionIdCriteria);
|
||||
}
|
||||
|
||||
predicates.add(cb.and(andPredicates.toArray(EMPTY_PREDICATE_ARRAY)));
|
||||
}
|
||||
|
||||
criteriaQuery.where(cb.or(predicates.toArray(EMPTY_PREDICATE_ARRAY)));
|
||||
|
||||
TypedQuery<ForcedId> query = myEntityManager.createQuery(criteriaQuery);
|
||||
List<ForcedId> results = query.getResultList();
|
||||
for (ForcedId nextId : results) {
|
||||
ResourcePersistentId persistentId = new ResourcePersistentId(nextId.getResourceId());
|
||||
populateAssociatedResourceId(nextId.getResourceType(), nextId.getForcedId(), persistentId);
|
||||
retVal.add(persistentId);
|
||||
|
||||
String key = toForcedIdToPidKey(theRequestPartitionId, nextId.getResourceType(), nextId.getForcedId());
|
||||
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.FORCED_ID_TO_PID, key, persistentId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void populateAssociatedResourceId(String nextResourceType, String forcedId, ResourcePersistentId persistentId) {
|
||||
IIdType resourceId = myFhirCtx.getVersion().newIdType();
|
||||
resourceId.setValue(nextResourceType + "/" + forcedId);
|
||||
persistentId.setAssociatedResourceId(resourceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a persistent ID, returns the associated resource ID
|
||||
*/
|
||||
|
@ -501,6 +524,10 @@ public class IdHelperService {
|
|||
*/
|
||||
public void addResolvedPidToForcedId(ResourcePersistentId theResourcePersistentId, @Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, @Nullable String theForcedId) {
|
||||
if (theForcedId != null) {
|
||||
if (theResourcePersistentId.getAssociatedResourceId() == null) {
|
||||
populateAssociatedResourceId(theResourceType, theForcedId, theResourcePersistentId);
|
||||
}
|
||||
|
||||
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, theResourcePersistentId.getIdAsLong(), Optional.of(theForcedId));
|
||||
String key = toForcedIdToPidKey(theRequestPartitionId, theResourceType, theForcedId);
|
||||
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.FORCED_ID_TO_PID, key, theResourcePersistentId);
|
||||
|
|
|
@ -111,8 +111,8 @@ public class SearchParamWithInlineReferencesExtractor {
|
|||
mySearchParamRegistry = theSearchParamRegistry;
|
||||
}
|
||||
|
||||
public void populateFromResource(ResourceIndexedSearchParams theParams, TransactionDetails theTransactionDetails, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams theExistingParams, RequestDetails theRequest) {
|
||||
extractInlineReferences(theResource, theRequest);
|
||||
public void populateFromResource(ResourceIndexedSearchParams theParams, TransactionDetails theTransactionDetails, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams theExistingParams, RequestDetails theRequest, boolean theFailOnInvalidReference) {
|
||||
extractInlineReferences(theResource, theTransactionDetails, theRequest);
|
||||
|
||||
RequestPartitionId partitionId;
|
||||
if (myPartitionSettings.isPartitioningEnabled()) {
|
||||
|
@ -121,7 +121,7 @@ public class SearchParamWithInlineReferencesExtractor {
|
|||
partitionId = RequestPartitionId.allPartitions();
|
||||
}
|
||||
|
||||
mySearchParamExtractorService.extractFromResource(partitionId, theRequest, theParams, theEntity, theResource, theTransactionDetails, true);
|
||||
mySearchParamExtractorService.extractFromResource(partitionId, theRequest, theParams, theEntity, theResource, theTransactionDetails, theFailOnInvalidReference);
|
||||
|
||||
Set<Map.Entry<String, RuntimeSearchParam>> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet();
|
||||
if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.ENABLED) {
|
||||
|
@ -245,7 +245,7 @@ public class SearchParamWithInlineReferencesExtractor {
|
|||
* Handle references within the resource that are match URLs, for example references like "Patient?identifier=foo". These match URLs are resolved and replaced with the ID of the
|
||||
* matching resource.
|
||||
*/
|
||||
public void extractInlineReferences(IBaseResource theResource, RequestDetails theRequest) {
|
||||
public void extractInlineReferences(IBaseResource theResource, TransactionDetails theTransactionDetails, RequestDetails theRequest) {
|
||||
if (!myDaoConfig.isAllowInlineMatchUrlReferences()) {
|
||||
return;
|
||||
}
|
||||
|
@ -277,7 +277,7 @@ public class SearchParamWithInlineReferencesExtractor {
|
|||
}
|
||||
Class<? extends IBaseResource> matchResourceType = matchResourceDef.getImplementingClass();
|
||||
//Attempt to find the target reference before creating a placeholder
|
||||
Set<ResourcePersistentId> matches = myMatchResourceUrlService.processMatchUrl(nextIdText, matchResourceType, theRequest);
|
||||
Set<ResourcePersistentId> matches = myMatchResourceUrlService.processMatchUrl(nextIdText, matchResourceType, theTransactionDetails, theRequest);
|
||||
|
||||
ResourcePersistentId match;
|
||||
if (matches.isEmpty()) {
|
||||
|
|
|
@ -48,8 +48,8 @@ public class FhirResourceDaoSubscriptionR4 extends BaseHapiFhirResourceDao<Subsc
|
|||
}
|
||||
|
||||
@Override
|
||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
||||
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
|
||||
ResourceTable entity = readEntityLatestVersion(theId, theRequest, theTransactionDetails);
|
||||
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
||||
if (table == null) {
|
||||
return null;
|
||||
|
@ -72,7 +72,7 @@ public class FhirResourceDaoSubscriptionR4 extends BaseHapiFhirResourceDao<Subsc
|
|||
ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate, theCreateNewHistoryEntry);
|
||||
|
||||
if (theDeletedTimestampOrNull != null) {
|
||||
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt(), theRequest);
|
||||
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt(), theRequest, theTransactionDetails);
|
||||
if (subscriptionId != null) {
|
||||
mySubscriptionTableDao.deleteAllForSubscription(retVal);
|
||||
}
|
||||
|
|
|
@ -48,8 +48,8 @@ public class FhirResourceDaoSubscriptionR5 extends BaseHapiFhirResourceDao<Subsc
|
|||
}
|
||||
|
||||
@Override
|
||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
||||
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
|
||||
ResourceTable entity = readEntityLatestVersion(theId, theRequest, theTransactionDetails);
|
||||
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
||||
if (table == null) {
|
||||
return null;
|
||||
|
@ -72,7 +72,7 @@ public class FhirResourceDaoSubscriptionR5 extends BaseHapiFhirResourceDao<Subsc
|
|||
ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate, theCreateNewHistoryEntry);
|
||||
|
||||
if (theDeletedTimestampOrNull != null) {
|
||||
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt(), theRequest);
|
||||
Long subscriptionId = getSubscriptionTablePidForSubscriptionResource(theEntity.getIdDt(), theRequest, theTransactionDetails);
|
||||
if (subscriptionId != null) {
|
||||
mySubscriptionTableDao.deleteAllForSubscription(retVal);
|
||||
}
|
||||
|
|
|
@ -54,7 +54,8 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {
|
|||
}
|
||||
|
||||
@Override
|
||||
public AddRemoveCount updatePresence(ResourceTable theResource, Map<String, Boolean> theParamNameToPresence) {
|
||||
public AddRemoveCount
|
||||
updatePresence(ResourceTable theResource, Map<String, Boolean> theParamNameToPresence) {
|
||||
AddRemoveCount retVal = new AddRemoveCount();
|
||||
if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.DISABLED) {
|
||||
return retVal;
|
||||
|
|
|
@ -378,6 +378,9 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe
|
|||
b.append(new InstantType(new Date(theQuery.getQueryTimestamp())).getValueAsString());
|
||||
b.append(" took ").append(StopWatch.formatMillis(theQuery.getElapsedTime()));
|
||||
b.append(" on Thread: ").append(theQuery.getThreadName());
|
||||
if (theQuery.getSize() > 1) {
|
||||
b.append("\nExecution Count: ").append(theQuery.getSize()).append(" (parameters shown are for first execution)");
|
||||
}
|
||||
b.append("\nSQL:\n").append(formattedSql);
|
||||
if (theQuery.getStackTrace() != null) {
|
||||
b.append("\nStack:\n ");
|
||||
|
|
|
@ -26,7 +26,6 @@ import org.hibernate.engine.jdbc.internal.BasicFormatterImpl;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.trim;
|
||||
|
||||
|
@ -93,11 +92,7 @@ public class SqlQuery {
|
|||
}
|
||||
}
|
||||
|
||||
if (mySize > 1) {
|
||||
retVal += "\nsize: " + mySize + "\n";
|
||||
}
|
||||
return trim(retVal);
|
||||
|
||||
}
|
||||
|
||||
public StackTraceElement[] getStackTrace() {
|
||||
|
|
|
@ -11,14 +11,17 @@ import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
|
|||
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
||||
import ca.uhn.fhir.jpa.bulk.export.api.IBulkDataExportSvc;
|
||||
import ca.uhn.fhir.jpa.config.BaseConfig;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamDateDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamTokenDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
|
||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||
import ca.uhn.fhir.jpa.entity.TermConcept;
|
||||
import ca.uhn.fhir.jpa.entity.TermValueSet;
|
||||
import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
|
||||
import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
|
||||
import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
|
||||
|
@ -162,6 +165,10 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||
private IValidationSupport myJpaPersistedValidationSupport;
|
||||
@Autowired
|
||||
private FhirInstanceValidator myFhirInstanceValidator;
|
||||
@Autowired
|
||||
private IResourceTableDao myResourceTableDao;
|
||||
@Autowired
|
||||
private IResourceHistoryTableDao myResourceHistoryTableDao;
|
||||
|
||||
@AfterEach
|
||||
public void afterPerformCleanup() {
|
||||
|
@ -242,6 +249,22 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||
});
|
||||
}
|
||||
|
||||
protected int logAllResources() {
|
||||
return runInTransaction(() -> {
|
||||
List<ResourceTable> resources = myResourceTableDao.findAll();
|
||||
ourLog.info("Resources:\n * {}", resources.stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
|
||||
return resources.size();
|
||||
});
|
||||
}
|
||||
|
||||
protected int logAllResourceVersions() {
|
||||
return runInTransaction(() -> {
|
||||
List<ResourceTable> resources = myResourceTableDao.findAll();
|
||||
ourLog.info("Resources Versions:\n * {}", resources.stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
|
||||
return resources.size();
|
||||
});
|
||||
}
|
||||
|
||||
protected void logAllDateIndexes() {
|
||||
runInTransaction(() -> {
|
||||
ourLog.info("Date indexes:\n * {}", myResourceIndexedSearchParamDateDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
|
||||
|
@ -630,7 +653,7 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||
throw new Error(theE);
|
||||
}
|
||||
}
|
||||
if (sw.getMillis() >= 16000) {
|
||||
if (sw.getMillis() >= 16000 || theList.size() > theTarget) {
|
||||
String describeResults = theList
|
||||
.stream()
|
||||
.map(t -> {
|
||||
|
|
|
@ -5,9 +5,13 @@ import ca.uhn.fhir.context.FhirVersionEnum;
|
|||
import ca.uhn.fhir.interceptor.executor.InterceptorService;
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||
import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4;
|
||||
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryResourceMatcher;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.hibernate.Session;
|
||||
|
@ -56,6 +60,14 @@ public class TransactionProcessorTest {
|
|||
private ModelConfig myModelConfig;
|
||||
@MockBean
|
||||
private InMemoryResourceMatcher myInMemoryResourceMatcher;
|
||||
@MockBean
|
||||
private IdHelperService myIdHelperService;
|
||||
@MockBean
|
||||
private PartitionSettings myPartitionSettings;
|
||||
@MockBean
|
||||
private MatchUrlService myMatchUrlService;
|
||||
@MockBean
|
||||
private IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
|
||||
|
||||
@MockBean(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private SessionImpl mySession;
|
||||
|
|
|
@ -429,19 +429,11 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2SystemTest {
|
|||
p.addIdentifier().setSystem("urn:system").setValue(methodName);
|
||||
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerbEnum.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName);
|
||||
|
||||
try {
|
||||
myCaptureQueriesListener.clear();
|
||||
mySystemDao.transaction(mySrd, request);
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
myCaptureQueriesListener.clear();
|
||||
mySystemDao.transaction(mySrd, request);
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
|
||||
runInTransaction(()->{
|
||||
ourLog.info("Tokens:\n * {}", myResourceIndexedSearchParamTokenDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
|
||||
});
|
||||
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals(e.getMessage(), "Unable to process Transaction - Request would cause multiple resources to match URL: \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateWithDuplicateMatchUrl01\". Does transaction request contain duplicates?");
|
||||
}
|
||||
assertEquals(1, logAllResources());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1008,29 +1008,6 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
|
|||
assertThat(oo.getIssue().get(0).getDiagnostics(), containsString("Unknown search parameter"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionCreateWithDuplicateMatchUrl01() {
|
||||
String methodName = "testTransactionCreateWithDuplicateMatchUrl01";
|
||||
Bundle request = new Bundle();
|
||||
|
||||
Patient p;
|
||||
p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue(methodName);
|
||||
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName);
|
||||
|
||||
p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue(methodName);
|
||||
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName);
|
||||
|
||||
try {
|
||||
mySystemDao.transaction(mySrd, request);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals(e.getMessage(),
|
||||
"Unable to process Transaction - Request would cause multiple resources to match URL: \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateWithDuplicateMatchUrl01\". Does transaction request contain duplicates?");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionCreateWithDuplicateMatchUrl02() {
|
||||
String methodName = "testTransactionCreateWithDuplicateMatchUrl02";
|
||||
|
@ -1127,27 +1104,6 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionCreateWithLinks() {
|
||||
Bundle request = new Bundle();
|
||||
request.setType(BundleType.TRANSACTION);
|
||||
|
||||
Observation o = new Observation();
|
||||
o.setId("A");
|
||||
o.setStatus(ObservationStatus.AMENDED);
|
||||
|
||||
request.addEntry()
|
||||
.setResource(o)
|
||||
.getRequest().setUrl("A").setMethod(HTTPVerb.PUT);
|
||||
|
||||
try {
|
||||
mySystemDao.transaction(mySrd, request);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals("Invalid match URL[A] - URL has no search parameters", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionCreateWithPutUsingAbsoluteUrl() {
|
||||
String methodName = "testTransactionCreateWithPutUsingAbsoluteUrl";
|
||||
|
@ -1699,69 +1655,6 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionDoubleConditionalCreateOnlyCreatesOne() {
|
||||
Bundle inputBundle = new Bundle();
|
||||
inputBundle.setType(Bundle.BundleType.TRANSACTION);
|
||||
|
||||
Encounter enc1 = new Encounter();
|
||||
enc1.addIdentifier().setSystem("urn:foo").setValue("12345");
|
||||
inputBundle
|
||||
.addEntry()
|
||||
.setResource(enc1)
|
||||
.getRequest()
|
||||
.setMethod(HTTPVerb.POST)
|
||||
.setIfNoneExist("Encounter?identifier=urn:foo|12345");
|
||||
Encounter enc2 = new Encounter();
|
||||
enc2.addIdentifier().setSystem("urn:foo").setValue("12345");
|
||||
inputBundle
|
||||
.addEntry()
|
||||
.setResource(enc2)
|
||||
.getRequest()
|
||||
.setMethod(HTTPVerb.POST)
|
||||
.setIfNoneExist("Encounter?identifier=urn:foo|12345");
|
||||
|
||||
try {
|
||||
mySystemDao.transaction(mySrd, inputBundle);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionDoubleConditionalUpdateOnlyCreatesOne() {
|
||||
Bundle inputBundle = new Bundle();
|
||||
inputBundle.setType(Bundle.BundleType.TRANSACTION);
|
||||
|
||||
Encounter enc1 = new Encounter();
|
||||
enc1.addIdentifier().setSystem("urn:foo").setValue("12345");
|
||||
inputBundle
|
||||
.addEntry()
|
||||
.setResource(enc1)
|
||||
.getRequest()
|
||||
.setMethod(HTTPVerb.PUT)
|
||||
.setUrl("Encounter?identifier=urn:foo|12345");
|
||||
Encounter enc2 = new Encounter();
|
||||
enc2.addIdentifier().setSystem("urn:foo").setValue("12345");
|
||||
inputBundle
|
||||
.addEntry()
|
||||
.setResource(enc2)
|
||||
.getRequest()
|
||||
.setMethod(HTTPVerb.PUT)
|
||||
.setUrl("Encounter?identifier=urn:foo|12345");
|
||||
|
||||
try {
|
||||
mySystemDao.transaction(mySrd, inputBundle);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?",
|
||||
e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionFailsWithDuplicateIds() {
|
||||
Bundle request = new Bundle();
|
||||
|
|
|
@ -68,6 +68,8 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest {
|
|||
|
||||
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
|
||||
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(new DaoConfig().isAutoCreatePlaceholderReferenceTargets());
|
||||
myDaoConfig.setMassIngestionMode(new DaoConfig().isMassIngestionMode());
|
||||
myDaoConfig.setMatchUrlCacheEnabled(new DaoConfig().getMatchUrlCache());
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
|
|
|
@ -231,7 +231,7 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
|
|||
p = myPatientDao.read(new IdType("Patient/" + firstClientAssignedId));
|
||||
assertEquals(true, p.getActive());
|
||||
|
||||
// Not create a client assigned numeric ID
|
||||
// Now create a client assigned numeric ID
|
||||
p = new Patient();
|
||||
p.setId("Patient/" + newId);
|
||||
p.addName().setFamily("FAM");
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package ca.uhn.fhir.jpa.dao.r4;
|
||||
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.api.model.HistoryCountModeEnum;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
|
@ -8,7 +7,6 @@ import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
|||
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.util.SqlQuery;
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.SortSpec;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
|
@ -28,6 +26,7 @@ import org.hl7.fhir.r4.model.Narrative;
|
|||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Practitioner;
|
||||
import org.hl7.fhir.r4.model.Quantity;
|
||||
import org.hl7.fhir.r4.model.Reference;
|
||||
import org.hl7.fhir.r4.model.ServiceRequest;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
|
@ -36,9 +35,8 @@ import org.junit.jupiter.api.BeforeEach;
|
|||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -65,6 +63,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
myModelConfig.setAutoVersionReferenceAtPaths(new ModelConfig().getAutoVersionReferenceAtPaths());
|
||||
myModelConfig.setRespectVersionsForSearchIncludes(new ModelConfig().isRespectVersionsForSearchIncludes());
|
||||
myFhirCtx.getParserOptions().setStripVersionsFromReferences(true);
|
||||
myDaoConfig.setTagStorageMode(new DaoConfig().getTagStorageMode());
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
|
@ -553,7 +552,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
|
||||
myCaptureQueriesListener.clear();
|
||||
assertEquals(1, myObservationDao.search(map).size().intValue());
|
||||
// Resolve forced ID, Perform search, load result
|
||||
// (not resolve forced ID), Perform search, load result
|
||||
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertNoPartitionSelectors();
|
||||
assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
|
@ -567,7 +566,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
myCaptureQueriesListener.clear();
|
||||
assertEquals(1, myObservationDao.search(map).size().intValue());
|
||||
myCaptureQueriesListener.logAllQueriesForCurrentThread();
|
||||
// Resolve forced ID, Perform search, load result (this time we reuse the cached forced-id resolution)
|
||||
// (not resolve forced ID), Perform search, load result (this time we reuse the cached forced-id resolution)
|
||||
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
|
@ -595,7 +594,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
myCaptureQueriesListener.clear();
|
||||
assertEquals(1, myObservationDao.search(map).size().intValue());
|
||||
myCaptureQueriesListener.logAllQueriesForCurrentThread();
|
||||
// Resolve forced ID, Perform search, load result
|
||||
// (not Resolve forced ID), Perform search, load result
|
||||
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
|
@ -691,11 +690,235 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionWithTwoCreates() {
|
||||
|
||||
BundleBuilder bb = new BundleBuilder(myFhirCtx);
|
||||
|
||||
Patient pt = new Patient();
|
||||
pt.setId(IdType.newRandomUuid());
|
||||
pt.addIdentifier().setSystem("http://foo").setValue("123");
|
||||
bb.addTransactionCreateEntry(pt);
|
||||
|
||||
Patient pt2 = new Patient();
|
||||
pt2.setId(IdType.newRandomUuid());
|
||||
pt2.addIdentifier().setSystem("http://foo").setValue("456");
|
||||
bb.addTransactionCreateEntry(pt2);
|
||||
|
||||
runInTransaction(() -> {
|
||||
assertEquals(0, myResourceTableDao.count());
|
||||
});
|
||||
|
||||
ourLog.info("About to start transaction");
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) bb.getBundle());
|
||||
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertEquals(0, myCaptureQueriesListener.countSelectQueries());
|
||||
myCaptureQueriesListener.logInsertQueries();
|
||||
assertEquals(3, myCaptureQueriesListener.countInsertQueries());
|
||||
myCaptureQueriesListener.logUpdateQueries();
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
|
||||
runInTransaction(() -> {
|
||||
assertEquals(2, myResourceTableDao.count());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionWithMultipleUpdates() {
|
||||
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
Supplier<Bundle> input = () -> {
|
||||
BundleBuilder bb = new BundleBuilder(myFhirCtx);
|
||||
|
||||
Patient pt = new Patient();
|
||||
pt.setId("Patient/A");
|
||||
pt.addIdentifier().setSystem("http://foo").setValue("123");
|
||||
bb.addTransactionUpdateEntry(pt);
|
||||
|
||||
Observation obsA = new Observation();
|
||||
obsA.setId("Observation/A");
|
||||
obsA.getCode().addCoding().setSystem("http://foo").setCode("bar");
|
||||
obsA.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
|
||||
obsA.setEffective(new DateTimeType(new Date()));
|
||||
obsA.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
|
||||
bb.addTransactionUpdateEntry(obsA);
|
||||
|
||||
Observation obsB = new Observation();
|
||||
obsB.setId("Observation/B");
|
||||
obsB.getCode().addCoding().setSystem("http://foo").setCode("bar");
|
||||
obsB.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
|
||||
obsB.setEffective(new DateTimeType(new Date()));
|
||||
obsB.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
|
||||
bb.addTransactionUpdateEntry(obsB);
|
||||
|
||||
return (Bundle) bb.getBundle();
|
||||
};
|
||||
|
||||
ourLog.info("About to start transaction");
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
Bundle outcome = mySystemDao.transaction(mySrd, input.get());
|
||||
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
|
||||
myCaptureQueriesListener.logInsertQueries();
|
||||
assertEquals(6, myCaptureQueriesListener.countInsertQueries());
|
||||
myCaptureQueriesListener.logUpdateQueries();
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
|
||||
/*
|
||||
* Run a second time
|
||||
*/
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
outcome = mySystemDao.transaction(mySrd, input.get());
|
||||
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertEquals(10, myCaptureQueriesListener.countSelectQueries());
|
||||
myCaptureQueriesListener.logInsertQueries();
|
||||
assertEquals(1, myCaptureQueriesListener.countInsertQueries());
|
||||
myCaptureQueriesListener.logUpdateQueries();
|
||||
assertEquals(2, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
|
||||
/*
|
||||
* Third time with mass ingestion mode enabled
|
||||
*/
|
||||
myDaoConfig.setMassIngestionMode(true);
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
outcome = mySystemDao.transaction(mySrd, input.get());
|
||||
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertEquals(7, myCaptureQueriesListener.countSelectQueries());
|
||||
myCaptureQueriesListener.logInsertQueries();
|
||||
assertEquals(1, myCaptureQueriesListener.countInsertQueries());
|
||||
myCaptureQueriesListener.logUpdateQueries();
|
||||
assertEquals(2, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTransactionWithMultipleConditionalUpdates() {
|
||||
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
Supplier<Bundle> input = () -> {
|
||||
BundleBuilder bb = new BundleBuilder(myFhirCtx);
|
||||
|
||||
Patient pt = new Patient();
|
||||
pt.setId(IdType.newRandomUuid());
|
||||
pt.addIdentifier().setSystem("http://foo").setValue("123");
|
||||
bb.addTransactionCreateEntry(pt).conditional("Patient?identifier=http://foo|123");
|
||||
|
||||
Observation obsA = new Observation();
|
||||
obsA.getSubject().setReference(pt.getId());
|
||||
obsA.getCode().addCoding().setSystem("http://foo").setCode("bar1");
|
||||
obsA.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
|
||||
obsA.setEffective(new DateTimeType(new Date()));
|
||||
obsA.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
|
||||
bb.addTransactionUpdateEntry(obsA).conditional("Observation?code=http://foo|bar1");
|
||||
|
||||
Observation obsB = new Observation();
|
||||
obsB.getSubject().setReference(pt.getId());
|
||||
obsB.getCode().addCoding().setSystem("http://foo").setCode("bar2");
|
||||
obsB.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
|
||||
obsB.setEffective(new DateTimeType(new Date()));
|
||||
obsB.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
|
||||
bb.addTransactionUpdateEntry(obsB).conditional("Observation?code=http://foo|bar2");
|
||||
|
||||
Observation obsC = new Observation();
|
||||
obsC.getSubject().setReference(pt.getId());
|
||||
obsC.getCode().addCoding().setSystem("http://foo").setCode("bar3");
|
||||
obsC.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
|
||||
obsC.setEffective(new DateTimeType(new Date()));
|
||||
obsC.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
|
||||
bb.addTransactionUpdateEntry(obsC).conditional("Observation?code=bar3");
|
||||
|
||||
Observation obsD = new Observation();
|
||||
obsD.getSubject().setReference(pt.getId());
|
||||
obsD.getCode().addCoding().setSystem("http://foo").setCode("bar4");
|
||||
obsD.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
|
||||
obsD.setEffective(new DateTimeType(new Date()));
|
||||
obsD.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
|
||||
bb.addTransactionUpdateEntry(obsD).conditional("Observation?code=bar4");
|
||||
|
||||
return (Bundle) bb.getBundle();
|
||||
};
|
||||
|
||||
ourLog.info("About to start transaction");
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
Bundle outcome = mySystemDao.transaction(mySrd, input.get());
|
||||
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
|
||||
myCaptureQueriesListener.logInsertQueries();
|
||||
assertEquals(6, myCaptureQueriesListener.countInsertQueries());
|
||||
myCaptureQueriesListener.logUpdateQueries();
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
|
||||
/*
|
||||
* Run a second time
|
||||
*/
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
outcome = mySystemDao.transaction(mySrd, input.get());
|
||||
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertEquals(11, myCaptureQueriesListener.countSelectQueries());
|
||||
myCaptureQueriesListener.logInsertQueries();
|
||||
assertEquals(1, myCaptureQueriesListener.countInsertQueries());
|
||||
myCaptureQueriesListener.logUpdateQueries();
|
||||
assertEquals(2, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
|
||||
/*
|
||||
* Third time with mass ingestion mode enabled
|
||||
*/
|
||||
myDaoConfig.setMassIngestionMode(true);
|
||||
myDaoConfig.setMatchUrlCache(true);
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
outcome = mySystemDao.transaction(mySrd, input.get());
|
||||
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertEquals(6, myCaptureQueriesListener.countSelectQueries());
|
||||
myCaptureQueriesListener.logInsertQueries();
|
||||
assertEquals(1, myCaptureQueriesListener.countInsertQueries());
|
||||
myCaptureQueriesListener.logUpdateQueries();
|
||||
assertEquals(2, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
|
||||
/*
|
||||
* Fourth time with mass ingestion mode enabled
|
||||
*/
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
outcome = mySystemDao.transaction(mySrd, input.get());
|
||||
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertEquals(5, myCaptureQueriesListener.countSelectQueries());
|
||||
myCaptureQueriesListener.logInsertQueries();
|
||||
assertEquals(1, myCaptureQueriesListener.countInsertQueries());
|
||||
myCaptureQueriesListener.logUpdateQueries();
|
||||
assertEquals(2, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTransactionWithConditionalCreate_MatchUrlCacheEnabled() {
|
||||
myDaoConfig.setMatchUrlCache(true);
|
||||
|
||||
Supplier<Bundle> bundleCreator = ()-> {
|
||||
Supplier<Bundle> bundleCreator = () -> {
|
||||
BundleBuilder bb = new BundleBuilder(myFhirCtx);
|
||||
|
||||
Patient pt = new Patient();
|
||||
|
@ -719,7 +942,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
assertEquals(5, myCaptureQueriesListener.countInsertQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
|
||||
runInTransaction(()->{
|
||||
runInTransaction(() -> {
|
||||
List<String> types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList());
|
||||
assertThat(types, containsInAnyOrder("Patient", "Observation"));
|
||||
});
|
||||
|
@ -733,7 +956,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
assertEquals(3, myCaptureQueriesListener.countInsertQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
|
||||
runInTransaction(()->{
|
||||
runInTransaction(() -> {
|
||||
List<String> types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList());
|
||||
assertThat(types, containsInAnyOrder("Patient", "Observation", "Observation"));
|
||||
});
|
||||
|
@ -746,7 +969,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
assertEquals(3, myCaptureQueriesListener.countInsertQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
|
||||
runInTransaction(()->{
|
||||
runInTransaction(() -> {
|
||||
List<String> types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList());
|
||||
assertThat(types, containsInAnyOrder("Patient", "Observation", "Observation", "Observation"));
|
||||
});
|
||||
|
@ -756,7 +979,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
@Test
|
||||
public void testTransactionWithConditionalCreate_MatchUrlCacheNotEnabled() {
|
||||
|
||||
Supplier<Bundle> bundleCreator = ()-> {
|
||||
Supplier<Bundle> bundleCreator = () -> {
|
||||
BundleBuilder bb = new BundleBuilder(myFhirCtx);
|
||||
|
||||
Patient pt = new Patient();
|
||||
|
@ -781,7 +1004,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
assertEquals(5, myCaptureQueriesListener.countInsertQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
|
||||
runInTransaction(()->{
|
||||
runInTransaction(() -> {
|
||||
List<String> types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList());
|
||||
assertThat(types, containsInAnyOrder("Patient", "Observation"));
|
||||
});
|
||||
|
@ -800,7 +1023,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
assertThat(matchUrlQuery, containsString("t0.HASH_SYS_AND_VALUE = '-4132452001562191669'"));
|
||||
assertThat(matchUrlQuery, containsString("limit '2'"));
|
||||
|
||||
runInTransaction(()->{
|
||||
runInTransaction(() -> {
|
||||
List<String> types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList());
|
||||
assertThat(types, containsInAnyOrder("Patient", "Observation", "Observation"));
|
||||
});
|
||||
|
@ -813,7 +1036,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
assertEquals(3, myCaptureQueriesListener.countInsertQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
|
||||
runInTransaction(()->{
|
||||
runInTransaction(() -> {
|
||||
List<String> types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList());
|
||||
assertThat(types, containsInAnyOrder("Patient", "Observation", "Observation", "Observation"));
|
||||
});
|
||||
|
@ -855,14 +1078,14 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
myCaptureQueriesListener.logInsertQueriesForCurrentThread();
|
||||
assertEquals(4, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
// Pass 2
|
||||
|
||||
input = new Bundle();
|
||||
|
||||
patient = new Patient();
|
||||
patient = new Patient();
|
||||
patient.setId("Patient/A");
|
||||
patient.setActive(true);
|
||||
input.addEntry()
|
||||
|
@ -872,7 +1095,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
.setMethod(Bundle.HTTPVerb.PUT)
|
||||
.setUrl("Patient/A");
|
||||
|
||||
observation = new Observation();
|
||||
observation = new Observation();
|
||||
observation.setId(IdType.newRandomUuid());
|
||||
observation.addReferenceRange().setText("A");
|
||||
input.addEntry()
|
||||
|
@ -883,7 +1106,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
.setUrl("Observation");
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
output = mySystemDao.transaction(mySrd, input);
|
||||
output = mySystemDao.transaction(mySrd, input);
|
||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
|
||||
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
|
@ -891,7 +1114,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
myCaptureQueriesListener.logInsertQueriesForCurrentThread();
|
||||
assertEquals(2, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
|
||||
|
@ -1013,7 +1236,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
// Do the same a second time - Deletes are enabled so we expect to have to resolve the
|
||||
|
@ -1051,7 +1274,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
}
|
||||
|
@ -1102,7 +1325,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
|
||||
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
// Do the same a second time - Deletes are enabled so we expect to have to resolve the
|
||||
|
@ -1140,7 +1363,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
}
|
||||
|
@ -1193,9 +1416,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
// See notes in testTransactionWithMultiplePreExistingReferences_Numeric_DeletesDisabled below
|
||||
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
|
||||
assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
// Do the same a second time - Deletes are enabled so we expect to have to resolve the
|
||||
|
@ -1233,7 +1454,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
assertEquals(0, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
}
|
||||
|
@ -1283,10 +1504,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
// TODO: We have 2 updates here that are caused by Hibernate deciding to flush its action queue half way through
|
||||
// the transaction because a read is about to take place. I think these are unnecessary but I don't see a simple
|
||||
// way of getting rid of them. Hopefully these can be optimized out later
|
||||
assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
// Do the same a second time - Deletes are enabled so we expect to have to resolve the
|
||||
|
@ -1324,8 +1542,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(0, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
// Similar to the note above - No idea why this update is here, it's basically a NO-OP
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
}
|
||||
|
@ -1398,9 +1615,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
|
||||
// Lookup the two existing IDs to make sure they are legit
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(4, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
// Do the same a second time
|
||||
|
@ -1457,9 +1674,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
|
||||
// Lookup the two existing IDs to make sure they are legit
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(1, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
}
|
||||
|
@ -1491,17 +1708,30 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(8, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
// Do the same a second time
|
||||
|
||||
input = new Bundle();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
Patient patient = new Patient();
|
||||
patient.getMeta().addProfile("http://example.com/profile");
|
||||
patient.getMeta().addTag().setSystem("http://example.com/tags").setCode("tag-1");
|
||||
patient.getMeta().addTag().setSystem("http://example.com/tags").setCode("tag-2");
|
||||
input.addEntry()
|
||||
.setResource(patient)
|
||||
.getRequest()
|
||||
.setMethod(Bundle.HTTPVerb.POST)
|
||||
.setUrl("Patient");
|
||||
}
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
mySystemDao.transaction(mySrd, input);
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(0, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(5, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
}
|
||||
|
@ -1556,24 +1786,63 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
|
||||
myCaptureQueriesListener.clear();
|
||||
mySystemDao.transaction(new SystemRequestDetails(), supplier.get());
|
||||
// myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(13, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(3, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(9, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
mySystemDao.transaction(new SystemRequestDetails(), supplier.get());
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(11, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(7, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(2, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(3, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
// assertEquals(15, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
// assertEquals(1, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
// assertEquals(3, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
// assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMassIngestionMode_TransactionWithChanges_2() throws IOException {
|
||||
myDaoConfig.setDeleteEnabled(false);
|
||||
myDaoConfig.setMatchUrlCache(true);
|
||||
myDaoConfig.setMassIngestionMode(true);
|
||||
myFhirCtx.getParserOptions().setStripVersionsFromReferences(false);
|
||||
myModelConfig.setRespectVersionsForSearchIncludes(true);
|
||||
myDaoConfig.setTagStorageMode(DaoConfig.TagStorageModeEnum.NON_VERSIONED);
|
||||
myModelConfig.setAutoVersionReferenceAtPaths(
|
||||
"ExplanationOfBenefit.patient",
|
||||
"ExplanationOfBenefit.insurance.coverage"
|
||||
);
|
||||
|
||||
// Pre-cache tag definitions
|
||||
Patient patient = new Patient();
|
||||
patient.getMeta().addProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient");
|
||||
patient.getMeta().addProfile("http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Organization");
|
||||
patient.getMeta().addProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner");
|
||||
patient.getMeta().addProfile("http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-ExplanationOfBenefit-Professional-NonClinician");
|
||||
patient.getMeta().addProfile("http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Coverage");
|
||||
patient.setActive(true);
|
||||
myPatientDao.create(patient);
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
mySystemDao.transaction(new SystemRequestDetails(), loadResourceFromClasspath(Bundle.class, "r4/transaction-perf-bundle.json"));
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(10, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
myCaptureQueriesListener.logUpdateQueriesForCurrentThread();
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
// Now a copy that has differences in the EOB and Patient resources
|
||||
myCaptureQueriesListener.clear();
|
||||
mySystemDao.transaction(new SystemRequestDetails(), loadResourceFromClasspath(Bundle.class, "r4/transaction-perf-bundle-smallchanges.json"));
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(6, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(1, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(3, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -402,9 +402,11 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
|
|||
|
||||
SearchParameterMap params = new SearchParameterMap();
|
||||
params.add("myDoctor", new ReferenceParam("A"));
|
||||
myCaptureQueriesListener.clear();
|
||||
IBundleProvider outcome = myPatientDao.search(params);
|
||||
List<String> ids = toUnqualifiedVersionlessIdValues(outcome);
|
||||
ourLog.info("IDS: " + ids);
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertThat(ids, Matchers.contains(pid.getValue()));
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -1196,12 +1197,12 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
|
|||
// Forced ID resolution
|
||||
resultingQueryNotFormatted = queries.get(0);
|
||||
assertThat(resultingQueryNotFormatted, containsString("RESOURCE_TYPE='Organization'"));
|
||||
assertThat(resultingQueryNotFormatted, containsString("FORCED_ID in ('ORG0' , 'ORG1' , 'ORG2' , 'ORG3' , 'ORG4')"));
|
||||
assertThat(resultingQueryNotFormatted, containsString("forcedid0_.RESOURCE_TYPE='Organization' and forcedid0_.FORCED_ID='ORG1' or forcedid0_.RESOURCE_TYPE='Organization' and forcedid0_.FORCED_ID='ORG2'"));
|
||||
|
||||
// The search itself
|
||||
resultingQueryNotFormatted = queries.get(1);
|
||||
assertEquals(1, StringUtils.countMatches(resultingQueryNotFormatted, "Patient.managingOrganization"), resultingQueryNotFormatted);
|
||||
assertThat(resultingQueryNotFormatted, matchesPattern(".*TARGET_RESOURCE_ID IN \\('[0-9]+','[0-9]+','[0-9]+','[0-9]+','[0-9]+'\\).*"));
|
||||
assertThat(resultingQueryNotFormatted.toUpperCase(Locale.US), matchesPattern(".*TARGET_RESOURCE_ID IN \\('[0-9]+','[0-9]+','[0-9]+','[0-9]+','[0-9]+'\\).*"));
|
||||
|
||||
// Ensure that the search actually worked
|
||||
assertEquals(5, search.size().intValue());
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
package ca.uhn.fhir.jpa.dao.r4;
|
||||
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@SuppressWarnings({"unchecked", "deprecation", "Duplicates"})
|
||||
public class FhirResourceDaoR4TagsTest extends BaseJpaR4Test {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4TagsTest.class);
|
||||
|
||||
@AfterEach
|
||||
public final void after() {
|
||||
myDaoConfig.setTagStorageMode(DaoConfig.DEFAULT_TAG_STORAGE_MODE);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testStoreAndRetrieveNonVersionedTags_Read() {
|
||||
initializeNonVersioned();
|
||||
|
||||
// Read
|
||||
|
||||
Patient patient;
|
||||
patient = myPatientDao.read(new IdType("Patient/A"), mySrd);
|
||||
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
|
||||
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreAndRetrieveVersionedTags_Read() {
|
||||
initializeVersioned();
|
||||
|
||||
// Read
|
||||
|
||||
Patient patient;
|
||||
patient = myPatientDao.read(new IdType("Patient/A"), mySrd);
|
||||
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
|
||||
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreAndRetrieveVersionedTags_VRead() {
|
||||
initializeVersioned();
|
||||
|
||||
Patient patient = myPatientDao.read(new IdType("Patient/A/_history/1"), mySrd);
|
||||
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile1"));
|
||||
assertThat(toTags(patient).toString(), toTags(patient), contains("http://tag1|vtag1|dtag1"));
|
||||
|
||||
patient = myPatientDao.read(new IdType("Patient/A/_history/2"), mySrd);
|
||||
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
|
||||
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreAndRetrieveNonVersionedTags_VRead() {
|
||||
initializeNonVersioned();
|
||||
|
||||
Patient patient = myPatientDao.read(new IdType("Patient/A/_history/1"), mySrd);
|
||||
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
|
||||
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
|
||||
|
||||
patient = myPatientDao.read(new IdType("Patient/A/_history/2"), mySrd);
|
||||
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
|
||||
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreAndRetrieveVersionedTags_History() {
|
||||
initializeVersioned();
|
||||
|
||||
IBundleProvider history = myPatientDao.history(null, null, mySrd);
|
||||
|
||||
// Version 1
|
||||
Patient patient = (Patient) history.getResources(0, 999).get(1);
|
||||
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile1"));
|
||||
assertThat(toTags(patient).toString(), toTags(patient), contains("http://tag1|vtag1|dtag1"));
|
||||
|
||||
// Version 2
|
||||
patient = (Patient) history.getResources(0, 999).get(0);
|
||||
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
|
||||
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testStoreAndRetrieveNonVersionedTags_History() {
|
||||
initializeNonVersioned();
|
||||
|
||||
IBundleProvider history = myPatientDao.history(null, null, mySrd);
|
||||
|
||||
// Version 1
|
||||
Patient patient = (Patient) history.getResources(0, 999).get(1);
|
||||
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
|
||||
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
|
||||
|
||||
// Version 2
|
||||
patient = (Patient) history.getResources(0, 999).get(0);
|
||||
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
|
||||
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testStoreAndRetrieveVersionedTags_Search() {
|
||||
initializeVersioned();
|
||||
|
||||
IBundleProvider search = myPatientDao.search(new SearchParameterMap());
|
||||
|
||||
Patient patient = (Patient) search.getResources(0, 999).get(0);
|
||||
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
|
||||
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testStoreAndRetrieveNonVersionedTags_Search() {
|
||||
initializeNonVersioned();
|
||||
|
||||
IBundleProvider search = myPatientDao.search(new SearchParameterMap());
|
||||
|
||||
Patient patient = (Patient) search.getResources(0, 999).get(0);
|
||||
assertThat(toProfiles(patient).toString(), toProfiles(patient), contains("http://profile2"));
|
||||
assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void initializeNonVersioned() {
|
||||
myDaoConfig.setTagStorageMode(DaoConfig.TagStorageModeEnum.NON_VERSIONED);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/A");
|
||||
patient.getMeta().addProfile("http://profile1");
|
||||
patient.getMeta().addTag("http://tag1", "vtag1", "dtag1");
|
||||
patient.setActive(true);
|
||||
myPatientDao.update(patient, mySrd);
|
||||
|
||||
patient = new Patient();
|
||||
patient.setId("Patient/A");
|
||||
patient.getMeta().addProfile("http://profile2");
|
||||
patient.getMeta().addTag("http://tag2", "vtag2", "dtag2");
|
||||
patient.setActive(false);
|
||||
assertEquals("2", myPatientDao.update(patient, mySrd).getId().getVersionIdPart());
|
||||
}
|
||||
|
||||
private void initializeVersioned() {
|
||||
myDaoConfig.setTagStorageMode(DaoConfig.TagStorageModeEnum.VERSIONED);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/A");
|
||||
patient.getMeta().addProfile("http://profile1");
|
||||
patient.getMeta().addTag("http://tag1", "vtag1", "dtag1");
|
||||
patient.setActive(true);
|
||||
myPatientDao.update(patient, mySrd);
|
||||
|
||||
patient = new Patient();
|
||||
patient.setId("Patient/A");
|
||||
patient.getMeta().addProfile("http://profile2");
|
||||
patient.getMeta().addTag("http://tag2", "vtag2", "dtag2");
|
||||
patient.setActive(false);
|
||||
assertEquals("2", myPatientDao.update(patient, mySrd).getId().getVersionIdPart());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private List<String> toTags(Patient patient) {
|
||||
return patient.getMeta().getTag().stream().map(t -> t.getSystem() + "|" + t.getCode() + "|" + t.getDisplay()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private List<String> toProfiles(Patient patient) {
|
||||
return patient.getMeta().getProfile().stream().map(t -> t.getValue()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
|
@ -279,7 +279,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
|
|||
table.setIndexStatus(null);
|
||||
table.setDeleted(new Date());
|
||||
table = myResourceTableDao.saveAndFlush(table);
|
||||
ResourceHistoryTable newHistory = table.toHistory();
|
||||
ResourceHistoryTable newHistory = table.toHistory(true);
|
||||
ResourceHistoryTable currentHistory = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(table.getId(), 1L);
|
||||
newHistory.setEncoding(currentHistory.getEncoding());
|
||||
newHistory.setResource(currentHistory.getResource());
|
||||
|
|
|
@ -1706,13 +1706,9 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
|
|||
p.addIdentifier().setSystem("urn:system").setValue(methodName);
|
||||
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName);
|
||||
|
||||
try {
|
||||
mySystemDao.transaction(mySrd, request);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals(e.getMessage(),
|
||||
"Unable to process Transaction - Request would cause multiple resources to match URL: \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateWithDuplicateMatchUrl01\". Does transaction request contain duplicates?");
|
||||
}
|
||||
mySystemDao.transaction(mySrd, request);
|
||||
assertEquals(1, logAllResources());
|
||||
assertEquals(1, logAllResourceVersions());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1828,7 +1824,7 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
|
|||
mySystemDao.transaction(mySrd, request);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals("Invalid match URL[A] - URL has no search parameters", e.getMessage());
|
||||
assertEquals("Invalid match URL[Observation?A] - URL has no search parameters", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2406,13 +2402,9 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
|
|||
.setMethod(HTTPVerb.POST)
|
||||
.setIfNoneExist("Encounter?identifier=urn:foo|12345");
|
||||
|
||||
try {
|
||||
mySystemDao.transaction(mySrd, inputBundle);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?",
|
||||
e.getMessage());
|
||||
}
|
||||
mySystemDao.transaction(mySrd, inputBundle);
|
||||
assertEquals(1, logAllResources());
|
||||
assertEquals(1, logAllResourceVersions());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -2437,14 +2429,10 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
|
|||
.setMethod(HTTPVerb.PUT)
|
||||
.setUrl("Encounter?identifier=urn:foo|12345");
|
||||
|
||||
try {
|
||||
mySystemDao.transaction(mySrd, inputBundle);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals("Unable to process Transaction - Request would cause multiple resources to match URL: \"Encounter?identifier=urn:foo|12345\". Does transaction request contain duplicates?",
|
||||
e.getMessage());
|
||||
}
|
||||
mySystemDao.transaction(mySrd, inputBundle);
|
||||
|
||||
assertEquals(1, logAllResources());
|
||||
assertEquals(1, logAllResourceVersions());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -39,11 +39,13 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.BundleBuilder;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.DateTimeType;
|
||||
import org.hl7.fhir.r4.model.Enumerations;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
|
@ -51,6 +53,7 @@ import org.hl7.fhir.r4.model.Organization;
|
|||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Practitioner;
|
||||
import org.hl7.fhir.r4.model.PractitionerRole;
|
||||
import org.hl7.fhir.r4.model.Quantity;
|
||||
import org.hl7.fhir.r4.model.SearchParameter;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -60,6 +63,9 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast;
|
||||
|
@ -609,6 +615,8 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
|||
createUniqueCompositeSp();
|
||||
createRequestId();
|
||||
|
||||
addReadPartition(myPartitionId);
|
||||
addReadPartition(myPartitionId);
|
||||
addCreatePartition(myPartitionId, myPartitionDate);
|
||||
addCreatePartition(myPartitionId, myPartitionDate);
|
||||
|
||||
|
@ -2548,7 +2556,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
|||
|
||||
ourLog.info("Search SQL:\n{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true));
|
||||
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
|
||||
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID IN ('1')"), searchSql);
|
||||
assertEquals(1, StringUtils.countMatches(searchSql.toUpperCase(Locale.US), "PARTITION_ID IN ('1')"), searchSql);
|
||||
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
|
||||
|
||||
// Same query, different partition
|
||||
|
@ -2583,7 +2591,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
|||
|
||||
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
|
||||
ourLog.info("Search SQL:\n{}", searchSql);
|
||||
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID IS NULL"), searchSql);
|
||||
assertEquals(1, StringUtils.countMatches(searchSql.toUpperCase(Locale.US), "PARTITION_ID IS NULL"), searchSql);
|
||||
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"), searchSql);
|
||||
|
||||
// Same query, different partition
|
||||
|
@ -2599,6 +2607,127 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransaction_MultipleConditionalUpdates() {
|
||||
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
|
||||
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
Supplier<Bundle> input = () -> {
|
||||
BundleBuilder bb = new BundleBuilder(myFhirCtx);
|
||||
|
||||
Patient pt = new Patient();
|
||||
pt.setId(IdType.newRandomUuid());
|
||||
pt.addIdentifier().setSystem("http://foo").setValue("123");
|
||||
bb.addTransactionCreateEntry(pt).conditional("Patient?identifier=http://foo|123");
|
||||
|
||||
Observation obsA = new Observation();
|
||||
obsA.getSubject().setReference(pt.getId());
|
||||
obsA.getCode().addCoding().setSystem("http://foo").setCode("bar1");
|
||||
obsA.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
|
||||
obsA.setEffective(new DateTimeType(new Date()));
|
||||
obsA.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
|
||||
bb.addTransactionUpdateEntry(obsA).conditional("Observation?code=http://foo|bar1");
|
||||
|
||||
Observation obsB = new Observation();
|
||||
obsB.getSubject().setReference(pt.getId());
|
||||
obsB.getCode().addCoding().setSystem("http://foo").setCode("bar2");
|
||||
obsB.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
|
||||
obsB.setEffective(new DateTimeType(new Date()));
|
||||
obsB.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
|
||||
bb.addTransactionUpdateEntry(obsB).conditional("Observation?code=http://foo|bar2");
|
||||
|
||||
Observation obsC = new Observation();
|
||||
obsC.getSubject().setReference(pt.getId());
|
||||
obsC.getCode().addCoding().setSystem("http://foo").setCode("bar3");
|
||||
obsC.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
|
||||
obsC.setEffective(new DateTimeType(new Date()));
|
||||
obsC.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
|
||||
bb.addTransactionUpdateEntry(obsC).conditional("Observation?code=bar3");
|
||||
|
||||
Observation obsD = new Observation();
|
||||
obsD.getSubject().setReference(pt.getId());
|
||||
obsD.getCode().addCoding().setSystem("http://foo").setCode("bar4");
|
||||
obsD.setValue(new Quantity(null, 1, "http://unitsofmeasure.org", "kg", "kg"));
|
||||
obsD.setEffective(new DateTimeType(new Date()));
|
||||
obsD.addNote().setText("Foo " + counter.incrementAndGet()); // changes every time
|
||||
bb.addTransactionUpdateEntry(obsD).conditional("Observation?code=bar4");
|
||||
|
||||
return (Bundle)bb.getBundle();
|
||||
};
|
||||
|
||||
ourLog.info("About to start transaction");
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
addReadPartition(1);
|
||||
}
|
||||
for (int i = 0; i < 8; i++) {
|
||||
addCreatePartition(1, null);
|
||||
}
|
||||
|
||||
// Pre-fetch the partition ID from the partition lookup table
|
||||
createPatient(withPartition(1), withActiveTrue());
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
Bundle outcome = mySystemDao.transaction(mySrd, input.get());
|
||||
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
|
||||
assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("resourcein0_.HASH_SYS_AND_VALUE='-4132452001562191669' and (resourcein0_.PARTITION_ID in ('1'))"));
|
||||
myCaptureQueriesListener.logInsertQueries();
|
||||
assertEquals(6, myCaptureQueriesListener.countInsertQueries());
|
||||
myCaptureQueriesListener.logUpdateQueries();
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
|
||||
/*
|
||||
* Run a second time
|
||||
*/
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
outcome = mySystemDao.transaction(mySrd, input.get());
|
||||
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertEquals(11, myCaptureQueriesListener.countSelectQueries());
|
||||
myCaptureQueriesListener.logInsertQueries();
|
||||
assertEquals(1, myCaptureQueriesListener.countInsertQueries());
|
||||
myCaptureQueriesListener.logUpdateQueries();
|
||||
assertEquals(2, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
|
||||
/*
|
||||
* Third time with mass ingestion mode enabled
|
||||
*/
|
||||
myDaoConfig.setMassIngestionMode(true);
|
||||
myDaoConfig.setMatchUrlCache(true);
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
outcome = mySystemDao.transaction(mySrd, input.get());
|
||||
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertEquals(6, myCaptureQueriesListener.countSelectQueries());
|
||||
myCaptureQueriesListener.logInsertQueries();
|
||||
assertEquals(1, myCaptureQueriesListener.countInsertQueries());
|
||||
myCaptureQueriesListener.logUpdateQueries();
|
||||
assertEquals(2, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
|
||||
/*
|
||||
* Fourth time with mass ingestion mode enabled
|
||||
*/
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
outcome = mySystemDao.transaction(mySrd, input.get());
|
||||
ourLog.info("Resp: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertEquals(5, myCaptureQueriesListener.countSelectQueries());
|
||||
myCaptureQueriesListener.logInsertQueries();
|
||||
assertEquals(1, myCaptureQueriesListener.countInsertQueries());
|
||||
myCaptureQueriesListener.logUpdateQueries();
|
||||
assertEquals(2, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUpdate_ResourcePreExistsInWrongPartition() {
|
||||
IIdType patientId = createPatient(withPutPartition(null), withId("ONE"), withBirthdate("2020-01-01"));
|
||||
|
|
|
@ -18,6 +18,7 @@ import ca.uhn.fhir.jpa.dao.JpaResourceDao;
|
|||
import ca.uhn.fhir.jpa.dao.TransactionProcessor;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
|
||||
import ca.uhn.fhir.jpa.dao.index.DaoSearchParamSynchronizer;
|
||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||
import ca.uhn.fhir.jpa.dao.index.SearchParamWithInlineReferencesExtractor;
|
||||
import ca.uhn.fhir.jpa.dao.r4.FhirSystemDaoR4;
|
||||
import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4;
|
||||
|
@ -131,6 +132,8 @@ public class GiantTransactionPerfTest {
|
|||
private MockResourceHistoryTableDao myResourceHistoryTableDao;
|
||||
private SearchParamPresenceSvcImpl mySearchParamPresenceSvc;
|
||||
private DaoSearchParamSynchronizer myDaoSearchParamSynchronizer;
|
||||
@Mock
|
||||
private IdHelperService myIdHelperService;
|
||||
|
||||
@AfterEach
|
||||
public void afterEach() {
|
||||
|
@ -172,6 +175,9 @@ public class GiantTransactionPerfTest {
|
|||
myTransactionProcessor.setModelConfig(myDaoConfig.getModelConfig());
|
||||
myTransactionProcessor.setHapiTransactionService(myHapiTransactionService);
|
||||
myTransactionProcessor.setDaoRegistry(myDaoRegistry);
|
||||
myTransactionProcessor.setPartitionSettingsForUnitTest(myPartitionSettings);
|
||||
myTransactionProcessor.setIdHelperServiceForUnitTest(myIdHelperService);
|
||||
myTransactionProcessor.setFhirContextForUnitTest(myCtx);
|
||||
myTransactionProcessor.start();
|
||||
|
||||
mySystemDao = new FhirSystemDaoR4();
|
||||
|
@ -248,6 +254,7 @@ public class GiantTransactionPerfTest {
|
|||
myEobDao.setSearchParamPresenceSvc(mySearchParamPresenceSvc);
|
||||
myEobDao.setDaoSearchParamSynchronizer(myDaoSearchParamSynchronizer);
|
||||
myEobDao.setDaoConfigForUnitTest(myDaoConfig);
|
||||
myEobDao.setIdHelperSvcForUnitTest(myIdHelperService);
|
||||
myEobDao.start();
|
||||
|
||||
myDaoRegistry.setResourceDaos(Lists.newArrayList(myEobDao));
|
||||
|
|
|
@ -0,0 +1,904 @@
|
|||
{
|
||||
"resourceType": "Bundle",
|
||||
"type": "transaction",
|
||||
"entry": [
|
||||
{
|
||||
"resource": {
|
||||
"resourceType": "ExplanationOfBenefit",
|
||||
"meta": {
|
||||
"lastUpdated": "2021-06-07",
|
||||
"profile": [
|
||||
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-ExplanationOfBenefit-Professional-NonClinician"
|
||||
]
|
||||
},
|
||||
"identifier": [
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
|
||||
"code": "payerid"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "https://hl7.org/fhir/sid/payerid",
|
||||
"value": "37525500673"
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
|
||||
"code": "uc"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "https://hl7.org/fhir/sid/claimid",
|
||||
"value": "26723516"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/claim-type",
|
||||
"code": "professional"
|
||||
}
|
||||
]
|
||||
},
|
||||
"use": "claim",
|
||||
"patient": {
|
||||
"reference": "Patient/d16f4424-9703-23bf-8331-3fc4bceb0c21"
|
||||
},
|
||||
"billablePeriod": {
|
||||
"start": "2018-01-09",
|
||||
"end": "2018-01-09"
|
||||
},
|
||||
"created": "2018-01-08T00:00:00-08:00",
|
||||
"insurer": {
|
||||
"reference": "Organization/b77d3b98-03d8-1f0a-07b7-30b636c6ea9b"
|
||||
},
|
||||
"provider": {
|
||||
"reference": "Organization/e03b46ec-94df-0849-49eb-f5bba0c024c2"
|
||||
},
|
||||
"payee": {
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/payeetype",
|
||||
"code": "provider"
|
||||
}
|
||||
],
|
||||
"text": "Claim paid to VENDOR"
|
||||
}
|
||||
},
|
||||
"facility": {
|
||||
"reference": "Location/11651884-37d2-eede-e1b9-059afd90811a"
|
||||
},
|
||||
"outcome": "complete",
|
||||
"disposition": "DENIED",
|
||||
"careTeam": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"provider": {
|
||||
"reference": "Practitioner/d2fc93e1-e1f8-c6d3-6c2c-9301f0e02c7c"
|
||||
},
|
||||
"responsible": true,
|
||||
"role": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBClaimCareTeamRole",
|
||||
"code": "performing"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"supportingInfo": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType",
|
||||
"code": "clmrecvddate"
|
||||
}
|
||||
]
|
||||
},
|
||||
"timingDate": "2018-01-08"
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType",
|
||||
"code": "billingnetworkcontractingstatus"
|
||||
}
|
||||
]
|
||||
},
|
||||
"code": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBPayerAdjudicationStatus",
|
||||
"code": "contracted"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"diagnosis": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"diagnosisCodeableConcept": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/sid/icd-10-cm",
|
||||
"code": "M47.012",
|
||||
"display": "ANT SPINAL ART COMPRESSION SYND CERVICAL REGION"
|
||||
}
|
||||
],
|
||||
"text": "ANT SPINAL ART COMPRESSION SYND CERVICAL REGION"
|
||||
},
|
||||
"type": [
|
||||
{
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/ex-diagnosistype",
|
||||
"code": "principal"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"procedure": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"date": "2018-01-08T00:00:00-08:00",
|
||||
"procedureCodeableConcept": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://www.ama-assn.org/go/cpt",
|
||||
"code": "L0454",
|
||||
"display": "TLSO FLEX PREFAB SACROCOC-T9"
|
||||
}
|
||||
],
|
||||
"text": "TLSO FLEXIBLE SC JUNCT TO T-9 PREFAB CUSTOM FIT"
|
||||
}
|
||||
}
|
||||
],
|
||||
"insurance": [
|
||||
{
|
||||
"focal": true,
|
||||
"coverage": {
|
||||
"reference": "urn:uuid:a8430b1b-1f26-44ea-8866-a605ebb48f21"
|
||||
}
|
||||
}
|
||||
],
|
||||
"item": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"diagnosisSequence": [
|
||||
1
|
||||
],
|
||||
"procedureSequence": [
|
||||
1
|
||||
],
|
||||
"productOrService": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://www.ama-assn.org/go/cpt",
|
||||
"code": "L0454",
|
||||
"display": "TLSO FLEX PREFAB SACROCOC-T9"
|
||||
}
|
||||
],
|
||||
"text": "TLSO FLEXIBLE SC JUNCT TO T-9 PREFAB CUSTOM FIT"
|
||||
},
|
||||
"modifier": [
|
||||
{
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://www.ama-assn.org/go/cpt",
|
||||
"code": "NU",
|
||||
"display": "NEW EQUIPMENT"
|
||||
}
|
||||
],
|
||||
"text": "NEW EQUIPMENT"
|
||||
}
|
||||
],
|
||||
"servicedPeriod": {
|
||||
"start": "2018-01-08",
|
||||
"end": "2018-01-08"
|
||||
},
|
||||
"locationCodeableConcept": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "https://www.cms.gov/Medicare/Coding/place-of-service-codes/Place_of_Service_Code_Set",
|
||||
"code": "11"
|
||||
}
|
||||
]
|
||||
},
|
||||
"quantity": {
|
||||
"value": 1,
|
||||
"unit": "Units",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "[arb'U]"
|
||||
},
|
||||
"net": {
|
||||
"value": 704.26,
|
||||
"currency": "USD"
|
||||
},
|
||||
"noteNumber": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"adjudication": [
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
|
||||
"code": "submitted"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 704.26,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
|
||||
"code": "benefit"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
|
||||
"code": "copay"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
|
||||
"code": "deductible"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "coinsurance"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "memberliability"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "noncovered"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "priorpayerpaid"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "paidtoprovider"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBPayerAdjudicationStatus",
|
||||
"code": "outofnetwork"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"total": [
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
|
||||
"code": "submitted"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 704.26,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
|
||||
"code": "benefit"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
|
||||
"code": "copay"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
|
||||
"code": "deductible"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "coinsurance"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "memberliability"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "noncovered"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "priorpayerpaid"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "paidtoprovider"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
}
|
||||
],
|
||||
"payment": {
|
||||
"date": "2021-01-22",
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
"processNote": [
|
||||
{
|
||||
"number": 1,
|
||||
"type": "display",
|
||||
"text": "AUD02: DENY, NOT AUTHORIZED, PROVIDER LIABILITY"
|
||||
},
|
||||
{
|
||||
"number": 2,
|
||||
"type": "display",
|
||||
"text": "BED08: DENY, PROCEDURE NOT COVERED"
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "ExplanationOfBenefit?identifier=37525500673"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource": {
|
||||
"resourceType": "Patient",
|
||||
"id": "d16f4424-9703-23bf-8331-3fc4bceb0c21",
|
||||
"meta": {
|
||||
"lastUpdated": "2021-06-07",
|
||||
"profile": [
|
||||
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"
|
||||
]
|
||||
},
|
||||
"identifier": [
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "MR"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "https://healthy.kaiserpermanente.org/front-door",
|
||||
"value": "1000116-GA"
|
||||
}
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"use": "usual",
|
||||
"text": "Gaabcseven Testing",
|
||||
"family": "Testing",
|
||||
"given": [
|
||||
"Gaabcsix"
|
||||
]
|
||||
}
|
||||
],
|
||||
"telecom": [
|
||||
{
|
||||
"system": "phone",
|
||||
"value": "662-123-3456",
|
||||
"use": "home"
|
||||
}
|
||||
],
|
||||
"gender": "male",
|
||||
"birthDate": "1961-01-01",
|
||||
"address": [
|
||||
{
|
||||
"use": "home",
|
||||
"type": "postal",
|
||||
"line": [
|
||||
"TEST ADDRESS AVE, APT 234"
|
||||
],
|
||||
"city": "ATLANTA",
|
||||
"state": "GA",
|
||||
"postalCode": "30301"
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Patient/d16f4424-9703-23bf-8331-3fc4bceb0c21"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fullUrl": "urn:uuid:a8430b1b-1f26-44ea-8866-a605ebb48f21",
|
||||
"resource": {
|
||||
"resourceType": "Coverage",
|
||||
"meta": {
|
||||
"lastUpdated": "2021-06-07",
|
||||
"profile": [
|
||||
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Coverage"
|
||||
]
|
||||
},
|
||||
"identifier": [
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "FILL"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "https://hl7.org/fhir/sid/coverageid",
|
||||
"value": "1000116-GA-10159"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
|
||||
"code": "HMO",
|
||||
"display": "health maintenance organization policy"
|
||||
}
|
||||
],
|
||||
"text": "COMMERCIAL HMO-HMO-Amb Accum"
|
||||
},
|
||||
"subscriberId": "1000116",
|
||||
"beneficiary": {
|
||||
"reference": "Patient/d16f4424-9703-23bf-8331-3fc4bceb0c21"
|
||||
},
|
||||
"relationship": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/subscriber-relationship",
|
||||
"code": "self",
|
||||
"display": "Self"
|
||||
}
|
||||
],
|
||||
"text": "The Beneficiary is the Subscriber"
|
||||
},
|
||||
"period": {
|
||||
"start": "2017-01-01"
|
||||
},
|
||||
"payor": [
|
||||
{
|
||||
"reference": "Organization/b77d3b98-03d8-1f0a-07b7-30b636c6ea9b"
|
||||
}
|
||||
],
|
||||
"class": [
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/coverage-class",
|
||||
"code": "group",
|
||||
"display": "Group"
|
||||
}
|
||||
],
|
||||
"text": "An employee group"
|
||||
},
|
||||
"value": "10159",
|
||||
"name": "10159-100 STATE DEPTS, DFACS, HEALTH-NON-MEDICARE"
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Coverage?identifier=1000116-GA-10159"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource": {
|
||||
"resourceType": "Organization",
|
||||
"id": "e03b46ec-94df-0849-49eb-f5bba0c024c2",
|
||||
"meta": {
|
||||
"lastUpdated": "2021-06-07",
|
||||
"profile": [
|
||||
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Organization"
|
||||
]
|
||||
},
|
||||
"identifier": [
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
|
||||
"code": "npi"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "http://hl7.org/fhir/sid/us-npi"
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "TAX"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "urn:oid:2.16.840.1.113883.4.4",
|
||||
"value": "330057155"
|
||||
}
|
||||
],
|
||||
"active": true,
|
||||
"type": [
|
||||
{
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/organization-type",
|
||||
"code": "prov"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"name": "APRIA HEALTHCARE LLC",
|
||||
"address": [
|
||||
{
|
||||
"use": "work",
|
||||
"type": "physical",
|
||||
"line": [
|
||||
"2508 SOLUTIONS CENTER"
|
||||
],
|
||||
"city": "CHICAGO",
|
||||
"state": "IL",
|
||||
"postalCode": "60677-2005",
|
||||
"country": "USA"
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Organization/e03b46ec-94df-0849-49eb-f5bba0c024c2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource": {
|
||||
"resourceType": "Organization",
|
||||
"id": "b77d3b98-03d8-1f0a-07b7-30b636c6ea9b",
|
||||
"meta": {
|
||||
"lastUpdated": "2021-06-07",
|
||||
"profile": [
|
||||
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Organization"
|
||||
]
|
||||
},
|
||||
"identifier": [
|
||||
{
|
||||
"use": "usual",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "FILL"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "https://hl7.org/fhir/sid/organizationid",
|
||||
"value": "NATLTAP GA-KFHP-GA"
|
||||
}
|
||||
],
|
||||
"active": true,
|
||||
"type": [
|
||||
{
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/organization-type",
|
||||
"code": "pay",
|
||||
"display": "Payer"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"name": "KAISER FOUNDATION HEALTHPLAN, INC",
|
||||
"telecom": [
|
||||
{
|
||||
"system": "phone",
|
||||
"value": "1-888-865-5813",
|
||||
"use": "work"
|
||||
}
|
||||
],
|
||||
"address": [
|
||||
{
|
||||
"use": "work",
|
||||
"type": "postal",
|
||||
"line": [
|
||||
"NATIONAL CLAIMS ADMINISTRATION GEORGIA",
|
||||
"PO Box 629028"
|
||||
],
|
||||
"city": "El Dorado Hills",
|
||||
"state": "CA",
|
||||
"postalCode": "95762-9028"
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Organization/b77d3b98-03d8-1f0a-07b7-30b636c6ea9b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource": {
|
||||
"resourceType": "Practitioner",
|
||||
"id": "d2fc93e1-e1f8-c6d3-6c2c-9301f0e02c7c",
|
||||
"meta": {
|
||||
"lastUpdated": "2021-06-07",
|
||||
"profile": [
|
||||
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner"
|
||||
]
|
||||
},
|
||||
"identifier": [
|
||||
{
|
||||
"use": "usual",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "NPI"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "http://hl7.org/fhir/sid/us-npi",
|
||||
"value": "PIN2001487498"
|
||||
}
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"use": "usual",
|
||||
"text": "APRIA HEALTHCARE LLC",
|
||||
"family": "APRIA HEALTHCARE LLC"
|
||||
}
|
||||
],
|
||||
"address": [
|
||||
{
|
||||
"use": "work",
|
||||
"line": [
|
||||
"805 MARATHON PARKWAY",
|
||||
"SUITE 160"
|
||||
],
|
||||
"city": "LAWRENCEVILLE",
|
||||
"state": "GA",
|
||||
"postalCode": "30046"
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Practitioner/d2fc93e1-e1f8-c6d3-6c2c-9301f0e02c7c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource": {
|
||||
"resourceType": "Location",
|
||||
"id": "11651884-37d2-eede-e1b9-059afd90811a",
|
||||
"meta": {
|
||||
"lastUpdated": "2021-06-07"
|
||||
},
|
||||
"identifier": [
|
||||
{
|
||||
"use": "usual",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "NPI"
|
||||
}
|
||||
]
|
||||
},
|
||||
"value": "PIN12120678"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"name": "APRIA HEALTHCARE INC-30013",
|
||||
"mode": "kind",
|
||||
"type": [
|
||||
{
|
||||
"coding": [
|
||||
{
|
||||
"system": "https://www.cms.gov/Medicare/Coding/place-of-service-codes/Place_of_Service_Code_Set",
|
||||
"code": "99"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"use": "work",
|
||||
"type": "physical",
|
||||
"line": [
|
||||
"594 SIGMAN RD STE 100"
|
||||
],
|
||||
"city": "CONYERS",
|
||||
"state": "GA",
|
||||
"postalCode": "30013-1365"
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Location/11651884-37d2-eede-e1b9-059afd90811a"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,904 @@
|
|||
{
|
||||
"resourceType": "Bundle",
|
||||
"type": "transaction",
|
||||
"entry": [
|
||||
{
|
||||
"resource": {
|
||||
"resourceType": "ExplanationOfBenefit",
|
||||
"meta": {
|
||||
"lastUpdated": "2021-06-07",
|
||||
"profile": [
|
||||
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-ExplanationOfBenefit-Professional-NonClinician"
|
||||
]
|
||||
},
|
||||
"identifier": [
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
|
||||
"code": "payerid"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "https://hl7.org/fhir/sid/payerid",
|
||||
"value": "37525500673"
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
|
||||
"code": "uc"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "https://hl7.org/fhir/sid/claimid",
|
||||
"value": "26723516"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/claim-type",
|
||||
"code": "professional"
|
||||
}
|
||||
]
|
||||
},
|
||||
"use": "claim",
|
||||
"patient": {
|
||||
"reference": "Patient/d16f4424-9703-23bf-8331-3fc4bceb0c21"
|
||||
},
|
||||
"billablePeriod": {
|
||||
"start": "2018-01-08",
|
||||
"end": "2018-01-08"
|
||||
},
|
||||
"created": "2018-01-08T00:00:00-08:00",
|
||||
"insurer": {
|
||||
"reference": "Organization/b77d3b98-03d8-1f0a-07b7-30b636c6ea9b"
|
||||
},
|
||||
"provider": {
|
||||
"reference": "Organization/e03b46ec-94df-0849-49eb-f5bba0c024c2"
|
||||
},
|
||||
"payee": {
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/payeetype",
|
||||
"code": "provider"
|
||||
}
|
||||
],
|
||||
"text": "Claim paid to VENDOR"
|
||||
}
|
||||
},
|
||||
"facility": {
|
||||
"reference": "Location/11651884-37d2-eede-e1b9-059afd90811a"
|
||||
},
|
||||
"outcome": "complete",
|
||||
"disposition": "DENIED",
|
||||
"careTeam": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"provider": {
|
||||
"reference": "Practitioner/d2fc93e1-e1f8-c6d3-6c2c-9301f0e02c7c"
|
||||
},
|
||||
"responsible": true,
|
||||
"role": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBClaimCareTeamRole",
|
||||
"code": "performing"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"supportingInfo": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType",
|
||||
"code": "clmrecvddate"
|
||||
}
|
||||
]
|
||||
},
|
||||
"timingDate": "2018-01-08"
|
||||
},
|
||||
{
|
||||
"sequence": 2,
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBSupportingInfoType",
|
||||
"code": "billingnetworkcontractingstatus"
|
||||
}
|
||||
]
|
||||
},
|
||||
"code": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBPayerAdjudicationStatus",
|
||||
"code": "contracted"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"diagnosis": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"diagnosisCodeableConcept": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/sid/icd-10-cm",
|
||||
"code": "M47.012",
|
||||
"display": "ANT SPINAL ART COMPRESSION SYND CERVICAL REGION"
|
||||
}
|
||||
],
|
||||
"text": "ANT SPINAL ART COMPRESSION SYND CERVICAL REGION"
|
||||
},
|
||||
"type": [
|
||||
{
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/ex-diagnosistype",
|
||||
"code": "principal"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"procedure": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"date": "2018-01-08T00:00:00-08:00",
|
||||
"procedureCodeableConcept": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://www.ama-assn.org/go/cpt",
|
||||
"code": "L0454",
|
||||
"display": "TLSO FLEX PREFAB SACROCOC-T9"
|
||||
}
|
||||
],
|
||||
"text": "TLSO FLEXIBLE SC JUNCT TO T-9 PREFAB CUSTOM FIT"
|
||||
}
|
||||
}
|
||||
],
|
||||
"insurance": [
|
||||
{
|
||||
"focal": true,
|
||||
"coverage": {
|
||||
"reference": "urn:uuid:a8430b1b-1f26-44ea-8866-a605ebb48f21"
|
||||
}
|
||||
}
|
||||
],
|
||||
"item": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"diagnosisSequence": [
|
||||
1
|
||||
],
|
||||
"procedureSequence": [
|
||||
1
|
||||
],
|
||||
"productOrService": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://www.ama-assn.org/go/cpt",
|
||||
"code": "L0454",
|
||||
"display": "TLSO FLEX PREFAB SACROCOC-T9"
|
||||
}
|
||||
],
|
||||
"text": "TLSO FLEXIBLE SC JUNCT TO T-9 PREFAB CUSTOM FIT"
|
||||
},
|
||||
"modifier": [
|
||||
{
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://www.ama-assn.org/go/cpt",
|
||||
"code": "NU",
|
||||
"display": "NEW EQUIPMENT"
|
||||
}
|
||||
],
|
||||
"text": "NEW EQUIPMENT"
|
||||
}
|
||||
],
|
||||
"servicedPeriod": {
|
||||
"start": "2018-01-08",
|
||||
"end": "2018-01-08"
|
||||
},
|
||||
"locationCodeableConcept": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "https://www.cms.gov/Medicare/Coding/place-of-service-codes/Place_of_Service_Code_Set",
|
||||
"code": "11"
|
||||
}
|
||||
]
|
||||
},
|
||||
"quantity": {
|
||||
"value": 1,
|
||||
"unit": "Units",
|
||||
"system": "http://unitsofmeasure.org",
|
||||
"code": "[arb'U]"
|
||||
},
|
||||
"net": {
|
||||
"value": 704.26,
|
||||
"currency": "USD"
|
||||
},
|
||||
"noteNumber": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"adjudication": [
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
|
||||
"code": "submitted"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 704.26,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
|
||||
"code": "benefit"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
|
||||
"code": "copay"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
|
||||
"code": "deductible"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "coinsurance"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "memberliability"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "noncovered"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "priorpayerpaid"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "paidtoprovider"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBPayerAdjudicationStatus",
|
||||
"code": "outofnetwork"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"total": [
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
|
||||
"code": "submitted"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 704.26,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
|
||||
"code": "benefit"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
|
||||
"code": "copay"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/adjudication",
|
||||
"code": "deductible"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "coinsurance"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "memberliability"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "noncovered"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "priorpayerpaid"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBAdjudication",
|
||||
"code": "paidtoprovider"
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
}
|
||||
],
|
||||
"payment": {
|
||||
"date": "2021-01-22",
|
||||
"amount": {
|
||||
"value": 0,
|
||||
"currency": "USD"
|
||||
}
|
||||
},
|
||||
"processNote": [
|
||||
{
|
||||
"number": 1,
|
||||
"type": "display",
|
||||
"text": "AUD02: DENY, NOT AUTHORIZED, PROVIDER LIABILITY"
|
||||
},
|
||||
{
|
||||
"number": 2,
|
||||
"type": "display",
|
||||
"text": "BED08: DENY, PROCEDURE NOT COVERED"
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "ExplanationOfBenefit?identifier=37525500673"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource": {
|
||||
"resourceType": "Patient",
|
||||
"id": "d16f4424-9703-23bf-8331-3fc4bceb0c21",
|
||||
"meta": {
|
||||
"lastUpdated": "2021-06-07",
|
||||
"profile": [
|
||||
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"
|
||||
]
|
||||
},
|
||||
"identifier": [
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "MR"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "https://healthy.kaiserpermanente.org/front-door",
|
||||
"value": "1000116-GA"
|
||||
}
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"use": "usual",
|
||||
"text": "Gaabcsix Testing",
|
||||
"family": "Testing",
|
||||
"given": [
|
||||
"Gaabcsix"
|
||||
]
|
||||
}
|
||||
],
|
||||
"telecom": [
|
||||
{
|
||||
"system": "phone",
|
||||
"value": "662-123-3456",
|
||||
"use": "home"
|
||||
}
|
||||
],
|
||||
"gender": "male",
|
||||
"birthDate": "1961-01-01",
|
||||
"address": [
|
||||
{
|
||||
"use": "home",
|
||||
"type": "postal",
|
||||
"line": [
|
||||
"TEST ADDRESS AVE, APT 234"
|
||||
],
|
||||
"city": "ATLANTA",
|
||||
"state": "GA",
|
||||
"postalCode": "30301"
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Patient/d16f4424-9703-23bf-8331-3fc4bceb0c21"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fullUrl": "urn:uuid:a8430b1b-1f26-44ea-8866-a605ebb48f21",
|
||||
"resource": {
|
||||
"resourceType": "Coverage",
|
||||
"meta": {
|
||||
"lastUpdated": "2021-06-07",
|
||||
"profile": [
|
||||
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Coverage"
|
||||
]
|
||||
},
|
||||
"identifier": [
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "FILL"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "https://hl7.org/fhir/sid/coverageid",
|
||||
"value": "1000116-GA-10159"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
|
||||
"code": "HMO",
|
||||
"display": "health maintenance organization policy"
|
||||
}
|
||||
],
|
||||
"text": "COMMERCIAL HMO-HMO-Amb Accum"
|
||||
},
|
||||
"subscriberId": "1000116",
|
||||
"beneficiary": {
|
||||
"reference": "Patient/d16f4424-9703-23bf-8331-3fc4bceb0c21"
|
||||
},
|
||||
"relationship": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/subscriber-relationship",
|
||||
"code": "self",
|
||||
"display": "Self"
|
||||
}
|
||||
],
|
||||
"text": "The Beneficiary is the Subscriber"
|
||||
},
|
||||
"period": {
|
||||
"start": "2017-01-01"
|
||||
},
|
||||
"payor": [
|
||||
{
|
||||
"reference": "Organization/b77d3b98-03d8-1f0a-07b7-30b636c6ea9b"
|
||||
}
|
||||
],
|
||||
"class": [
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/coverage-class",
|
||||
"code": "group",
|
||||
"display": "Group"
|
||||
}
|
||||
],
|
||||
"text": "An employee group"
|
||||
},
|
||||
"value": "10159",
|
||||
"name": "10159-100 STATE DEPTS, DFACS, HEALTH-NON-MEDICARE"
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Coverage?identifier=1000116-GA-10159"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource": {
|
||||
"resourceType": "Organization",
|
||||
"id": "e03b46ec-94df-0849-49eb-f5bba0c024c2",
|
||||
"meta": {
|
||||
"lastUpdated": "2021-06-07",
|
||||
"profile": [
|
||||
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Organization"
|
||||
]
|
||||
},
|
||||
"identifier": [
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://hl7.org/fhir/us/carin-bb/CodeSystem/C4BBIdentifierType",
|
||||
"code": "npi"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "http://hl7.org/fhir/sid/us-npi"
|
||||
},
|
||||
{
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "TAX"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "urn:oid:2.16.840.1.113883.4.4",
|
||||
"value": "330057155"
|
||||
}
|
||||
],
|
||||
"active": true,
|
||||
"type": [
|
||||
{
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/organization-type",
|
||||
"code": "prov"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"name": "APRIA HEALTHCARE LLC",
|
||||
"address": [
|
||||
{
|
||||
"use": "work",
|
||||
"type": "physical",
|
||||
"line": [
|
||||
"2508 SOLUTIONS CENTER"
|
||||
],
|
||||
"city": "CHICAGO",
|
||||
"state": "IL",
|
||||
"postalCode": "60677-2005",
|
||||
"country": "USA"
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Organization/e03b46ec-94df-0849-49eb-f5bba0c024c2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource": {
|
||||
"resourceType": "Organization",
|
||||
"id": "b77d3b98-03d8-1f0a-07b7-30b636c6ea9b",
|
||||
"meta": {
|
||||
"lastUpdated": "2021-06-07",
|
||||
"profile": [
|
||||
"http://hl7.org/fhir/us/carin-bb/StructureDefinition/C4BB-Organization"
|
||||
]
|
||||
},
|
||||
"identifier": [
|
||||
{
|
||||
"use": "usual",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "FILL"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "https://hl7.org/fhir/sid/organizationid",
|
||||
"value": "NATLTAP GA-KFHP-GA"
|
||||
}
|
||||
],
|
||||
"active": true,
|
||||
"type": [
|
||||
{
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/organization-type",
|
||||
"code": "pay",
|
||||
"display": "Payer"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"name": "KAISER FOUNDATION HEALTHPLAN, INC",
|
||||
"telecom": [
|
||||
{
|
||||
"system": "phone",
|
||||
"value": "1-888-865-5813",
|
||||
"use": "work"
|
||||
}
|
||||
],
|
||||
"address": [
|
||||
{
|
||||
"use": "work",
|
||||
"type": "postal",
|
||||
"line": [
|
||||
"NATIONAL CLAIMS ADMINISTRATION GEORGIA",
|
||||
"PO Box 629028"
|
||||
],
|
||||
"city": "El Dorado Hills",
|
||||
"state": "CA",
|
||||
"postalCode": "95762-9028"
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Organization/b77d3b98-03d8-1f0a-07b7-30b636c6ea9b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource": {
|
||||
"resourceType": "Practitioner",
|
||||
"id": "d2fc93e1-e1f8-c6d3-6c2c-9301f0e02c7c",
|
||||
"meta": {
|
||||
"lastUpdated": "2021-06-07",
|
||||
"profile": [
|
||||
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-practitioner"
|
||||
]
|
||||
},
|
||||
"identifier": [
|
||||
{
|
||||
"use": "usual",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "NPI"
|
||||
}
|
||||
]
|
||||
},
|
||||
"system": "http://hl7.org/fhir/sid/us-npi",
|
||||
"value": "PIN2001487498"
|
||||
}
|
||||
],
|
||||
"name": [
|
||||
{
|
||||
"use": "usual",
|
||||
"text": "APRIA HEALTHCARE LLC",
|
||||
"family": "APRIA HEALTHCARE LLC"
|
||||
}
|
||||
],
|
||||
"address": [
|
||||
{
|
||||
"use": "work",
|
||||
"line": [
|
||||
"805 MARATHON PARKWAY",
|
||||
"SUITE 160"
|
||||
],
|
||||
"city": "LAWRENCEVILLE",
|
||||
"state": "GA",
|
||||
"postalCode": "30046"
|
||||
}
|
||||
]
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Practitioner/d2fc93e1-e1f8-c6d3-6c2c-9301f0e02c7c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"resource": {
|
||||
"resourceType": "Location",
|
||||
"id": "11651884-37d2-eede-e1b9-059afd90811a",
|
||||
"meta": {
|
||||
"lastUpdated": "2021-06-07"
|
||||
},
|
||||
"identifier": [
|
||||
{
|
||||
"use": "usual",
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
|
||||
"code": "NPI"
|
||||
}
|
||||
]
|
||||
},
|
||||
"value": "PIN12120678"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"name": "APRIA HEALTHCARE INC-30013",
|
||||
"mode": "kind",
|
||||
"type": [
|
||||
{
|
||||
"coding": [
|
||||
{
|
||||
"system": "https://www.cms.gov/Medicare/Coding/place-of-service-codes/Place_of_Service_Code_Set",
|
||||
"code": "99"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"address": {
|
||||
"use": "work",
|
||||
"type": "physical",
|
||||
"line": [
|
||||
"594 SIGMAN RD STE 100"
|
||||
],
|
||||
"city": "CONYERS",
|
||||
"state": "GA",
|
||||
"postalCode": "30013-1365"
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"url": "Location/11651884-37d2-eede-e1b9-059afd90811a"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -184,7 +184,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
|
|||
myHashIdentity = theHashIdentity;
|
||||
}
|
||||
|
||||
Long getHashSystemAndValue() {
|
||||
public Long getHashSystemAndValue() {
|
||||
return myHashSystemAndValue;
|
||||
}
|
||||
|
||||
|
@ -192,7 +192,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
|
|||
myHashSystemAndValue = theHashSystemAndValue;
|
||||
}
|
||||
|
||||
Long getHashValue() {
|
||||
public Long getHashValue() {
|
||||
return myHashValue;
|
||||
}
|
||||
|
||||
|
|
|
@ -604,7 +604,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
myNarrativeText = theNarrativeText;
|
||||
}
|
||||
|
||||
public ResourceHistoryTable toHistory() {
|
||||
public ResourceHistoryTable toHistory(boolean theCreateVersionTags) {
|
||||
ResourceHistoryTable retVal = new ResourceHistoryTable();
|
||||
|
||||
retVal.setResourceId(myId);
|
||||
|
@ -623,7 +623,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
retVal.getTags().clear();
|
||||
|
||||
retVal.setHasTags(isHasTags());
|
||||
if (isHasTags()) {
|
||||
if (isHasTags() && theCreateVersionTags) {
|
||||
for (ResourceTag next : getTags()) {
|
||||
retVal.addTag(next);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.api.server.storage;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.util.ObjectUtil;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -32,9 +33,9 @@ import java.util.Optional;
|
|||
* a Long, a String, or something else.
|
||||
*/
|
||||
public class ResourcePersistentId {
|
||||
|
||||
private Object myId;
|
||||
private Long myVersion;
|
||||
private IIdType myAssociatedResourceId;
|
||||
|
||||
public ResourcePersistentId(Object theId) {
|
||||
this(theId, null);
|
||||
|
@ -50,6 +51,15 @@ public class ResourcePersistentId {
|
|||
myVersion = theVersion;
|
||||
}
|
||||
|
||||
public IIdType getAssociatedResourceId() {
|
||||
return myAssociatedResourceId;
|
||||
}
|
||||
|
||||
public ResourcePersistentId setAssociatedResourceId(IIdType theAssociatedResourceId) {
|
||||
myAssociatedResourceId = theAssociatedResourceId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object theO) {
|
||||
if (!(theO instanceof ResourcePersistentId)) {
|
||||
|
|
|
@ -28,6 +28,7 @@ import com.google.common.collect.ListMultimap;
|
|||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
@ -50,8 +51,11 @@ import java.util.function.Supplier;
|
|||
*/
|
||||
public class TransactionDetails {
|
||||
|
||||
public static final ResourcePersistentId NOT_FOUND = new ResourcePersistentId(-1L);
|
||||
|
||||
private final Date myTransactionDate;
|
||||
private Map<String, ResourcePersistentId> myResolvedResourceIds = Collections.emptyMap();
|
||||
private Map<String, ResourcePersistentId> myResolvedMatchUrls = Collections.emptyMap();
|
||||
private Map<String, Object> myUserData;
|
||||
private ListMultimap<Pointcut, HookParams> myDeferredInterceptorBroadcasts;
|
||||
private EnumSet<Pointcut> myDeferredInterceptorBroadcastPointcuts;
|
||||
|
@ -82,14 +86,28 @@ public class TransactionDetails {
|
|||
return myResolvedResourceIds.get(idValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Was the given resource ID resolved previously in this transaction as not existing
|
||||
*/
|
||||
public boolean isResolvedResourceIdEmpty(IIdType theId) {
|
||||
if (myResolvedResourceIds != null) {
|
||||
if (myResolvedResourceIds.containsKey(theId.toVersionless().getValue())) {
|
||||
if (myResolvedResourceIds.get(theId.toVersionless().getValue()) == null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A <b>Resolved Resource ID</b> is a mapping between a resource ID (e.g. "<code>Patient/ABC</code>" or
|
||||
* "<code>Observation/123</code>") and a storage ID for that resource. Resources should only be placed within
|
||||
* the TransactionDetails if they are known to exist and be valid targets for other resources to link to.
|
||||
*/
|
||||
public void addResolvedResourceId(IIdType theResourceId, ResourcePersistentId thePersistentId) {
|
||||
public void addResolvedResourceId(IIdType theResourceId, @Nullable ResourcePersistentId thePersistentId) {
|
||||
assert theResourceId != null;
|
||||
assert thePersistentId != null;
|
||||
|
||||
if (myResolvedResourceIds.isEmpty()) {
|
||||
myResolvedResourceIds = new HashMap<>();
|
||||
|
@ -97,6 +115,25 @@ public class TransactionDetails {
|
|||
myResolvedResourceIds.put(theResourceId.toVersionless().getValue(), thePersistentId);
|
||||
}
|
||||
|
||||
public Map<String, ResourcePersistentId> getResolvedMatchUrls() {
|
||||
return myResolvedMatchUrls;
|
||||
}
|
||||
|
||||
/**
|
||||
* A <b>Resolved Conditional URL</b> is a mapping between a conditional URL (e.g. "<code>Patient?identifier=foo|bar</code>" or
|
||||
* "<code>Observation/123</code>") and a storage ID for that resource. Resources should only be placed within
|
||||
* the TransactionDetails if they are known to exist and be valid targets for other resources to link to.
|
||||
*/
|
||||
public void addResolvedMatchUrl(String theConditionalUrl, @Nonnull ResourcePersistentId thePersistentId) {
|
||||
Validate.notBlank(theConditionalUrl);
|
||||
Validate.notNull(thePersistentId);
|
||||
|
||||
if (myResolvedMatchUrls.isEmpty()) {
|
||||
myResolvedMatchUrls = new HashMap<>();
|
||||
}
|
||||
myResolvedMatchUrls.put(theConditionalUrl, thePersistentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the wall-clock time that a given transaction started.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue