Avoid redundant PID lookups in transaction processing (#3061)
* Avoid redundant PID lookups in transaction processing * Add changelog * Test fix
This commit is contained in:
parent
6ad0dffd00
commit
aed718bd6f
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: perf
|
||||
issue: 3061
|
||||
title: "When ingesting Synthea data (or simnilarly structured transaction bundles) into a JPA server, a redundant
|
||||
resource ID lookup has been optimized out. This should particularly speed up larger sized transactions."
|
|
@ -336,7 +336,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
ResourcePersistentId persistentId = new ResourcePersistentId(updatedEntity.getResourceId());
|
||||
theTransactionDetails.addResolvedResourceId(id, persistentId);
|
||||
if (entity.getForcedId() != null) {
|
||||
myIdHelperService.addResolvedPidToForcedId(persistentId, theRequestPartitionId, updatedEntity.getResourceType(), updatedEntity.getForcedId().getForcedId());
|
||||
myIdHelperService.addResolvedPidToForcedId(persistentId, theRequestPartitionId, updatedEntity.getResourceType(), updatedEntity.getForcedId().getForcedId(), updatedEntity.getDeleted());
|
||||
}
|
||||
|
||||
theResource.setId(entity.getIdDt());
|
||||
|
@ -355,9 +355,14 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
}
|
||||
|
||||
ResourcePersistentId resourcePersistentId = new ResourcePersistentId(entity.getResourceId());
|
||||
resourcePersistentId.setAssociatedResourceId(entity.getIdType(myFhirContext));
|
||||
|
||||
theTransactionDetails.addResolvedResourceId(resourcePersistentId.getAssociatedResourceId(), resourcePersistentId);
|
||||
|
||||
// Pre-cache the match URL
|
||||
if (theIfNoneExist != null) {
|
||||
// Pre-cache the match URL
|
||||
myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, getResourceName(), theIfNoneExist, new ResourcePersistentId(entity.getResourceId()));
|
||||
myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, getResourceName(), theIfNoneExist, resourcePersistentId);
|
||||
}
|
||||
|
||||
// Update the version/last updated in the resource so that interceptors get
|
||||
|
@ -390,7 +395,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
if (updatedEntity.getForcedId() != null) {
|
||||
forcedId = updatedEntity.getForcedId().getForcedId();
|
||||
}
|
||||
myIdHelperService.addResolvedPidToForcedId(persistentId, theRequestPartitionId, getResourceName(), forcedId);
|
||||
myIdHelperService.addResolvedPidToForcedId(persistentId, theRequestPartitionId, getResourceName(), forcedId, null);
|
||||
|
||||
ourLog.debug(msg);
|
||||
return outcome;
|
||||
|
|
|
@ -33,8 +33,11 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
|||
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver;
|
||||
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
||||
import ca.uhn.fhir.mdm.util.CanonicalIdentifier;
|
||||
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.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
|
@ -74,7 +77,7 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
|
|||
private DaoRegistry myDaoRegistry;
|
||||
|
||||
@Override
|
||||
public IResourceLookup findTargetResource(@Nonnull RequestPartitionId theRequestPartitionId, RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theResourceType, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest) {
|
||||
public IResourceLookup findTargetResource(@Nonnull RequestPartitionId theRequestPartitionId, RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theResourceType, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
|
||||
IResourceLookup resolvedResource;
|
||||
String idPart = theSourceResourceId.getIdPart();
|
||||
try {
|
||||
|
@ -82,7 +85,7 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
|
|||
ourLog.trace("Translated {}/{} to resource PID {}", theType, idPart, resolvedResource);
|
||||
} catch (ResourceNotFoundException e) {
|
||||
|
||||
Optional<ResourceTable> createdTableOpt = createPlaceholderTargetIfConfiguredToDoSo(theType, theReference, idPart, theRequest);
|
||||
Optional<ResourceTable> createdTableOpt = createPlaceholderTargetIfConfiguredToDoSo(theType, theReference, idPart, theRequest, theTransactionDetails);
|
||||
if (!createdTableOpt.isPresent()) {
|
||||
|
||||
if (myDaoConfig.isEnforceReferentialIntegrityOnWrite() == false) {
|
||||
|
@ -118,7 +121,7 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
|
|||
/**
|
||||
* @param theIdToAssignToPlaceholder If specified, the placeholder resource created will be given a specific ID
|
||||
*/
|
||||
public <T extends IBaseResource> Optional<ResourceTable> createPlaceholderTargetIfConfiguredToDoSo(Class<T> theType, IBaseReference theReference, @Nullable String theIdToAssignToPlaceholder, RequestDetails theRequest) {
|
||||
public <T extends IBaseResource> Optional<ResourceTable> createPlaceholderTargetIfConfiguredToDoSo(Class<T> theType, IBaseReference theReference, @Nullable String theIdToAssignToPlaceholder, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
|
||||
ResourceTable valueOf = null;
|
||||
|
||||
if (myDaoConfig.isAutoCreatePlaceholderReferenceTargets()) {
|
||||
|
@ -143,6 +146,10 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
|
|||
} else {
|
||||
valueOf = ((ResourceTable) placeholderResourceDao.create(newResource, theRequest).getEntity());
|
||||
}
|
||||
|
||||
ResourcePersistentId persistentId = new ResourcePersistentId(valueOf.getResourceId(), 1L);
|
||||
persistentId.setAssociatedResourceId(valueOf.getIdType(myContext));
|
||||
theTransactionDetails.addResolvedResourceId(persistentId.getAssociatedResourceId(), persistentId);
|
||||
}
|
||||
|
||||
return Optional.ofNullable(valueOf);
|
||||
|
|
|
@ -40,6 +40,7 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
|||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.MultimapBuilder;
|
||||
import org.apache.commons.lang3.Functions;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
|
@ -339,6 +340,10 @@ public class IdHelperService {
|
|||
*/
|
||||
@Nonnull
|
||||
public IIdType translatePidIdToForcedId(FhirContext theCtx, String theResourceType, ResourcePersistentId theId) {
|
||||
if (theId.getAssociatedResourceId() != null) {
|
||||
return theId.getAssociatedResourceId();
|
||||
}
|
||||
|
||||
IIdType retVal = theCtx.getVersion().newIdType();
|
||||
|
||||
Optional<String> forcedId = translatePidIdToForcedIdWithCache(theId);
|
||||
|
@ -630,7 +635,7 @@ public class IdHelperService {
|
|||
/**
|
||||
* Pre-cache a PID-to-Resource-ID mapping for later retrieval by {@link #translatePidsToForcedIds(Set)} and related methods
|
||||
*/
|
||||
public void addResolvedPidToForcedId(ResourcePersistentId theResourcePersistentId, @Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, @Nullable String theForcedId) {
|
||||
public void addResolvedPidToForcedId(ResourcePersistentId theResourcePersistentId, @Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, @Nullable String theForcedId, @Nullable Date theDeletedAt) {
|
||||
if (theForcedId != null) {
|
||||
if (theResourcePersistentId.getAssociatedResourceId() == null) {
|
||||
populateAssociatedResourceId(theResourceType, theForcedId, theResourcePersistentId);
|
||||
|
@ -642,6 +647,13 @@ public class IdHelperService {
|
|||
} else {
|
||||
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, theResourcePersistentId.getIdAsLong(), Optional.empty());
|
||||
}
|
||||
|
||||
if (!myDaoConfig.isDeleteEnabled()) {
|
||||
ResourceLookup lookup = new ResourceLookup(theResourceType, theResourcePersistentId.getIdAsLong(), theDeletedAt);
|
||||
String nextKey = theResourcePersistentId.toString();
|
||||
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.RESOURCE_LOOKUP, nextKey, lookup);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
|
|
@ -39,6 +39,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
|||
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService;
|
||||
import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil;
|
||||
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
|
@ -315,6 +316,12 @@ public class SearchParamWithInlineReferencesExtractor {
|
|||
myContext = theContext;
|
||||
}
|
||||
|
||||
|
||||
@Autowired
|
||||
private MemoryCacheService myMemoryCacheService;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -350,15 +357,19 @@ public class SearchParamWithInlineReferencesExtractor {
|
|||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
Class<? extends IBaseResource> matchResourceType = matchResourceDef.getImplementingClass();
|
||||
|
||||
//Attempt to find the target reference before creating a placeholder
|
||||
Set<ResourcePersistentId> matches = myMatchResourceUrlService.processMatchUrl(nextIdText, matchResourceType, theTransactionDetails, theRequest);
|
||||
|
||||
ResourcePersistentId match;
|
||||
if (matches.isEmpty()) {
|
||||
|
||||
Optional<ResourceTable> placeholderOpt = myDaoResourceLinkResolver.createPlaceholderTargetIfConfiguredToDoSo(matchResourceType, nextRef, null, theRequest);
|
||||
Optional<ResourceTable> placeholderOpt = myDaoResourceLinkResolver.createPlaceholderTargetIfConfiguredToDoSo(matchResourceType, nextRef, null, theRequest, theTransactionDetails);
|
||||
if (placeholderOpt.isPresent()) {
|
||||
match = new ResourcePersistentId(placeholderOpt.get().getResourceId());
|
||||
match.setAssociatedResourceId(placeholderOpt.get().getIdType(myContext));
|
||||
theTransactionDetails.addResolvedMatchUrl(nextIdText, match);
|
||||
myMemoryCacheService.putAfterCommit(MemoryCacheService.CacheEnum.MATCH_URL, nextIdText, match);
|
||||
} else {
|
||||
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlNoMatches", nextId.getValue());
|
||||
throw new ResourceNotFoundException(msg);
|
||||
|
@ -373,6 +384,7 @@ public class SearchParamWithInlineReferencesExtractor {
|
|||
|
||||
IIdType newId = myIdHelperService.translatePidIdToForcedId(myContext, resourceTypeString, match);
|
||||
ourLog.debug("Replacing inline match URL[{}] with ID[{}}", nextId.getValue(), newId);
|
||||
|
||||
nextRef.setReference(newId.getValue());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,17 +38,23 @@ import org.junit.jupiter.api.AfterEach;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.in;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
@ -68,6 +74,8 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
|||
myModelConfig.setRespectVersionsForSearchIncludes(new ModelConfig().isRespectVersionsForSearchIncludes());
|
||||
myFhirCtx.getParserOptions().setStripVersionsFromReferences(true);
|
||||
myDaoConfig.setTagStorageMode(new DaoConfig().getTagStorageMode());
|
||||
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(new DaoConfig().isAutoCreatePlaceholderReferenceTargets());
|
||||
myDaoConfig.setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(new DaoConfig().isPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -782,6 +790,121 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
|||
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionWithMultipleCreates() {
|
||||
myDaoConfig.setMassIngestionMode(true);
|
||||
myDaoConfig.setMatchUrlCacheEnabled(true);
|
||||
myDaoConfig.setDeleteEnabled(false);
|
||||
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
|
||||
myDaoConfig.setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(true);
|
||||
|
||||
// First pass
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
mySystemDao.transaction(mySrd, createTransactionWithCreatesAndOneMatchUrl());
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
// 1 lookup for the match URL only
|
||||
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
|
||||
assertEquals(5, myCaptureQueriesListener.countInsertQueries());
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
runInTransaction(()->assertEquals(4, myResourceTableDao.count()));
|
||||
|
||||
// Run it again - This time even the match URL should be cached
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
mySystemDao.transaction(mySrd, createTransactionWithCreatesAndOneMatchUrl());
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(0, myCaptureQueriesListener.countSelectQueries());
|
||||
assertEquals(4, myCaptureQueriesListener.countInsertQueries());
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
runInTransaction(()->assertEquals(7, myResourceTableDao.count()));
|
||||
|
||||
// Once more for good measure
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
mySystemDao.transaction(mySrd, createTransactionWithCreatesAndOneMatchUrl());
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(0, myCaptureQueriesListener.countSelectQueries());
|
||||
assertEquals(4, myCaptureQueriesListener.countInsertQueries());
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
runInTransaction(()->assertEquals(10, myResourceTableDao.count()));
|
||||
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private Bundle createTransactionWithCreatesAndOneMatchUrl() {
|
||||
BundleBuilder bb = new BundleBuilder(myFhirCtx);
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setId(IdType.newRandomUuid());
|
||||
p.setActive(true);
|
||||
bb.addTransactionCreateEntry(p);
|
||||
|
||||
Encounter enc = new Encounter();
|
||||
enc.setSubject(new Reference(p.getId()));
|
||||
enc.addParticipant().setIndividual(new Reference("Practitioner?identifier=foo|bar"));
|
||||
bb.addTransactionCreateEntry(enc);
|
||||
|
||||
enc = new Encounter();
|
||||
enc.setSubject(new Reference(p.getId()));
|
||||
enc.addParticipant().setIndividual(new Reference("Practitioner?identifier=foo|bar"));
|
||||
bb.addTransactionCreateEntry(enc);
|
||||
|
||||
return (Bundle) bb.getBundle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionWithMultipleCreates_PreExistingMatchUrl() {
|
||||
myDaoConfig.setMassIngestionMode(true);
|
||||
myDaoConfig.setMatchUrlCacheEnabled(true);
|
||||
myDaoConfig.setDeleteEnabled(false);
|
||||
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
|
||||
myDaoConfig.setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(true);
|
||||
|
||||
Practitioner pract = new Practitioner();
|
||||
pract.addIdentifier().setSystem("foo").setValue("bar");
|
||||
myPractitionerDao.create(pract);
|
||||
|
||||
// First pass
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
mySystemDao.transaction(mySrd, createTransactionWithCreatesAndOneMatchUrl());
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
// 1 lookup for the match URL only
|
||||
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
|
||||
assertEquals(5, myCaptureQueriesListener.countInsertQueries());
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
runInTransaction(()->assertEquals(4, myResourceTableDao.count()));
|
||||
|
||||
// Run it again - This time even the match URL should be cached
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
mySystemDao.transaction(mySrd, createTransactionWithCreatesAndOneMatchUrl());
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(0, myCaptureQueriesListener.countSelectQueries());
|
||||
assertEquals(4, myCaptureQueriesListener.countInsertQueries());
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
runInTransaction(()->assertEquals(7, myResourceTableDao.count()));
|
||||
|
||||
// Once more for good measure
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
mySystemDao.transaction(mySrd, createTransactionWithCreatesAndOneMatchUrl());
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(0, myCaptureQueriesListener.countSelectQueries());
|
||||
assertEquals(4, myCaptureQueriesListener.countInsertQueries());
|
||||
assertEquals(1, myCaptureQueriesListener.countUpdateQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
runInTransaction(()->assertEquals(10, myResourceTableDao.count()));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTransactionWithTwoCreates() {
|
||||
|
||||
|
@ -1194,7 +1317,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
|||
myCaptureQueriesListener.clear();
|
||||
mySystemDao.transaction(mySrd, bundleCreator.get());
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
assertEquals(1, myCaptureQueriesListener.countSelectQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countSelectQueries());
|
||||
assertEquals(3, myCaptureQueriesListener.countInsertQueries());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
|
||||
|
||||
|
@ -1744,7 +1867,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
|||
|
||||
// Lookup the two existing IDs to make sure they are legit
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
||||
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
||||
import ca.uhn.fhir.jpa.model.search.ResourceTableRoutingBinder;
|
||||
|
@ -39,6 +40,7 @@ import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
|
|||
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexingDependency;
|
||||
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ObjectPath;
|
||||
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.PropertyValue;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.io.Serializable;
|
||||
|
@ -709,17 +711,32 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
myForcedId = theForcedId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public IdDt getIdDt() {
|
||||
IdDt retVal = new IdDt();
|
||||
populateId(retVal);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
public IIdType getIdType(FhirContext theContext) {
|
||||
IIdType retVal = theContext.getVersion().newIdType();
|
||||
populateId(retVal);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void populateId(IIdType retVal) {
|
||||
if (getTransientForcedId() != null) {
|
||||
// Avoid a join query if possible
|
||||
return new IdDt(getResourceType() + '/' + getTransientForcedId() + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
||||
retVal.setValue(getResourceType() + '/' + getTransientForcedId() + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
||||
} else if (getForcedId() == null) {
|
||||
Long id = this.getResourceId();
|
||||
return new IdDt(getResourceType() + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
||||
retVal.setValue(getResourceType() + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
||||
} else {
|
||||
String forcedId = getForcedId().getForcedId();
|
||||
return new IdDt(getResourceType() + '/' + forcedId + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
||||
retVal.setValue(getResourceType() + '/' + forcedId + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import ca.uhn.fhir.context.RuntimeSearchParam;
|
|||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
|
||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
@ -37,16 +38,17 @@ public interface IResourceLinkResolver {
|
|||
* <p>
|
||||
* This method returns an {@link IResourceLookup} so as to avoid needing to resolve the entire resource.
|
||||
*
|
||||
* @param theRequestPartitionId The partition ID of the target resource
|
||||
* @param theSearchParam The param that is being indexed
|
||||
* @param theSourcePath The path within the resource where this reference was found
|
||||
* @param theSourceResourceId The ID of the resource containing the reference to the target being resolved
|
||||
* @param theTypeString The type of the resource being resolved
|
||||
* @param theType The resource type of the target
|
||||
* @param theReference The reference being resolved
|
||||
* @param theRequest The incoming request, if any
|
||||
* @param theRequestPartitionId The partition ID of the target resource
|
||||
* @param theSearchParam The param that is being indexed
|
||||
* @param theSourcePath The path within the resource where this reference was found
|
||||
* @param theSourceResourceId The ID of the resource containing the reference to the target being resolved
|
||||
* @param theTypeString The type of the resource being resolved
|
||||
* @param theType The resource type of the target
|
||||
* @param theReference The reference being resolved
|
||||
* @param theRequest The incoming request, if any
|
||||
* @param theTransactionDetails
|
||||
*/
|
||||
IResourceLookup findTargetResource(RequestPartitionId theRequestPartitionId, RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest);
|
||||
IResourceLookup findTargetResource(RequestPartitionId theRequestPartitionId, RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest, TransactionDetails theTransactionDetails);
|
||||
|
||||
void validateTypeOrThrowException(Class<? extends IBaseResource> theType);
|
||||
|
||||
|
|
|
@ -377,7 +377,7 @@ public class SearchParamExtractorService {
|
|||
* if the reference is invalid
|
||||
*/
|
||||
myResourceLinkResolver.validateTypeOrThrowException(type);
|
||||
resourceLink = resolveTargetAndCreateResourceLinkOrReturnNull(theRequestPartitionId, theEntity, transactionDate, theRuntimeSearchParam, path, thePathAndRef, nextId, typeString, type, nextReference, theRequest);
|
||||
resourceLink = resolveTargetAndCreateResourceLinkOrReturnNull(theRequestPartitionId, theEntity, transactionDate, theRuntimeSearchParam, path, thePathAndRef, nextId, typeString, type, nextReference, theRequest, theTransactionDetails);
|
||||
if (resourceLink == null) {
|
||||
return;
|
||||
} else {
|
||||
|
@ -402,7 +402,17 @@ public class SearchParamExtractorService {
|
|||
theParams.myLinks.add(resourceLink);
|
||||
}
|
||||
|
||||
private ResourceLink resolveTargetAndCreateResourceLinkOrReturnNull(@Nonnull RequestPartitionId theRequestPartitionId, ResourceTable theEntity, Date theUpdateTime, RuntimeSearchParam nextSpDef, String theNextPathsUnsplit, PathAndRef nextPathAndRef, IIdType theNextId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest) {
|
||||
private ResourceLink resolveTargetAndCreateResourceLinkOrReturnNull(@Nonnull RequestPartitionId theRequestPartitionId, ResourceTable theEntity, Date theUpdateTime, RuntimeSearchParam nextSpDef, String theNextPathsUnsplit, PathAndRef nextPathAndRef, IIdType theNextId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
|
||||
|
||||
ResourcePersistentId resolvedResourceId = theTransactionDetails.getResolvedResourceId(theNextId);
|
||||
if (resolvedResourceId != null) {
|
||||
String targetResourceType = theNextId.getResourceType();
|
||||
Long targetResourcePid = resolvedResourceId.getIdAsLong();
|
||||
String targetResourceIdPart = theNextId.getIdPart();
|
||||
Long targetVersion = theNextId.getVersionIdPartAsLong();
|
||||
return ResourceLink.forLocalReference(nextPathAndRef.getPath(), theEntity, targetResourceType, targetResourcePid, targetResourceIdPart, theUpdateTime, targetVersion);
|
||||
}
|
||||
|
||||
/*
|
||||
* We keep a cache of resolved target resources. This is good since for some resource types, there
|
||||
* are multiple search parameters that map to the same element path within a resource (e.g.
|
||||
|
@ -415,7 +425,7 @@ public class SearchParamExtractorService {
|
|||
targetRequestPartitionId = RequestPartitionId.allPartitions();
|
||||
}
|
||||
|
||||
IResourceLookup targetResource = myResourceLinkResolver.findTargetResource(targetRequestPartitionId, nextSpDef, theNextPathsUnsplit, theNextId, theTypeString, theType, theReference, theRequest);
|
||||
IResourceLookup targetResource = myResourceLinkResolver.findTargetResource(targetRequestPartitionId, nextSpDef, theNextPathsUnsplit, theNextId, theTypeString, theType, theReference, theRequest, theTransactionDetails);
|
||||
|
||||
if (targetResource == null) {
|
||||
return null;
|
||||
|
|
|
@ -117,7 +117,7 @@ public class TransactionDetails {
|
|||
*/
|
||||
@Nullable
|
||||
public ResourcePersistentId getResolvedResourceId(IIdType theId) {
|
||||
String idValue = theId.toVersionless().getValue();
|
||||
String idValue = theId.toUnqualifiedVersionless().getValue();
|
||||
return myResolvedResourceIds.get(idValue);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue