From d45a9b67dc96f45013f47c6f0b467931e3c2ad54 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Sun, 24 Apr 2016 19:24:55 -0400 Subject: [PATCH] Allow forced IDs to be reused between resource types --- .../java/ca/uhn/fhir/rest/gclient/IQuery.java | 2 +- .../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 68 ++++++++---- .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 11 +- .../fhir/jpa/dao/BaseHapiFhirSystemDao.java | 19 +++- .../jpa/dao/FhirResourceDaoPatientDstu2.java | 2 +- .../fhir/jpa/dao/FulltextSearchSvcImpl.java | 6 +- .../ca/uhn/fhir/jpa/dao/SearchBuilder.java | 103 +++++++++--------- .../uhn/fhir/jpa/dao/data/IForcedIdDao.java | 8 ++ .../dstu3/FhirResourceDaoPatientDstu3.java | 2 +- .../java/ca/uhn/fhir/jpa/entity/ForcedId.java | 35 ++++-- .../fhir/jpa/entity/ResourceHistoryTable.java | 8 +- .../ca/uhn/fhir/jpa/entity/ResourceTable.java | 8 +- .../dao/dstu2/FhirResourceDaoDstu2Test.java | 4 +- .../dao/dstu3/FhirResourceDaoDstu3Test.java | 23 +++- .../StaleSearchDeletingSvcDstu3Test.java | 3 +- ...rchReturningProfiledResourceDstu2Test.java | 57 +++++++--- src/changes/changes.xml | 14 ++- 17 files changed, 251 insertions(+), 122 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java index d6343037e41..3ebcdc2e340 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java @@ -113,7 +113,7 @@ public interface IQuery extends IClientExecutable, T>, IBaseQueryorg.hl7.fhir.instance.model.Bundle.class * or ca.uhn.fhir.model.dstu2.resource.Bundle.class */ - IQuery returnBundle(Class theClass); + IClientExecutable, B> returnBundle(Class theClass); /** * {@inheritDoc} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index 915d4636c6c..afe8c02c6ee 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -45,6 +45,7 @@ import javax.persistence.Tuple; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Expression; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.xml.stream.events.Characters; @@ -52,6 +53,7 @@ import javax.xml.stream.events.XMLEvent; import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URLEncodedUtils; import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; @@ -195,7 +197,7 @@ public abstract class BaseHapiFhirDao implements IDao { protected EntityManager myEntityManager; @Autowired - private IForcedIdDao myForcedIdDao; + protected IForcedIdDao myForcedIdDao; @Autowired private PlatformTransactionManager myPlatformTransactionManager; @@ -216,15 +218,17 @@ public abstract class BaseHapiFhirDao implements IDao { @Autowired private ISearchResultDao mySearchResultDao; - protected void createForcedIdIfNeeded(ResourceTable entity, IIdType id) { - if (id.isEmpty() == false && id.hasIdPart()) { - if (isValidPid(id)) { + protected void createForcedIdIfNeeded(ResourceTable theEntity, IIdType theId) { + if (theId.isEmpty() == false && theId.hasIdPart()) { + if (isValidPid(theId)) { return; } + ForcedId fid = new ForcedId(); - fid.setForcedId(id.getIdPart()); - fid.setResource(entity); - entity.setForcedId(fid); + fid.setResourceType(theEntity.getResourceType()); + fid.setForcedId(theId.getIdPart()); + fid.setResource(theEntity); + theEntity.setForcedId(fid); } } @@ -309,7 +313,7 @@ public abstract class BaseHapiFhirDao implements IDao { } Long valueOf; try { - valueOf = translateForcedIdToPid(nextValue.getReferenceElement()); + valueOf = translateForcedIdToPid(typeString, id); } catch (ResourceNotFoundException e) { String resName = getContext().getResourceDefinition(type).getName(); throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit); @@ -496,7 +500,7 @@ public abstract class BaseHapiFhirDao implements IDao { if (theResourceName != null) { Predicate typePredicate = builder.equal(from.get("myResourceType"), theResourceName); if (theResourceId != null) { - cq.where(typePredicate, builder.equal(from.get("myResourceId"), translateForcedIdToPid(theResourceId))); + cq.where(typePredicate, builder.equal(from.get("myResourceId"), translateForcedIdToPid(theResourceName, theResourceId.getIdPart()))); } else { cq.where(typePredicate); } @@ -509,6 +513,7 @@ public abstract class BaseHapiFhirDao implements IDao { } } + protected DaoConfig getConfig() { return myConfig; } @@ -1083,16 +1088,24 @@ public abstract class BaseHapiFhirDao implements IDao { return myContext.getResourceDefinition(theResource).getName(); } - protected Long translateForcedIdToPid(IIdType theId) { - return translateForcedIdToPid(theId, myEntityManager); + protected List translateForcedIdToPids(IIdType theId) { + return translateForcedIdToPids(theId, myForcedIdDao); } - protected String translatePidIdToForcedId(Long theId) { + protected static Long translateForcedIdToPid(String theResourceName, String theResourceId, IForcedIdDao theForcedIdDao) { + return translateForcedIdToPids(new IdDt(theResourceName, theResourceId), theForcedIdDao).get(0); + } + + protected Long translateForcedIdToPid(String theResourceName, String theResourceId) { + return translateForcedIdToPids(new IdDt(theResourceName, theResourceId), myForcedIdDao).get(0); + } + + protected String translatePidIdToForcedId(String theResourceType, Long theId) { ForcedId forcedId = myForcedIdDao.findByResourcePid(theId); if (forcedId != null) { - return forcedId.getForcedId(); + return forcedId.getResourceType() + '/' + forcedId.getForcedId(); } else { - return theId.toString(); + return theResourceType + '/' + theId.toString(); } } @@ -1247,7 +1260,7 @@ public abstract class BaseHapiFhirDao implements IDao { throw new InvalidRequestException(msg); } Long next = matches.iterator().next(); - String newId = resourceTypeString + '/' + translatePidIdToForcedId(next); + String newId = translatePidIdToForcedId(resourceTypeString, next); ourLog.info("Replacing inline match URL[{}] with ID[{}}", nextId.getValue(), newId); nextRef.setReference(newId); } @@ -1557,15 +1570,26 @@ public abstract class BaseHapiFhirDao implements IDao { return retVal; } - static Long translateForcedIdToPid(IIdType theId, EntityManager entityManager) { + static List translateForcedIdToPids(IIdType theId, IForcedIdDao theForcedIdDao) { + Validate.isTrue(theId.hasIdPart()); + if (isValidPid(theId)) { - return theId.getIdPartAsLong(); + return Collections.singletonList(theId.getIdPartAsLong()); } else { - TypedQuery q = entityManager.createNamedQuery("Q_GET_FORCED_ID", ForcedId.class); - q.setParameter("ID", theId.getIdPart()); - try { - return q.getSingleResult().getResourcePid(); - } catch (NoResultException e) { + List forcedId; + if (theId.hasResourceType()) { + forcedId = theForcedIdDao.findByTypeAndForcedId(theId.getResourceType(), theId.getIdPart()); + } else { + forcedId = theForcedIdDao.findByForcedId(theId.getIdPart()); + } + + if (forcedId.isEmpty() == false) { + List retVal = new ArrayList(forcedId.size()); + for (ForcedId next : forcedId) { + retVal.add(next.getResourcePid()); + } + return retVal; + } else { throw new ResourceNotFoundException(theId); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 8b05e923960..9c7032f4892 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -305,7 +305,7 @@ public abstract class BaseHapiFhirResourceDao extends B if (entity.getForcedId() != null) { try { - translateForcedIdToPid(theResource.getIdElement()); + translateForcedIdToPid(getResourceName(), theResource.getIdElement().getIdPart()); throw new UnprocessableEntityException(getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "duplicateCreateForcedId", theResource.getIdElement().getIdPart())); } catch (ResourceNotFoundException e) { // good, this ID doesn't exist so we can create it @@ -750,12 +750,13 @@ public abstract class BaseHapiFhirResourceDao extends B return entity; } + @Override public BaseHasResource readEntity(IIdType theId, boolean theCheckForForcedId) { validateResourceTypeAndThrowIllegalArgumentException(theId); - Long pid = translateForcedIdToPid(theId); + Long pid = translateForcedIdToPid(getResourceName(), theId.getIdPart()); BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid); if (entity == null) { @@ -794,7 +795,7 @@ public abstract class BaseHapiFhirResourceDao extends B } protected ResourceTable readEntityLatestVersion(IIdType theId) { - ResourceTable entity = myEntityManager.find(ResourceTable.class, translateForcedIdToPid(theId)); + ResourceTable entity = myEntityManager.find(ResourceTable.class, translateForcedIdToPid(getResourceName(), theId.getIdPart())); if (entity == null) { throw new ResourceNotFoundException(theId); } @@ -854,7 +855,7 @@ public abstract class BaseHapiFhirResourceDao extends B ActionRequestDetails requestDetails = new ActionRequestDetails(null, getResourceName(), getContext(), theParams.getRequestDetails()); notifyInterceptors(RestOperationTypeEnum.SEARCH_TYPE, requestDetails); - SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao); + SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao, myForcedIdDao); builder.setType(getResourceType(), getResourceName()); return builder.search(theParams); } @@ -880,7 +881,7 @@ public abstract class BaseHapiFhirResourceDao extends B @Override public Set searchForIdsWithAndOr(SearchParameterMap theParams, DateRangeParam theLastUpdated) { - SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao); + SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao, myForcedIdDao); builder.setType(getResourceType(), getResourceName()); builder.searchForIdsWithAndOr(theParams, theLastUpdated); return builder.doGetPids(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java index 234703684fa..21eb91a5093 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java @@ -42,6 +42,8 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; +import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; +import ca.uhn.fhir.jpa.entity.ForcedId; import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.util.ReindexFailureException; import ca.uhn.fhir.jpa.util.StopWatch; @@ -60,6 +62,9 @@ public abstract class BaseHapiFhirSystemDao extends BaseHapiFhirDao extends BaseHapiFhirDao extends BaseHapiFhirDaoim paramMap.add("_id", new StringParam(theId.getIdPart())); } - SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao); + SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao, myForcedIdDao); builder.setType(getResourceType(), getResourceName()); return builder.search(paramMap); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java index edda8f9d7a2..2450170f735 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java @@ -55,7 +55,6 @@ import com.google.common.collect.Sets; import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.dstu.resource.BaseResource; -import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -193,7 +192,7 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao implem Long pid = null; if (theParams.get(BaseResource.SP_RES_ID) != null) { StringParam idParm = (StringParam) theParams.get(BaseResource.SP_RES_ID).get(0).get(0); - pid = BaseHapiFhirDao.translateForcedIdToPid(new IdDt(idParm.getValue()), myEntityManager); + pid = BaseHapiFhirDao.translateForcedIdToPid(theResourceName, idParm.getValue(), myForcedIdDao); } Long referencingPid = pid; @@ -222,8 +221,7 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao implem if (contextParts.length != 3 || "Patient".equals(contextParts[0]) == false || "$everything".equals(contextParts[2]) == false) { throw new InvalidRequestException("Invalid context: " + theContext); } - IdDt contextId = new IdDt(contextParts[0], contextParts[1]); - Long pid = BaseHapiFhirDao.translateForcedIdToPid(contextId, myEntityManager); + Long pid = BaseHapiFhirDao.translateForcedIdToPid(contextParts[0], contextParts[1], myForcedIdDao); FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index d187c159d54..0f02358344c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -63,6 +63,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; @@ -77,6 +78,7 @@ import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeChildResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; import ca.uhn.fhir.jpa.entity.BaseHasResource; @@ -138,6 +140,7 @@ public class SearchBuilder { private BaseHapiFhirDao myCallingDao; private FhirContext myContext; private EntityManager myEntityManager; + private IForcedIdDao myForcedIdDao; private SearchParameterMap myParams; private Collection myPids; private PlatformTransactionManager myPlatformTransactionManager; @@ -149,7 +152,7 @@ public class SearchBuilder { private ISearchResultDao mySearchResultDao; public SearchBuilder(FhirContext theFhirContext, EntityManager theEntityManager, PlatformTransactionManager thePlatformTransactionManager, IFulltextSearchSvc theSearchDao, - ISearchResultDao theSearchResultDao, BaseHapiFhirDao theDao, IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao) { + ISearchResultDao theSearchResultDao, BaseHapiFhirDao theDao, IResourceIndexedSearchParamUriDao theResourceIndexedSearchParamUriDao, IForcedIdDao theForcedIdDao) { myContext = theFhirContext; myEntityManager = theEntityManager; myPlatformTransactionManager = thePlatformTransactionManager; @@ -157,6 +160,7 @@ public class SearchBuilder { mySearchResultDao = theSearchResultDao; myCallingDao = theDao; myResourceIndexedSearchParamUriDao = theResourceIndexedSearchParamUriDao; + myForcedIdDao = theForcedIdDao; } private void addPredicateComposite(RuntimeSearchParam theParamDef, List theNextAnd) { @@ -548,16 +552,13 @@ public class SearchBuilder { if (isBlank(ref.getChain())) { String resourceId = ref.getValueAsQueryToken(myContext); - if (resourceId.contains("/")) { - IIdType dt = new IdDt(resourceId); - resourceId = dt.getIdPart(); + IIdType dt = new IdDt(resourceId); + List targetPid = myCallingDao.translateForcedIdToPids(dt); + for (Long next : targetPid) { + ourLog.debug("Searching for resource link with target PID: {}", next); + Predicate eq = builder.equal(from.get("myTargetResourcePid"), next); + codePredicates.add(eq); } - Long targetPid = myCallingDao.translateForcedIdToPid(new IdDt(resourceId)); - ourLog.debug("Searching for resource link with target PID: {}", targetPid); - Predicate eq = builder.equal(from.get("myTargetResourcePid"), targetPid); - - codePredicates.add(eq); - } else { String paramPath = myContext.getResourceDefinition(myResourceType).getSearchParam(theParamName).getPath(); @@ -889,22 +890,6 @@ public class SearchBuilder { } - private List createPredicateTagList(Path theDefJoin, CriteriaBuilder theBuilder, TagTypeEnum theTagType, List> theTokens) { - Predicate typePrediate = theBuilder.equal(theDefJoin.get("myTagType"), theTagType); - - List orPredicates = Lists.newArrayList(); - for (Pair next : theTokens) { - Predicate codePrediate = theBuilder.equal(theDefJoin.get("myCode"), next.getRight()); - if (isNotBlank(next.getLeft())) { - Predicate systemPrediate = theBuilder.equal(theDefJoin.get("mySystem"), next.getLeft()); - orPredicates.add(theBuilder.and(typePrediate, systemPrediate, codePrediate)); - } else { - orPredicates.add(theBuilder.and(typePrediate, codePrediate)); - } - } - return orPredicates; - } - private void addPredicateToken(String theParamName, List theList) { if (Boolean.TRUE.equals(theList.get(0).getMissing())) { @@ -1238,6 +1223,22 @@ public class SearchBuilder { return singleCode; } + private List createPredicateTagList(Path theDefJoin, CriteriaBuilder theBuilder, TagTypeEnum theTagType, List> theTokens) { + Predicate typePrediate = theBuilder.equal(theDefJoin.get("myTagType"), theTagType); + + List orPredicates = Lists.newArrayList(); + for (Pair next : theTokens) { + Predicate codePrediate = theBuilder.equal(theDefJoin.get("myCode"), next.getRight()); + if (isNotBlank(next.getLeft())) { + Predicate systemPrediate = theBuilder.equal(theDefJoin.get("mySystem"), next.getLeft()); + orPredicates.add(theBuilder.and(typePrediate, systemPrediate, codePrediate)); + } else { + orPredicates.add(theBuilder.and(typePrediate, codePrediate)); + } + } + return orPredicates; + } + private Predicate createPredicateToken(IQueryParameterType theParameter, String theParamName, CriteriaBuilder theBuilder, From theFrom) { String code; @@ -1441,30 +1442,6 @@ public class SearchBuilder { } } - private void reinitializeSearch() { - mySearchEntity = new Search(); - mySearchEntity.setUuid(UUID.randomUUID().toString()); - mySearchEntity.setCreated(new Date()); - mySearchEntity.setTotalCount(-1); - mySearchEntity.setPreferredPageSize(myParams.getCount()); - mySearchEntity.setSearchType(myParams.getEverythingMode() != null ? SearchTypeEnum.EVERYTHING : SearchTypeEnum.SEARCH); - mySearchEntity.setLastUpdated(myParams.getLastUpdated()); - - for (Include next : myParams.getIncludes()) { - mySearchEntity.getIncludes().add(new SearchInclude(mySearchEntity, next.getValue(), false, next.isRecurse())); - } - for (Include next : myParams.getRevIncludes()) { - mySearchEntity.getIncludes().add(new SearchInclude(mySearchEntity, next.getValue(), true, next.isRecurse())); - } - - if (myParams.isPersistResults()) { - myEntityManager.persist(mySearchEntity); - for (SearchInclude next : mySearchEntity.getIncludes()) { - myEntityManager.persist(next); - } - } - } - private IBundleProvider doReturnProvider() { if (myParams.isPersistResults()) { return new PersistedJpaBundleProvider(mySearchEntity.getUuid(), myCallingDao); @@ -1575,6 +1552,30 @@ public class SearchBuilder { } + private void reinitializeSearch() { + mySearchEntity = new Search(); + mySearchEntity.setUuid(UUID.randomUUID().toString()); + mySearchEntity.setCreated(new Date()); + mySearchEntity.setTotalCount(-1); + mySearchEntity.setPreferredPageSize(myParams.getCount()); + mySearchEntity.setSearchType(myParams.getEverythingMode() != null ? SearchTypeEnum.EVERYTHING : SearchTypeEnum.SEARCH); + mySearchEntity.setLastUpdated(myParams.getLastUpdated()); + + for (Include next : myParams.getIncludes()) { + mySearchEntity.getIncludes().add(new SearchInclude(mySearchEntity, next.getValue(), false, next.isRecurse())); + } + for (Include next : myParams.getRevIncludes()) { + mySearchEntity.getIncludes().add(new SearchInclude(mySearchEntity, next.getValue(), true, next.isRecurse())); + } + + if (myParams.isPersistResults()) { + myEntityManager.persist(mySearchEntity); + for (SearchInclude next : mySearchEntity.getIncludes()) { + myEntityManager.persist(next); + } + } + } + public IBundleProvider search(final SearchParameterMap theParams) { myParams = theParams; StopWatch w = new StopWatch(); @@ -1589,7 +1590,7 @@ public class SearchBuilder { Long pid = null; if (theParams.get(BaseResource.SP_RES_ID) != null) { StringParam idParm = (StringParam) theParams.get(BaseResource.SP_RES_ID).get(0).get(0); - pid = BaseHapiFhirDao.translateForcedIdToPid(new IdDt(idParm.getValue()), myEntityManager); + pid = BaseHapiFhirDao.translateForcedIdToPid(myResourceName, idParm.getValue(), myForcedIdDao); } if (theParams.containsKey(Constants.PARAM_CONTENT) || theParams.containsKey(Constants.PARAM_TEXT)) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java index 5abd3907349..985edda0ad9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IForcedIdDao.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.jpa.dao.data; +import java.util.List; + /* * #%L * HAPI FHIR JPA Server @@ -28,6 +30,12 @@ import ca.uhn.fhir.jpa.entity.ForcedId; public interface IForcedIdDao extends JpaRepository { + @Query("SELECT f FROM ForcedId f WHERE myForcedId = :forced_id") + public List findByForcedId(@Param("forced_id") String theForcedId); + + @Query("SELECT f FROM ForcedId f WHERE myResourceType = :resource_type AND myForcedId = :forced_id") + public List findByTypeAndForcedId(@Param("resource_type") String theResourceType, @Param("forced_id") String theForcedId); + @Query("SELECT f FROM ForcedId f WHERE f.myResourcePid = :resource_pid") public ForcedId findByResourcePid(@Param("resource_pid") Long theResourcePid); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java index ac54d9d93d3..e62e7f6ceb5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoPatientDstu3.java @@ -64,7 +64,7 @@ public class FhirResourceDaoPatientDstu3 extends FhirResourceDaoDstu3im paramMap.add("_id", new StringParam(theId.getIdPart())); } - SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao); + SearchBuilder builder = new SearchBuilder(getContext(), myEntityManager, myPlatformTransactionManager, mySearchDao, mySearchResultDao, this, myResourceIndexedSearchParamUriDao, myForcedIdDao); builder.setType(getResourceType(), getResourceName()); return builder.search(paramMap); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ForcedId.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ForcedId.java index e21fb56ef32..e8e69455f79 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ForcedId.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ForcedId.java @@ -25,21 +25,21 @@ import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; +import javax.persistence.Index; import javax.persistence.JoinColumn; -import javax.persistence.NamedQueries; -import javax.persistence.NamedQuery; import javax.persistence.OneToOne; import javax.persistence.Table; import javax.persistence.UniqueConstraint; +import org.hibernate.annotations.ColumnDefault; + //@formatter:off @Entity() @Table(name = "HFJ_FORCED_ID", uniqueConstraints = { - @UniqueConstraint(name = "IDX_FORCEDID", columnNames = {"FORCED_ID"}), - @UniqueConstraint(name = "IDX_FORCEDID_RESID", columnNames = {"RESOURCE_PID"}) -}) -@NamedQueries(value = { - @NamedQuery(name = "Q_GET_FORCED_ID", query = "SELECT f FROM ForcedId f WHERE myForcedId = :ID") + @UniqueConstraint(name = "IDX_FORCEDID_RESID", columnNames = {"RESOURCE_PID"}), + @UniqueConstraint(name = "IDX_FORCEDID_TYPE_RESID", columnNames = {"RESOURCE_TYPE", "RESOURCE_PID"}) +}, indexes= { + @Index(name = "IDX_FORCEDID", columnList = "FORCED_ID"), }) //@formatter:on public class ForcedId { @@ -61,6 +61,17 @@ public class ForcedId { @Column(name = "RESOURCE_PID", nullable = false, updatable = false, insertable=false) private Long myResourcePid; + @ColumnDefault("''") + @Column(name = "RESOURCE_TYPE", nullable = true, length = 100, updatable = false) + private String myResourceType; + + /** + * Constructor + */ + public ForcedId() { + super(); + } + public String getForcedId() { return myForcedId; } @@ -68,7 +79,7 @@ public class ForcedId { public ResourceTable getResource() { return myResource; } - + public Long getResourcePid() { if (myResourcePid==null) { return myResource.getId(); @@ -76,6 +87,10 @@ public class ForcedId { return myResourcePid; } + public String getResourceType() { + return myResourceType; + } + public void setForcedId(String theForcedId) { myForcedId = theForcedId; } @@ -92,4 +107,8 @@ public class ForcedId { myResource = theResourcePid; } + public void setResourceType(String theResourceType) { + myResourceType = theResourceType; + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTable.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTable.java index c761794a3e6..458ec502149 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTable.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTable.java @@ -101,8 +101,12 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl @Override public IdDt getIdDt() { - Object id = getForcedId() == null ? getResourceId() : getForcedId().getForcedId(); - return new IdDt(getResourceType() + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + getVersion()); + if (getForcedId() == null) { + Long id = myResourceId; + return new IdDt(myResourceType + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + getVersion()); + } else { + return new IdDt(getForcedId().getResourceType() + '/' + getForcedId().getForcedId() + '/' + Constants.PARAM_HISTORY + '/' + getVersion()); + } } public Long getResourceId() { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java index f8e55eb09bf..51d19b7b54f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java @@ -246,8 +246,12 @@ public class ResourceTable extends BaseHasResource implements Serializable { @Override public IdDt getIdDt() { - Object id = getForcedId() == null ? myId : getForcedId().getForcedId(); - return new IdDt(myResourceType + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + myVersion); + if (getForcedId() == null) { + Long id = myId; + return new IdDt(myResourceType + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + myVersion); + } else { + return new IdDt(getForcedId().getResourceType() + '/' + getForcedId().getForcedId() + '/' + Constants.PARAM_HISTORY + '/' + myVersion); + } } public Long getIndexStatus() { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java index 9b4adac25a9..67b628ce177 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java @@ -569,8 +569,8 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test { p.getManagingOrganization().setReference(new IdDt("Organization", id1.getIdPart())); myPatientDao.create(p, mySrd); fail(); - } catch (UnprocessableEntityException e) { - assertEquals("Resource contains reference to Organization/testCreateWithIllegalReference but resource with ID testCreateWithIllegalReference is actually of type Observation", e.getMessage()); + } catch (InvalidRequestException e) { + assertEquals("Resource Organization/testCreateWithIllegalReference not found, specified in path: Patient.managingOrganization", e.getMessage()); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java index 61368051f1e..e7fa54a8327 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java @@ -255,6 +255,25 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { } } + @Test + public void testCreateDifferentTypesWithSameForcedId() { + String idName = "forcedId"; + + Patient pat = new Patient(); + pat.setId(idName); + pat.addName().addFamily("FAM"); + IIdType patId = myPatientDao.update(pat, mySrd).getId(); + assertEquals("Patient/" + idName, patId.toUnqualifiedVersionless().getValue()); + + Observation obs = new Observation(); + obs.setId(idName); + obs.getCode().addCoding().setSystem("foo").setCode("testCreateDifferentTypesWithSameForcedId"); + IIdType obsId = myObservationDao.update(obs, mySrd).getId(); + assertEquals("Observation/" + idName, obsId.toUnqualifiedVersionless().getValue()); + + pat = myPatientDao.read(patId.toUnqualifiedVersionless(), mySrd); + obs = myObservationDao.read(obsId.toUnqualifiedVersionless(), mySrd); + } @Test public void testChoiceParamDateAlt() { @@ -672,8 +691,8 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test { p.getManagingOrganization().setReferenceElement(new IdType("Organization", id1.getIdPart())); myPatientDao.create(p, mySrd); fail(); - } catch (UnprocessableEntityException e) { - assertEquals("Resource contains reference to Organization/testCreateWithIllegalReference but resource with ID testCreateWithIllegalReference is actually of type Observation", e.getMessage()); + } catch (InvalidRequestException e) { + assertEquals("Resource Organization/testCreateWithIllegalReference not found, specified in path: Patient.managingOrganization", e.getMessage()); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/StaleSearchDeletingSvcDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/StaleSearchDeletingSvcDstu3Test.java index 921b6b9d473..32d18c7649b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/StaleSearchDeletingSvcDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/StaleSearchDeletingSvcDstu3Test.java @@ -14,6 +14,7 @@ import org.hl7.fhir.dstu3.model.Patient; import org.junit.AfterClass; import org.junit.Test; +import ca.uhn.fhir.rest.gclient.IClientExecutable; import ca.uhn.fhir.rest.gclient.IQuery; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; @@ -47,7 +48,7 @@ public class StaleSearchDeletingSvcDstu3Test extends BaseResourceProviderDstu3Te } //@formatter:off - IQuery search = ourClient + IClientExecutable, Bundle> search = ourClient .search() .forResource(Patient.class) .where(Patient.NAME.matches().value("Everything")) diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/SearchReturningProfiledResourceDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/SearchReturningProfiledResourceDstu2Test.java index f3dcafe72a9..449908b88d6 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/SearchReturningProfiledResourceDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/SearchReturningProfiledResourceDstu2Test.java @@ -27,10 +27,12 @@ import org.junit.Test; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.annotation.Description; +import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.annotation.OptionalParam; import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.util.PortUtil; @@ -41,16 +43,36 @@ public class SearchReturningProfiledResourceDstu2Test { private static CloseableHttpClient ourClient; private static FhirContext ourCtx = FhirContext.forDstu2(); - private static String ourLastMethod; - private static StringParam ourLastRef; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchReturningProfiledResourceDstu2Test.class); private static int ourPort; private static Server ourServer; - @Before - public void before() { - ourLastMethod = null; - ourLastRef = null; + @Test + public void testClientTypedRequest() throws Exception { + ourCtx = FhirContext.forDstu2(); + IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/"); + Bundle bundle = client.search().forResource(PatientProfileDstu2.class).returnBundle(Bundle.class).execute(); + + assertEquals(PatientProfileDstu2.class, bundle.getEntry().get(0).getResource().getClass()); + } + + @Test + public void testClientUntypedRequestWithHint() throws Exception { + ourCtx = FhirContext.forDstu2(); + ourCtx.setDefaultTypeForProfile("http://ahr.copa.inso.tuwien.ac.at/StructureDefinition/Patient", PatientProfileDstu2.class); + IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/"); + Bundle bundle = client.search().forResource(Patient.class).returnBundle(Bundle.class).execute(); + + assertEquals(PatientProfileDstu2.class, bundle.getEntry().get(0).getResource().getClass()); + } + + @Test + public void testClientUntypedRequestWithoutHint() throws Exception { + ourCtx = FhirContext.forDstu2(); + IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/"); + Bundle bundle = client.search().forResource(Patient.class).returnBundle(Bundle.class).execute(); + + assertEquals(Patient.class, bundle.getEntry().get(0).getResource().getClass()); } @Test @@ -60,12 +82,11 @@ public class SearchReturningProfiledResourceDstu2Test { String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent()); ourLog.info(responseContent); - + assertThat(responseContent, containsString("")); assertThat(responseContent, containsString("")); - - } + } @AfterClass public static void afterClassClearContext() throws Exception { @@ -96,6 +117,11 @@ public class SearchReturningProfiledResourceDstu2Test { public static class DummyPatientResourceProvider implements IResourceProvider { + @Override + public Class getResourceType() { + return Patient.class; + } + /** * Basic search, available for every resource. Allows search per id (use of read is better, though), fulltext * content @@ -118,24 +144,19 @@ public class SearchReturningProfiledResourceDstu2Test { //@formatter:on List result = new ArrayList(); - + PatientProfileDstu2 pp = new PatientProfileDstu2(); - + ResourceMetadataKeyEnum.PROFILES.put(pp, Collections.singletonList(new IdDt("http://foo"))); - + pp.setId("123"); pp.getOwningOrganization().setReference("Organization/456"); result.add(pp); - + ourLog.info("Search: Everything ok. Going to return results!"); return result; } - @Override - public Class getResourceType() { - return Patient.class; - } - } } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 6596f943ad1..57b8c6cdf3c 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -18,7 +18,12 @@ Deprecate fluent client search operations without an explicit declaration of the - bundle type being used + bundle type being used. This also means that in a client + .search()]]> + operation, the + .returnBundle(Bundle.class)]]> + needs to be the last statement before + .execute()]]> Server now respects the parameter _format=application/xml+fhir"]]> @@ -39,6 +44,13 @@ (the correct one is "id"). Previously _id was allowed because some early FHIR examples used that form, but this was never actually valid so it is now being removed. + + JPA server now allows "forced IDs" (ids containing non-numeric, client assigned IDs) + to use the same logical ID part on different resource types. E.g. A server may now have + both Patient/foo and Obervation/foo on the same server.
]]> + Note that existing databases will need to modify index "IDX_FORCEDID" as + it is no longer unique, and perform a reindexing pass. +