Allow forced IDs to be reused between resource types

This commit is contained in:
jamesagnew 2016-04-24 19:24:55 -04:00
parent 9631160942
commit d45a9b67dc
17 changed files with 251 additions and 122 deletions

View File

@ -113,7 +113,7 @@ public interface IQuery<T> extends IClientExecutable<IQuery<T>, T>, IBaseQuery<I
* Request that the client return the specified bundle type, e.g. <code>org.hl7.fhir.instance.model.Bundle.class</code>
* or <code>ca.uhn.fhir.model.dstu2.resource.Bundle.class</code>
*/
<B extends IBaseBundle> IQuery<B> returnBundle(Class<B> theClass);
<B extends IBaseBundle> IClientExecutable<IQuery<B>, B> returnBundle(Class<B> theClass);
/**
* {@inheritDoc}

View File

@ -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<T extends IBaseResource> implements IDao {
protected EntityManager myEntityManager;
@Autowired
private IForcedIdDao myForcedIdDao;
protected IForcedIdDao myForcedIdDao;
@Autowired
private PlatformTransactionManager myPlatformTransactionManager;
@ -216,15 +218,17 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> 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<T extends IBaseResource> 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<T extends IBaseResource> 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<T extends IBaseResource> implements IDao {
}
}
protected DaoConfig getConfig() {
return myConfig;
}
@ -1083,16 +1088,24 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return myContext.getResourceDefinition(theResource).getName();
}
protected Long translateForcedIdToPid(IIdType theId) {
return translateForcedIdToPid(theId, myEntityManager);
protected List<Long> 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<T extends IBaseResource> 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<T extends IBaseResource> implements IDao {
return retVal;
}
static Long translateForcedIdToPid(IIdType theId, EntityManager entityManager) {
static List<Long> translateForcedIdToPids(IIdType theId, IForcedIdDao theForcedIdDao) {
Validate.isTrue(theId.hasIdPart());
if (isValidPid(theId)) {
return theId.getIdPartAsLong();
return Collections.singletonList(theId.getIdPartAsLong());
} else {
TypedQuery<ForcedId> q = entityManager.createNamedQuery("Q_GET_FORCED_ID", ForcedId.class);
q.setParameter("ID", theId.getIdPart());
try {
return q.getSingleResult().getResourcePid();
} catch (NoResultException e) {
List<ForcedId> forcedId;
if (theId.hasResourceType()) {
forcedId = theForcedIdDao.findByTypeAndForcedId(theId.getResourceType(), theId.getIdPart());
} else {
forcedId = theForcedIdDao.findByForcedId(theId.getIdPart());
}
if (forcedId.isEmpty() == false) {
List<Long> retVal = new ArrayList<Long>(forcedId.size());
for (ForcedId next : forcedId) {
retVal.add(next.getResourcePid());
}
return retVal;
} else {
throw new ResourceNotFoundException(theId);
}
}

View File

@ -305,7 +305,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> 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<T extends IBaseResource> 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<T extends IBaseResource> 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<T extends IBaseResource> 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<T extends IBaseResource> extends B
@Override
public Set<Long> 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();

View File

@ -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<T, MT> extends BaseHapiFhirDao<IBase
@Autowired
private PlatformTransactionManager myTxManager;
@Autowired
private IForcedIdDao myForcedIdDao;
@Transactional(propagation = Propagation.REQUIRED)
@Override
@ -98,6 +103,18 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
for (ResourceTable resourceTable : resources) {
try {
/*
* This part is because from HAPI 1.5 - 1.6 we changed the format of
* forced ID to be "type/id" instead of just "id"
*/
ForcedId forcedId = resourceTable.getForcedId();
if (forcedId != null) {
if (forcedId.getResourceType() == null) {
forcedId.setResourceType(resourceTable.getResourceType());
myForcedIdDao.save(forcedId);
}
}
final IBaseResource resource = toResource(resourceTable, false);
@SuppressWarnings("rawtypes")
@ -212,7 +229,7 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
protected ResourceTable tryToLoadEntity(IdDt nextId) {
ResourceTable entity;
try {
Long pid = translateForcedIdToPid(nextId);
Long pid = translateForcedIdToPid(nextId.getResourceType(), nextId.getIdPart());
entity = myEntityManager.find(ResourceTable.class, pid);
} catch (ResourceNotFoundException e) {
entity = null;

View File

@ -61,7 +61,7 @@ public class FhirResourceDaoPatientDstu2 extends FhirResourceDaoDstu2<Patient>im
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);
}

View File

@ -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<IBaseResource> 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<IBaseResource> 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);

View File

@ -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<Long> 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<? extends IQueryParameterType> 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<Long> 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<Predicate> createPredicateTagList(Path<TagDefinition> theDefJoin, CriteriaBuilder theBuilder, TagTypeEnum theTagType, List<Pair<String, String>> theTokens) {
Predicate typePrediate = theBuilder.equal(theDefJoin.get("myTagType"), theTagType);
List<Predicate> orPredicates = Lists.newArrayList();
for (Pair<String, String> 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<? extends IQueryParameterType> theList) {
if (Boolean.TRUE.equals(theList.get(0).getMissing())) {
@ -1238,6 +1223,22 @@ public class SearchBuilder {
return singleCode;
}
private List<Predicate> createPredicateTagList(Path<TagDefinition> theDefJoin, CriteriaBuilder theBuilder, TagTypeEnum theTagType, List<Pair<String, String>> theTokens) {
Predicate typePrediate = theBuilder.equal(theDefJoin.get("myTagType"), theTagType);
List<Predicate> orPredicates = Lists.newArrayList();
for (Pair<String, String> 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<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> 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)) {

View File

@ -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<ForcedId, Long> {
@Query("SELECT f FROM ForcedId f WHERE myForcedId = :forced_id")
public List<ForcedId> findByForcedId(@Param("forced_id") String theForcedId);
@Query("SELECT f FROM ForcedId f WHERE myResourceType = :resource_type AND myForcedId = :forced_id")
public List<ForcedId> 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);

View File

@ -64,7 +64,7 @@ public class FhirResourceDaoPatientDstu3 extends FhirResourceDaoDstu3<Patient>im
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);
}

View File

@ -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;
}
}

View File

@ -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() {

View File

@ -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() {

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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<Bundle> search = ourClient
IClientExecutable<IQuery<Bundle>, Bundle> search = ourClient
.search()
.forResource(Patient.class)
.where(Patient.NAME.matches().value("Everything"))

View File

@ -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("<profile value=\"http://foo\"/>"));
assertThat(responseContent, containsString("<profile value=\"http://ahr.copa.inso.tuwien.ac.at/StructureDefinition/Patient\"/>"));
}
}
@AfterClass
public static void afterClassClearContext() throws Exception {
@ -96,6 +117,11 @@ public class SearchReturningProfiledResourceDstu2Test {
public static class DummyPatientResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> 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<Patient> result = new ArrayList<Patient>();
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<? extends IBaseResource> getResourceType() {
return Patient.class;
}
}
}

View File

@ -18,7 +18,12 @@
</action>
<action type="add">
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
<![CDATA[<code>.search()</code>]]>
operation, the
<![CDATA[<code>.returnBundle(Bundle.class)</code>]]>
needs to be the last statement before
<![CDATA[<code>.execute()</code>]]>
</action>
<action type="add" issue="346">
Server now respects the parameter <![CDATA[<code>_format=application/xml+fhir"</code>]]>
@ -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.
</action>
<action type="add">
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.<![CDATA[<br/><br/>]]>
Note that existing databases will need to modify index "IDX_FORCEDID" as
it is no longer unique, and perform a reindexing pass.
</action>
</release>
<release version="1.5" date="2016-04-20">
<action type="fix" issue="339">