Merge branch 'master' into bugfix/1752-support-chained-parameters-inside-reverse-chained-parameter

This commit is contained in:
Tadgh 2020-03-13 18:40:11 -07:00
commit 4777d75e6b
60 changed files with 1095 additions and 1538 deletions

View File

@ -0,0 +1,9 @@
package ca.uhn.fhir.model.api;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public interface IModelJson {
}

View File

@ -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.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.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.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.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} ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.incorrectResourceType=Incorrect resource type detected for endpoint, found {0} but expected {1}

View File

@ -324,6 +324,17 @@
"city": "Germany", "city": "Germany",
"lat": 51.165691, "lat": 51.165691,
"lon": 10.451526 "lon": 10.451526
},
{
"title": "VEIG",
"description": "Vietnam Ehealth Innovation Group",
"link": "http://emr.com.vn",
"contactName": "Nguyen Hai Phong",
"contactEmail": "haiphong.nguyen@gmail.com",
"city": "Hanoi,Vietnam",
"lat": 21.026058,
"lon": 105.822715,
"added": "2020-03-06"
} }
] ]

View File

@ -43,6 +43,7 @@ page.server_jpa.get_started=Get Started ⚡
page.server_jpa.architecture=Architecture page.server_jpa.architecture=Architecture
page.server_jpa.configuration=Configuration page.server_jpa.configuration=Configuration
page.server_jpa.search=Search page.server_jpa.search=Search
page.server_jpa.performance=Performance
page.server_jpa.upgrading=Upgrade Guide page.server_jpa.upgrading=Upgrade Guide
section.interceptors.title=Interceptors section.interceptors.title=Interceptors

View File

@ -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.

View File

@ -22,8 +22,7 @@ package ca.uhn.fhir.jpa.binstore;
import ca.uhn.fhir.jpa.util.JsonDateDeserializer; import ca.uhn.fhir.jpa.util.JsonDateDeserializer;
import ca.uhn.fhir.jpa.util.JsonDateSerializer; import ca.uhn.fhir.jpa.util.JsonDateSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect; import ca.uhn.fhir.model.api.IModelJson;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@ -33,9 +32,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.Date; import java.util.Date;
@JsonInclude(JsonInclude.Include.NON_NULL) public class StoredDetails implements IModelJson {
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class StoredDetails {
@JsonProperty("blobId") @JsonProperty("blobId")
private String myBlobId; private String myBlobId;

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.bulk;
import ca.uhn.fhir.jpa.util.JsonDateDeserializer; import ca.uhn.fhir.jpa.util.JsonDateDeserializer;
import ca.uhn.fhir.jpa.util.JsonDateSerializer; import ca.uhn.fhir.jpa.util.JsonDateSerializer;
import ca.uhn.fhir.model.api.IModelJson;
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
@ -98,9 +99,7 @@ public class BulkExportResponseJson {
return retVal; return retVal;
} }
@JsonInclude(JsonInclude.Include.NON_NULL) public static class Output implements IModelJson {
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public static class Output {
@JsonProperty("type") @JsonProperty("type")
private String myType; private String myType;

View File

@ -1146,7 +1146,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
} }
} }
// Syncrhonize composite params // Synchronize composite params
mySearchParamWithInlineReferencesExtractor.storeCompositeStringUniques(newParams, entity, existingParams); mySearchParamWithInlineReferencesExtractor.storeCompositeStringUniques(newParams, entity, existingParams);
} }
} }

View File

@ -181,8 +181,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Override @Override
public DaoMethodOutcome delete(IIdType theId, DeleteConflictList theDeleteConflicts, RequestDetails theRequest) { public DaoMethodOutcome delete(IIdType theId, DeleteConflictList theDeleteConflicts, RequestDetails theRequest) {
validateIdPresentForDelete(theId); validateIdPresentForDelete(theId);
validateDeleteEnabled();
final ResourceTable entity = readEntityLatestVersion(theId, theRequest); final ResourceTable entity = readEntityLatestVersion(theId);
if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) { if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) {
throw new ResourceVersionConflictException("Trying to delete " + theId + " but this is not the current version"); 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 @Override
public DaoMethodOutcome delete(IIdType theId, RequestDetails theRequestDetails) { public DaoMethodOutcome delete(IIdType theId, RequestDetails theRequestDetails) {
validateIdPresentForDelete(theId); validateIdPresentForDelete(theId);
validateDeleteEnabled();
DeleteConflictList deleteConflicts = new DeleteConflictList(); DeleteConflictList deleteConflicts = new DeleteConflictList();
if (isNotBlank(theId.getValue())) { if (isNotBlank(theId.getValue())) {
@ -280,6 +282,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
*/ */
@Override @Override
public DeleteMethodOutcome deleteByUrl(String theUrl, DeleteConflictList deleteConflicts, RequestDetails theRequest) { public DeleteMethodOutcome deleteByUrl(String theUrl, DeleteConflictList deleteConflicts, RequestDetails theRequest) {
validateDeleteEnabled();
StopWatch w = new StopWatch(); StopWatch w = new StopWatch();
Set<ResourcePersistentId> resourceIds = myMatchResourceUrlService.processMatchUrl(theUrl, myResourceType, theRequest); Set<ResourcePersistentId> resourceIds = myMatchResourceUrlService.processMatchUrl(theUrl, myResourceType, theRequest);
@ -355,6 +359,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Override @Override
public DeleteMethodOutcome deleteByUrl(String theUrl, RequestDetails theRequestDetails) { public DeleteMethodOutcome deleteByUrl(String theUrl, RequestDetails theRequestDetails) {
validateDeleteEnabled();
DeleteConflictList deleteConflicts = new DeleteConflictList(); DeleteConflictList deleteConflicts = new DeleteConflictList();
DeleteMethodOutcome outcome = deleteByUrl(theUrl, deleteConflicts, theRequestDetails); DeleteMethodOutcome outcome = deleteByUrl(theUrl, deleteConflicts, theRequestDetails);
@ -364,6 +370,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return outcome; 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) { private void validateIdPresentForDelete(IIdType theId) {
if (theId == null || !theId.hasIdPart()) { if (theId == null || !theId.hasIdPart()) {
throw new InvalidRequestException("Can not perform delete, no ID provided"); 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); throw new ResourceNotFoundException(theResourceId);
} }
ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest); ResourceTable latestVersion = readEntityLatestVersion(theResourceId);
if (latestVersion.getVersion() != entity.getVersion()) { if (latestVersion.getVersion() != entity.getVersion()) {
doMetaAdd(theMetaAdd, entity); doMetaAdd(theMetaAdd, entity);
} else { } else {
@ -715,7 +728,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
throw new ResourceNotFoundException(theResourceId); throw new ResourceNotFoundException(theResourceId);
} }
ResourceTable latestVersion = readEntityLatestVersion(theResourceId, theRequest); ResourceTable latestVersion = readEntityLatestVersion(theResourceId);
if (latestVersion.getVersion() != entity.getVersion()) { if (latestVersion.getVersion() != entity.getVersion()) {
doMetaDelete(theMetaDel, entity); doMetaDelete(theMetaDel, entity);
} else { } else {
@ -791,7 +804,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
} }
} else { } else {
entityToUpdate = readEntityLatestVersion(theId, theRequest); entityToUpdate = readEntityLatestVersion(theId);
if (theId.hasVersionIdPart()) { if (theId.hasVersionIdPart()) {
if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) { if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) {
throw new ResourceVersionConflictException("Version " + theId.getVersionIdPart() + " is not the most recent version of this resource, unable to apply patch"); 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) { public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId, RequestDetails theRequest) {
validateResourceTypeAndThrowInvalidRequestException(theId); 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()); BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid.getIdAsLong());
if (entity == null) { if (entity == null) {
@ -964,8 +977,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return entity; return entity;
} }
protected ResourceTable readEntityLatestVersion(IIdType theId, RequestDetails theRequest) { protected ResourceTable readEntityLatestVersion(IIdType theId) {
ResourcePersistentId persistentId = myIdHelperService.translateForcedIdToPid(getResourceName(), theId.getIdPart(), theRequest); ResourcePersistentId persistentId = myIdHelperService.resolveResourcePersistentIds(getResourceName(), theId.getIdPart());
ResourceTable entity = myEntityManager.find(ResourceTable.class, persistentId.getId()); ResourceTable entity = myEntityManager.find(ResourceTable.class, persistentId.getId());
if (entity == null) { if (entity == null) {
throw new ResourceNotFoundException(theId); throw new ResourceNotFoundException(theId);
@ -1206,7 +1219,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
resourceId = theResource.getIdElement(); resourceId = theResource.getIdElement();
try { try {
entity = readEntityLatestVersion(resourceId, theRequest); entity = readEntityLatestVersion(resourceId);
} catch (ResourceNotFoundException e) { } catch (ResourceNotFoundException e) {
return doCreate(theResource, null, thePerformIndexing, new Date(), theRequest); 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) { if (theId == null || theId.hasIdPart() == false) {
throw new InvalidRequestException("No ID supplied. ID is required when validating with mode=DELETE"); 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 // Validate that there are no resources pointing to the candidate that
// would prevent deletion // would prevent deletion

View File

@ -183,6 +183,11 @@ public class DaoConfig {
*/ */
private boolean myPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets; private boolean myPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets;
/**
* @since 5.0.0
*/
private boolean myDeleteEnabled = true;
/** /**
* Constructor * Constructor
*/ */
@ -1907,7 +1912,33 @@ public class DaoConfig {
setPreExpandValueSetsDefaultCount(Math.min(getPreExpandValueSetsDefaultCount(), getPreExpandValueSetsMaxCount())); 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), NONE(false, false),
SOURCE_URI(true, false), SOURCE_URI(true, false),
REQUEST_ID(false, true), REQUEST_ID(false, true),

View File

@ -20,16 +20,12 @@ package ca.uhn.fhir.jpa.dao;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao; import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.SubscriptionTable; import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.model.dstu2.resource.Subscription; 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.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.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -37,8 +33,6 @@ import org.springframework.transaction.PlatformTransactionManager;
import java.util.Date; import java.util.Date;
import static org.apache.commons.lang3.StringUtils.isBlank;
public class FhirResourceDaoSubscriptionDstu2 extends BaseHapiFhirResourceDao<Subscription> implements IFhirResourceDaoSubscription<Subscription> { public class FhirResourceDaoSubscriptionDstu2 extends BaseHapiFhirResourceDao<Subscription> implements IFhirResourceDaoSubscription<Subscription> {
@Autowired @Autowired
@ -56,7 +50,7 @@ public class FhirResourceDaoSubscriptionDstu2 extends BaseHapiFhirResourceDao<Su
@Override @Override
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) { public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
ResourceTable entity = readEntityLatestVersion(theId, theRequest); ResourceTable entity = readEntityLatestVersion(theId);
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId()); SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
if (table == null) { if (table == null) {
return null; return null;

View File

@ -229,7 +229,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
StringParam idParm = (StringParam) idParam; StringParam idParm = (StringParam) idParam;
idParamValue = idParm.getValue(); idParamValue = idParm.getValue();
} }
pid = myIdHelperService.translateForcedIdToPid(theResourceName, idParamValue, theRequest); // pid = myIdHelperService.translateForcedIdToPid_(theResourceName, idParamValue, theRequest);
} }
ResourcePersistentId referencingPid = pid; 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) { if (contextParts.length != 3 || "Patient".equals(contextParts[0]) == false || "$everything".equals(contextParts[2]) == false) {
throw new InvalidRequestException("Invalid context: " + theContext); 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); FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager);

View File

@ -267,7 +267,7 @@ public class SearchBuilder implements ISearchBuilder {
if (myParams.get(IAnyResource.SP_RES_ID) != null) { if (myParams.get(IAnyResource.SP_RES_ID) != null) {
StringParam idParam = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0); StringParam idParam = (StringParam) myParams.get(IAnyResource.SP_RES_ID).get(0).get(0);
ResourcePersistentId pid = myIdHelperService.translateForcedIdToPid(myResourceName, idParam.getValue(), theRequest); ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(myResourceName, idParam.getValue());
if (myAlsoIncludePids == null) { if (myAlsoIncludePids == null) {
myAlsoIncludePids = new ArrayList<>(1); myAlsoIncludePids = new ArrayList<>(1);
} }

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao.data;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional;
/* /*
* #%L * #%L
@ -35,8 +36,8 @@ public interface IForcedIdDao extends JpaRepository<ForcedId, Long> {
@Query("SELECT f.myResourcePid FROM ForcedId f WHERE myForcedId IN (:forced_id)") @Query("SELECT f.myResourcePid FROM ForcedId f WHERE myForcedId IN (:forced_id)")
List<Long> findByForcedId(@Param("forced_id") Collection<String> theForcedId); 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)") @Query("SELECT f.myResourcePid FROM ForcedId f WHERE myResourceType = :resource_type AND myForcedId = :forced_id")
List<Long> findByTypeAndForcedId(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedId); Optional<Long> findByTypeAndForcedId(@Param("resource_type") String theResourceType, @Param("forced_id") String theForcedId);
@Query("SELECT f FROM ForcedId f WHERE f.myResourcePid = :resource_pid") @Query("SELECT f FROM ForcedId f WHERE f.myResourcePid = :resource_pid")
ForcedId findByResourcePid(@Param("resource_pid") Long theResourcePid); ForcedId findByResourcePid(@Param("resource_pid") Long theResourcePid);
@ -44,4 +45,37 @@ public interface IForcedIdDao extends JpaRepository<ForcedId, Long> {
@Modifying @Modifying
@Query("DELETE FROM ForcedId t WHERE t.myId = :pid") @Query("DELETE FROM ForcedId t WHERE t.myId = :pid")
void deleteByPid(@Param("pid") Long theId); 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);
} }

View File

@ -8,9 +8,11 @@ import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
/* /*
* #%L * #%L
@ -63,4 +65,6 @@ public interface IResourceTableDao extends JpaRepository<ResourceTable, Long> {
@Query("DELETE FROM ResourceTable t WHERE t.myId = :pid") @Query("DELETE FROM ResourceTable t WHERE t.myId = :pid")
void deleteByPid(@Param("pid") Long theId); 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);
} }

View File

@ -48,7 +48,7 @@ public class FhirResourceDaoSubscriptionDstu3 extends BaseHapiFhirResourceDao<Su
@Override @Override
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) { public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
ResourceTable entity = readEntityLatestVersion(theId, theRequest); ResourceTable entity = readEntityLatestVersion(theId);
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId()); SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
if (table == null) { if (table == null) {
return null; return null;

View File

@ -29,7 +29,7 @@ import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao; 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.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver; import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
@ -64,17 +64,16 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
private DaoRegistry myDaoRegistry; private DaoRegistry myDaoRegistry;
@Override @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) {
ResourceTable target; IResourceLookup resolvedResource;
ResourcePersistentId valueOf; String idPart = theSourceResourceId.getIdPart();
String idPart = theNextId.getIdPart();
try { try {
valueOf = myIdHelperService.translateForcedIdToPid(theTypeString, idPart, theRequest); resolvedResource = myIdHelperService.resolveResourceIdentity(theTypeString, idPart, theRequest);
ourLog.trace("Translated {}/{} to resource PID {}", theType, idPart, valueOf); ourLog.trace("Translated {}/{} to resource PID {}", theType, idPart, resolvedResource);
} catch (ResourceNotFoundException e) { } catch (ResourceNotFoundException e) {
Optional<ResourcePersistentId> pidOpt = createPlaceholderTargetIfConfiguredToDoSo(theType, theReference, idPart); Optional<ResourceTable> createdTableOpt = createPlaceholderTargetIfConfiguredToDoSo(theType, theReference, idPart);
if (!pidOpt.isPresent()) { if (!createdTableOpt.isPresent()) {
if (myDaoConfig.isEnforceReferentialIntegrityOnWrite() == false) { if (myDaoConfig.isEnforceReferentialIntegrityOnWrite() == false) {
return null; return null;
@ -82,43 +81,36 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
RuntimeResourceDefinition missingResourceDef = myContext.getResourceDefinition(theType); RuntimeResourceDefinition missingResourceDef = myContext.getResourceDefinition(theType);
String resName = missingResourceDef.getName(); 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()); ourLog.trace("Resolved resource of type {} as PID: {}", resolvedResource.getResourceType(), resolvedResource.getResourceId());
RuntimeResourceDefinition targetResourceDef = myContext.getResourceDefinition(theType); if (!theTypeString.equals(resolvedResource.getResourceType())) {
if (target == null) { ourLog.error("Resource with PID {} was of type {} and wanted {}", resolvedResource.getResourceId(), theTypeString, resolvedResource.getResourceType());
String resName = targetResourceDef.getName(); throw new UnprocessableEntityException("Resource contains reference to unknown resource ID " + theSourceResourceId.getValue());
throw new InvalidRequestException("Resource " + resName + "/" + idPart + " not found, specified in path: " + theNextPathsUnsplit);
} }
ourLog.trace("Resource PID {} is of type {}", valueOf, target.getResourceType()); if (resolvedResource.getDeleted() != null) {
if (!theTypeString.equals(target.getResourceType())) { String resName = resolvedResource.getResourceType();
ourLog.error("Resource {} with PID {} was not of type {}", target.getIdDt().getValue(), target.getId(), theTypeString); throw new InvalidRequestException("Resource " + resName + "/" + idPart + " is deleted, specified in path: " + theSourcePath);
throw new UnprocessableEntityException(
"Resource contains reference to " + theNextId.getValue() + " but resource with ID " + theNextId.getIdPart() + " is actually of type " + target.getResourceType());
} }
if (target.getDeleted() != null) { if (!theSearchParam.hasTargets() && theSearchParam.getTargets().contains(theTypeString)) {
String resName = targetResourceDef.getName();
throw new InvalidRequestException("Resource " + resName + "/" + idPart + " is deleted, specified in path: " + theNextPathsUnsplit);
}
if (!theNextSpDef.hasTargets() && theNextSpDef.getTargets().contains(theTypeString)) {
return null; return null;
} }
return target;
return resolvedResource;
} }
/** /**
* @param theIdToAssignToPlaceholder If specified, the placeholder resource created will be given a specific ID * @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) { public <T extends IBaseResource> Optional<ResourceTable> createPlaceholderTargetIfConfiguredToDoSo(Class<T> theType, IBaseReference theReference, @Nullable String theIdToAssignToPlaceholder) {
ResourcePersistentId valueOf = null; ResourceTable valueOf = null;
if (myDaoConfig.isAutoCreatePlaceholderReferenceTargets()) { if (myDaoConfig.isAutoCreatePlaceholderReferenceTargets()) {
RuntimeResourceDefinition missingResourceDef = myContext.getResourceDefinition(theType); RuntimeResourceDefinition missingResourceDef = myContext.getResourceDefinition(theType);
@ -136,9 +128,9 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
if (theIdToAssignToPlaceholder != null) { if (theIdToAssignToPlaceholder != null) {
newResource.setId(resName + "/" + theIdToAssignToPlaceholder); newResource.setId(resName + "/" + theIdToAssignToPlaceholder);
valueOf = placeholderResourceDao.update(newResource).getEntity().getPersistentId(); valueOf = ((ResourceTable) placeholderResourceDao.update(newResource).getEntity());
} else { } else {
valueOf = placeholderResourceDao.create(newResource).getEntity().getPersistentId(); valueOf = ((ResourceTable) placeholderResourceDao.create(newResource).getEntity());
} }
} }

View File

@ -21,7 +21,8 @@ package ca.uhn.fhir.jpa.dao.index;
*/ */
import ca.uhn.fhir.jpa.dao.DaoConfig; 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.searchparam.extractor.ResourceIndexedSearchParams;
import ca.uhn.fhir.jpa.util.AddRemoveCount; import ca.uhn.fhir.jpa.util.AddRemoveCount;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -36,23 +37,22 @@ import java.util.List;
@Service @Service
public class DaoSearchParamSynchronizer { public class DaoSearchParamSynchronizer {
@Autowired
private DaoConfig myDaoConfig;
@PersistenceContext(type = PersistenceContextType.TRANSACTION) @PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager; protected EntityManager myEntityManager;
@Autowired
private DaoConfig myDaoConfig;
public AddRemoveCount synchronizeSearchParamsToDatabase(ResourceIndexedSearchParams theParams, ResourceTable theEntity, ResourceIndexedSearchParams existingParams) { public AddRemoveCount synchronizeSearchParamsToDatabase(ResourceIndexedSearchParams theParams, ResourceTable theEntity, ResourceIndexedSearchParams existingParams) {
AddRemoveCount retVal = new AddRemoveCount(); AddRemoveCount retVal = new AddRemoveCount();
synchronize(theParams, theEntity, retVal, theParams.myStringParams, existingParams.myStringParams); synchronize(theParams, theEntity, retVal, theParams.myStringParams, existingParams.myStringParams);
synchronize(theParams, theEntity, retVal, theParams.myTokenParams, existingParams.myTokenParams); synchronize(theParams, theEntity, retVal, theParams.myTokenParams, existingParams.myTokenParams);
synchronize(theParams, theEntity,retVal, theParams.myNumberParams, existingParams.myNumberParams); synchronize(theParams, theEntity, retVal, theParams.myNumberParams, existingParams.myNumberParams);
synchronize(theParams, theEntity,retVal, theParams.myQuantityParams, existingParams.myQuantityParams); synchronize(theParams, theEntity, retVal, theParams.myQuantityParams, existingParams.myQuantityParams);
synchronize(theParams, theEntity,retVal, theParams.myDateParams, existingParams.myDateParams); synchronize(theParams, theEntity, retVal, theParams.myDateParams, existingParams.myDateParams);
synchronize(theParams, theEntity,retVal, theParams.myUriParams, existingParams.myUriParams); synchronize(theParams, theEntity, retVal, theParams.myUriParams, existingParams.myUriParams);
synchronize(theParams, theEntity, retVal, theParams.myCoordsParams, existingParams.myCoordsParams); 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 // make sure links are indexed
theEntity.setResourceLinks(theParams.myLinks); theEntity.setResourceLinks(theParams.myLinks);
@ -85,7 +85,7 @@ public class DaoSearchParamSynchronizer {
* "one delete + one insert" with "one update" * "one delete + one insert" with "one update"
* *
* @param theIndexesToRemove The rows that would be removed * @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) { private <T extends BaseResourceIndex> void tryToReuseIndexEntities(List<T> theIndexesToRemove, List<T> theIndexesToAdd) {
for (int addIndex = 0; addIndex < theIndexesToAdd.size(); addIndex++) { 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) { <T> List<T> subtract(Collection<T> theSubtractFrom, Collection<T> theToSubtract) {
assert theSubtractFrom != theToSubtract; assert theSubtractFrom != theToSubtract;

View File

@ -22,45 +22,98 @@ package ca.uhn.fhir.jpa.dao.index;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.HookParams; 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.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut; 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.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 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.ListMultimap;
import com.google.common.collect.MultimapBuilder; import com.google.common.collect.MultimapBuilder;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.hl7.fhir.instance.model.api.IIdType; 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.*; import javax.annotation.PostConstruct;
import java.util.stream.Stream; 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; 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 @Service
public class IdHelperService { public class IdHelperService {
private static final Logger ourLog = LoggerFactory.getLogger(IdHelperService.class);
@Autowired @Autowired
protected IForcedIdDao myForcedIdDao; protected IForcedIdDao myForcedIdDao;
@Autowired
protected IResourceTableDao myResourceTableDao;
@Autowired(required = true) @Autowired(required = true)
private DaoConfig myDaoConfig; private DaoConfig myDaoConfig;
@Autowired @Autowired
private IInterceptorBroadcaster myInterceptorBroadcaster; 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) { public void delete(ForcedId forcedId) {
myForcedIdDao.deleteByPid(forcedId.getId()); 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 * @throws ResourceNotFoundException If the ID can not be found
*/ */
@Nonnull @Nonnull
@ -75,79 +128,117 @@ public class IdHelperService {
* @throws ResourceNotFoundException If the ID can not be found * @throws ResourceNotFoundException If the ID can not be found
*/ */
@Nonnull @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 // We only pass 1 input in so only 0..1 will come back
IdDt id = new IdDt(theResourceName, theResourceId); 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; assert matches.size() <= 1;
if (matches.isEmpty()) { if (matches.isEmpty()) {
throw new ResourceNotFoundException(id); 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(); return Collections.emptyList();
} }
List<ResourcePersistentId> retVal = new ArrayList<>(); List<ResourcePersistentId> retVal = new ArrayList<>();
ListMultimap<String, String> typeToIds = MultimapBuilder.hashKeys().arrayListValues().build(); if (myDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY) {
//If the PID is fully numeric and we aren't in ClientIdStrategyEnum.ANY, add it to return value. theIds
//Otherwise, add it to a map of resource type -> ID. .stream()
for (IIdType nextId : theId) { .filter(t -> isValidPid(t))
if (theDaoConfig.getResourceClientIdStrategy() != DaoConfig.ClientIdStrategyEnum.ANY && isValidPid(nextId)) { .map(t -> t.getIdPartAsLong())
retVal.add(new ResourcePersistentId(nextId.getIdPartAsLong())); .map(t -> new ResourcePersistentId(t))
} else { .forEach(t -> retVal.add(t));
if (nextId.hasResourceType()) {
typeToIds.put(nextId.getResourceType(), nextId.getIdPart());
} else {
typeToIds.put("", nextId.getIdPart());
}
}
} }
// For every resource type, fetch all of the requested ids against the forcedPidDao, and add them to the return value. ListMultimap<String, String> typeToIds = organizeIdsByResourceType(theIds);
for (Map.Entry<String, Collection<String>> nextEntry : typeToIds.asMap().entrySet()) { for (Map.Entry<String, Collection<String>> nextEntry : typeToIds.asMap().entrySet()) {
String nextResourceType = nextEntry.getKey(); String nextResourceType = nextEntry.getKey();
Collection<String> nextIds = nextEntry.getValue(); Collection<String> nextIds = nextEntry.getValue();
List<Long> convertedPidStream;
if (isBlank(nextResourceType)) { if (isBlank(nextResourceType)) {
StorageProcessingMessage msg = new StorageProcessingMessage() List<Long> views = myForcedIdDao.findByForcedId(nextIds);
.setMessage("This search uses unqualified resource IDs (an ID without a resource type). This is less efficient than using a qualified type."); views.forEach(t -> retVal.add(new ResourcePersistentId(t)));
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);
convertedPidStream = theForcedIdDao.findByForcedId(nextIds);
} else { } else {
convertedPidStream = theForcedIdDao.findByTypeAndForcedId(nextResourceType, nextIds);
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);
}
}
}
} }
convertedPidStream
.stream()
.map(ResourcePersistentId::new)
.forEach(retVal::add);
} }
return retVal;
return retVal;
} }
/**
* Given a persistent ID, returns the associated resource ID
*/
@Nonnull
public IIdType translatePidIdToForcedId(FhirContext theCtx, String theResourceType, ResourcePersistentId theId) { public IIdType translatePidIdToForcedId(FhirContext theCtx, String theResourceType, ResourcePersistentId theId) {
IIdType retVal = theCtx.getVersion().newIdType(); IIdType retVal = theCtx.getVersion().newIdType();
retVal.setValue(translatePidIdToForcedId(theResourceType, theId)); retVal.setValue(translatePidIdToForcedId(theResourceType, theId));
return retVal; return retVal;
} }
public String translatePidIdToForcedId(String theResourceType, ResourcePersistentId theId) { private String translatePidIdToForcedId(String theResourceType, ResourcePersistentId theId) {
ForcedId forcedId = myForcedIdDao.findByResourcePid(theId.getIdAsLong()); ForcedId forcedId = myForcedIdDao.findByResourcePid(theId.getIdAsLong());
if (forcedId != null) { if (forcedId != null) {
return forcedId.getResourceType() + '/' + forcedId.getForcedId(); return forcedId.getResourceType() + '/' + forcedId.getForcedId();
@ -156,17 +247,139 @@ public class IdHelperService {
} }
} }
public static boolean isValidPid(IIdType theId) { private ListMultimap<String, String> organizeIdsByResourceType(Collection<IIdType> theIds) {
if (theId == null || theId.getIdPart() == null) { ListMultimap<String, String> typeToIds = MultimapBuilder.hashKeys().arrayListValues().build();
return false; for (IIdType nextId : theIds) {
} if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(nextId)) {
String idPart = theId.getIdPart(); if (nextId.hasResourceType()) {
for (int i = 0; i < idPart.length(); i++) { typeToIds.put(nextId.getResourceType(), nextId.getIdPart());
char nextChar = idPart.charAt(i); } else {
if (nextChar < '0' || nextChar > '9') { typeToIds.put("", nextId.getIdPart());
return false; }
} }
} }
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);
} }
} }

View File

@ -27,6 +27,7 @@ import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.MatchResourceUrlService; import ca.uhn.fhir.jpa.dao.MatchResourceUrlService;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao; 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.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
@ -187,7 +188,9 @@ public class SearchParamWithInlineReferencesExtractor {
if (linksForCompositePart != null) { if (linksForCompositePart != null) {
for (ResourceLink nextLink : linksForCompositePart) { for (ResourceLink nextLink : linksForCompositePart) {
if (linksForCompositePartWantPaths.contains(nextLink.getSourcePath())) { 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)) { if (isNotBlank(value)) {
value = UrlUtil.escapeUrlParam(value); value = UrlUtil.escapeUrlParam(value);
nextChoicesList.add(key + "=" + value); nextChoicesList.add(key + "=" + value);
@ -250,13 +253,14 @@ public class SearchParamWithInlineReferencesExtractor {
ResourcePersistentId match; ResourcePersistentId match;
if (matches.isEmpty()) { if (matches.isEmpty()) {
Optional<ResourcePersistentId> placeholderOpt = myDaoResourceLinkResolver.createPlaceholderTargetIfConfiguredToDoSo(matchResourceType, nextRef, null); Optional<ResourceTable> placeholderOpt = myDaoResourceLinkResolver.createPlaceholderTargetIfConfiguredToDoSo(matchResourceType, nextRef, null);
if (placeholderOpt.isPresent()) { if (placeholderOpt.isPresent()) {
match = placeholderOpt.get(); match = new ResourcePersistentId(placeholderOpt.get().getResourceId());
} else { } else {
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlNoMatches", nextId.getValue()); String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlNoMatches", nextId.getValue());
throw new ResourceNotFoundException(msg); throw new ResourceNotFoundException(msg);
} }
} else if (matches.size() > 1) { } else if (matches.size() > 1) {
String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlMultipleMatches", nextId.getValue()); String msg = myContext.getLocalizer().getMessage(BaseHapiFhirDao.class, "invalidMatchUrlMultipleMatches", nextId.getValue());
throw new PreconditionFailedException(msg); throw new PreconditionFailedException(msg);
@ -264,9 +268,9 @@ public class SearchParamWithInlineReferencesExtractor {
match = matches.iterator().next(); 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); ourLog.debug("Replacing inline match URL[{}] with ID[{}}", nextId.getValue(), newId);
nextRef.setReference(newId); nextRef.setReference(newId.getValue());
} }
} }
} }

View File

@ -160,7 +160,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
List<Predicate> codePredicates = new ArrayList<>(); List<Predicate> codePredicates = new ArrayList<>();
// Resources by ID // Resources by ID
List<ResourcePersistentId> targetPids = myIdHelperService.translateForcedIdToPids(targetIds, theRequest); List<ResourcePersistentId> targetPids = myIdHelperService.resolveResourcePersistentIds(targetIds, theRequest);
if (!targetPids.isEmpty()) { if (!targetPids.isEmpty()) {
ourLog.debug("Searching for resource link with target PIDs: {}", targetPids); ourLog.debug("Searching for resource link with target PIDs: {}", targetPids);
Predicate pathPredicate; Predicate pathPredicate;

View File

@ -60,7 +60,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
@Nullable @Nullable
Predicate addPredicateResourceId(List<List<IQueryParameterType>> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestDetails theRequest) { 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) { if (nextPredicate != null) {
myQueryRoot.addPredicate(nextPredicate); myQueryRoot.addPredicate(nextPredicate);
@ -71,7 +71,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
} }
@Nullable @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; Predicate nextPredicate = null;
Set<ResourcePersistentId> allOrPids = null; Set<ResourcePersistentId> allOrPids = null;
@ -89,7 +89,7 @@ class PredicateBuilderResourceId extends BasePredicateBuilder {
if (isNotBlank(value)) { if (isNotBlank(value)) {
haveValue = true; haveValue = true;
try { try {
ResourcePersistentId pid = myIdHelperService.translateForcedIdToPid(theResourceName, valueAsId.getIdPart(), theRequest); ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(theResourceName, valueAsId.getIdPart());
orPids.add(pid); orPids.add(pid);
} catch (ResourceNotFoundException e) { } catch (ResourceNotFoundException e) {
// This is not an error in a search, it just results in no matchesFhirResourceDaoR4InterceptorTest // This is not an error in a search, it just results in no matchesFhirResourceDaoR4InterceptorTest

View File

@ -48,7 +48,7 @@ public class FhirResourceDaoSubscriptionR4 extends BaseHapiFhirResourceDao<Subsc
@Override @Override
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) { public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
ResourceTable entity = readEntityLatestVersion(theId, theRequest); ResourceTable entity = readEntityLatestVersion(theId);
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId()); SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
if (table == null) { if (table == null) {
return null; return null;

View File

@ -48,7 +48,7 @@ public class FhirResourceDaoSubscriptionR5 extends BaseHapiFhirResourceDao<Subsc
@Override @Override
public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) { public Long getSubscriptionTablePidForSubscriptionResource(IIdType theId, RequestDetails theRequest) {
ResourceTable entity = readEntityLatestVersion(theId, theRequest); ResourceTable entity = readEntityLatestVersion(theId);
SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId()); SubscriptionTable table = mySubscriptionTableDao.findOneByResourcePid(entity.getId());
if (table == null) { if (table == null) {
return null; return null;

View File

@ -117,11 +117,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
@Override @Override
public ResourcePersistentId getValueSetResourcePid(IIdType theIdType) { public ResourcePersistentId getValueSetResourcePid(IIdType theIdType) {
return getValueSetResourcePid(theIdType, null); return myIdHelperService.resolveResourcePersistentIds(theIdType.getResourceType(), theIdType.getIdPart());
}
private ResourcePersistentId getValueSetResourcePid(IIdType theIdType, RequestDetails theRequestDetails) {
return myIdHelperService.translateForcedIdToPid(theIdType, theRequestDetails);
} }
@Transactional @Transactional
@ -295,7 +291,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL"); Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL");
IIdType csId = myTerminologyVersionAdapterSvc.createOrUpdateCodeSystem(theCodeSystemResource); 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()); ResourceTable resource = myResourceTableDao.getOne(codeSystemResourcePid.getIdAsLong());
ourLog.info("CodeSystem resource has ID: {}", csId.getValue()); ourLog.info("CodeSystem resource has ID: {}", csId.getValue());
@ -555,11 +551,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
} }
private ResourcePersistentId getCodeSystemResourcePid(IIdType theIdType) { private ResourcePersistentId getCodeSystemResourcePid(IIdType theIdType) {
return getCodeSystemResourcePid(theIdType, null); return myIdHelperService.resolveResourcePersistentIds(theIdType.getResourceType(), theIdType.getIdPart());
}
private ResourcePersistentId getCodeSystemResourcePid(IIdType theIdType, RequestDetails theRequestDetails) {
return myIdHelperService.translateForcedIdToPid(theIdType, theRequestDetails);
} }
private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap<TermConcept, Object> theConceptsStack, int theTotalConcepts) { private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap<TermConcept, Object> theConceptsStack, int theTotalConcepts) {

View File

@ -28,6 +28,7 @@ import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ClassInfo; import com.google.common.reflect.ClassPath.ClassInfo;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hibernate.annotations.Subselect;
import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.Length;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.InstantType; import org.hl7.fhir.r4.model.InstantType;
@ -39,7 +40,11 @@ import java.io.InputStream;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Modifier; 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 java.util.stream.Collectors;
import static com.google.common.base.Ascii.toUpperCase; 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) { private static void scanClass(Set<String> theNames, Class<?> theClazz, boolean theIsSuperClass) {
ourLog.info("Scanning: {}", theClazz.getSimpleName()); 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()) { for (Field nextField : theClazz.getDeclaredFields()) {
if (Modifier.isStatic(nextField.getModifiers())) { if (Modifier.isStatic(nextField.getModifiers())) {
@ -104,7 +112,7 @@ public class TestUtil {
} }
ourLog.info(" * Scanning field: {}", nextField.getName()); ourLog.info(" * Scanning field: {}", nextField.getName());
scan(nextField, theNames, theIsSuperClass); scan(nextField, theNames, theIsSuperClass, isView);
Lob lobClass = nextField.getAnnotation(Lob.class); Lob lobClass = nextField.getAnnotation(Lob.class);
if (lobClass != null) { if (lobClass != null) {
@ -140,7 +148,7 @@ public class TestUtil {
scanClass(theNames, theClazz.getSuperclass(), true); 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); Table table = theAnnotatedElement.getAnnotation(Table.class);
if (table != null) { if (table != null) {
@ -198,7 +206,7 @@ public class TestUtil {
*/ */
if (field.getType().equals(String.class)) { if (field.getType().equals(String.class)) {
if (!hasLob) { if (!hasLob) {
if (column.length() == 255) { if (!theIsView && column.length() == 255) {
throw new IllegalStateException("Field does not have an explicit maximum length specified: " + field); throw new IllegalStateException("Field does not have an explicit maximum length specified: " + field);
} }
if (column.length() > MAX_COL_LENGTH) { if (column.length() > MAX_COL_LENGTH) {

View File

@ -426,7 +426,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
myPatientDao.create(p, mySrd); myPatientDao.create(p, mySrd);
fail(); fail();
} catch (UnprocessableEntityException e) { } 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 // Now with a forced ID

View File

@ -36,6 +36,7 @@ import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
@ -491,7 +492,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
assertEquals(2, results.size()); assertEquals(2, results.size());
List<IIdType> actual = toUnqualifiedVersionlessIds( 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)); assertThat(actual, contains(id));
} }
@ -1588,7 +1589,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
dr01.setSubject(new Reference(patientId01)); dr01.setSubject(new Reference(patientId01));
IIdType drId01 = myDiagnosticReportDao.create(dr01, mySrd).getId(); 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 List<Observation> result = toList(myObservationDao
.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_IDENTIFIER, "urn:system|testSearchResourceLinkWithChain01")))); .search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_IDENTIFIER, "urn:system|testSearchResourceLinkWithChain01"))));
@ -1693,7 +1694,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
Date after = new Date(); Date after = new Date();
ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick(); 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; List<IIdType> result;
SearchParameterMap params; SearchParameterMap params;
@ -1756,7 +1757,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
dr01.setSubject(new Reference(patientId01)); dr01.setSubject(new Reference(patientId01));
IIdType drId01 = myDiagnosticReportDao.create(dr01, mySrd).getId(); 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( List<Observation> result = toList(
myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam("testSearchResourceLinkWithTextLogicalId01")))); myObservationDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_SUBJECT, new ReferenceParam("testSearchResourceLinkWithTextLogicalId01"))));
@ -2847,7 +2848,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
ourLog.info("Initial size: " + value.size()); ourLog.info("Initial size: " + value.size());
for (IBaseResource next : value.getResources(0, value.size())) { for (IBaseResource next : value.getResources(0, value.size())) {
ourLog.info("Deleting: {}", next.getIdElement()); ourLog.info("Deleting: {}", next.getIdElement());
myDeviceDao.delete((IIdType) next.getIdElement(), mySrd); myDeviceDao.delete(next.getIdElement(), mySrd);
} }
value = myDeviceDao.search(new SearchParameterMap()); value = myDeviceDao.search(new SearchParameterMap());
@ -2866,7 +2867,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
public void testSearchWithRevIncludes() { public void testSearchWithRevIncludes() {
final String methodName = "testSearchWithRevIncludes"; final String methodName = "testSearchWithRevIncludes";
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionMgr); TransactionTemplate txTemplate = new TransactionTemplate(myTransactionMgr);
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
IIdType pid = txTemplate.execute(new TransactionCallback<IIdType>() { IIdType pid = txTemplate.execute(new TransactionCallback<IIdType>() {
@Override @Override
@ -2884,7 +2885,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
}); });
SearchParameterMap map = new SearchParameterMap(); 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); map.addRevInclude(Condition.INCLUDE_PATIENT);
IBundleProvider results = myPatientDao.search(map); IBundleProvider results = myPatientDao.search(map);
List<IBaseResource> foundResources = results.getResources(0, results.size()); List<IBaseResource> foundResources = results.getResources(0, results.size());

View File

@ -700,7 +700,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
myPatientDao.create(p, mySrd); myPatientDao.create(p, mySrd);
fail(); fail();
} catch (UnprocessableEntityException e) { } 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 // Now with a forced ID

View File

@ -9,6 +9,7 @@ import ca.uhn.fhir.jpa.config.TestR4Config;
import ca.uhn.fhir.jpa.dao.*; import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.dao.data.*; import ca.uhn.fhir.jpa.dao.data.*;
import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest; 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.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
@ -352,6 +353,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
private DaoRegistry myDaoRegistry; private DaoRegistry myDaoRegistry;
@Autowired @Autowired
private IBulkDataExportSvc myBulkDataExportSvc; private IBulkDataExportSvc myBulkDataExportSvc;
@Autowired
private IdHelperService myIdHelperService;
@After() @After()
public void afterCleanupDao() { public void afterCleanupDao() {
@ -380,6 +383,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
BaseTermReadSvcImpl.clearOurLastResultsFromTranslationWithReverseCache(); BaseTermReadSvcImpl.clearOurLastResultsFromTranslationWithReverseCache();
TermDeferredStorageSvcImpl termDeferredStorageSvc = AopTestUtils.getTargetObject(myTerminologyDeferredStorageSvc); TermDeferredStorageSvcImpl termDeferredStorageSvc = AopTestUtils.getTargetObject(myTerminologyDeferredStorageSvc);
termDeferredStorageSvc.clearDeferred(); termDeferredStorageSvc.clearDeferred();
myIdHelperService.clearCache();
} }
@After() @After()

View File

@ -1,12 +1,18 @@
package ca.uhn.fhir.jpa.dao.r4; 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.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; 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.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.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.AfterClass;
import org.junit.Test; import org.junit.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -14,11 +20,20 @@ import org.slf4j.LoggerFactory;
import java.io.IOException; 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 { public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4DeleteTest.class); private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4DeleteTest.class);
@After
public void after() {
myDaoConfig.setDeleteEnabled(new DaoConfig().isDeleteEnabled());
}
@Test @Test
public void testDeleteMarksResourceAndVersionAsDeleted() { public void testDeleteMarksResourceAndVersionAsDeleted() {
@ -29,19 +44,19 @@ public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
myPatientDao.delete(id); myPatientDao.delete(id);
// Table should be marked as deleted // Table should be marked as deleted
runInTransaction(()->{ runInTransaction(() -> {
ResourceTable resourceTable = myResourceTableDao.findById(id.getIdPartAsLong()).get(); ResourceTable resourceTable = myResourceTableDao.findById(id.getIdPartAsLong()).get();
assertNotNull(resourceTable.getDeleted()); assertNotNull(resourceTable.getDeleted());
assertTrue(resourceTable.isDeleted()); assertTrue(resourceTable.isDeleted());
}); });
// Current version should be marked as deleted // Current version should be marked as deleted
runInTransaction(()->{ runInTransaction(() -> {
ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 1); ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 1);
assertNull(resourceTable.getDeleted()); assertNull(resourceTable.getDeleted());
assertNotNull(resourceTable.getPersistentId()); assertNotNull(resourceTable.getPersistentId());
}); });
runInTransaction(()->{ runInTransaction(() -> {
ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 2); ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 2);
assertNotNull(resourceTable.getDeleted()); 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 @Test
public void testDeleteCircularReferenceInTransaction() throws IOException { public void testDeleteCircularReferenceInTransaction() throws IOException {
@ -157,14 +189,14 @@ public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
myPatientDao.delete(id); myPatientDao.delete(id);
// Table should be marked as deleted // Table should be marked as deleted
runInTransaction(()->{ runInTransaction(() -> {
ResourceTable resourceTable = myResourceTableDao.findById(id.getIdPartAsLong()).get(); ResourceTable resourceTable = myResourceTableDao.findById(id.getIdPartAsLong()).get();
assertNotNull(resourceTable.getDeleted()); assertNotNull(resourceTable.getDeleted());
}); });
// Mark the current history version as not-deleted even though the actual resource // Mark the current history version as not-deleted even though the actual resource
// table entry is marked deleted // table entry is marked deleted
runInTransaction(()->{ runInTransaction(() -> {
ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 2); ResourceHistoryTable resourceTable = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(id.getIdPartAsLong(), 2);
resourceTable.setDeleted(null); resourceTable.setDeleted(null);
myResourceHistoryTableDao.save(resourceTable); myResourceHistoryTableDao.save(resourceTable);

View File

@ -1,29 +1,20 @@
package ca.uhn.fhir.jpa.dao.r4; package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.DaoConfig; 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.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.TestUtil; import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Observation;
import org.junit.*; import org.hl7.fhir.r4.model.Patient;
import org.springframework.test.context.TestPropertySource; import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import java.util.*; import static org.hamcrest.Matchers.empty;
import static org.junit.Assert.assertEquals;
import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat;
import static org.junit.Assert.*;
import static org.mockito.Mockito.reset;
public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4QueryCountTest.class); 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() { public void afterResetDao() {
myDaoConfig.setResourceMetaCountHardLimit(new DaoConfig().getResourceMetaCountHardLimit()); myDaoConfig.setResourceMetaCountHardLimit(new DaoConfig().getResourceMetaCountHardLimit());
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields()); myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
myDaoConfig.setDeleteEnabled(new DaoConfig().isDeleteEnabled());
} }
@Before @Before
@ -53,7 +45,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
Patient p = new Patient(); Patient p = new Patient();
p.setId(id.getIdPart()); p.setId(id.getIdPart());
p.addIdentifier().setSystem("urn:system").setValue("2"); p.addIdentifier().setSystem("urn:system").setValue("2");
myPatientDao.update(p).getResource(); myPatientDao.update(p);
}); });
myCaptureQueriesListener.logSelectQueriesForCurrentThread(); myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(5, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); 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 @AfterClass
public static void afterClassClearContext() { public static void afterClassClearContext() {

View File

@ -910,7 +910,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
myPatientDao.create(p, mySrd); myPatientDao.create(p, mySrd);
fail(); fail();
} catch (UnprocessableEntityException e) { } 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 // Now with a forced ID

View File

@ -193,8 +193,8 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
@Update @Update
public MethodOutcome update(@ResourceParam Observation theObservation, HttpServletRequest theRequest) { public MethodOutcome update(@ResourceParam Observation theObservation, HttpServletRequest theRequest) {
ourLog.info("Received Listener Update"); ourLog.info("Received Listener Update");
ourUpdatedObservations.add(theObservation);
ourContentTypes.add(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE).replaceAll(";.*", "")); ourContentTypes.add(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE).replaceAll(";.*", ""));
ourUpdatedObservations.add(theObservation);
extractHeaders(theRequest); extractHeaders(theRequest);
return new MethodOutcome(new IdType("Observation/1"), false); return new MethodOutcome(new IdType("Observation/1"), false);
} }

View File

@ -56,6 +56,7 @@ public class FhirClientSubscriptionProviderTest extends BaseSubscriptionsR4Test
waitForSize(0, ourCreatedObservations); waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations); waitForSize(1, ourUpdatedObservations);
waitForSize(1, ourContentTypes);
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0)); assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
} }
} }

View File

@ -1,110 +0,0 @@
package ca.uhn.fhir.jpa.model.any;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
public class AnyBundle {
private final FhirVersionEnum myFhirVersion;
private final IBaseBundle myBundle;
public static AnyBundle fromFhirContext(FhirContext theFhirContext) {
FhirVersionEnum version = theFhirContext.getVersion().getVersion();
switch (version) {
case DSTU2:
return new AnyBundle(new ca.uhn.fhir.model.dstu2.resource.Bundle());
case DSTU3:
return new AnyBundle(new org.hl7.fhir.dstu3.model.Bundle());
case R4:
return new AnyBundle(new org.hl7.fhir.r4.model.Bundle());
default:
throw new UnsupportedOperationException(version + " not supported");
}
}
public AnyBundle(ca.uhn.fhir.model.dstu2.resource.Bundle theBundleR2) {
myFhirVersion = FhirVersionEnum.DSTU2;
myBundle = theBundleR2;
}
public AnyBundle(org.hl7.fhir.dstu3.model.Bundle theBundleR3) {
myFhirVersion = FhirVersionEnum.DSTU3;
myBundle = theBundleR3;
}
public AnyBundle(org.hl7.fhir.r4.model.Bundle theBundleR4) {
myFhirVersion = FhirVersionEnum.R4;
myBundle = theBundleR4;
}
public static AnyBundle fromResource(IBaseResource theBundle) {
if (theBundle instanceof ca.uhn.fhir.model.dstu2.resource.Bundle) {
return new AnyBundle((ca.uhn.fhir.model.dstu2.resource.Bundle) theBundle);
} else if (theBundle instanceof org.hl7.fhir.dstu3.model.Bundle) {
return new AnyBundle((org.hl7.fhir.dstu3.model.Bundle) theBundle);
} else if (theBundle instanceof org.hl7.fhir.r4.model.Bundle) {
return new AnyBundle((org.hl7.fhir.r4.model.Bundle) theBundle);
} else {
throw new UnsupportedOperationException("Cannot convert " + theBundle.getClass().getName() + " to AnyBundle");
}
}
public IBaseBundle get() {
return myBundle;
}
public ca.uhn.fhir.model.dstu2.resource.Bundle getDstu2() {
Validate.isTrue(myFhirVersion == FhirVersionEnum.DSTU2);
return (ca.uhn.fhir.model.dstu2.resource.Bundle) get();
}
public org.hl7.fhir.dstu3.model.Bundle getDstu3() {
Validate.isTrue(myFhirVersion == FhirVersionEnum.DSTU3);
return (org.hl7.fhir.dstu3.model.Bundle) get();
}
public org.hl7.fhir.r4.model.Bundle getR4() {
Validate.isTrue(myFhirVersion == FhirVersionEnum.R4);
return (org.hl7.fhir.r4.model.Bundle) get();
}
public void addResource(IBaseResource theResource) {
switch (myFhirVersion) {
case DSTU3:
org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent entry = new org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent();
entry.setResource((org.hl7.fhir.dstu3.model.Resource) theResource);
getDstu3().getEntry().add(entry);
break;
case R4:
org.hl7.fhir.r4.model.Bundle.BundleEntryComponent entryr4 = new org.hl7.fhir.r4.model.Bundle.BundleEntryComponent();
entryr4.setResource((org.hl7.fhir.r4.model.Resource) theResource);
getR4().getEntry().add(entryr4);
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
}

View File

@ -1,235 +0,0 @@
package ca.uhn.fhir.jpa.model.any;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.List;
public class AnyComposition {
private final FhirVersionEnum myFhirVersion;
private final IBaseResource myComposition;
public static AnyComposition fromFhirContext(FhirContext theFhirContext) {
FhirVersionEnum version = theFhirContext.getVersion().getVersion();
switch (version) {
case DSTU3:
return new AnyComposition(new org.hl7.fhir.dstu3.model.Composition());
case R4:
return new AnyComposition(new org.hl7.fhir.r4.model.Composition());
default:
throw new UnsupportedOperationException(version + " not supported");
}
}
public AnyComposition(org.hl7.fhir.dstu3.model.Composition theCompositionR3) {
myFhirVersion = FhirVersionEnum.DSTU3;
myComposition = theCompositionR3;
}
public AnyComposition(org.hl7.fhir.r4.model.Composition theCompositionR4) {
myFhirVersion = FhirVersionEnum.R4;
myComposition = theCompositionR4;
}
public static AnyComposition fromResource(IBaseResource theComposition) {
if (theComposition instanceof org.hl7.fhir.dstu3.model.Composition) {
return new AnyComposition((org.hl7.fhir.dstu3.model.Composition) theComposition);
} else if (theComposition instanceof org.hl7.fhir.r4.model.Composition) {
return new AnyComposition((org.hl7.fhir.r4.model.Composition) theComposition);
} else {
throw new UnsupportedOperationException("Cannot convert " + theComposition.getClass().getName() + " to AnyList");
}
}
public IBaseResource get() {
return myComposition;
}
public org.hl7.fhir.dstu3.model.Composition getDstu3() {
Validate.isTrue(myFhirVersion == FhirVersionEnum.DSTU3);
return (org.hl7.fhir.dstu3.model.Composition) get();
}
public org.hl7.fhir.r4.model.Composition getR4() {
Validate.isTrue(myFhirVersion == FhirVersionEnum.R4);
return (org.hl7.fhir.r4.model.Composition) get();
}
public void setIdentifier(String theSystem, String theValue) {
switch (myFhirVersion) {
case DSTU3:
getDstu3().setIdentifier(new org.hl7.fhir.dstu3.model.Identifier().setSystem(theSystem).setValue(theValue));
break;
case R4:
getR4().setIdentifier(new org.hl7.fhir.r4.model.Identifier().setSystem(theSystem).setValue(theValue));
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public String getIdentifier() {
switch (myFhirVersion) {
case DSTU3:
return getDstu3().getIdentifier().getValue();
case R4:
return getR4().getIdentifier().getValue();
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public void setClass(String theSystem, String theCode) {
switch (myFhirVersion) {
case DSTU3:
setClassDstu3(theSystem, theCode);
break;
case R4:
setClassR4(theSystem, theCode);
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
private void setClassDstu3(String theSystem, String theCode) {
org.hl7.fhir.dstu3.model.CodeableConcept codeableConcept = new org.hl7.fhir.dstu3.model.CodeableConcept();
codeableConcept.addCoding().setSystem(theSystem).setCode(theCode);
getDstu3().setClass_(codeableConcept);
}
private void setClassR4(String theSystem, String theCode) {
org.hl7.fhir.r4.model.CodeableConcept codeableConcept = new org.hl7.fhir.r4.model.CodeableConcept();
codeableConcept.addCoding().setSystem(theSystem).setCode(theCode);
getR4().addCategory(codeableConcept);
}
public void addStringExtension(String theUrl, String theValue) {
switch (myFhirVersion) {
case DSTU3:
getDstu3().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.dstu3.model.StringType(theValue));
break;
case R4:
getR4().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.r4.model.StringType(theValue));
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
// TODO KHS Consolidate with other classes in this package
public String getStringExtensionValueOrNull(String theUrl) {
switch (myFhirVersion) {
case DSTU3:
return getStringExtensionValueOrNullDstu3(theUrl);
case R4:
return getStringExtensionValueOrNullR4(theUrl);
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
private String getStringExtensionValueOrNullDstu3(String theUrl) {
List<org.hl7.fhir.dstu3.model.Extension> targetTypes = getDstu3().getExtensionsByUrl(theUrl);
if (targetTypes.size() < 1) {
return null;
}
org.hl7.fhir.dstu3.model.StringType targetType = (org.hl7.fhir.dstu3.model.StringType) targetTypes.get(0).getValue();
return targetType.getValue();
}
private String getStringExtensionValueOrNullR4(String theUrl) {
List<org.hl7.fhir.r4.model.Extension> targetTypes = getR4().getExtensionsByUrl(theUrl);
if (targetTypes.size() < 1) {
return null;
}
org.hl7.fhir.r4.model.StringType targetType = (org.hl7.fhir.r4.model.StringType) targetTypes.get(0).getValue();
return targetType.getValue();
}
public void setSubject(String theReferenceId) {
switch (myFhirVersion) {
case DSTU3:
getDstu3().setSubject(new org.hl7.fhir.dstu3.model.Reference(theReferenceId));
break;
case R4:
getR4().setSubject(new org.hl7.fhir.r4.model.Reference(theReferenceId));
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public void setTitle(String theTitle) {
switch (myFhirVersion) {
case DSTU3:
getDstu3().setTitle(theTitle);
break;
case R4:
getR4().setTitle(theTitle);
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public String getTitle() {
switch (myFhirVersion) {
case DSTU3:
return getDstu3().getTitle();
case R4:
return getR4().getTitle();
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public void addEntry(String theReferenceId) {
switch (myFhirVersion) {
case DSTU3:
getDstu3().getSectionFirstRep().addEntry(new org.hl7.fhir.dstu3.model.Reference(theReferenceId));
break;
case R4:
getR4().getSectionFirstRep().addEntry(new org.hl7.fhir.r4.model.Reference(theReferenceId));
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public void setRandomUuid() {
switch (myFhirVersion) {
case DSTU3:
getDstu3().setId(org.hl7.fhir.dstu3.model.IdType.newRandomUuid());
break;
case R4:
getR4().setId(org.hl7.fhir.r4.model.IdType.newRandomUuid());
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
}

View File

@ -1,361 +0,0 @@
package ca.uhn.fhir.jpa.model.any;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.param.TokenParam;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.List;
import java.util.stream.Stream;
public class AnyListResource {
private final FhirVersionEnum myFhirVersion;
private final IBaseResource myListResource;
public static AnyListResource fromFhirContext(FhirContext theFhirContext) {
FhirVersionEnum version = theFhirContext.getVersion().getVersion();
switch (version) {
case DSTU2:
return new AnyListResource(new ca.uhn.fhir.model.dstu2.resource.ListResource());
case DSTU3:
return new AnyListResource(new org.hl7.fhir.dstu3.model.ListResource());
case R4:
return new AnyListResource(new org.hl7.fhir.r4.model.ListResource());
case R5:
return new AnyListResource(new org.hl7.fhir.r5.model.ListResource());
default:
throw new UnsupportedOperationException(version + " not supported");
}
}
public AnyListResource(ca.uhn.fhir.model.dstu2.resource.ListResource theListResourceR2) {
myFhirVersion = FhirVersionEnum.DSTU2;
myListResource = theListResourceR2;
}
public AnyListResource(org.hl7.fhir.dstu3.model.ListResource theListResourceR3) {
myFhirVersion = FhirVersionEnum.DSTU3;
myListResource = theListResourceR3;
}
public AnyListResource(org.hl7.fhir.r4.model.ListResource theListResourceR4) {
myFhirVersion = FhirVersionEnum.R4;
myListResource = theListResourceR4;
}
public AnyListResource(org.hl7.fhir.r5.model.ListResource theListResourceR5) {
myFhirVersion = FhirVersionEnum.R5;
myListResource = theListResourceR5;
}
public static AnyListResource fromResource(IBaseResource theListResource) {
if (theListResource instanceof ca.uhn.fhir.model.dstu2.resource.ListResource) {
return new AnyListResource((ca.uhn.fhir.model.dstu2.resource.ListResource) theListResource);
} else if (theListResource instanceof org.hl7.fhir.dstu3.model.ListResource) {
return new AnyListResource((org.hl7.fhir.dstu3.model.ListResource) theListResource);
} else if (theListResource instanceof org.hl7.fhir.r4.model.ListResource) {
return new AnyListResource((org.hl7.fhir.r4.model.ListResource) theListResource);
} else if (theListResource instanceof org.hl7.fhir.r5.model.ListResource) {
return new AnyListResource((org.hl7.fhir.r5.model.ListResource) theListResource);
} else {
throw new UnsupportedOperationException("Cannot convert " + theListResource.getClass().getName() + " to AnyList");
}
}
public IBaseResource get() {
return myListResource;
}
public ca.uhn.fhir.model.dstu2.resource.ListResource getDstu2() {
Validate.isTrue(myFhirVersion == FhirVersionEnum.DSTU2);
return (ca.uhn.fhir.model.dstu2.resource.ListResource) get();
}
public org.hl7.fhir.dstu3.model.ListResource getDstu3() {
Validate.isTrue(myFhirVersion == FhirVersionEnum.DSTU3);
return (org.hl7.fhir.dstu3.model.ListResource) get();
}
public org.hl7.fhir.r4.model.ListResource getR4() {
Validate.isTrue(myFhirVersion == FhirVersionEnum.R4);
return (org.hl7.fhir.r4.model.ListResource) get();
}
public org.hl7.fhir.r5.model.ListResource getR5() {
Validate.isTrue(myFhirVersion == FhirVersionEnum.R5);
return (org.hl7.fhir.r5.model.ListResource) get();
}
public FhirVersionEnum getFhirVersion() {
return myFhirVersion;
}
public void addCode(String theSystem, String theCode) {
switch (myFhirVersion) {
case DSTU3:
getDstu3().getCode().addCoding().setSystem(theSystem).setCode(theCode);
break;
case R4:
getR4().getCode().addCoding().setSystem(theSystem).setCode(theCode);
break;
case R5:
getR5().getCode().addCoding().setSystem(theSystem).setCode(theCode);
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public void addIdentifier(String theSystem, String theValue) {
switch (myFhirVersion) {
case DSTU3:
getDstu3().getIdentifier().add(new org.hl7.fhir.dstu3.model.Identifier().setSystem(theSystem).setValue(theValue));
break;
case R4:
getR4().getIdentifier().add(new org.hl7.fhir.r4.model.Identifier().setSystem(theSystem).setValue(theValue));
break;
case R5:
getR5().getIdentifier().add(new org.hl7.fhir.r5.model.Identifier().setSystem(theSystem).setValue(theValue));
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public void addStringExtension(String theUrl, String theValue) {
switch (myFhirVersion) {
case DSTU3:
getDstu3().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.dstu3.model.StringType(theValue));
break;
case R4:
getR4().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.r4.model.StringType(theValue));
break;
case R5:
getR5().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.r5.model.StringType(theValue));
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public String getStringExtensionValueOrNull(String theUrl) {
switch (myFhirVersion) {
case DSTU3:
return getStringExtensionValueOrNullDstu3(theUrl);
case R4:
return getStringExtensionValueOrNullR4(theUrl);
case R5:
return getStringExtensionValueOrNullR5(theUrl);
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
private String getStringExtensionValueOrNullDstu3(String theUrl) {
List<org.hl7.fhir.dstu3.model.Extension> targetTypes = getDstu3().getExtensionsByUrl(theUrl);
if (targetTypes.size() < 1) {
return null;
}
org.hl7.fhir.dstu3.model.StringType targetType = (org.hl7.fhir.dstu3.model.StringType) targetTypes.get(0).getValue();
return targetType.getValue();
}
private String getStringExtensionValueOrNullR4(String theUrl) {
List<org.hl7.fhir.r4.model.Extension> targetTypes = getR4().getExtensionsByUrl(theUrl);
if (targetTypes.size() < 1) {
return null;
}
org.hl7.fhir.r4.model.StringType targetType = (org.hl7.fhir.r4.model.StringType) targetTypes.get(0).getValue();
return targetType.getValue();
}
private String getStringExtensionValueOrNullR5(String theUrl) {
List<org.hl7.fhir.r5.model.Extension> targetTypes = getR5().getExtensionsByUrl(theUrl);
if (targetTypes.size() < 1) {
return null;
}
org.hl7.fhir.r5.model.StringType targetType = (org.hl7.fhir.r5.model.StringType) targetTypes.get(0).getValue();
return targetType.getValue();
}
public void addReference(IBaseReference theReference) {
switch (myFhirVersion) {
case DSTU3:
getDstu3().addEntry().setItem((org.hl7.fhir.dstu3.model.Reference) theReference);
break;
case R4:
getR4().addEntry().setItem((org.hl7.fhir.r4.model.Reference) theReference);
break;
case R5:
getR5().addEntry().setItem((org.hl7.fhir.r5.model.Reference) theReference);
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public void addReference(String theReferenceId) {
switch (myFhirVersion) {
case DSTU3:
getDstu3().addEntry().setItem(new org.hl7.fhir.dstu3.model.Reference(theReferenceId));
break;
case R4:
getR4().addEntry().setItem(new org.hl7.fhir.r4.model.Reference(theReferenceId));
break;
case R5:
getR5().addEntry().setItem(new org.hl7.fhir.r5.model.Reference(theReferenceId));
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public Stream<String> getReferenceStream() {
switch (myFhirVersion) {
case DSTU3:
return getDstu3().getEntry().stream()
.map(entry -> entry.getItem().getReference())
.map(reference -> new org.hl7.fhir.dstu3.model.IdType(reference).toUnqualifiedVersionless().getValue());
case R4:
return getR4().getEntry().stream()
.map(entry -> entry.getItem().getReference())
.map(reference -> new org.hl7.fhir.r4.model.IdType(reference).toUnqualifiedVersionless().getValue());
case R5:
return getR5().getEntry().stream()
.map(entry -> entry.getItem().getReference())
.map(reference -> new org.hl7.fhir.r5.model.IdType(reference).toUnqualifiedVersionless().getValue());
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public boolean removeItem(String theReferenceId) {
switch (myFhirVersion) {
case DSTU3:
return removeItemDstu3(theReferenceId);
case R4:
return removeItemR4(theReferenceId);
case R5:
return removeItemR5(theReferenceId);
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
private boolean removeItemDstu3(String theReferenceId) {
boolean removed = false;
for (org.hl7.fhir.dstu3.model.ListResource.ListEntryComponent entry : getDstu3().getEntry()) {
if (theReferenceId.equals(entry.getItem().getReference()) && !entry.getDeleted()) {
entry.setDeleted(true);
removed = true;
break;
}
}
if (removed) {
getDstu3().getEntry().removeIf(entry -> entry.getDeleted());
}
return removed;
}
private boolean removeItemR4(String theReferenceId) {
boolean removed = false;
for (org.hl7.fhir.r4.model.ListResource.ListEntryComponent entry : getR4().getEntry()) {
if (theReferenceId.equals(entry.getItem().getReference()) && !entry.getDeleted()) {
entry.setDeleted(true);
removed = true;
break;
}
}
if (removed) {
getR4().getEntry().removeIf(entry -> entry.getDeleted());
}
return removed;
}
private boolean removeItemR5(String theReferenceId) {
boolean removed = false;
for (org.hl7.fhir.r5.model.ListResource.ListResourceEntryComponent entry : getR5().getEntry()) {
if (theReferenceId.equals(entry.getItem().getReference()) && !entry.getDeleted()) {
entry.setDeleted(true);
removed = true;
break;
}
}
if (removed) {
getR5().getEntry().removeIf(entry -> entry.getDeleted());
}
return removed;
}
public TokenParam getCodeFirstRep() {
switch (myFhirVersion) {
case DSTU3:
org.hl7.fhir.dstu3.model.Coding codingDstu3 = getDstu3().getCode().getCodingFirstRep();
return new TokenParam(codingDstu3.getSystem(), codingDstu3.getCode());
case R4:
org.hl7.fhir.r4.model.Coding codingR4 = getR4().getCode().getCodingFirstRep();
return new TokenParam(codingR4.getSystem(), codingR4.getCode());
case R5:
org.hl7.fhir.r5.model.Coding codingR5 = getR5().getCode().getCodingFirstRep();
return new TokenParam(codingR5.getSystem(), codingR5.getCode());
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public TokenParam getIdentifierirstRep() {
switch (myFhirVersion) {
case DSTU3:
org.hl7.fhir.dstu3.model.Identifier identDstu3 = getDstu3().getIdentifierFirstRep();
return new TokenParam(identDstu3.getSystem(), identDstu3.getValue());
case R4:
org.hl7.fhir.r4.model.Identifier identR4 = getR4().getIdentifierFirstRep();
return new TokenParam(identR4.getSystem(), identR4.getValue());
case R5:
org.hl7.fhir.r5.model.Identifier identR5 = getR5().getIdentifierFirstRep();
return new TokenParam(identR5.getSystem(), identR5.getValue());
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public boolean isEmpty() {
switch (myFhirVersion) {
case DSTU3:
return getDstu3().getEntry().isEmpty();
case R4:
return getR4().getEntry().isEmpty();
case R5:
return getR5().getEntry().isEmpty();
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
}

View File

@ -1,451 +0,0 @@
package ca.uhn.fhir.jpa.model.any;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.param.TokenParam;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class AnyMeasure {
private final FhirVersionEnum myFhirVersion;
private final IBaseResource myMeasure;
public static AnyMeasure fromFhirContext(FhirContext theFhirContext) {
FhirVersionEnum version = theFhirContext.getVersion().getVersion();
switch (version) {
case DSTU3:
return new AnyMeasure(new org.hl7.fhir.dstu3.model.Measure());
case R4:
return new AnyMeasure(new org.hl7.fhir.r4.model.Measure());
default:
throw new UnsupportedOperationException(version + " not supported");
}
}
public AnyMeasure(org.hl7.fhir.dstu3.model.Measure theMeasureR3) {
myFhirVersion = FhirVersionEnum.DSTU3;
myMeasure = theMeasureR3;
}
public AnyMeasure(org.hl7.fhir.r4.model.Measure theMeasureR4) {
myFhirVersion = FhirVersionEnum.R4;
myMeasure = theMeasureR4;
}
public static AnyMeasure fromResource(IBaseResource theMeasure) {
if (theMeasure instanceof org.hl7.fhir.dstu3.model.Measure) {
return new AnyMeasure((org.hl7.fhir.dstu3.model.Measure) theMeasure);
} else if (theMeasure instanceof org.hl7.fhir.r4.model.Measure) {
return new AnyMeasure((org.hl7.fhir.r4.model.Measure) theMeasure);
} else {
throw new UnsupportedOperationException("Cannot convert " + theMeasure.getClass().getName() + " to AnyList");
}
}
public IBaseResource get() {
return myMeasure;
}
public org.hl7.fhir.dstu3.model.Measure getDstu3() {
Validate.isTrue(myFhirVersion == FhirVersionEnum.DSTU3);
return (org.hl7.fhir.dstu3.model.Measure) get();
}
public org.hl7.fhir.r4.model.Measure getR4() {
Validate.isTrue(myFhirVersion == FhirVersionEnum.R4);
return (org.hl7.fhir.r4.model.Measure) get();
}
public void addIdentifier(String theSystem, String theValue) {
switch (myFhirVersion) {
case DSTU3:
getDstu3().getIdentifier().add(new org.hl7.fhir.dstu3.model.Identifier().setSystem(theSystem).setValue(theValue));
break;
case R4:
getR4().getIdentifier().add(new org.hl7.fhir.r4.model.Identifier().setSystem(theSystem).setValue(theValue));
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public void addType(String theSystem, String theCode) {
switch (myFhirVersion) {
case DSTU3:
org.hl7.fhir.dstu3.model.CodeableConcept codeableConcept = new org.hl7.fhir.dstu3.model.CodeableConcept();
codeableConcept.addCoding().setSystem(theSystem).setCode(theCode);
getDstu3().getType().add(codeableConcept);
break;
case R4:
org.hl7.fhir.r4.model.CodeableConcept codeableConceptR4 = new org.hl7.fhir.r4.model.CodeableConcept();
codeableConceptR4.addCoding().setSystem(theSystem).setCode(theCode);
getR4().getType().add(codeableConceptR4);
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public void addStringExtension(String theUrl, String theValue) {
switch (myFhirVersion) {
case DSTU3:
getDstu3().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.dstu3.model.StringType(theValue));
break;
case R4:
getR4().addExtension().setUrl(theUrl).setValue(new org.hl7.fhir.r4.model.StringType(theValue));
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public String getStringExtensionValueOrNull(String theUrl) {
switch (myFhirVersion) {
case DSTU3:
return getStringExtensionValueOrNullDstu3(theUrl);
case R4:
return getStringExtensionValueOrNullR4(theUrl);
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
private String getStringExtensionValueOrNullDstu3(String theUrl) {
List<org.hl7.fhir.dstu3.model.Extension> targetTypes = getDstu3().getExtensionsByUrl(theUrl);
if (targetTypes.size() < 1) {
return null;
}
org.hl7.fhir.dstu3.model.StringType targetType = (org.hl7.fhir.dstu3.model.StringType) targetTypes.get(0).getValue();
return targetType.getValue();
}
private String getStringExtensionValueOrNullR4(String theUrl) {
List<org.hl7.fhir.r4.model.Extension> targetTypes = getR4().getExtensionsByUrl(theUrl);
if (targetTypes.size() < 1) {
return null;
}
org.hl7.fhir.r4.model.StringType targetType = (org.hl7.fhir.r4.model.StringType) targetTypes.get(0).getValue();
return targetType.getValue();
}
public String getIdentifierFirstRep() {
switch (myFhirVersion) {
case DSTU3:
return getDstu3().getIdentifierFirstRep().getValue();
case R4:
return getR4().getIdentifierFirstRep().getValue();
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public void setComposedOf(String theReferenceId) {
switch (myFhirVersion) {
case DSTU3:
getRelatedArtifactDstu3(theReferenceId, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.COMPOSEDOF);
break;
case R4:
getRelatedArtifactR4(theReferenceId, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.COMPOSEDOF);
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
private void getRelatedArtifactDstu3(String theReferenceId, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType theArtifactType) {
org.hl7.fhir.dstu3.model.RelatedArtifact artifact = new org.hl7.fhir.dstu3.model.RelatedArtifact();
artifact.setType(theArtifactType);
artifact.setResource(new org.hl7.fhir.dstu3.model.Reference(theReferenceId));
getDstu3().getRelatedArtifact().add(artifact);
}
private void getRelatedArtifactR4(String theReferenceId, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType theArtifactType) {
org.hl7.fhir.r4.model.RelatedArtifact artifact = new org.hl7.fhir.r4.model.RelatedArtifact();
artifact.setType(theArtifactType);
artifact.setResource(theReferenceId);
getR4().getRelatedArtifact().add(artifact);
}
public IBaseReference getComposedOf() {
switch (myFhirVersion) {
case DSTU3:
return getArtifactOfTypeDstu3(getDstu3(), org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.COMPOSEDOF);
case R4:
return getArtifactOfTypeR4(getR4(), org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.COMPOSEDOF);
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public void setPredecessor(String theReferenceId) {
switch (myFhirVersion) {
case DSTU3:
getRelatedArtifactDstu3(theReferenceId, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.PREDECESSOR);
break;
case R4:
getRelatedArtifactR4(theReferenceId, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.PREDECESSOR);
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public IBaseReference getPredecessor() {
switch (myFhirVersion) {
case DSTU3:
return getArtifactOfTypeDstu3(getDstu3(), org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.PREDECESSOR);
case R4:
return getArtifactOfTypeR4(getR4(), org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.PREDECESSOR);
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public IBaseReference getDerivedFrom() {
switch (myFhirVersion) {
case DSTU3:
return getArtifactOfTypeDstu3(getDstu3(), org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.DERIVEDFROM);
case R4:
return getArtifactOfTypeR4(getR4(), org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.DERIVEDFROM);
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public void setDerivedFrom(String theReferenceId) {
switch (myFhirVersion) {
case DSTU3:
getRelatedArtifactDstu3(theReferenceId, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.DERIVEDFROM);
break;
case R4:
getRelatedArtifactR4(theReferenceId, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.DERIVEDFROM);
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public IBaseReference getSuccessor() {
switch (myFhirVersion) {
case DSTU3:
return getArtifactOfTypeDstu3(getDstu3(), org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.SUCCESSOR);
case R4:
return getArtifactOfTypeR4(getR4(), org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.SUCCESSOR);
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public void setSuccessor(String theReferenceId) {
switch (myFhirVersion) {
case DSTU3:
getRelatedArtifactDstu3(theReferenceId, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType.SUCCESSOR);
break;
case R4:
getRelatedArtifactR4(theReferenceId, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType.SUCCESSOR);
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
private IBaseReference getArtifactOfTypeDstu3(org.hl7.fhir.dstu3.model.Measure theMeasure, org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType theType) {
return theMeasure.getRelatedArtifact()
.stream()
.filter(artifact -> theType == artifact.getType())
.map(org.hl7.fhir.dstu3.model.RelatedArtifact::getResource)
.findFirst()
.get();
}
private IBaseReference getArtifactOfTypeR4(org.hl7.fhir.r4.model.Measure theMeasure, org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType theType) {
return new org.hl7.fhir.r4.model.Reference(theMeasure.getRelatedArtifact()
.stream()
.filter(artifact -> theType == artifact.getType())
.map(org.hl7.fhir.r4.model.RelatedArtifact::getResource)
.findFirst()
.get());
}
public void setPublisher(String thePublisher) {
switch (myFhirVersion) {
case DSTU3:
getDstu3().setPublisher(thePublisher);
break;
case R4:
getR4().setPublisher(thePublisher);
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public String getPublisher() {
switch (myFhirVersion) {
case DSTU3:
return getDstu3().getPublisher();
case R4:
return getR4().getPublisher();
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public void setName(String theName) {
switch (myFhirVersion) {
case DSTU3:
getDstu3().setName(theName);
break;
case R4:
getR4().setName(theName);
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public String getName() {
switch (myFhirVersion) {
case DSTU3:
return getDstu3().getName();
case R4:
return getR4().getName();
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public void setEffectivePeriod(Date start, Date end) {
switch (myFhirVersion) {
case DSTU3:
org.hl7.fhir.dstu3.model.Period effectivePeriod = new org.hl7.fhir.dstu3.model.Period();
effectivePeriod.setStart(start);
effectivePeriod.setEnd(end);
getDstu3().setEffectivePeriod(effectivePeriod);
break;
case R4:
org.hl7.fhir.r4.model.Period effectivePeriodr4 = new org.hl7.fhir.r4.model.Period();
effectivePeriodr4.setStart(start);
effectivePeriodr4.setEnd(end);
getR4().setEffectivePeriod(effectivePeriodr4);
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public Date getEffectivePeriodStart() {
switch (myFhirVersion) {
case DSTU3:
return getDstu3().getEffectivePeriod().getStart();
case R4:
return getR4().getEffectivePeriod().getStart();
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public Date getEffectivePeriodEnd() {
switch (myFhirVersion) {
case DSTU3:
return getDstu3().getEffectivePeriod().getEnd();
case R4:
return getR4().getEffectivePeriod().getEnd();
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public void setTopics(List<TokenParam> theTokenParamList) {
switch (myFhirVersion) {
case DSTU3:
setTopicsDstu3(theTokenParamList);
break;
case R4:
setTopicsR4(theTokenParamList);
break;
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
private void setTopicsDstu3(List<TokenParam> theTokenParamList) {
List<org.hl7.fhir.dstu3.model.CodeableConcept> topicList = new ArrayList<>();
for (TokenParam tokenParam : theTokenParamList) {
org.hl7.fhir.dstu3.model.CodeableConcept codeableConcept = new org.hl7.fhir.dstu3.model.CodeableConcept();
codeableConcept.addCoding().setSystem(tokenParam.getSystem()).setCode(tokenParam.getValue());
topicList.add(codeableConcept);
}
getDstu3().setTopic(topicList);
}
private void setTopicsR4(List<TokenParam> theTokenParamList) {
List<org.hl7.fhir.r4.model.CodeableConcept> topicList = new ArrayList<>();
for (TokenParam tokenParam : theTokenParamList) {
org.hl7.fhir.r4.model.CodeableConcept codeableConcept = new org.hl7.fhir.r4.model.CodeableConcept();
codeableConcept.addCoding().setSystem(tokenParam.getSystem()).setCode(tokenParam.getValue());
topicList.add(codeableConcept);
}
getR4().setTopic(topicList);
}
public TokenParam getTopicFirstRep() {
switch (myFhirVersion) {
case DSTU3:
org.hl7.fhir.dstu3.model.Coding codingDstu3 = getDstu3().getTopicFirstRep().getCodingFirstRep();
return new TokenParam(codingDstu3.getSystem(), codingDstu3.getCode());
case R4:
org.hl7.fhir.r4.model.Coding codingR4 = getR4().getTopicFirstRep().getCodingFirstRep();
return new TokenParam(codingR4.getSystem(), codingR4.getCode());
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
public TokenParam getTopicSecondRepOrNull() {
switch (myFhirVersion) {
case DSTU3:
if (getDstu3().getTopic().size() < 2) {
return null;
}
org.hl7.fhir.dstu3.model.Coding codingDstu3 = getDstu3().getTopic().get(1).getCodingFirstRep();
return new TokenParam(codingDstu3.getSystem(), codingDstu3.getCode());
case R4:
if (getR4().getTopic().size() < 2) {
return null;
}
org.hl7.fhir.r4.model.Coding codingR4 = getR4().getTopic().get(1).getCodingFirstRep();
return new TokenParam(codingR4.getSystem(), codingR4.getCode());
default:
throw new UnsupportedOperationException(myFhirVersion + " not supported");
}
}
}

View File

@ -0,0 +1,31 @@
package ca.uhn.fhir.jpa.model.cross;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.util.Date;
public interface IResourceLookup {
String getResourceType();
Long getResourceId();
Date getDeleted();
}

View File

@ -0,0 +1,50 @@
package ca.uhn.fhir.jpa.model.cross;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
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;
}
}

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.util.ObjectUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; 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 * 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; private Object myId;
public ResourcePersistentId(Object theId) { public ResourcePersistentId(Object theId) {
assert !(theId instanceof Optional);
myId = theId; myId = theId;
} }

View File

@ -60,30 +60,40 @@ public class ResourceLink extends BaseResourceIndex {
private String mySourceResourceType; private String mySourceResourceType;
@ManyToOne(optional = true, fetch = FetchType.LAZY) @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; 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() @Field()
private Long myTargetResourcePid; private Long myTargetResourcePid;
@Column(name = "TARGET_RESOURCE_TYPE", nullable = false, length = ResourceTable.RESTYPE_LEN) @Column(name = "TARGET_RESOURCE_TYPE", nullable = false, length = ResourceTable.RESTYPE_LEN)
@Field() @Field()
private String myTargetResourceType; private String myTargetResourceType;
@Column(name = "TARGET_RESOURCE_URL", length = 200, nullable = true) @Column(name = "TARGET_RESOURCE_URL", length = 200, nullable = true)
@Field() @Field()
private String myTargetResourceUrl; private String myTargetResourceUrl;
@Field() @Field()
@Column(name = "SP_UPDATED", nullable = true) // TODO: make this false after HAPI 2.3 @Column(name = "SP_UPDATED", nullable = true) // TODO: make this false after HAPI 2.3
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
private Date myUpdated; private Date myUpdated;
@Transient
private transient String myTargetResourceId;
public ResourceLink() { public ResourceLink() {
super(); super();
} }
public String getTargetResourceId() {
if (myTargetResourceId == null && myTargetResource != null) {
myTargetResourceId = getTargetResource().getIdDt().getIdPart();
}
return myTargetResourceId;
}
public String getTargetResourceType() {
return myTargetResourceType;
}
@Override @Override
public boolean equals(Object theObj) { public boolean equals(Object theObj) {
if (this == theObj) { if (this == theObj) {
@ -99,8 +109,9 @@ public class ResourceLink extends BaseResourceIndex {
EqualsBuilder b = new EqualsBuilder(); EqualsBuilder b = new EqualsBuilder();
b.append(mySourcePath, obj.mySourcePath); b.append(mySourcePath, obj.mySourcePath);
b.append(mySourceResource, obj.mySourceResource); b.append(mySourceResource, obj.mySourceResource);
b.append(myTargetResourcePid, obj.myTargetResourcePid);
b.append(myTargetResourceUrl, obj.myTargetResourceUrl); b.append(myTargetResourceUrl, obj.myTargetResourceUrl);
b.append(myTargetResourceType, obj.myTargetResourceType);
b.append(getTargetResourceId(), obj.getTargetResourceId());
return b.isEquals(); return b.isEquals();
} }
@ -122,15 +133,12 @@ public class ResourceLink extends BaseResourceIndex {
mySourceResourceType = theSourceResource.getResourceType(); mySourceResourceType = theSourceResource.getResourceType();
} }
public ResourceTable getTargetResource() { public void setTargetResource(String theResourceType, Long theResourcePid, String theTargetResourceId) {
return myTargetResource; Validate.notBlank(theResourceType);
}
public void setTargetResource(ResourceTable theTargetResource) { myTargetResourceType = theResourceType;
Validate.notNull(theTargetResource); myTargetResourcePid = theResourcePid;
myTargetResource = theTargetResource; myTargetResourceId = theTargetResourceId;
myTargetResourcePid = theTargetResource.getId();
myTargetResourceType = theTargetResource.getResourceType();
} }
public Long getTargetResourcePid() { public Long getTargetResourcePid() {
@ -189,8 +197,9 @@ public class ResourceLink extends BaseResourceIndex {
HashCodeBuilder b = new HashCodeBuilder(); HashCodeBuilder b = new HashCodeBuilder();
b.append(mySourcePath); b.append(mySourcePath);
b.append(mySourceResource); b.append(mySourceResource);
b.append(myTargetResourcePid);
b.append(myTargetResourceUrl); b.append(myTargetResourceUrl);
b.append(getTargetResourceType());
b.append(getTargetResourceId());
return b.toHashCode(); return b.toHashCode();
} }
@ -207,6 +216,10 @@ public class ResourceLink extends BaseResourceIndex {
return b.toString(); return b.toString();
} }
public ResourceTable getTargetResource() {
return myTargetResource;
}
public static ResourceLink forAbsoluteReference(String theSourcePath, ResourceTable theSourceResource, IIdType theTargetResourceUrl, Date theUpdated) { public static ResourceLink forAbsoluteReference(String theSourcePath, ResourceTable theSourceResource, IIdType theTargetResourceUrl, Date theUpdated) {
ResourceLink retVal = new ResourceLink(); ResourceLink retVal = new ResourceLink();
retVal.setSourcePath(theSourcePath); retVal.setSourcePath(theSourcePath);
@ -228,11 +241,11 @@ public class ResourceLink extends BaseResourceIndex {
return retVal; 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(); ResourceLink retVal = new ResourceLink();
retVal.setSourcePath(theSourcePath); retVal.setSourcePath(theSourcePath);
retVal.setSourceResource(theSourceResource); retVal.setSourceResource(theSourceResource);
retVal.setTargetResource(theTargetResource); retVal.setTargetResource(theTargetResourceType, theTargetResourcePid, theTargetResourceId);
retVal.setUpdated(theUpdated); retVal.setUpdated(theUpdated);
return retVal; return retVal;
} }

View File

@ -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.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.search.IndexNonDeletedInterceptor; import ca.uhn.fhir.jpa.model.search.IndexNonDeletedInterceptor;
import ca.uhn.fhir.model.primitive.IdDt; 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.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.annotations.OptimisticLock; 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 javax.persistence.*;
import java.io.Serializable; 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 java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultString; 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_RES_TYPE", columnList = "RES_TYPE"),
@Index(name = "IDX_INDEXSTATUS", columnList = "SP_INDEX_STATUS") @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; public static final int RESTYPE_LEN = 40;
private static final int MAX_LANGUAGE_LENGTH = 20; private static final int MAX_LANGUAGE_LENGTH = 20;
private static final int MAX_PROFILE_LENGTH = 200; private static final int MAX_PROFILE_LENGTH = 200;
@ -626,7 +635,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
@Override @Override
public IdDt getIdDt() { public IdDt getIdDt() {
if (getForcedId() == null) { if (getForcedId() == null) {
Long id = getResourceId(); Long id = this.getResourceId();
return new IdDt(getResourceType() + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + getVersion()); return new IdDt(getResourceType() + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
} else { } else {
// Avoid a join query if possible // Avoid a join query if possible

View File

@ -1,24 +0,0 @@
package ca.uhn.fhir.jpa.model.any;
import org.hl7.fhir.r5.model.ListResource;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class AnyListResourceTest {
@Test
public void getCodeFirstRep() {
AnyListResource listResource = AnyListResource.fromResource(new ListResource());
listResource.addCode("foo", "bar");
assertEquals("foo", listResource.getCodeFirstRep().getSystem());
assertEquals("bar", listResource.getCodeFirstRep().getValue());
}
@Test
public void getIdentifierFirstRep() {
AnyListResource listResource = AnyListResource.fromResource(new ListResource());
listResource.addIdentifier("foo", "bar");
assertEquals("foo", listResource.getIdentifierirstRep().getSystem());
assertEquals("bar", listResource.getIdentifierirstRep().getValue());
}
}

View File

@ -21,14 +21,31 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
*/ */
import ca.uhn.fhir.context.RuntimeSearchParam; 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 ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
public interface IResourceLinkResolver { 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); void validateTypeOrThrowException(Class<? extends IBaseResource> theType);
} }

View File

@ -23,16 +23,17 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.model.api.IQueryParameterType; 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.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.ReferenceParam;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.DependsOn;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.function.Predicate; import java.util.function.Predicate;
import static org.apache.commons.lang3.StringUtils.compare; import static org.apache.commons.lang3.StringUtils.compare;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public final class ResourceIndexedSearchParams { public final class ResourceIndexedSearchParams {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceIndexedSearchParams.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceIndexedSearchParams.class);
@ -217,7 +218,7 @@ public final class ResourceIndexedSearchParams {
return myPopulatedResourceLinkParameters; 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) { if (theParamDef == null) {
return false; return false;
} }
@ -242,7 +243,7 @@ public final class ResourceIndexedSearchParams {
resourceParams = myDateParams; resourceParams = myDateParams;
break; break;
case REFERENCE: case REFERENCE:
return matchResourceLinks(theResourceName, theParamName, theParam, theParamDef.getPath()); return matchResourceLinks(theModelConfig, theResourceName, theParamName, theParam, theParamDef.getPath());
case COMPOSITE: case COMPOSITE:
case HAS: case HAS:
case SPECIAL: case SPECIAL:
@ -259,39 +260,56 @@ public final class ResourceIndexedSearchParams {
return resourceParams.stream().anyMatch(namedParamPredicate); 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 // 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) { 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; ReferenceParam reference = (ReferenceParam)theParam;
Predicate<ResourceLink> namedParamPredicate = resourceLink -> Predicate<ResourceLink> namedParamPredicate = resourceLink ->
resourceLinkMatches(theResourceName, resourceLink, theParamName, theParamPath) searchParameterPathMatches(theResourceName, resourceLink, theParamName, theParamPath)
&& resourceIdMatches(resourceLink, reference); && resourceIdMatches(theModelConfig, resourceLink, reference);
return myLinks.stream().anyMatch(namedParamPredicate); return myLinks.stream().anyMatch(namedParamPredicate);
} }
private boolean resourceIdMatches(ResourceLink theResourceLink, ReferenceParam theReference) { private boolean resourceIdMatches(ModelConfig theModelConfig, ResourceLink theResourceLink, ReferenceParam theReference) {
ResourceTable target = theResourceLink.getTargetResource(); String baseUrl = theReference.getBaseUrl();
IdDt idDt = target.getIdDt(); if (isNotBlank(baseUrl)) {
if (idDt.isIdPartValidLong()) { if (!theModelConfig.getTreatBaseUrlsAsLocal().contains(baseUrl)) {
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 {
return false; 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) { private boolean searchParameterPathMatches(String theResourceName, ResourceLink theResourceLink, String theParamName, String theParamPath) {
return theResourceLink.getTargetResource().getResourceType().equalsIgnoreCase(theParamName) || String sourcePath = theResourceLink.getSourcePath();
theResourceLink.getSourcePath().equalsIgnoreCase(theParamPath); return sourcePath.equalsIgnoreCase(theParamPath);
} }
@Override @Override

View File

@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; 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.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceLink; import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; 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 org.springframework.stereotype.Service;
import java.util.Date; 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.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -63,15 +66,17 @@ public class ResourceLinkExtractor {
ISearchParamExtractor.SearchParamSet<PathAndRef> refs = mySearchParamExtractor.extractResourceLinks(theResource); ISearchParamExtractor.SearchParamSet<PathAndRef> refs = mySearchParamExtractor.extractResourceLinks(theResource);
SearchParamExtractorService.handleWarnings(theRequest, myInterceptorBroadcaster, refs); SearchParamExtractorService.handleWarnings(theRequest, myInterceptorBroadcaster, refs);
Map<String, IResourceLookup> resourceIdToResolvedTarget = new HashMap<>();
for (PathAndRef nextPathAndRef : refs) { for (PathAndRef nextPathAndRef : refs) {
RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(resourceName, nextPathAndRef.getSearchParamName()); 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); 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(); IBaseReference nextReference = thePathAndRef.getRef();
IIdType nextId = nextReference.getReferenceElement(); IIdType nextId = nextReference.getReferenceElement();
String path = thePathAndRef.getPath(); String path = thePathAndRef.getPath();
@ -153,21 +158,36 @@ public class ResourceLinkExtractor {
} }
theResourceLinkResolver.validateTypeOrThrowException(type); 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) { if (resourceLink == null) {
return; return;
} }
theParams.myLinks.add(resourceLink); 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) { 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) {
ResourceTable targetResource = theResourceLinkResolver.findTargetResource(nextSpDef, theNextPathsUnsplit, theNextId, theTypeString, theType, theReference, theRequest); /*
* 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) { if (targetResource == null) {
return 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);
} }
} }

View File

@ -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.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.searchparam.util.SourceParam; import ca.uhn.fhir.jpa.searchparam.util.SourceParam;
import ca.uhn.fhir.model.api.IQueryParameterType; 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.Constants;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.param.BaseParamWithPrefix; import ca.uhn.fhir.rest.param.BaseParamWithPrefix;
@ -146,7 +145,7 @@ public class InMemoryResourceMatcher {
case Constants.PARAM_SOURCE: case Constants.PARAM_SOURCE:
return InMemoryMatchResult.fromBoolean(matchSourcesAndOr(theAndOrParams, theResource)); return InMemoryMatchResult.fromBoolean(matchSourcesAndOr(theAndOrParams, theResource));
default: 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()); 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) { if (theParamDef != null) {
switch (theParamDef.getParamType()) { switch (theParamDef.getParamType()) {
case QUANTITY: case QUANTITY:
@ -202,7 +201,7 @@ public class InMemoryResourceMatcher {
if (theSearchParams == null) { if (theSearchParams == null) {
return InMemoryMatchResult.successfulMatch(); return InMemoryMatchResult.successfulMatch();
} else { } 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 COMPOSITE:
case HAS: case HAS:
@ -219,28 +218,8 @@ public class InMemoryResourceMatcher {
} }
} }
private boolean matchParams(String theResourceName, String theParamName, RuntimeSearchParam paramDef, List<? extends IQueryParameterType> theNextAnd, ResourceIndexedSearchParams theSearchParams) { private boolean matchParams(ModelConfig theModelConfig, String theResourceName, String theParamName, RuntimeSearchParam paramDef, List<? extends IQueryParameterType> theNextAnd, ResourceIndexedSearchParams theSearchParams) {
if (paramDef.getParamType() == RestSearchParameterTypeEnum.REFERENCE) { return theNextAnd.stream().anyMatch(token -> theSearchParams.matchParam(theModelConfig, theResourceName, theParamName, paramDef, token));
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 hasChain(List<List<IQueryParameterType>> theAndOrParams) { private boolean hasChain(List<List<IQueryParameterType>> theAndOrParams) {
@ -256,7 +235,7 @@ public class InMemoryResourceMatcher {
for (List<IQueryParameterType> theAndOrParam : theAndOrParams) { for (List<IQueryParameterType> theAndOrParam : theAndOrParams) {
for (IQueryParameterType param : theAndOrParam) { for (IQueryParameterType param : theAndOrParam) {
if (param instanceof BaseParamWithPrefix) { if (param instanceof BaseParamWithPrefix) {
ParamPrefixEnum prefix = ((BaseParamWithPrefix) param).getPrefix(); ParamPrefixEnum prefix = ((BaseParamWithPrefix<?>) param).getPrefix();
RestSearchParameterTypeEnum paramType = theParamDef.getParamType(); RestSearchParameterTypeEnum paramType = theParamDef.getParamType();
if (!supportedPrefix(prefix, paramType)) { if (!supportedPrefix(prefix, paramType)) {
return InMemoryMatchResult.unsupportedFromParameterAndReason(theParamName, String.format("The prefix %s is not supported for param type %s", 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(); return InMemoryMatchResult.successfulMatch();
} }
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private boolean supportedPrefix(ParamPrefixEnum theParam, RestSearchParameterTypeEnum theParamType) { private boolean supportedPrefix(ParamPrefixEnum theParam, RestSearchParameterTypeEnum theParamType) {
if (theParam == null || theParamType == null) { if (theParam == null || theParamType == null) {
return true; return true;

View File

@ -21,7 +21,8 @@ package ca.uhn.fhir.jpa.searchparam.matcher;
*/ */
import ca.uhn.fhir.context.RuntimeSearchParam; 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.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver; import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
@ -34,18 +35,17 @@ import org.springframework.stereotype.Service;
public class InlineResourceLinkResolver implements IResourceLinkResolver { public class InlineResourceLinkResolver implements IResourceLinkResolver {
@Override @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; ResourceTable target;
target = new ResourceTable(); target = new ResourceTable();
target.setResourceType(theTypeString); target.setResourceType(theTypeString);
if (theNextId.isIdPartValidLong()) { return new ResourceLookup(theTypeString, null, null);
target.setId(theNextId.getIdPartAsLong());
} else {
ForcedId forcedId = new ForcedId();
forcedId.setForcedId(theNextId.getIdPart());
target.setForcedId(forcedId);
}
return target;
} }
@Override @Override

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.searchparam.util; package ca.uhn.fhir.jpa.searchparam.util;
/*-
* #%L
* HAPI FHIR Search Parameters
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.QuantityParam; import ca.uhn.fhir.rest.param.QuantityParam;

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.jpa.searchparam.extractor; 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.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.param.ReferenceParam; 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.containsInAnyOrder;
import static org.hamcrest.Matchers.empty; 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 class ResourceIndexedSearchParamsTest {
public static final String STRING_ID = "StringId"; public static final String STRING_ID = "StringId";
public static final String LONG_ID = "123"; public static final String LONG_ID = "123";
private ResourceIndexedSearchParams myParams; private ResourceIndexedSearchParams myParams;
private ResourceTable myTarget; private ResourceTable mySource;
private ModelConfig myModelConfig = new ModelConfig();
@Before @Before
public void before() { public void before() {
ResourceTable source = new ResourceTable(); mySource = new ResourceTable();
source.setResourceType("Patient"); mySource.setResourceType("Patient");
myTarget = new ResourceTable(); myParams = new ResourceIndexedSearchParams(mySource);
myTarget.setResourceType("Organization");
myParams = new ResourceIndexedSearchParams(source);
ResourceLink link = ResourceLink.forLocalReference("organization", source, myTarget, new Date());
myParams.getResourceLinks().add(link);
} }
@Test @Test
public void matchResourceLinksStringCompareToLong() { public void matchResourceLinksStringCompareToLong() {
ReferenceParam referenceParam = getReferenceParam(STRING_ID); ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, LONG_ID, new Date());
myTarget.setId(123L); 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); assertFalse(result);
} }
@Test @Test
public void matchResourceLinksStringCompareToString() { public void matchResourceLinksStringCompareToString() {
ReferenceParam referenceParam = getReferenceParam(STRING_ID); ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, STRING_ID, new Date());
ForcedId forcedid = new ForcedId(); myParams.getResourceLinks().add(link);
forcedid.setForcedId(STRING_ID);
myTarget.setForcedId(forcedid);
boolean result = myParams.matchResourceLinks("Patient", "organization", referenceParam, "organization"); ReferenceParam referenceParam = getReferenceParam(STRING_ID);
boolean result = myParams.matchResourceLinks(myModelConfig, "Patient", "organization", referenceParam, "organization");
assertTrue(result); assertTrue(result);
} }
@Test @Test
public void matchResourceLinksLongCompareToString() { public void matchResourceLinksLongCompareToString() {
ReferenceParam referenceParam = getReferenceParam(LONG_ID); ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, STRING_ID, new Date());
ForcedId forcedid = new ForcedId(); myParams.getResourceLinks().add(link);
forcedid.setForcedId(STRING_ID);
myTarget.setForcedId(forcedid);
boolean result = myParams.matchResourceLinks("Patient", "organization", referenceParam, "organization"); ReferenceParam referenceParam = getReferenceParam(LONG_ID);
boolean result = myParams.matchResourceLinks(myModelConfig, "Patient", "organization", referenceParam, "organization");
assertFalse(result); assertFalse(result);
} }
@Test @Test
public void matchResourceLinksLongCompareToLong() { public void matchResourceLinksLongCompareToLong() {
ReferenceParam referenceParam = getReferenceParam(LONG_ID); ResourceLink link = ResourceLink.forLocalReference("organization", mySource, "Organization", 123L, LONG_ID, new Date());
myTarget.setId(123L); 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); assertTrue(result);
} }
private ReferenceParam getReferenceParam(String theId) { private ReferenceParam getReferenceParam(String theId) {
ReferenceParam retval = new ReferenceParam(); ReferenceParam retVal = new ReferenceParam();
retval.setValue(theId); retVal.setValue(theId);
return retval; return retVal;
} }
@ -93,14 +91,14 @@ public class ResourceIndexedSearchParamsTest {
Lists.newArrayList("name=SMITH", "name=JOHN") Lists.newArrayList("name=SMITH", "name=JOHN")
); );
values = ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains("Patient", partsChoices); 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( partsChoices = Lists.newArrayList(
Lists.newArrayList("gender=male", ""), Lists.newArrayList("gender=male", ""),
Lists.newArrayList("name=SMITH", "name=JOHN", "") Lists.newArrayList("name=SMITH", "name=JOHN", "")
); );
values = ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains("Patient", partsChoices); 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( partsChoices = Lists.newArrayList(
); );

View File

@ -21,8 +21,7 @@ package ca.uhn.fhir.jpa.subscription.module;
*/ */
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import com.fasterxml.jackson.annotation.JsonAutoDetect; import ca.uhn.fhir.model.api.IModelJson;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
@ -39,9 +38,7 @@ import java.util.*;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@JsonInclude(JsonInclude.Include.NON_NULL) public class CanonicalSubscription implements Serializable, Cloneable, IModelJson {
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class CanonicalSubscription implements Serializable, Cloneable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -279,9 +276,7 @@ public class CanonicalSubscription implements Serializable, Cloneable {
} }
} }
@JsonInclude(JsonInclude.Include.NON_NULL) public static class EmailDetails implements IModelJson {
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public static class EmailDetails {
@JsonProperty("from") @JsonProperty("from")
private String myFrom; private String myFrom;
@ -334,9 +329,7 @@ public class CanonicalSubscription implements Serializable, Cloneable {
} }
} }
@JsonInclude(JsonInclude.Include.NON_NULL) public static class RestHookDetails implements IModelJson {
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public static class RestHookDetails {
@JsonProperty("stripVersionId") @JsonProperty("stripVersionId")
private boolean myStripVersionId; private boolean myStripVersionId;
@ -391,9 +384,7 @@ public class CanonicalSubscription implements Serializable, Cloneable {
} }
@JsonInclude(JsonInclude.Include.NON_NULL) public static class CanonicalEventDefinition implements IModelJson {
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public static class CanonicalEventDefinition {
/** /**
* Constructor * Constructor

View File

@ -23,10 +23,9 @@ package ca.uhn.fhir.jpa.subscription.module;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.subscription.module.subscriber.BaseResourceMessage; import ca.uhn.fhir.jpa.subscription.module.subscriber.BaseResourceMessage;
import ca.uhn.fhir.jpa.subscription.module.subscriber.IResourceMessage; import ca.uhn.fhir.jpa.subscription.module.subscriber.IResourceMessage;
import ca.uhn.fhir.model.api.IModelJson;
import ca.uhn.fhir.util.ResourceReferenceInfo; import ca.uhn.fhir.util.ResourceReferenceInfo;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
@ -37,9 +36,7 @@ import java.util.List;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@JsonInclude(JsonInclude.Include.NON_NULL) public class ResourceModifiedMessage extends BaseResourceMessage implements IResourceMessage, IModelJson {
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class ResourceModifiedMessage extends BaseResourceMessage implements IResourceMessage {
@JsonProperty("resourceId") @JsonProperty("resourceId")
private String myId; private String myId;

View File

@ -20,15 +20,12 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber;
* #L% * #L%
*/ */
import com.fasterxml.jackson.annotation.JsonAutoDetect; import ca.uhn.fhir.model.api.IModelJson;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.MessageHeaders;
@JsonInclude(JsonInclude.Include.NON_NULL) public abstract class BaseJsonMessage<T> implements Message<T>, IModelJson {
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public abstract class BaseJsonMessage<T> implements Message<T> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@JsonProperty("headers") @JsonProperty("headers")

View File

@ -20,8 +20,7 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber;
* #L% * #L%
*/ */
import com.fasterxml.jackson.annotation.JsonAutoDetect; import ca.uhn.fhir.model.api.IModelJson;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
@ -30,9 +29,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@JsonInclude(JsonInclude.Include.NON_NULL) public abstract class BaseResourceMessage implements IResourceMessage, IModelJson {
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public abstract class BaseResourceMessage implements IResourceMessage {
@JsonProperty("attributes") @JsonProperty("attributes")
private Map<String, String> myAttributes; private Map<String, String> myAttributes;

View File

@ -20,13 +20,9 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber;
* #L% * #L%
*/ */
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class ResourceDeliveryJsonMessage extends BaseJsonMessage<ResourceDeliveryMessage> { public class ResourceDeliveryJsonMessage extends BaseJsonMessage<ResourceDeliveryMessage> {
@JsonProperty("payload") @JsonProperty("payload")

View File

@ -24,9 +24,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
@ -35,8 +33,6 @@ import org.hl7.fhir.instance.model.api.IIdType;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class ResourceDeliveryMessage extends BaseResourceMessage implements IResourceMessage { public class ResourceDeliveryMessage extends BaseResourceMessage implements IResourceMessage {
@JsonProperty("canonicalSubscription") @JsonProperty("canonicalSubscription")

View File

@ -21,13 +21,9 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber;
*/ */
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class ResourceModifiedJsonMessage extends BaseJsonMessage<ResourceModifiedMessage> { public class ResourceModifiedJsonMessage extends BaseJsonMessage<ResourceModifiedMessage> {
@JsonProperty("payload") @JsonProperty("payload")

View File

@ -11,6 +11,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class ReferenceParamTest { public class ReferenceParamTest {
@ -202,6 +203,101 @@ public class ReferenceParamTest {
} }
@Test
public void testGetIdPartAsBigDecimal() {
ReferenceParam rp = new ReferenceParam();
rp.setValueAsQueryToken(ourCtx, null, null, "123");
assertEquals("123", rp.getIdPartAsBigDecimal().toPlainString());
}
@Test
public void testGetIdPart() {
ReferenceParam rp = new ReferenceParam();
rp.setValueAsQueryToken(ourCtx, null, null, "123");
assertTrue(rp.isIdPartValidLong());
assertEquals("123", rp.getIdPart());
assertEquals(null, rp.getResourceType(ourCtx));
}
@Test
public void testGetIdPartWithType() {
ReferenceParam rp = new ReferenceParam();
rp.setValueAsQueryToken(ourCtx, null, ":Patient", "123");
assertEquals("123", rp.getIdPart());
assertEquals("Patient", rp.getResourceType(ourCtx).getSimpleName());
}
@Test
public void testSetValueWithType() {
ReferenceParam rp = new ReferenceParam();
rp.setValue("Patient/123");
assertEquals("123", rp.getIdPart());
assertEquals("Patient", rp.getResourceType(ourCtx).getSimpleName());
}
@Test
public void testSetValueWithoutType() {
ReferenceParam rp = new ReferenceParam();
rp.setValue("123");
assertEquals("123", rp.getIdPart());
assertEquals(null, rp.getResourceType(ourCtx));
}
@Test
public void testGetIdPartAsLong() {
ReferenceParam rp = new ReferenceParam();
rp.setValueAsQueryToken(ourCtx, null, null, "123");
assertEquals(123L, rp.getIdPartAsLong().longValue());
}
@Test
public void testToStringParam() {
ReferenceParam rp = new ReferenceParam();
rp.setValueAsQueryToken(ourCtx, null, null, "123");
assertEquals("123", rp.toStringParam(ourCtx).getValue());
}
@Test
public void testToTokenParam() {
ReferenceParam rp = new ReferenceParam();
rp.setValueAsQueryToken(ourCtx, null, null, "123");
assertEquals("123", rp.toTokenParam(ourCtx).getValue());
}
@Test
public void testToDateParam() {
ReferenceParam rp = new ReferenceParam();
rp.setValueAsQueryToken(ourCtx, null, null, "2020-10-01");
assertEquals("2020-10-01", rp.toDateParam(ourCtx).getValueAsString());
}
@Test
public void testToNumberParam() {
ReferenceParam rp = new ReferenceParam();
rp.setValueAsQueryToken(ourCtx, null, null, "1.23");
assertEquals("1.23", rp.toNumberParam(ourCtx).getValue().toPlainString());
}
@Test
public void testToQuantityParam() {
ReferenceParam rp = new ReferenceParam();
rp.setValueAsQueryToken(ourCtx, null, null, "1.23|http://unitsofmeasure.org|cm");
assertEquals("1.23", rp.toQuantityParam(ourCtx).getValue().toPlainString());
assertEquals("http://unitsofmeasure.org", rp.toQuantityParam(ourCtx).getSystem());
assertEquals("cm", rp.toQuantityParam(ourCtx).getUnits());
}
@AfterClass @AfterClass
public static void afterClassClearContext() { public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest(); TestUtil.clearAllStaticFieldsForUnitTest();