Optmize forced ID resolution (#1754)
* Add forced ID caching * Work on id optimization * Test fixes * More optimization work * Add some docs * Work on docs * Test fixes * Test fixes * Test fixes * Address review comments * Compile fix
This commit is contained in:
parent
fe51c08168
commit
1dc5d89013
|
@ -83,6 +83,7 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionMissingUrl=Unable to perfor
|
|||
ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao.transactionInvalidUrl=Unable to perform {0}, URL provided is invalid: {1}
|
||||
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.cantValidateWithNoResource=No resource supplied for $validate operation (resource is required unless mode is \"delete\")
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.deleteBlockedBecauseDisabled=Resource deletion is not permitted on this server
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.duplicateCreateForcedId=Can not create entity with ID[{0}], a resource with this ID already exists
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.failedToCreateWithInvalidId=Can not process entity with ID[{0}], this is not a valid FHIR ID
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.incorrectResourceType=Incorrect resource type detected for endpoint, found {0} but expected {1}
|
||||
|
|
|
@ -43,6 +43,7 @@ page.server_jpa.get_started=Get Started ⚡
|
|||
page.server_jpa.architecture=Architecture
|
||||
page.server_jpa.configuration=Configuration
|
||||
page.server_jpa.search=Search
|
||||
page.server_jpa.performance=Performance
|
||||
page.server_jpa.upgrading=Upgrade Guide
|
||||
|
||||
section.interceptors.title=Interceptors
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# Performance
|
||||
|
||||
This page contains information for performance optimization.
|
||||
|
||||
# Bulk Loading
|
||||
|
||||
On servers where a large amount of data will be ingested, the following considerations may be helpful:
|
||||
|
||||
* Optimize your database thread pool count and HTTP client thread count: Every environment will have a different optimal setting for the number of concurrent writes that are permitted, and the maximum number of database connections allowed.
|
||||
|
||||
* Disable deletes: If the JPA server is configured to have the FHIR delete operation disabled, it is able to skip some resource reference deletion checks during resource creation, which can have a measurable improvement to performance over large datasets.
|
|
@ -1146,7 +1146,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
}
|
||||
}
|
||||
|
||||
// Syncrhonize composite params
|
||||
// Synchronize composite params
|
||||
mySearchParamWithInlineReferencesExtractor.storeCompositeStringUniques(newParams, entity, existingParams);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -181,8 +181,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
@Override
|
||||
public DaoMethodOutcome delete(IIdType theId, DeleteConflictList theDeleteConflicts, RequestDetails theRequest) {
|
||||
validateIdPresentForDelete(theId);
|
||||
validateDeleteEnabled();
|
||||
|
||||
final ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||
final ResourceTable entity = readEntityLatestVersion(theId);
|
||||
if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) {
|
||||
throw new ResourceVersionConflictException("Trying to delete " + theId + " but this is not the current version");
|
||||
}
|
||||
|
@ -258,6 +259,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
@Override
|
||||
public DaoMethodOutcome delete(IIdType theId, RequestDetails theRequestDetails) {
|
||||
validateIdPresentForDelete(theId);
|
||||
validateDeleteEnabled();
|
||||
|
||||
DeleteConflictList deleteConflicts = new DeleteConflictList();
|
||||
if (isNotBlank(theId.getValue())) {
|
||||
|
@ -280,6 +282,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
*/
|
||||
@Override
|
||||
public DeleteMethodOutcome deleteByUrl(String theUrl, DeleteConflictList deleteConflicts, RequestDetails theRequest) {
|
||||
validateDeleteEnabled();
|
||||
|
||||
StopWatch w = new StopWatch();
|
||||
|
||||
Set<ResourcePersistentId> resourceIds = myMatchResourceUrlService.processMatchUrl(theUrl, myResourceType, theRequest);
|
||||
|
@ -355,6 +359,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
@Override
|
||||
public DeleteMethodOutcome deleteByUrl(String theUrl, RequestDetails theRequestDetails) {
|
||||
validateDeleteEnabled();
|
||||
|
||||
DeleteConflictList deleteConflicts = new DeleteConflictList();
|
||||
|
||||
DeleteMethodOutcome outcome = deleteByUrl(theUrl, deleteConflicts, theRequestDetails);
|
||||
|
@ -364,6 +370,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return outcome;
|
||||
}
|
||||
|
||||
private void validateDeleteEnabled() {
|
||||
if (!myDaoConfig.isDeleteEnabled()) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "deleteBlockedBecauseDisabled");
|
||||
throw new PreconditionFailedException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateIdPresentForDelete(IIdType theId) {
|
||||
if (theId == null || !theId.hasIdPart()) {
|
||||
throw new InvalidRequestException("Can not perform delete, no ID provided");
|
||||
|
@ -683,7 +696,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
throw new ResourceNotFoundException(theResourceId);
|
||||
}
|
||||
|
||||
ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest);
|
||||
ResourceTable latestVersion = readEntityLatestVersion(theResourceId);
|
||||
if (latestVersion.getVersion() != entity.getVersion()) {
|
||||
doMetaAdd(theMetaAdd, entity);
|
||||
} else {
|
||||
|
@ -715,7 +728,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
throw new ResourceNotFoundException(theResourceId);
|
||||
}
|
||||
|
||||
ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest);
|
||||
ResourceTable latestVersion = readEntityLatestVersion(theResourceId);
|
||||
if (latestVersion.getVersion() != entity.getVersion()) {
|
||||
doMetaDelete(theMetaDel, entity);
|
||||
} else {
|
||||
|
@ -791,7 +804,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
|
||||
} else {
|
||||
entityToUpdate = readEntityLatestVersion(theId, theRequest);
|
||||
entityToUpdate = readEntityLatestVersion(theId);
|
||||
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");
|
||||
|
@ -926,7 +939,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) {
|
||||
validateResourceTypeAndThrowInvalidRequestException(theId);
|
||||
|
||||
ResourcePersistentId pid = myIdHelperService.translateForcedIdToPid(getResourceName(), theId.getIdPart(), theRequest);
|
||||
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(getResourceName(), theId.getIdPart());
|
||||
BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid.getIdAsLong());
|
||||
|
||||
if (entity == null) {
|
||||
|
@ -964,8 +977,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return entity;
|
||||
}
|
||||
|
||||
protected ResourceTable readEntityLatestVersion(IIdType theId, RequestDetails theRequest) {
|
||||
ResourcePersistentId persistentId = myIdHelperService.translateForcedIdToPid(getResourceName(), theId.getIdPart(), theRequest);
|
||||
protected ResourceTable readEntityLatestVersion(IIdType theId) {
|
||||
ResourcePersistentId persistentId = myIdHelperService.resolveResourcePersistentIds(getResourceName(), theId.getIdPart());
|
||||
ResourceTable entity = myEntityManager.find(ResourceTable.class, persistentId.getId());
|
||||
if (entity == null) {
|
||||
throw new ResourceNotFoundException(theId);
|
||||
|
@ -1206,7 +1219,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
resourceId = theResource.getIdElement();
|
||||
|
||||
try {
|
||||
entity = readEntityLatestVersion(resourceId, theRequest);
|
||||
entity = readEntityLatestVersion(resourceId);
|
||||
} catch (ResourceNotFoundException e) {
|
||||
return doCreate(theResource, null, thePerformIndexing, new Date(), theRequest);
|
||||
}
|
||||
|
@ -1278,7 +1291,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);
|
||||
|
||||
// Validate that there are no resources pointing to the candidate that
|
||||
// would prevent deletion
|
||||
|
|
|
@ -183,6 +183,11 @@ public class DaoConfig {
|
|||
*/
|
||||
private boolean myPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets;
|
||||
|
||||
/**
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private boolean myDeleteEnabled = true;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
|
@ -1907,7 +1912,33 @@ public class DaoConfig {
|
|||
setPreExpandValueSetsDefaultCount(Math.min(getPreExpandValueSetsDefaultCount(), getPreExpandValueSetsMaxCount()));
|
||||
}
|
||||
|
||||
public enum StoreMetaSourceInformationEnum {
|
||||
/**
|
||||
* This setting should be disabled (set to <code>false</code>) on servers that are not allowing
|
||||
* deletes. Default is <code>true</code>. If deletes are disabled, some checks for resource
|
||||
* deletion can be skipped, which improves performance. This is particularly helpful when large
|
||||
* amounts of data containing client-assigned IDs are being loaded, but it can also improve
|
||||
* search performance.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public void setDeleteEnabled(boolean theDeleteEnabled) {
|
||||
myDeleteEnabled = theDeleteEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* This setting should be disabled (set to <code>false</code>) on servers that are not allowing
|
||||
* deletes. Default is <code>true</code>. If deletes are disabled, some checks for resource
|
||||
* deletion can be skipped, which improves performance. This is particularly helpful when large
|
||||
* amounts of data containing client-assigned IDs are being loaded, but it can also improve
|
||||
* search performance.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public boolean isDeleteEnabled() {
|
||||
return myDeleteEnabled;
|
||||
}
|
||||
|
||||
public enum StoreMetaSourceInformationEnum {
|
||||
NONE(false, false),
|
||||
SOURCE_URI(true, false),
|
||||
REQUEST_ID(false, true),
|
||||
|
|
|
@ -20,16 +20,12 @@ package ca.uhn.fhir.jpa.dao;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
|
||||
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -37,8 +33,6 @@ import org.springframework.transaction.PlatformTransactionManager;
|
|||
|
||||
import java.util.Date;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
public class FhirResourceDaoSubscriptionDstu2 extends BaseHapiFhirResourceDao<Subscription> implements IFhirResourceDaoSubscription<Subscription> {
|
||||
|
||||
@Autowired
|
||||
|
@ -56,7 +50,7 @@ public class FhirResourceDaoSubscriptionDstu2 extends BaseHapiFhirResourceDao<Su
|
|||
|
||||
@Override
|
||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
||||
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||
ResourceTable entity = readEntityLatestVersion(theId);
|
||||
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
||||
if (table == null) {
|
||||
return null;
|
||||
|
|
|
@ -229,7 +229,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
|
|||
StringParam idParm = (StringParam) idParam;
|
||||
idParamValue = idParm.getValue();
|
||||
}
|
||||
pid = myIdHelperService.translateForcedIdToPid(theResourceName, idParamValue, theRequest);
|
||||
// pid = myIdHelperService.translateForcedIdToPid_(theResourceName, idParamValue, theRequest);
|
||||
}
|
||||
|
||||
ResourcePersistentId referencingPid = pid;
|
||||
|
@ -282,7 +282,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
|
|||
if (contextParts.length != 3 || "Patient".equals(contextParts[0]) == false || "$everything".equals(contextParts[2]) == false) {
|
||||
throw new InvalidRequestException("Invalid context: " + theContext);
|
||||
}
|
||||
ResourcePersistentId pid = myIdHelperService.translateForcedIdToPid(contextParts[0], contextParts[1], theRequest);
|
||||
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(contextParts[0], contextParts[1]);
|
||||
|
||||
FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager);
|
||||
|
||||
|
|
|
@ -266,7 +266,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
|
||||
if (myParams.get(IAnyResource.SP_RES_ID) != null) {
|
||||
StringParam idParm = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0);
|
||||
ResourcePersistentId pid = myIdHelperService.translateForcedIdToPid(myResourceName, idParm.getValue(), theRequest);
|
||||
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(myResourceName, idParm.getValue());
|
||||
if (myAlsoIncludePids == null) {
|
||||
myAlsoIncludePids = new ArrayList<>(1);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao.data;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -35,8 +36,8 @@ public interface IForcedIdDao extends JpaRepository<ForcedId, Long> {
|
|||
@Query("SELECT f.myResourcePid FROM ForcedId f WHERE myForcedId IN (:forced_id)")
|
||||
List<Long> findByForcedId(@Param("forced_id") Collection<String> theForcedId);
|
||||
|
||||
@Query("SELECT f.myResourcePid FROM ForcedId f WHERE myResourceType = :resource_type AND myForcedId IN (:forced_id)")
|
||||
List<Long> findByTypeAndForcedId(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedId);
|
||||
@Query("SELECT f.myResourcePid FROM ForcedId f WHERE myResourceType = :resource_type AND myForcedId = :forced_id")
|
||||
Optional<Long> findByTypeAndForcedId(@Param("resource_type") String theResourceType, @Param("forced_id") String theForcedId);
|
||||
|
||||
@Query("SELECT f FROM ForcedId f WHERE f.myResourcePid = :resource_pid")
|
||||
ForcedId findByResourcePid(@Param("resource_pid") Long theResourcePid);
|
||||
|
@ -44,4 +45,37 @@ public interface IForcedIdDao extends JpaRepository<ForcedId, Long> {
|
|||
@Modifying
|
||||
@Query("DELETE FROM ForcedId t WHERE t.myId = :pid")
|
||||
void deleteByPid(@Param("pid") Long theId);
|
||||
|
||||
/**
|
||||
* This method returns a Collection where each row is an element in the collection. Each element in the collection
|
||||
* is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way.
|
||||
*/
|
||||
@Query("SELECT f.myForcedId, f.myResourcePid FROM ForcedId f WHERE myResourceType = :resource_type AND myForcedId IN ( :forced_id )")
|
||||
Collection<Object[]> findByTypeAndForcedId(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedId);
|
||||
|
||||
/**
|
||||
* Warning: No DB index exists for this particular query, so it may not perform well
|
||||
*
|
||||
* This method returns a Collection where each row is an element in the collection. Each element in the collection
|
||||
* is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way.
|
||||
*/
|
||||
@Query("" +
|
||||
"SELECT " +
|
||||
" f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " +
|
||||
"FROM ForcedId f " +
|
||||
"JOIN ResourceTable t ON t.myId = f.myResourcePid " +
|
||||
"WHERE f.myForcedId IN ( :forced_id )")
|
||||
Collection<Object[]> findAndResolveByForcedIdWithNoType(@Param("forced_id") Collection<String> theForcedIds);
|
||||
|
||||
/**
|
||||
* This method returns a Collection where each row is an element in the collection. Each element in the collection
|
||||
* is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way.
|
||||
*/
|
||||
@Query("" +
|
||||
"SELECT " +
|
||||
" f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " +
|
||||
"FROM ForcedId f " +
|
||||
"JOIN ResourceTable t ON t.myId = f.myResourcePid " +
|
||||
"WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id )")
|
||||
Collection<Object[]> findAndResolveByForcedIdWithNoType(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedIds);
|
||||
}
|
||||
|
|
|
@ -8,9 +8,11 @@ import org.springframework.data.jpa.repository.Modifying;
|
|||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -63,4 +65,6 @@ public interface IResourceTableDao extends JpaRepository<ResourceTable, Long> {
|
|||
@Query("DELETE FROM ResourceTable t WHERE t.myId = :pid")
|
||||
void deleteByPid(@Param("pid") Long theId);
|
||||
|
||||
@Query("SELECT t.myResourceType, t.myId, t.myDeleted FROM ResourceTable t WHERE t.myId IN (:pid)")
|
||||
Collection<Object[]> findLookupFieldsByResourcePid(@Param("pid") List<Long> thePids);
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ public class FhirResourceDaoSubscriptionDstu3 extends BaseHapiFhirResourceDao<Su
|
|||
|
||||
@Override
|
||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
||||
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||
ResourceTable entity = readEntityLatestVersion(theId);
|
||||
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
||||
if (table == null) {
|
||||
return null;
|
||||
|
|
|
@ -29,7 +29,7 @@ import ca.uhn.fhir.context.RuntimeSearchParam;
|
|||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
||||
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.rest.api.server.RequestDetails;
|
||||
|
@ -64,17 +64,16 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
|
|||
private DaoRegistry myDaoRegistry;
|
||||
|
||||
@Override
|
||||
public ResourceTable findTargetResource(RuntimeSearchParam theNextSpDef, String theNextPathsUnsplit, IIdType theNextId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest) {
|
||||
ResourceTable target;
|
||||
ResourcePersistentId valueOf;
|
||||
String idPart = theNextId.getIdPart();
|
||||
public IResourceLookup findTargetResource(RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest) {
|
||||
IResourceLookup resolvedResource;
|
||||
String idPart = theSourceResourceId.getIdPart();
|
||||
try {
|
||||
valueOf = myIdHelperService.translateForcedIdToPid(theTypeString, idPart, theRequest);
|
||||
ourLog.trace("Translated {}/{} to resource PID {}", theType, idPart, valueOf);
|
||||
resolvedResource = myIdHelperService.resolveResourceIdentity(theTypeString, idPart, theRequest);
|
||||
ourLog.trace("Translated {}/{} to resource PID {}", theType, idPart, resolvedResource);
|
||||
} catch (ResourceNotFoundException e) {
|
||||
|
||||
Optional<ResourcePersistentId> pidOpt = createPlaceholderTargetIfConfiguredToDoSo(theType, theReference, idPart);
|
||||
if (!pidOpt.isPresent()) {
|
||||
Optional<ResourceTable> createdTableOpt = createPlaceholderTargetIfConfiguredToDoSo(theType, theReference, idPart);
|
||||
if (!createdTableOpt.isPresent()) {
|
||||
|
||||
if (myDaoConfig.isEnforceReferentialIntegrityOnWrite() == false) {
|
||||
return null;
|
||||
|
@ -82,43 +81,36 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
|
|||
|
||||
RuntimeResourceDefinition missingResourceDef = myContext.getResourceDefinition(theType);
|
||||
String resName = missingResourceDef.getName();
|
||||
throw new InvalidRequestException("Resource " + resName + "/" + idPart + " not found, specified in path: " + theNextPathsUnsplit);
|
||||
throw new InvalidRequestException("Resource " + resName + "/" + idPart + " not found, specified in path: " + theSourcePath);
|
||||
|
||||
}
|
||||
|
||||
valueOf = pidOpt.get();
|
||||
resolvedResource = createdTableOpt.get();
|
||||
}
|
||||
|
||||
target = myEntityManager.find(ResourceTable.class, valueOf.getIdAsLong());
|
||||
RuntimeResourceDefinition targetResourceDef = myContext.getResourceDefinition(theType);
|
||||
if (target == null) {
|
||||
String resName = targetResourceDef.getName();
|
||||
throw new InvalidRequestException("Resource " + resName + "/" + idPart + " not found, specified in path: " + theNextPathsUnsplit);
|
||||
ourLog.trace("Resolved resource of type {} as PID: {}", resolvedResource.getResourceType(), resolvedResource.getResourceId());
|
||||
if (!theTypeString.equals(resolvedResource.getResourceType())) {
|
||||
ourLog.error("Resource with PID {} was of type {} and wanted {}", resolvedResource.getResourceId(), theTypeString, resolvedResource.getResourceType());
|
||||
throw new UnprocessableEntityException("Resource contains reference to unknown resource ID " + theSourceResourceId.getValue());
|
||||
}
|
||||
|
||||
ourLog.trace("Resource PID {} is of type {}", valueOf, target.getResourceType());
|
||||
if (!theTypeString.equals(target.getResourceType())) {
|
||||
ourLog.error("Resource {} with PID {} was not of type {}", target.getIdDt().getValue(), target.getId(), theTypeString);
|
||||
throw new UnprocessableEntityException(
|
||||
"Resource contains reference to " + theNextId.getValue() + " but resource with ID " + theNextId.getIdPart() + " is actually of type " + target.getResourceType());
|
||||
if (resolvedResource.getDeleted() != null) {
|
||||
String resName = resolvedResource.getResourceType();
|
||||
throw new InvalidRequestException("Resource " + resName + "/" + idPart + " is deleted, specified in path: " + theSourcePath);
|
||||
}
|
||||
|
||||
if (target.getDeleted() != null) {
|
||||
String resName = targetResourceDef.getName();
|
||||
throw new InvalidRequestException("Resource " + resName + "/" + idPart + " is deleted, specified in path: " + theNextPathsUnsplit);
|
||||
}
|
||||
|
||||
if (!theNextSpDef.hasTargets() && theNextSpDef.getTargets().contains(theTypeString)) {
|
||||
if (!theSearchParam.hasTargets() && theSearchParam.getTargets().contains(theTypeString)) {
|
||||
return null;
|
||||
}
|
||||
return target;
|
||||
|
||||
return resolvedResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param theIdToAssignToPlaceholder If specified, the placeholder resource created will be given a specific ID
|
||||
*/
|
||||
public <T extends IBaseResource> Optional<ResourcePersistentId> createPlaceholderTargetIfConfiguredToDoSo(Class<T> theType, IBaseReference theReference, @Nullable String theIdToAssignToPlaceholder) {
|
||||
ResourcePersistentId valueOf = null;
|
||||
public <T extends IBaseResource> Optional<ResourceTable> createPlaceholderTargetIfConfiguredToDoSo(Class<T> theType, IBaseReference theReference, @Nullable String theIdToAssignToPlaceholder) {
|
||||
ResourceTable valueOf = null;
|
||||
|
||||
if (myDaoConfig.isAutoCreatePlaceholderReferenceTargets()) {
|
||||
RuntimeResourceDefinition missingResourceDef = myContext.getResourceDefinition(theType);
|
||||
|
@ -136,9 +128,9 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
|
|||
|
||||
if (theIdToAssignToPlaceholder != null) {
|
||||
newResource.setId(resName + "/" + theIdToAssignToPlaceholder);
|
||||
valueOf = placeholderResourceDao.update(newResource).getEntity().getPersistentId();
|
||||
valueOf = ((ResourceTable) placeholderResourceDao.update(newResource).getEntity());
|
||||
} else {
|
||||
valueOf = placeholderResourceDao.create(newResource).getEntity().getPersistentId();
|
||||
valueOf = ((ResourceTable) placeholderResourceDao.create(newResource).getEntity());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,8 @@ package ca.uhn.fhir.jpa.dao.index;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.*;
|
||||
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndex;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
|
||||
import ca.uhn.fhir.jpa.util.AddRemoveCount;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -36,23 +37,22 @@ import java.util.List;
|
|||
|
||||
@Service
|
||||
public class DaoSearchParamSynchronizer {
|
||||
@Autowired
|
||||
private DaoConfig myDaoConfig;
|
||||
|
||||
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
||||
protected EntityManager myEntityManager;
|
||||
@Autowired
|
||||
private DaoConfig myDaoConfig;
|
||||
|
||||
public AddRemoveCount synchronizeSearchParamsToDatabase(ResourceIndexedSearchParams theParams, ResourceTable theEntity, ResourceIndexedSearchParams existingParams) {
|
||||
AddRemoveCount retVal = new AddRemoveCount();
|
||||
|
||||
synchronize(theParams, theEntity, retVal, theParams.myStringParams, existingParams.myStringParams);
|
||||
synchronize(theParams, theEntity, retVal, theParams.myTokenParams, existingParams.myTokenParams);
|
||||
synchronize(theParams, theEntity,retVal, theParams.myNumberParams, existingParams.myNumberParams);
|
||||
synchronize(theParams, theEntity,retVal, theParams.myQuantityParams, existingParams.myQuantityParams);
|
||||
synchronize(theParams, theEntity,retVal, theParams.myDateParams, existingParams.myDateParams);
|
||||
synchronize(theParams, theEntity,retVal, theParams.myUriParams, existingParams.myUriParams);
|
||||
synchronize(theParams, theEntity, retVal, theParams.myNumberParams, existingParams.myNumberParams);
|
||||
synchronize(theParams, theEntity, retVal, theParams.myQuantityParams, existingParams.myQuantityParams);
|
||||
synchronize(theParams, theEntity, retVal, theParams.myDateParams, existingParams.myDateParams);
|
||||
synchronize(theParams, theEntity, retVal, theParams.myUriParams, existingParams.myUriParams);
|
||||
synchronize(theParams, theEntity, retVal, theParams.myCoordsParams, existingParams.myCoordsParams);
|
||||
synchronize(theParams, theEntity,retVal, theParams.myLinks, existingParams.myLinks);
|
||||
synchronize(theParams, theEntity, retVal, theParams.myLinks, existingParams.myLinks);
|
||||
|
||||
// make sure links are indexed
|
||||
theEntity.setResourceLinks(theParams.myLinks);
|
||||
|
@ -85,7 +85,7 @@ public class DaoSearchParamSynchronizer {
|
|||
* "one delete + one insert" with "one update"
|
||||
*
|
||||
* @param theIndexesToRemove The rows that would be removed
|
||||
* @param theIndexesToAdd The rows that would be added
|
||||
* @param theIndexesToAdd The rows that would be added
|
||||
*/
|
||||
private <T extends BaseResourceIndex> void tryToReuseIndexEntities(List<T> theIndexesToRemove, List<T> theIndexesToAdd) {
|
||||
for (int addIndex = 0; addIndex < theIndexesToAdd.size(); addIndex++) {
|
||||
|
@ -107,8 +107,6 @@ public class DaoSearchParamSynchronizer {
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
<T> List<T> subtract(Collection<T> theSubtractFrom, Collection<T> theToSubtract) {
|
||||
assert theSubtractFrom != theToSubtract;
|
||||
|
||||
|
|
|
@ -22,132 +22,212 @@ package ca.uhn.fhir.jpa.dao.index;
|
|||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
||||
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
||||
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.cross.ResourcePersistentId;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.MultimapBuilder;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.*;
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
/**
|
||||
* This class is used to convert between PIDs (the internal primary key for a particular resource as
|
||||
* stored in the {@link ca.uhn.fhir.jpa.model.entity.ResourceTable HFJ_RESOURCE} table), and the
|
||||
* public ID that a resource has.
|
||||
* <p>
|
||||
* These IDs are sometimes one and the same (by default, a resource that the server assigns the ID of
|
||||
* <code>Patient/1</code> will simply use a PID of 1 and and ID of 1. However, they may also be different
|
||||
* in cases where a forced ID is used (an arbitrary client-assigned ID).
|
||||
* </p>
|
||||
* <p>
|
||||
* This service is highly optimized in order to minimize the number of DB calls as much as possible,
|
||||
* since ID resolution is fundamental to many basic operations. This service returns either
|
||||
* {@link IResourceLookup} or {@link ResourcePersistentId} depending on the method being called.
|
||||
* The former involves an extra database join that the latter does not require, so selecting the
|
||||
* right method here is important.
|
||||
* </p>
|
||||
*/
|
||||
@Service
|
||||
public class IdHelperService {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(IdHelperService.class);
|
||||
|
||||
@Autowired
|
||||
protected IForcedIdDao myForcedIdDao;
|
||||
@Autowired
|
||||
protected IResourceTableDao myResourceTableDao;
|
||||
@Autowired(required = true)
|
||||
private DaoConfig myDaoConfig;
|
||||
@Autowired
|
||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||
|
||||
private Cache<String, Long> myPersistentIdCache;
|
||||
private Cache<String, IResourceLookup> myResourceLookupCache;
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
myPersistentIdCache = newCache();
|
||||
myResourceLookupCache = newCache();
|
||||
}
|
||||
|
||||
|
||||
public void delete(ForcedId forcedId) {
|
||||
myForcedIdDao.deleteByPid(forcedId.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a resource type and ID, looks up the resource and returns a {@link IResourceLookup}. This
|
||||
* object contains the internal PID for the resource and the resource deletion status, making it sufficient
|
||||
* for persisting resource links between resources without adding any further database calls after the
|
||||
* single one performed by this call.
|
||||
*
|
||||
* @throws ResourceNotFoundException If the ID can not be found
|
||||
*/
|
||||
@Nonnull
|
||||
public ResourcePersistentId translateForcedIdToPid(IIdType theId, RequestDetails theRequestDetails) {
|
||||
return translateForcedIdToPid(theId.getResourceType(), theId.getIdPart(), theRequestDetails);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ResourceNotFoundException If the ID can not be found
|
||||
*/
|
||||
@Nonnull
|
||||
public ResourcePersistentId translateForcedIdToPid(String theResourceName, String theResourceId, RequestDetails theRequestDetails) throws ResourceNotFoundException {
|
||||
public IResourceLookup resolveResourceIdentity(String theResourceName, String theResourceId, RequestDetails theRequestDetails) throws ResourceNotFoundException {
|
||||
// We only pass 1 input in so only 0..1 will come back
|
||||
IdDt id = new IdDt(theResourceName, theResourceId);
|
||||
List<ResourcePersistentId> matches = translateForcedIdToPids(myDaoConfig, myInterceptorBroadcaster, theRequestDetails, myForcedIdDao, Collections.singletonList(id));
|
||||
Collection<IResourceLookup> matches = translateForcedIdToPids(theRequestDetails, Collections.singletonList(id));
|
||||
assert matches.size() <= 1;
|
||||
if (matches.isEmpty()) {
|
||||
throw new ResourceNotFoundException(id);
|
||||
}
|
||||
return matches.get(0);
|
||||
return matches.iterator().next();
|
||||
}
|
||||
|
||||
public List<ResourcePersistentId> translateForcedIdToPids(Collection<IIdType> theId, RequestDetails theRequestDetails) {
|
||||
return IdHelperService.translateForcedIdToPids(myDaoConfig, myInterceptorBroadcaster, theRequestDetails, myForcedIdDao, theId);
|
||||
/**
|
||||
* Given a resource type and ID, determines the internal persistent ID for the resource.
|
||||
*
|
||||
* @throws ResourceNotFoundException If the ID can not be found
|
||||
*/
|
||||
@Nonnull
|
||||
public ResourcePersistentId resolveResourcePersistentIds(String theResourceType, String theId) {
|
||||
Long retVal;
|
||||
if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(theId)) {
|
||||
if (myDaoConfig.isDeleteEnabled()) {
|
||||
retVal = resolveResourceIdentity(theResourceType, theId);
|
||||
} else {
|
||||
String key = theResourceType + "/" + theId;
|
||||
retVal = myPersistentIdCache.get(key, t -> resolveResourceIdentity(theResourceType, theId));
|
||||
}
|
||||
|
||||
} else {
|
||||
retVal = Long.parseLong(theId);
|
||||
}
|
||||
|
||||
return new ResourcePersistentId(retVal);
|
||||
}
|
||||
|
||||
private static List<ResourcePersistentId> translateForcedIdToPids(DaoConfig theDaoConfig, IInterceptorBroadcaster theInterceptorBroadcaster, RequestDetails theRequest, IForcedIdDao theForcedIdDao, Collection<IIdType> theId) {
|
||||
theId.forEach(id -> Validate.isTrue(id.hasIdPart()));
|
||||
/**
|
||||
* Given a collection of resource IDs (resource type + id), resolves the internal persistent IDs
|
||||
*/
|
||||
@Nonnull
|
||||
public List<ResourcePersistentId> resolveResourcePersistentIds(List<IIdType> theIds, RequestDetails theRequest) {
|
||||
theIds.forEach(id -> Validate.isTrue(id.hasIdPart()));
|
||||
|
||||
if (theId.isEmpty()) {
|
||||
if (theIds.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<ResourcePersistentId> retVal = new ArrayList<>();
|
||||
|
||||
ListMultimap<String, String> typeToIds = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||
for (IIdType nextId : theId) {
|
||||
if (theDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY && isValidPid(nextId)) {
|
||||
retVal.add(new ResourcePersistentId(nextId.getIdPartAsLong()));
|
||||
} else {
|
||||
if (nextId.hasResourceType()) {
|
||||
typeToIds.put(nextId.getResourceType(), nextId.getIdPart());
|
||||
} else {
|
||||
typeToIds.put("", nextId.getIdPart());
|
||||
}
|
||||
}
|
||||
if (myDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY) {
|
||||
theIds
|
||||
.stream()
|
||||
.filter(t -> isValidPid(t))
|
||||
.map(t -> t.getIdPartAsLong())
|
||||
.map(t -> new ResourcePersistentId(t))
|
||||
.forEach(t -> retVal.add(t));
|
||||
}
|
||||
|
||||
ListMultimap<String, String> typeToIds = organizeIdsByResourceType(theIds);
|
||||
|
||||
for (Map.Entry<String, Collection<String>> nextEntry : typeToIds.asMap().entrySet()) {
|
||||
String nextResourceType = nextEntry.getKey();
|
||||
Collection<String> nextIds = nextEntry.getValue();
|
||||
if (isBlank(nextResourceType)) {
|
||||
|
||||
StorageProcessingMessage msg = new StorageProcessingMessage()
|
||||
.setMessage("This search uses unqualified resource IDs (an ID without a resource type). This is less efficient than using a qualified type.");
|
||||
HookParams params = new HookParams()
|
||||
.add(RequestDetails.class, theRequest)
|
||||
.addIfMatchesType(ServletRequestDetails.class, theRequest)
|
||||
.add(StorageProcessingMessage.class, msg);
|
||||
JpaInterceptorBroadcaster.doCallHooks(theInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_WARNING, params);
|
||||
|
||||
theForcedIdDao
|
||||
.findByForcedId(nextIds)
|
||||
.stream()
|
||||
.map(t->new ResourcePersistentId(t))
|
||||
.forEach(t->retVal.add(t));
|
||||
List<Long> views = myForcedIdDao.findByForcedId(nextIds);
|
||||
views.forEach(t -> retVal.add(new ResourcePersistentId(t)));
|
||||
|
||||
} else {
|
||||
|
||||
theForcedIdDao
|
||||
.findByTypeAndForcedId(nextResourceType, nextIds)
|
||||
.stream()
|
||||
.map(t->new ResourcePersistentId(t))
|
||||
.forEach(t->retVal.add(t));
|
||||
if (!myDaoConfig.isDeleteEnabled()) {
|
||||
for (Iterator<String> idIterator = nextIds.iterator(); idIterator.hasNext(); ) {
|
||||
String nextId = idIterator.next();
|
||||
String key = nextResourceType + "/" + nextId;
|
||||
Long nextCachedPid = myPersistentIdCache.getIfPresent(key);
|
||||
if (nextCachedPid != null) {
|
||||
idIterator.remove();
|
||||
retVal.add(new ResourcePersistentId(nextCachedPid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nextIds.size() > 0) {
|
||||
Collection<Object[]> views = myForcedIdDao.findByTypeAndForcedId(nextResourceType, nextIds);
|
||||
for (Object[] nextView : views) {
|
||||
String forcedId = (String) nextView[0];
|
||||
Long pid = (Long) nextView[1];
|
||||
retVal.add(new ResourcePersistentId(pid));
|
||||
|
||||
if (!myDaoConfig.isDeleteEnabled()) {
|
||||
String key = nextResourceType + "/" + forcedId;
|
||||
myPersistentIdCache.put(key, pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a persistent ID, returns the associated resource ID
|
||||
*/
|
||||
@Nonnull
|
||||
public IIdType translatePidIdToForcedId(FhirContext theCtx, String theResourceType, ResourcePersistentId theId) {
|
||||
IIdType retVal = theCtx.getVersion().newIdType();
|
||||
retVal.setValue(translatePidIdToForcedId(theResourceType, theId));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public String translatePidIdToForcedId(String theResourceType, ResourcePersistentId theId) {
|
||||
private String translatePidIdToForcedId(String theResourceType, ResourcePersistentId theId) {
|
||||
ForcedId forcedId = myForcedIdDao.findByResourcePid(theId.getIdAsLong());
|
||||
if (forcedId != null) {
|
||||
return forcedId.getResourceType() + '/' + forcedId.getForcedId();
|
||||
|
@ -156,17 +236,139 @@ public class IdHelperService {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean isValidPid(IIdType theId) {
|
||||
if (theId == null || theId.getIdPart() == null) {
|
||||
return false;
|
||||
}
|
||||
String idPart = theId.getIdPart();
|
||||
for (int i = 0; i < idPart.length(); i++) {
|
||||
char nextChar = idPart.charAt(i);
|
||||
if (nextChar < '0' || nextChar > '9') {
|
||||
return false;
|
||||
private ListMultimap<String, String> organizeIdsByResourceType(Collection<IIdType> theIds) {
|
||||
ListMultimap<String, String> typeToIds = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||
for (IIdType nextId : theIds) {
|
||||
if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(nextId)) {
|
||||
if (nextId.hasResourceType()) {
|
||||
typeToIds.put(nextId.getResourceType(), nextId.getIdPart());
|
||||
} else {
|
||||
typeToIds.put("", nextId.getIdPart());
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return typeToIds;
|
||||
}
|
||||
|
||||
private Long resolveResourceIdentity(String theResourceType, String theId) {
|
||||
Long retVal;
|
||||
retVal = myForcedIdDao
|
||||
.findByTypeAndForcedId(theResourceType, theId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException(new IdDt(theResourceType, theId)));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private Collection<IResourceLookup> translateForcedIdToPids(RequestDetails theRequest, Collection<IIdType> theId) {
|
||||
theId.forEach(id -> Validate.isTrue(id.hasIdPart()));
|
||||
|
||||
if (theId.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<IResourceLookup> retVal = new ArrayList<>();
|
||||
|
||||
if (myDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY) {
|
||||
List<Long> pids = theId
|
||||
.stream()
|
||||
.filter(t -> isValidPid(t))
|
||||
.map(t -> t.getIdPartAsLong())
|
||||
.collect(Collectors.toList());
|
||||
if (!pids.isEmpty()) {
|
||||
|
||||
Collection<Object[]> lookups = myResourceTableDao.findLookupFieldsByResourcePid(pids);
|
||||
for (Object[] next : lookups) {
|
||||
String resourceType = (String) next[0];
|
||||
Long resourcePid = (Long) next[1];
|
||||
Date deletedAt = (Date) next[2];
|
||||
retVal.add(new ResourceLookup(resourceType, resourcePid, deletedAt));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ListMultimap<String, String> typeToIds = organizeIdsByResourceType(theId);
|
||||
for (Map.Entry<String, Collection<String>> nextEntry : typeToIds.asMap().entrySet()) {
|
||||
String nextResourceType = nextEntry.getKey();
|
||||
Collection<String> nextIds = nextEntry.getValue();
|
||||
|
||||
if (!myDaoConfig.isDeleteEnabled()) {
|
||||
for (Iterator<String> forcedIdIterator = nextIds.iterator(); forcedIdIterator.hasNext(); ) {
|
||||
String nextForcedId = forcedIdIterator.next();
|
||||
String nextKey = nextResourceType + "/" + nextForcedId;
|
||||
IResourceLookup cachedLookup = myResourceLookupCache.getIfPresent(nextKey);
|
||||
if (cachedLookup != null) {
|
||||
forcedIdIterator.remove();
|
||||
retVal.add(cachedLookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nextIds.size() > 0) {
|
||||
Collection<Object[]> views;
|
||||
if (isBlank(nextResourceType)) {
|
||||
warnAboutUnqualifiedForcedIdResolution(theRequest);
|
||||
views = myForcedIdDao.findAndResolveByForcedIdWithNoType(nextIds);
|
||||
|
||||
} else {
|
||||
|
||||
views = myForcedIdDao.findAndResolveByForcedIdWithNoType(nextResourceType, nextIds);
|
||||
|
||||
}
|
||||
|
||||
for (Object[] next : views) {
|
||||
String resourceType = (String) next[0];
|
||||
Long resourcePid = (Long) next[1];
|
||||
String forcedId = (String) next[2];
|
||||
Date deletedAt = (Date) next[3];
|
||||
ResourceLookup lookup = new ResourceLookup(resourceType, resourcePid, deletedAt);
|
||||
retVal.add(lookup);
|
||||
|
||||
if (!myDaoConfig.isDeleteEnabled()) {
|
||||
String key = resourceType + "/" + forcedId;
|
||||
myResourceLookupCache.put(key, lookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void warnAboutUnqualifiedForcedIdResolution(RequestDetails theRequest) {
|
||||
StorageProcessingMessage msg = new StorageProcessingMessage()
|
||||
.setMessage("This search uses unqualified resource IDs (an ID without a resource type). This is less efficient than using a qualified type.");
|
||||
ourLog.debug(msg.getMessage());
|
||||
HookParams params = new HookParams()
|
||||
.add(RequestDetails.class, theRequest)
|
||||
.addIfMatchesType(ServletRequestDetails.class, theRequest)
|
||||
.add(StorageProcessingMessage.class, msg);
|
||||
JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_WARNING, params);
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
myPersistentIdCache.invalidateAll();
|
||||
myResourceLookupCache.invalidateAll();
|
||||
}
|
||||
|
||||
private <T, V> @NonNull Cache<T, V> newCache() {
|
||||
return Caffeine
|
||||
.newBuilder()
|
||||
.maximumSize(10000)
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static boolean isValidPid(IIdType theId) {
|
||||
if (theId == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String idPart = theId.getIdPart();
|
||||
return isValidPid(idPart);
|
||||
}
|
||||
|
||||
public static boolean isValidPid(String theIdPart) {
|
||||
return StringUtils.isNumeric(theIdPart);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
|||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.MatchResourceUrlService;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao;
|
||||
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
||||
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
|
||||
|
@ -187,7 +188,9 @@ public class SearchParamWithInlineReferencesExtractor {
|
|||
if (linksForCompositePart != null) {
|
||||
for (ResourceLink nextLink : linksForCompositePart) {
|
||||
if (linksForCompositePartWantPaths.contains(nextLink.getSourcePath())) {
|
||||
String value = nextLink.getTargetResource().getIdDt().toUnqualifiedVersionless().getValue();
|
||||
assert isNotBlank(nextLink.getTargetResourceType());
|
||||
assert isNotBlank(nextLink.getTargetResourceId());
|
||||
String value = nextLink.getTargetResourceType() + "/" + nextLink.getTargetResourceId();
|
||||
if (isNotBlank(value)) {
|
||||
value = UrlUtil.escapeUrlParam(value);
|
||||
nextChoicesList.add(key + "=" + value);
|
||||
|
@ -250,13 +253,14 @@ public class SearchParamWithInlineReferencesExtractor {
|
|||
ResourcePersistentId match;
|
||||
if (matches.isEmpty()) {
|
||||
|
||||
Optional<ResourcePersistentId> placeholderOpt = myDaoResourceLinkResolver.createPlaceholderTargetIfConfiguredToDoSo(matchResourceType, nextRef, null);
|
||||
Optional<ResourceTable> placeholderOpt = myDaoResourceLinkResolver.createPlaceholderTargetIfConfiguredToDoSo(matchResourceType, nextRef, null);
|
||||
if (placeholderOpt.isPresent()) {
|
||||
match = placeholderOpt.get();
|
||||
match = new ResourcePersistentId(placeholderOpt.get().getResourceId());
|
||||
} else {
|
||||
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlNoMatches", nextId.getValue());
|
||||
throw new ResourceNotFoundException(msg);
|
||||
}
|
||||
|
||||
} else if (matches.size() > 1) {
|
||||
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlMultipleMatches", nextId.getValue());
|
||||
throw new PreconditionFailedException(msg);
|
||||
|
@ -264,9 +268,9 @@ public class SearchParamWithInlineReferencesExtractor {
|
|||
match = matches.iterator().next();
|
||||
}
|
||||
|
||||
String newId = myIdHelperService.translatePidIdToForcedId(resourceTypeString, match);
|
||||
IIdType newId = myIdHelperService.translatePidIdToForcedId(myContext, resourceTypeString, match);
|
||||
ourLog.debug("Replacing inline match URL[{}] with ID[{}}", nextId.getValue(), newId);
|
||||
nextRef.setReference(newId);
|
||||
nextRef.setReference(newId.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -149,7 +149,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
|||
List<Predicate> codePredicates = new ArrayList<>();
|
||||
|
||||
// Resources by ID
|
||||
List<ResourcePersistentId> targetPids = myIdHelperService.translateForcedIdToPids(targetIds, theRequest);
|
||||
List<ResourcePersistentId> targetPids = myIdHelperService.resolveResourcePersistentIds(targetIds, theRequest);
|
||||
if (!targetPids.isEmpty()) {
|
||||
ourLog.debug("Searching for resource link with target PIDs: {}", targetPids);
|
||||
Predicate pathPredicate;
|
||||
|
|
|
@ -60,7 +60,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
|
|||
@Nullable
|
||||
Predicate addPredicateResourceId(List<List<IQueryParameterType>> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) {
|
||||
|
||||
Predicate nextPredicate = createPredicate(myQueryRoot.getRoot(), theResourceName, theValues, theOperation, theRequest);
|
||||
Predicate nextPredicate = createPredicate(myQueryRoot.getRoot(), theResourceName, theValues, theOperation);
|
||||
|
||||
if (nextPredicate != null) {
|
||||
myQueryRoot.addPredicate(nextPredicate);
|
||||
|
@ -71,7 +71,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
|
|||
}
|
||||
|
||||
@Nullable
|
||||
private Predicate createPredicate(Root<ResourceTable> theRoot, String theResourceName, List<List<IQueryParameterType>> theValues, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) {
|
||||
private Predicate createPredicate(Root<ResourceTable> theRoot, String theResourceName, List<List<IQueryParameterType>> theValues, SearchFilterParser.CompareOperation theOperation) {
|
||||
Predicate nextPredicate = null;
|
||||
|
||||
Set<ResourcePersistentId> allOrPids = null;
|
||||
|
@ -89,7 +89,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
|
|||
if (isNotBlank(value)) {
|
||||
haveValue = true;
|
||||
try {
|
||||
ResourcePersistentId pid = myIdHelperService.translateForcedIdToPid(theResourceName, valueAsId.getIdPart(), theRequest);
|
||||
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(theResourceName, valueAsId.getIdPart());
|
||||
orPids.add(pid);
|
||||
} catch (ResourceNotFoundException e) {
|
||||
// This is not an error in a search, it just results in no matchesFhirResourceDaoR4InterceptorTest
|
||||
|
|
|
@ -48,7 +48,7 @@ public class FhirResourceDaoSubscriptionR4 extends BaseHapiFhirResourceDao<Subsc
|
|||
|
||||
@Override
|
||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
||||
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||
ResourceTable entity = readEntityLatestVersion(theId);
|
||||
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
||||
if (table == null) {
|
||||
return null;
|
||||
|
|
|
@ -48,7 +48,7 @@ public class FhirResourceDaoSubscriptionR5 extends BaseHapiFhirResourceDao<Subsc
|
|||
|
||||
@Override
|
||||
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
|
||||
ResourceTable entity = readEntityLatestVersion(theId, theRequest);
|
||||
ResourceTable entity = readEntityLatestVersion(theId);
|
||||
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
|
||||
if (table == null) {
|
||||
return null;
|
||||
|
|
|
@ -117,11 +117,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
|
|||
|
||||
@Override
|
||||
public ResourcePersistentId getValueSetResourcePid(IIdType theIdType) {
|
||||
return getValueSetResourcePid(theIdType, null);
|
||||
}
|
||||
|
||||
private ResourcePersistentId getValueSetResourcePid(IIdType theIdType, RequestDetails theRequestDetails) {
|
||||
return myIdHelperService.translateForcedIdToPid(theIdType, theRequestDetails);
|
||||
return myIdHelperService.resolveResourcePersistentIds(theIdType.getResourceType(), theIdType.getIdPart());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
|
@ -295,7 +291,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
|
|||
Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL");
|
||||
|
||||
IIdType csId = myTerminologyVersionAdapterSvc.createOrUpdateCodeSystem(theCodeSystemResource);
|
||||
ResourcePersistentId codeSystemResourcePid = myIdHelperService.translateForcedIdToPid(csId, theRequest);
|
||||
ResourcePersistentId codeSystemResourcePid = myIdHelperService.resolveResourcePersistentIds(csId.getResourceType(), csId.getIdPart());
|
||||
ResourceTable resource = myResourceTableDao.getOne(codeSystemResourcePid.getIdAsLong());
|
||||
|
||||
ourLog.info("CodeSystem resource has ID: {}", csId.getValue());
|
||||
|
@ -555,11 +551,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
|
|||
}
|
||||
|
||||
private ResourcePersistentId getCodeSystemResourcePid(IIdType theIdType) {
|
||||
return getCodeSystemResourcePid(theIdType, null);
|
||||
}
|
||||
|
||||
private ResourcePersistentId getCodeSystemResourcePid(IIdType theIdType, RequestDetails theRequestDetails) {
|
||||
return myIdHelperService.translateForcedIdToPid(theIdType, theRequestDetails);
|
||||
return myIdHelperService.resolveResourcePersistentIds(theIdType.getResourceType(), theIdType.getIdPart());
|
||||
}
|
||||
|
||||
private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap<TermConcept, Object> theConceptsStack, int theTotalConcepts) {
|
||||
|
|
|
@ -28,6 +28,7 @@ import com.google.common.reflect.ClassPath;
|
|||
import com.google.common.reflect.ClassPath.ClassInfo;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hibernate.annotations.Subselect;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.InstantType;
|
||||
|
@ -39,7 +40,11 @@ import java.io.InputStream;
|
|||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.google.common.base.Ascii.toUpperCase;
|
||||
|
@ -96,7 +101,10 @@ public class TestUtil {
|
|||
private static void scanClass(Set<String> theNames, Class<?> theClazz, boolean theIsSuperClass) {
|
||||
ourLog.info("Scanning: {}", theClazz.getSimpleName());
|
||||
|
||||
scan(theClazz, theNames, theIsSuperClass);
|
||||
Subselect subselect = theClazz.getAnnotation(Subselect.class);
|
||||
boolean isView = (subselect != null);
|
||||
|
||||
scan(theClazz, theNames, theIsSuperClass, isView);
|
||||
|
||||
for (Field nextField : theClazz.getDeclaredFields()) {
|
||||
if (Modifier.isStatic(nextField.getModifiers())) {
|
||||
|
@ -104,7 +112,7 @@ public class TestUtil {
|
|||
}
|
||||
|
||||
ourLog.info(" * Scanning field: {}", nextField.getName());
|
||||
scan(nextField, theNames, theIsSuperClass);
|
||||
scan(nextField, theNames, theIsSuperClass, isView);
|
||||
|
||||
Lob lobClass = nextField.getAnnotation(Lob.class);
|
||||
if (lobClass != null) {
|
||||
|
@ -140,7 +148,7 @@ public class TestUtil {
|
|||
scanClass(theNames, theClazz.getSuperclass(), true);
|
||||
}
|
||||
|
||||
private static void scan(AnnotatedElement theAnnotatedElement, Set<String> theNames, boolean theIsSuperClass) {
|
||||
private static void scan(AnnotatedElement theAnnotatedElement, Set<String> theNames, boolean theIsSuperClass, boolean theIsView) {
|
||||
Table table = theAnnotatedElement.getAnnotation(Table.class);
|
||||
if (table != null) {
|
||||
|
||||
|
@ -198,7 +206,7 @@ public class TestUtil {
|
|||
*/
|
||||
if (field.getType().equals(String.class)) {
|
||||
if (!hasLob) {
|
||||
if (column.length() == 255) {
|
||||
if (!theIsView && column.length() == 255) {
|
||||
throw new IllegalStateException("Field does not have an explicit maximum length specified: " + field);
|
||||
}
|
||||
if (column.length() > MAX_COL_LENGTH) {
|
||||
|
|
|
@ -426,7 +426,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
|
|||
myPatientDao.create(p, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertEquals("Resource contains reference to Organization/" + id1.getIdPart() + " but resource with ID " + id1.getIdPart() + " is actually of type Observation", e.getMessage());
|
||||
assertEquals("Resource contains reference to unknown resource ID Organization/" + id1.getIdPart(), e.getMessage());
|
||||
}
|
||||
|
||||
// Now with a forced ID
|
||||
|
|
|
@ -34,6 +34,7 @@ import org.junit.AfterClass;
|
|||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
@ -488,7 +489,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
|
|||
assertEquals(2, results.size());
|
||||
|
||||
List<IIdType> actual = toUnqualifiedVersionlessIds(
|
||||
mySubstanceDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Substance.SP_QUANTITY, new QuantityParam((ParamPrefixEnum) null, 123, "http://foo", "UNIT"))));
|
||||
mySubstanceDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Substance.SP_QUANTITY, new QuantityParam(null, 123, "http://foo", "UNIT"))));
|
||||
assertThat(actual, contains(id));
|
||||
}
|
||||
|
||||
|
@ -1585,7 +1586,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
|
|||
dr01.setSubject(new Reference(patientId01));
|
||||
IIdType drId01 = myDiagnosticReportDao.create(dr01, mySrd).getId();
|
||||
|
||||
ourLog.info("P1[{}] P2[{}] O1[{}] O2[{}] D1[{}]", new Object[]{patientId01, patientId02, obsId01, obsId02, drId01});
|
||||
ourLog.info("P1[{}] P2[{}] O1[{}] O2[{}] D1[{}]", patientId01, patientId02, obsId01, obsId02, drId01);
|
||||
|
||||
List<Observation> result = toList(myObservationDao
|
||||
.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_IDENTIFIER, "urn:system|testSearchResourceLinkWithChain01"))));
|
||||
|
@ -1690,7 +1691,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
|
|||
Date after = new Date();
|
||||
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick();
|
||||
|
||||
ourLog.info("P1[{}] L1[{}] Obs1[{}] Obs2[{}]", new Object[]{patientId01, locId01, obsId01, obsId02});
|
||||
ourLog.info("P1[{}] L1[{}] Obs1[{}] Obs2[{}]", patientId01, locId01, obsId01, obsId02);
|
||||
|
||||
List<IIdType> result;
|
||||
SearchParameterMap params;
|
||||
|
@ -1753,7 +1754,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
|
|||
dr01.setSubject(new Reference(patientId01));
|
||||
IIdType drId01 = myDiagnosticReportDao.create(dr01, mySrd).getId();
|
||||
|
||||
ourLog.info("P1[{}] P2[{}] O1[{}] O2[{}] D1[{}]", new Object[]{patientId01, patientId02, obsId01, obsId02, drId01});
|
||||
ourLog.info("P1[{}] P2[{}] O1[{}] O2[{}] D1[{}]", patientId01, patientId02, obsId01, obsId02, drId01);
|
||||
|
||||
List<Observation> result = toList(
|
||||
myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam("testSearchResourceLinkWithTextLogicalId01"))));
|
||||
|
@ -2844,7 +2845,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
|
|||
ourLog.info("Initial size: " + value.size());
|
||||
for (IBaseResource next : value.getResources(0, value.size())) {
|
||||
ourLog.info("Deleting: {}", next.getIdElement());
|
||||
myDeviceDao.delete((IIdType) next.getIdElement(), mySrd);
|
||||
myDeviceDao.delete(next.getIdElement(), mySrd);
|
||||
}
|
||||
|
||||
value = myDeviceDao.search(new SearchParameterMap());
|
||||
|
@ -2863,7 +2864,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
|
|||
public void testSearchWithRevIncludes() {
|
||||
final String methodName = "testSearchWithRevIncludes";
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionMgr);
|
||||
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
|
||||
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
IIdType pid = txTemplate.execute(new TransactionCallback<IIdType>() {
|
||||
|
||||
@Override
|
||||
|
@ -2881,7 +2882,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
|
|||
});
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.add(Patient.SP_RES_ID, new StringParam(pid.getIdPart()));
|
||||
map.add(IAnyResource.SP_RES_ID, new StringParam(pid.getIdPart()));
|
||||
map.addRevInclude(Condition.INCLUDE_PATIENT);
|
||||
IBundleProvider results = myPatientDao.search(map);
|
||||
List<IBaseResource> foundResources = results.getResources(0, results.size());
|
||||
|
|
|
@ -700,7 +700,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
|
|||
myPatientDao.create(p, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertEquals("Resource contains reference to Organization/" + id1.getIdPart() + " but resource with ID " + id1.getIdPart() + " is actually of type Observation", e.getMessage());
|
||||
assertEquals("Resource contains reference to unknown resource ID Organization/" + id1.getIdPart(), e.getMessage());
|
||||
}
|
||||
|
||||
// Now with a forced ID
|
||||
|
|
|
@ -9,6 +9,7 @@ import ca.uhn.fhir.jpa.config.TestR4Config;
|
|||
import ca.uhn.fhir.jpa.dao.*;
|
||||
import ca.uhn.fhir.jpa.dao.data.*;
|
||||
import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest;
|
||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||
import ca.uhn.fhir.jpa.entity.TermCodeSystem;
|
||||
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
|
||||
import ca.uhn.fhir.jpa.entity.TermConcept;
|
||||
|
@ -352,6 +353,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
|
|||
private DaoRegistry myDaoRegistry;
|
||||
@Autowired
|
||||
private IBulkDataExportSvc myBulkDataExportSvc;
|
||||
@Autowired
|
||||
private IdHelperService myIdHelperService;
|
||||
|
||||
@After()
|
||||
public void afterCleanupDao() {
|
||||
|
@ -380,6 +383,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
|
|||
BaseTermReadSvcImpl.clearOurLastResultsFromTranslationWithReverseCache();
|
||||
TermDeferredStorageSvcImpl termDeferredStorageSvc = AopTestUtils.getTargetObject(myTerminologyDeferredStorageSvc);
|
||||
termDeferredStorageSvc.clearDeferred();
|
||||
|
||||
myIdHelperService.clearCache();
|
||||
}
|
||||
|
||||
@After()
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
package ca.uhn.fhir.jpa.dao.r4;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.Organization;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -14,11 +20,20 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4DeleteTest.class);
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
myDaoConfig.setDeleteEnabled(new DaoConfig().isDeleteEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteMarksResourceAndVersionAsDeleted() {
|
||||
|
||||
|
@ -29,19 +44,19 @@ public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
|
|||
myPatientDao.delete(id);
|
||||
|
||||
// Table should be marked as deleted
|
||||
runInTransaction(()->{
|
||||
runInTransaction(() -> {
|
||||
ResourceTable resourceTable = myResourceTableDao.findById(id.getIdPartAsLong()).get();
|
||||
assertNotNull(resourceTable.getDeleted());
|
||||
assertTrue(resourceTable.isDeleted());
|
||||
});
|
||||
|
||||
// Current version should be marked as deleted
|
||||
runInTransaction(()->{
|
||||
runInTransaction(() -> {
|
||||
ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 1);
|
||||
assertNull(resourceTable.getDeleted());
|
||||
assertNotNull(resourceTable.getPersistentId());
|
||||
});
|
||||
runInTransaction(()->{
|
||||
runInTransaction(() -> {
|
||||
ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 2);
|
||||
assertNotNull(resourceTable.getDeleted());
|
||||
});
|
||||
|
@ -66,6 +81,23 @@ public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
|
|||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDeleteDisabled() {
|
||||
myDaoConfig.setDeleteEnabled(false);
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setActive(true);
|
||||
IIdType pId = myPatientDao.create(p).getId().toUnqualifiedVersionless();
|
||||
|
||||
try {
|
||||
myPatientDao.delete(pId);
|
||||
fail();
|
||||
} catch (PreconditionFailedException e) {
|
||||
assertEquals("Resource deletion is not permitted on this server", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDeleteCircularReferenceInTransaction() throws IOException {
|
||||
|
||||
|
@ -157,14 +189,14 @@ public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
|
|||
myPatientDao.delete(id);
|
||||
|
||||
// Table should be marked as deleted
|
||||
runInTransaction(()->{
|
||||
runInTransaction(() -> {
|
||||
ResourceTable resourceTable = myResourceTableDao.findById(id.getIdPartAsLong()).get();
|
||||
assertNotNull(resourceTable.getDeleted());
|
||||
});
|
||||
|
||||
// Mark the current history version as not-deleted even though the actual resource
|
||||
// table entry is marked deleted
|
||||
runInTransaction(()->{
|
||||
runInTransaction(() -> {
|
||||
ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 2);
|
||||
resourceTable.setDeleted(null);
|
||||
myResourceHistoryTableDao.save(resourceTable);
|
||||
|
|
|
@ -1,29 +1,20 @@
|
|||
package ca.uhn.fhir.jpa.dao.r4;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.util.TestUtil;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.junit.*;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4QueryCountTest.class);
|
||||
|
@ -32,6 +23,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
public void afterResetDao() {
|
||||
myDaoConfig.setResourceMetaCountHardLimit(new DaoConfig().getResourceMetaCountHardLimit());
|
||||
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
|
||||
myDaoConfig.setDeleteEnabled(new DaoConfig().isDeleteEnabled());
|
||||
}
|
||||
|
||||
@Before
|
||||
|
@ -53,7 +45,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
Patient p = new Patient();
|
||||
p.setId(id.getIdPart());
|
||||
p.addIdentifier().setSystem("urn:system").setValue("2");
|
||||
myPatientDao.update(p).getResource();
|
||||
myPatientDao.update(p);
|
||||
});
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
assertEquals(5, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
|
||||
|
@ -164,6 +156,177 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
|||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testReferenceToForcedId() {
|
||||
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("P");
|
||||
patient.setActive(true);
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
myPatientDao.update(patient);
|
||||
|
||||
/*
|
||||
* Add a resource with a forced ID target link
|
||||
*/
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
Observation observation = new Observation();
|
||||
observation.getSubject().setReference("Patient/P");
|
||||
myObservationDao.create(observation);
|
||||
myCaptureQueriesListener.logAllQueriesForCurrentThread();
|
||||
// select: lookup forced ID
|
||||
assertEquals(1, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
// insert to: HFJ_RESOURCE, HFJ_RES_VER, HFJ_RES_LINK
|
||||
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
|
||||
/*
|
||||
* Add another
|
||||
*/
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
observation = new Observation();
|
||||
observation.getSubject().setReference("Patient/P");
|
||||
myObservationDao.create(observation);
|
||||
// select: lookup forced ID
|
||||
assertEquals(1, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
// insert to: HFJ_RESOURCE, HFJ_RES_VER, HFJ_RES_LINK
|
||||
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testReferenceToForcedId_DeletesDisabled() {
|
||||
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
|
||||
myDaoConfig.setDeleteEnabled(false);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("P");
|
||||
patient.setActive(true);
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
myPatientDao.update(patient);
|
||||
|
||||
/*
|
||||
* Add a resource with a forced ID target link
|
||||
*/
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
Observation observation = new Observation();
|
||||
observation.getSubject().setReference("Patient/P");
|
||||
myObservationDao.create(observation);
|
||||
myCaptureQueriesListener.logAllQueriesForCurrentThread();
|
||||
// select: lookup forced ID
|
||||
assertEquals(1, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
// insert to: HFJ_RESOURCE, HFJ_RES_VER, HFJ_RES_LINK
|
||||
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
|
||||
/*
|
||||
* Add another
|
||||
*/
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
observation = new Observation();
|
||||
observation.getSubject().setReference("Patient/P");
|
||||
myObservationDao.create(observation);
|
||||
// select: no lookups needed because of cache
|
||||
assertEquals(0, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
// insert to: HFJ_RESOURCE, HFJ_RES_VER, HFJ_RES_LINK
|
||||
assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchUsingForcedIdReference() {
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("P");
|
||||
patient.setActive(true);
|
||||
myPatientDao.update(patient);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getSubject().setReference("Patient/P");
|
||||
myObservationDao.update(obs);
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.setLoadSynchronous(true);
|
||||
map.add("subject", new ReferenceParam("Patient/P"));
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
assertEquals(1, myObservationDao.search(map).size().intValue());
|
||||
// myCaptureQueriesListener.logAllQueriesForCurrentThread();
|
||||
// Resolve forced ID, Perform search, load result
|
||||
assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
/*
|
||||
* Again
|
||||
*/
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
assertEquals(1, myObservationDao.search(map).size().intValue());
|
||||
myCaptureQueriesListener.logAllQueriesForCurrentThread();
|
||||
// Resolve forced ID, Perform search, load result
|
||||
assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchUsingForcedIdReference_DeletedDisabled() {
|
||||
myDaoConfig.setDeleteEnabled(false);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("P");
|
||||
patient.setActive(true);
|
||||
myPatientDao.update(patient);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getSubject().setReference("Patient/P");
|
||||
myObservationDao.update(obs);
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.setLoadSynchronous(true);
|
||||
map.add("subject", new ReferenceParam("Patient/P"));
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
assertEquals(1, myObservationDao.search(map).size().intValue());
|
||||
myCaptureQueriesListener.logAllQueriesForCurrentThread();
|
||||
// Resolve forced ID, Perform search, load result
|
||||
assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
/*
|
||||
* Again
|
||||
*/
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
assertEquals(1, myObservationDao.search(map).size().intValue());
|
||||
myCaptureQueriesListener.logAllQueriesForCurrentThread();
|
||||
// (NO resolve forced ID), Perform search, load result
|
||||
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
}
|
||||
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() {
|
||||
|
|
|
@ -910,7 +910,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
|
|||
myPatientDao.create(p, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertEquals("Resource contains reference to Organization/" + id1.getIdPart() + " but resource with ID " + id1.getIdPart() + " is actually of type Observation", e.getMessage());
|
||||
assertEquals("Resource contains reference to unknown resource ID Organization/" + id1.getIdPart(), e.getMessage());
|
||||
}
|
||||
|
||||
// Now with a forced ID
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package ca.uhn.fhir.jpa.model.cross;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public interface IResourceLookup {
|
||||
String getResourceType();
|
||||
|
||||
Long getResourceId();
|
||||
|
||||
Date getDeleted();
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package ca.uhn.fhir.jpa.model.cross;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class ResourceLookup implements IResourceLookup {
|
||||
private final String myResourceType;
|
||||
private final Long myResourcePid;
|
||||
private final Date myDeletedAt;
|
||||
|
||||
public ResourceLookup(String theResourceType, Long theResourcePid, Date theDeletedAt) {
|
||||
myResourceType = theResourceType;
|
||||
myResourcePid = theResourcePid;
|
||||
myDeletedAt = theDeletedAt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResourceType() {
|
||||
return myResourceType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getResourceId() {
|
||||
return myResourcePid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getDeleted() {
|
||||
return myDeletedAt;
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ import ca.uhn.fhir.util.ObjectUtil;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* This class is an abstraction for however primary keys are stored in the underlying storage engine. This might be
|
||||
|
@ -35,6 +36,7 @@ public class ResourcePersistentId {
|
|||
private Object myId;
|
||||
|
||||
public ResourcePersistentId(Object theId) {
|
||||
assert !(theId instanceof Optional);
|
||||
myId = theId;
|
||||
}
|
||||
|
||||
|
|
|
@ -60,30 +60,40 @@ public class ResourceLink extends BaseResourceIndex {
|
|||
private String mySourceResourceType;
|
||||
|
||||
@ManyToOne(optional = true, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "TARGET_RESOURCE_ID", referencedColumnName = "RES_ID", nullable = true, foreignKey = @ForeignKey(name = "FK_RESLINK_TARGET"))
|
||||
@JoinColumn(name = "TARGET_RESOURCE_ID", referencedColumnName = "RES_ID", nullable = true, insertable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_RESLINK_TARGET"))
|
||||
private ResourceTable myTargetResource;
|
||||
|
||||
@Column(name = "TARGET_RESOURCE_ID", insertable = false, updatable = false, nullable = true)
|
||||
@Column(name = "TARGET_RESOURCE_ID", insertable = true, updatable = true, nullable = true)
|
||||
@Field()
|
||||
private Long myTargetResourcePid;
|
||||
|
||||
@Column(name = "TARGET_RESOURCE_TYPE", nullable = false, length = ResourceTable.RESTYPE_LEN)
|
||||
@Field()
|
||||
private String myTargetResourceType;
|
||||
|
||||
@Column(name = "TARGET_RESOURCE_URL", length = 200, nullable = true)
|
||||
@Field()
|
||||
private String myTargetResourceUrl;
|
||||
|
||||
@Field()
|
||||
@Column(name = "SP_UPDATED", nullable = true) // TODO: make this false after HAPI 2.3
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date myUpdated;
|
||||
@Transient
|
||||
private transient String myTargetResourceId;
|
||||
|
||||
public ResourceLink() {
|
||||
super();
|
||||
}
|
||||
|
||||
public String getTargetResourceId() {
|
||||
if (myTargetResourceId == null && myTargetResource != null) {
|
||||
myTargetResourceId = myTargetResource.getIdDt().getIdPart();
|
||||
}
|
||||
return myTargetResourceId;
|
||||
}
|
||||
|
||||
public String getTargetResourceType() {
|
||||
return myTargetResourceType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object theObj) {
|
||||
if (this == theObj) {
|
||||
|
@ -99,8 +109,9 @@ public class ResourceLink extends BaseResourceIndex {
|
|||
EqualsBuilder b = new EqualsBuilder();
|
||||
b.append(mySourcePath, obj.mySourcePath);
|
||||
b.append(mySourceResource, obj.mySourceResource);
|
||||
b.append(myTargetResourcePid, obj.myTargetResourcePid);
|
||||
b.append(myTargetResourceUrl, obj.myTargetResourceUrl);
|
||||
b.append(myTargetResourceType, obj.myTargetResourceType);
|
||||
b.append(getTargetResourceId(), obj.getTargetResourceId());
|
||||
return b.isEquals();
|
||||
}
|
||||
|
||||
|
@ -122,15 +133,12 @@ public class ResourceLink extends BaseResourceIndex {
|
|||
mySourceResourceType = theSourceResource.getResourceType();
|
||||
}
|
||||
|
||||
public ResourceTable getTargetResource() {
|
||||
return myTargetResource;
|
||||
}
|
||||
public void setTargetResource(String theResourceType, Long theResourcePid, String theTargetResourceId) {
|
||||
Validate.notBlank(theResourceType);
|
||||
|
||||
public void setTargetResource(ResourceTable theTargetResource) {
|
||||
Validate.notNull(theTargetResource);
|
||||
myTargetResource = theTargetResource;
|
||||
myTargetResourcePid = theTargetResource.getId();
|
||||
myTargetResourceType = theTargetResource.getResourceType();
|
||||
myTargetResourceType = theResourceType;
|
||||
myTargetResourcePid = theResourcePid;
|
||||
myTargetResourceId = theTargetResourceId;
|
||||
}
|
||||
|
||||
public Long getTargetResourcePid() {
|
||||
|
@ -189,8 +197,9 @@ public class ResourceLink extends BaseResourceIndex {
|
|||
HashCodeBuilder b = new HashCodeBuilder();
|
||||
b.append(mySourcePath);
|
||||
b.append(mySourceResource);
|
||||
b.append(myTargetResourcePid);
|
||||
b.append(myTargetResourceUrl);
|
||||
b.append(getTargetResourceType());
|
||||
b.append(getTargetResourceId());
|
||||
return b.toHashCode();
|
||||
}
|
||||
|
||||
|
@ -228,11 +237,11 @@ public class ResourceLink extends BaseResourceIndex {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
public static ResourceLink forLocalReference(String theSourcePath, ResourceTable theSourceResource, ResourceTable theTargetResource, Date theUpdated) {
|
||||
public static ResourceLink forLocalReference(String theSourcePath, ResourceTable theSourceResource, String theTargetResourceType, Long theTargetResourcePid, String theTargetResourceId, Date theUpdated) {
|
||||
ResourceLink retVal = new ResourceLink();
|
||||
retVal.setSourcePath(theSourcePath);
|
||||
retVal.setSourceResource(theSourceResource);
|
||||
retVal.setTargetResource(theTargetResource);
|
||||
retVal.setTargetResource(theTargetResourceType, theTargetResourcePid, theTargetResourceId);
|
||||
retVal.setUpdated(theUpdated);
|
||||
return retVal;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
|
||||
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
||||
import ca.uhn.fhir.jpa.model.search.IndexNonDeletedInterceptor;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
|
@ -29,12 +30,20 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
|||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import org.hibernate.annotations.OptimisticLock;
|
||||
import org.hibernate.search.annotations.*;
|
||||
import org.hibernate.search.annotations.Analyze;
|
||||
import org.hibernate.search.annotations.Analyzer;
|
||||
import org.hibernate.search.annotations.Field;
|
||||
import org.hibernate.search.annotations.Fields;
|
||||
import org.hibernate.search.annotations.Indexed;
|
||||
import org.hibernate.search.annotations.Store;
|
||||
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.*;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
|
@ -48,7 +57,7 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
|
|||
@Index(name = "IDX_RES_TYPE", columnList = "RES_TYPE"),
|
||||
@Index(name = "IDX_INDEXSTATUS", columnList = "SP_INDEX_STATUS")
|
||||
})
|
||||
public class ResourceTable extends BaseHasResource implements Serializable, IBasePersistedResource {
|
||||
public class ResourceTable extends BaseHasResource implements Serializable, IBasePersistedResource, IResourceLookup {
|
||||
public static final int RESTYPE_LEN = 40;
|
||||
private static final int MAX_LANGUAGE_LENGTH = 20;
|
||||
private static final int MAX_PROFILE_LENGTH = 200;
|
||||
|
@ -626,7 +635,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
@Override
|
||||
public IdDt getIdDt() {
|
||||
if (getForcedId() == null) {
|
||||
Long id = getResourceId();
|
||||
Long id = this.getResourceId();
|
||||
return new IdDt(getResourceType() + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
|
||||
} else {
|
||||
// Avoid a join query if possible
|
||||
|
|
|
@ -21,14 +21,31 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
public interface IResourceLinkResolver {
|
||||
ResourceTable findTargetResource(RuntimeSearchParam theNextSpDef, String theNextPathsUnsplit, IIdType theNextId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest);
|
||||
|
||||
/**
|
||||
* This method resolves the target of a reference found within a resource that is being created/updated. We do this
|
||||
* so that we can create indexed links between resources, and so that we can validate that the target actually
|
||||
* exists in cases where we need to check that.
|
||||
* <p>
|
||||
* This method returns an {@link IResourceLookup} so as to avoid needing to resolve the entire 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
|
||||
*/
|
||||
IResourceLookup findTargetResource(RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest);
|
||||
|
||||
void validateTypeOrThrowException(Class<? extends IBaseResource> theType);
|
||||
|
||||
}
|
||||
|
|
|
@ -23,16 +23,17 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
|
|||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.model.entity.*;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.compare;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public final class ResourceIndexedSearchParams {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceIndexedSearchParams.class);
|
||||
|
@ -217,7 +218,7 @@ public final class ResourceIndexedSearchParams {
|
|||
return myPopulatedResourceLinkParameters;
|
||||
}
|
||||
|
||||
public boolean matchParam(String theResourceName, String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam) {
|
||||
public boolean matchParam(ModelConfig theModelConfig, String theResourceName, String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam) {
|
||||
if (theParamDef == null) {
|
||||
return false;
|
||||
}
|
||||
|
@ -242,7 +243,7 @@ public final class ResourceIndexedSearchParams {
|
|||
resourceParams = myDateParams;
|
||||
break;
|
||||
case REFERENCE:
|
||||
return matchResourceLinks(theResourceName, theParamName, theParam, theParamDef.getPath());
|
||||
return matchResourceLinks(theModelConfig, theResourceName, theParamName, theParam, theParamDef.getPath());
|
||||
case COMPOSITE:
|
||||
case HAS:
|
||||
case SPECIAL:
|
||||
|
@ -259,39 +260,56 @@ public final class ResourceIndexedSearchParams {
|
|||
return resourceParams.stream().anyMatch(namedParamPredicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Replace with the method below
|
||||
*/
|
||||
// KHS This needs to be public as libraries outside of hapi call it directly
|
||||
@Deprecated
|
||||
public boolean matchResourceLinks(String theResourceName, String theParamName, IQueryParameterType theParam, String theParamPath) {
|
||||
return matchResourceLinks(new ModelConfig(), theResourceName, theParamName, theParam, theParamPath);
|
||||
}
|
||||
|
||||
// KHS This needs to be public as libraries outside of hapi call it directly
|
||||
public boolean matchResourceLinks(ModelConfig theModelConfig, String theResourceName, String theParamName, IQueryParameterType theParam, String theParamPath) {
|
||||
ReferenceParam reference = (ReferenceParam)theParam;
|
||||
|
||||
Predicate<ResourceLink> namedParamPredicate = resourceLink ->
|
||||
resourceLinkMatches(theResourceName, resourceLink, theParamName, theParamPath)
|
||||
&& resourceIdMatches(resourceLink, reference);
|
||||
searchParameterPathMatches(theResourceName, resourceLink, theParamName, theParamPath)
|
||||
&& resourceIdMatches(theModelConfig, resourceLink, reference);
|
||||
|
||||
return myLinks.stream().anyMatch(namedParamPredicate);
|
||||
}
|
||||
|
||||
private boolean resourceIdMatches(ResourceLink theResourceLink, ReferenceParam theReference) {
|
||||
ResourceTable target = theResourceLink.getTargetResource();
|
||||
IdDt idDt = target.getIdDt();
|
||||
if (idDt.isIdPartValidLong()) {
|
||||
if (theReference.isIdPartValidLong()) {
|
||||
return theReference.getIdPartAsLong().equals(idDt.getIdPartAsLong());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
ForcedId forcedId = target.getForcedId();
|
||||
if (forcedId != null) {
|
||||
return forcedId.getForcedId().equals(theReference.getValue());
|
||||
} else {
|
||||
private boolean resourceIdMatches(ModelConfig theModelConfig, ResourceLink theResourceLink, ReferenceParam theReference) {
|
||||
String baseUrl = theReference.getBaseUrl();
|
||||
if (isNotBlank(baseUrl)) {
|
||||
if (!theModelConfig.getTreatBaseUrlsAsLocal().contains(baseUrl)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
String targetType = theResourceLink.getTargetResourceType();
|
||||
String targetId = theResourceLink.getTargetResourceId();
|
||||
|
||||
assert isNotBlank(targetType);
|
||||
assert isNotBlank(targetId);
|
||||
|
||||
if (theReference.hasResourceType()) {
|
||||
if (!theReference.getResourceType().equals(targetType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetId.equals(theReference.getIdPart())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean resourceLinkMatches(String theResourceName, ResourceLink theResourceLink, String theParamName, String theParamPath) {
|
||||
return theResourceLink.getTargetResource().getResourceType().equalsIgnoreCase(theParamName) ||
|
||||
theResourceLink.getSourcePath().equalsIgnoreCase(theParamPath);
|
||||
private boolean searchParameterPathMatches(String theResourceName, ResourceLink theResourceLink, String theParamName, String theParamPath) {
|
||||
String sourcePath = theResourceLink.getSourcePath();
|
||||
return sourcePath.equalsIgnoreCase(theParamPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
|
@ -39,6 +40,8 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
@ -63,15 +66,17 @@ public class ResourceLinkExtractor {
|
|||
|
||||
ISearchParamExtractor.SearchParamSet<PathAndRef> refs = mySearchParamExtractor.extractResourceLinks(theResource);
|
||||
SearchParamExtractorService.handleWarnings(theRequest, myInterceptorBroadcaster, refs);
|
||||
|
||||
Map<String, IResourceLookup> resourceIdToResolvedTarget = new HashMap<>();
|
||||
for (PathAndRef nextPathAndRef : refs) {
|
||||
RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(resourceName, nextPathAndRef.getSearchParamName());
|
||||
extractResourceLinks(theParams, theEntity, theUpdateTime, theResourceLinkResolver, searchParam, nextPathAndRef, theFailOnInvalidReference, theRequest);
|
||||
extractResourceLinks(theParams, theEntity, theUpdateTime, theResourceLinkResolver, searchParam, nextPathAndRef, theFailOnInvalidReference, theRequest, resourceIdToResolvedTarget);
|
||||
}
|
||||
|
||||
theEntity.setHasLinks(theParams.myLinks.size() > 0);
|
||||
}
|
||||
|
||||
private void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver, RuntimeSearchParam theRuntimeSearchParam, PathAndRef thePathAndRef, boolean theFailOnInvalidReference, RequestDetails theRequest) {
|
||||
private void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver, RuntimeSearchParam theRuntimeSearchParam, PathAndRef thePathAndRef, boolean theFailOnInvalidReference, RequestDetails theRequest, Map<String, IResourceLookup> theResourceIdToResolvedTarget) {
|
||||
IBaseReference nextReference = thePathAndRef.getRef();
|
||||
IIdType nextId = nextReference.getReferenceElement();
|
||||
String path = thePathAndRef.getPath();
|
||||
|
@ -153,21 +158,36 @@ public class ResourceLinkExtractor {
|
|||
}
|
||||
|
||||
theResourceLinkResolver.validateTypeOrThrowException(type);
|
||||
ResourceLink resourceLink = createResourceLink(theEntity, theUpdateTime, theResourceLinkResolver, theRuntimeSearchParam, path, thePathAndRef, nextId, typeString, type, nextReference, theRequest);
|
||||
ResourceLink resourceLink = createResourceLink(theEntity, theUpdateTime, theResourceLinkResolver, theRuntimeSearchParam, path, thePathAndRef, nextId, typeString, type, nextReference, theRequest, theResourceIdToResolvedTarget);
|
||||
if (resourceLink == null) {
|
||||
return;
|
||||
}
|
||||
theParams.myLinks.add(resourceLink);
|
||||
}
|
||||
|
||||
private ResourceLink createResourceLink(ResourceTable theEntity, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver, RuntimeSearchParam nextSpDef, String theNextPathsUnsplit, PathAndRef nextPathAndRef, IIdType theNextId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest) {
|
||||
ResourceTable targetResource = theResourceLinkResolver.findTargetResource(nextSpDef, theNextPathsUnsplit, theNextId, theTypeString, theType, theReference, theRequest);
|
||||
private ResourceLink createResourceLink(ResourceTable theEntity, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver, RuntimeSearchParam nextSpDef, String theNextPathsUnsplit, PathAndRef nextPathAndRef, IIdType theNextId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest, Map<String, IResourceLookup> theResourceIdToResolvedTarget) {
|
||||
/*
|
||||
* 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.
|
||||
* Observation:patient and Observation.subject and we don't want to force a resolution of the
|
||||
* target any more times than we have to.
|
||||
*/
|
||||
|
||||
IResourceLookup targetResource = theResourceIdToResolvedTarget.get(theNextId.getValue());
|
||||
if (targetResource == null) {
|
||||
targetResource = theResourceLinkResolver.findTargetResource(nextSpDef, theNextPathsUnsplit, theNextId, theTypeString, theType, theReference, theRequest);
|
||||
}
|
||||
|
||||
if (targetResource == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ResourceLink.forLocalReference(nextPathAndRef.getPath(), theEntity, targetResource, theUpdateTime);
|
||||
theResourceIdToResolvedTarget.put(theNextId.getValue(), targetResource);
|
||||
|
||||
String targetResourceType = targetResource.getResourceType();
|
||||
Long targetResourcePid = targetResource.getResourceId();
|
||||
String targetResourceIdPart = theNextId.getIdPart();
|
||||
return ResourceLink.forLocalReference(nextPathAndRef.getPath(), theEntity, targetResourceType, targetResourcePid, targetResourceIdPart, theUpdateTime);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
|
|||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.searchparam.util.SourceParam;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.param.BaseParamWithPrefix;
|
||||
|
@ -146,7 +145,7 @@ public class InMemoryResourceMatcher {
|
|||
case Constants.PARAM_SOURCE:
|
||||
return InMemoryMatchResult.fromBoolean(matchSourcesAndOr(theAndOrParams, theResource));
|
||||
default:
|
||||
return matchResourceParam(theParamName, theAndOrParams, theSearchParams, resourceName, paramDef);
|
||||
return matchResourceParam(myModelConfig, theParamName, theAndOrParams, theSearchParams, resourceName, paramDef);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,7 +188,7 @@ public class InMemoryResourceMatcher {
|
|||
return theValue.equals(theId.getValue()) || theValue.equals(theId.getIdPart());
|
||||
}
|
||||
|
||||
private InMemoryMatchResult matchResourceParam(String theParamName, List<List<IQueryParameterType>> theAndOrParams, ResourceIndexedSearchParams theSearchParams, String theResourceName, RuntimeSearchParam theParamDef) {
|
||||
private InMemoryMatchResult matchResourceParam(ModelConfig theModelConfig, String theParamName, List<List<IQueryParameterType>> theAndOrParams, ResourceIndexedSearchParams theSearchParams, String theResourceName, RuntimeSearchParam theParamDef) {
|
||||
if (theParamDef != null) {
|
||||
switch (theParamDef.getParamType()) {
|
||||
case QUANTITY:
|
||||
|
@ -202,7 +201,7 @@ public class InMemoryResourceMatcher {
|
|||
if (theSearchParams == null) {
|
||||
return InMemoryMatchResult.successfulMatch();
|
||||
} else {
|
||||
return InMemoryMatchResult.fromBoolean(theAndOrParams.stream().anyMatch(nextAnd -> matchParams(theResourceName, theParamName, theParamDef, nextAnd, theSearchParams)));
|
||||
return InMemoryMatchResult.fromBoolean(theAndOrParams.stream().anyMatch(nextAnd -> matchParams(theModelConfig, theResourceName, theParamName, theParamDef, nextAnd, theSearchParams)));
|
||||
}
|
||||
case COMPOSITE:
|
||||
case HAS:
|
||||
|
@ -219,28 +218,8 @@ public class InMemoryResourceMatcher {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean matchParams(String theResourceName, String theParamName, RuntimeSearchParam paramDef, List<? extends IQueryParameterType> theNextAnd, ResourceIndexedSearchParams theSearchParams) {
|
||||
if (paramDef.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
|
||||
stripBaseUrlsFromReferenceParams(theNextAnd);
|
||||
}
|
||||
return theNextAnd.stream().anyMatch(token -> theSearchParams.matchParam(theResourceName, theParamName, paramDef, token));
|
||||
}
|
||||
|
||||
private void stripBaseUrlsFromReferenceParams(List<? extends IQueryParameterType> theNextAnd) {
|
||||
if (myModelConfig.getTreatBaseUrlsAsLocal().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (IQueryParameterType param : theNextAnd) {
|
||||
ReferenceParam ref = (ReferenceParam) param;
|
||||
IIdType dt = new IdDt(ref.getBaseUrl(), ref.getResourceType(), ref.getIdPart(), null);
|
||||
|
||||
if (dt.hasBaseUrl()) {
|
||||
if (myModelConfig.getTreatBaseUrlsAsLocal().contains(dt.getBaseUrl())) {
|
||||
ref.setValue(dt.toUnqualified().getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
private boolean matchParams(ModelConfig theModelConfig, String theResourceName, String theParamName, RuntimeSearchParam paramDef, List<? extends IQueryParameterType> theNextAnd, ResourceIndexedSearchParams theSearchParams) {
|
||||
return theNextAnd.stream().anyMatch(token -> theSearchParams.matchParam(theModelConfig, theResourceName, theParamName, paramDef, token));
|
||||
}
|
||||
|
||||
private boolean hasChain(List<List<IQueryParameterType>> theAndOrParams) {
|
||||
|
@ -256,7 +235,7 @@ public class InMemoryResourceMatcher {
|
|||
for (List<IQueryParameterType> theAndOrParam : theAndOrParams) {
|
||||
for (IQueryParameterType param : theAndOrParam) {
|
||||
if (param instanceof BaseParamWithPrefix) {
|
||||
ParamPrefixEnum prefix = ((BaseParamWithPrefix) param).getPrefix();
|
||||
ParamPrefixEnum prefix = ((BaseParamWithPrefix<?>) param).getPrefix();
|
||||
RestSearchParameterTypeEnum paramType = theParamDef.getParamType();
|
||||
if (!supportedPrefix(prefix, paramType)) {
|
||||
return InMemoryMatchResult.unsupportedFromParameterAndReason(theParamName, String.format("The prefix %s is not supported for param type %s", prefix, paramType));
|
||||
|
@ -268,6 +247,7 @@ public class InMemoryResourceMatcher {
|
|||
return InMemoryMatchResult.successfulMatch();
|
||||
}
|
||||
|
||||
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
|
||||
private boolean supportedPrefix(ParamPrefixEnum theParam, RestSearchParameterTypeEnum theParamType) {
|
||||
if (theParam == null || theParamType == null) {
|
||||
return true;
|
||||
|
|
|
@ -21,7 +21,8 @@ package ca.uhn.fhir.jpa.searchparam.matcher;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
|
||||
import ca.uhn.fhir.jpa.model.cross.ResourceLookup;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
|
@ -34,18 +35,17 @@ import org.springframework.stereotype.Service;
|
|||
public class InlineResourceLinkResolver implements IResourceLinkResolver {
|
||||
|
||||
@Override
|
||||
public ResourceTable findTargetResource(RuntimeSearchParam theNextSpDef, String theNextPathsUnsplit, IIdType theNextId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest) {
|
||||
public IResourceLookup findTargetResource(RuntimeSearchParam theSearchParam, String theSourcePath, IIdType theSourceResourceId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest) {
|
||||
|
||||
/*
|
||||
* TODO: JA - This gets used during runtime in-memory matching for subscription. It's not
|
||||
* really clear if it's useful or not.
|
||||
*/
|
||||
|
||||
ResourceTable target;
|
||||
target = new ResourceTable();
|
||||
target.setResourceType(theTypeString);
|
||||
if (theNextId.isIdPartValidLong()) {
|
||||
target.setId(theNextId.getIdPartAsLong());
|
||||
} else {
|
||||
ForcedId forcedId = new ForcedId();
|
||||
forcedId.setForcedId(theNextId.getIdPart());
|
||||
target.setForcedId(forcedId);
|
||||
}
|
||||
return target;
|
||||
return new ResourceLookup(theTypeString, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package ca.uhn.fhir.jpa.searchparam.extractor;
|
||||
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
|
@ -14,72 +14,70 @@ import java.util.Set;
|
|||
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ResourceIndexedSearchParamsTest {
|
||||
|
||||
public static final String STRING_ID = "StringId";
|
||||
public static final String LONG_ID = "123";
|
||||
private ResourceIndexedSearchParams myParams;
|
||||
private ResourceTable myTarget;
|
||||
private ResourceTable mySource;
|
||||
private ModelConfig myModelConfig = new ModelConfig();
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
ResourceTable source = new ResourceTable();
|
||||
source.setResourceType("Patient");
|
||||
mySource = new ResourceTable();
|
||||
mySource.setResourceType("Patient");
|
||||
|
||||
myTarget = new ResourceTable();
|
||||
myTarget.setResourceType("Organization");
|
||||
|
||||
myParams = new ResourceIndexedSearchParams(source);
|
||||
ResourceLink link = ResourceLink.forLocalReference("organization", source, myTarget, new Date());
|
||||
myParams.getResourceLinks().add(link);
|
||||
myParams = new ResourceIndexedSearchParams(mySource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchResourceLinksStringCompareToLong() {
|
||||
ReferenceParam referenceParam = getReferenceParam(STRING_ID);
|
||||
myTarget.setId(123L);
|
||||
ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, LONG_ID, new Date());
|
||||
myParams.getResourceLinks().add(link);
|
||||
|
||||
boolean result = myParams.matchResourceLinks("Patient", "organization", referenceParam, "organization");
|
||||
ReferenceParam referenceParam = getReferenceParam(STRING_ID);
|
||||
boolean result = myParams.matchResourceLinks(myModelConfig, "Patient", "organization", referenceParam, "organization");
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchResourceLinksStringCompareToString() {
|
||||
ReferenceParam referenceParam = getReferenceParam(STRING_ID);
|
||||
ForcedId forcedid = new ForcedId();
|
||||
forcedid.setForcedId(STRING_ID);
|
||||
myTarget.setForcedId(forcedid);
|
||||
ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, STRING_ID, new Date());
|
||||
myParams.getResourceLinks().add(link);
|
||||
|
||||
boolean result = myParams.matchResourceLinks("Patient", "organization", referenceParam, "organization");
|
||||
ReferenceParam referenceParam = getReferenceParam(STRING_ID);
|
||||
boolean result = myParams.matchResourceLinks(myModelConfig, "Patient", "organization", referenceParam, "organization");
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchResourceLinksLongCompareToString() {
|
||||
ReferenceParam referenceParam = getReferenceParam(LONG_ID);
|
||||
ForcedId forcedid = new ForcedId();
|
||||
forcedid.setForcedId(STRING_ID);
|
||||
myTarget.setForcedId(forcedid);
|
||||
ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, STRING_ID, new Date());
|
||||
myParams.getResourceLinks().add(link);
|
||||
|
||||
boolean result = myParams.matchResourceLinks("Patient", "organization", referenceParam, "organization");
|
||||
ReferenceParam referenceParam = getReferenceParam(LONG_ID);
|
||||
boolean result = myParams.matchResourceLinks(myModelConfig, "Patient", "organization", referenceParam, "organization");
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchResourceLinksLongCompareToLong() {
|
||||
ReferenceParam referenceParam = getReferenceParam(LONG_ID);
|
||||
myTarget.setId(123L);
|
||||
ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, LONG_ID, new Date());
|
||||
myParams.getResourceLinks().add(link);
|
||||
|
||||
boolean result = myParams.matchResourceLinks("Patient", "organization", referenceParam, "organization");
|
||||
ReferenceParam referenceParam = getReferenceParam(LONG_ID);
|
||||
boolean result = myParams.matchResourceLinks(myModelConfig, "Patient", "organization", referenceParam, "organization");
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
private ReferenceParam getReferenceParam(String theId) {
|
||||
ReferenceParam retval = new ReferenceParam();
|
||||
retval.setValue(theId);
|
||||
return retval;
|
||||
ReferenceParam retVal = new ReferenceParam();
|
||||
retVal.setValue(theId);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
|
@ -93,14 +91,14 @@ public class ResourceIndexedSearchParamsTest {
|
|||
Lists.newArrayList("name=SMITH", "name=JOHN")
|
||||
);
|
||||
values = ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains("Patient", partsChoices);
|
||||
assertThat(values.toString(), values, containsInAnyOrder("Patient?gender=male&name=JOHN","Patient?gender=male&name=SMITH"));
|
||||
assertThat(values.toString(), values, containsInAnyOrder("Patient?gender=male&name=JOHN", "Patient?gender=male&name=SMITH"));
|
||||
|
||||
partsChoices = Lists.newArrayList(
|
||||
Lists.newArrayList("gender=male", ""),
|
||||
Lists.newArrayList("name=SMITH", "name=JOHN", "")
|
||||
);
|
||||
values = ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains("Patient", partsChoices);
|
||||
assertThat(values.toString(), values, containsInAnyOrder("Patient?gender=male&name=JOHN","Patient?gender=male&name=SMITH"));
|
||||
assertThat(values.toString(), values, containsInAnyOrder("Patient?gender=male&name=JOHN", "Patient?gender=male&name=SMITH"));
|
||||
|
||||
partsChoices = Lists.newArrayList(
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue