Fix #148 - Add _count support to everything operation

This commit is contained in:
jamesagnew 2015-04-05 14:53:15 -04:00
parent 8c37973a78
commit 11507ef97c
16 changed files with 620 additions and 1381 deletions

View File

@ -246,12 +246,11 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
}
}
Integer count = RestfulServerUtils.extractCountParameter(theRequest.getServletRequest());
boolean respondGzip = theRequest.isRespondGzip();
HttpServletResponse response = theRequest.getServletResponse();
Object resultObj = invokeServer(theRequest, params);
Integer count = RestfulServerUtils.extractCountParameter(theRequest.getServletRequest());
boolean respondGzip = theRequest.isRespondGzip();
HttpServletResponse response = theRequest.getServletResponse();
switch (getReturnType()) {
case BUNDLE: {
@ -283,12 +282,15 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
RestfulServerUtils.streamResponseAsResource(theServer, response, resource, responseEncoding, prettyPrint, requestIsBrowser, narrativeMode, respondGzip, theRequest.getFhirServerBase());
break;
} else {
Set<Include> includes = getRequestIncludesFromParams(params);
Set<Include> includes = getRequestIncludesFromParams(params);
IBundleProvider result = (IBundleProvider) resultObj;
if (count == null) {
count = result.preferredPageSize();
}
IVersionSpecificBundleFactory bundleFactory = theServer.getFhirContext().newBundleFactory();
bundleFactory.initializeBundleFromBundleProvider(theServer, result, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, 0, count, null,
getResponseBundleType(), includes);
bundleFactory.initializeBundleFromBundleProvider(theServer, result, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, 0, count, null, getResponseBundleType(), includes);
Bundle bundle = bundleFactory.getDstu1Bundle();
if (bundle != null) {
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) {

View File

@ -208,6 +208,11 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
public int size() {
return resources.size();
}
@Override
public Integer preferredPageSize() {
return null;
}
};
}

View File

@ -56,6 +56,11 @@ public class BundleProviders {
public InstantDt getPublished() {
return published;
}
@Override
public Integer preferredPageSize() {
return null;
}
};
}

View File

@ -26,21 +26,24 @@ import java.util.List;
import java.util.Set;
import java.util.UUID;
import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.util.ResourceReferenceInfo;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.ResourceReferenceInfo;
public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
@ -253,7 +256,7 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
}
@Override
public IBaseResource getResourceBundle() {
public IResource getResourceBundle() {
return null;
}

View File

@ -28,14 +28,37 @@ import ca.uhn.fhir.model.primitive.InstantDt;
public interface IBundleProvider {
/**
* Load the given collection of resources by index, plus any additional resources per the
* server's processing rules (e.g. _include'd resources, OperationOutcome, etc.). For example,
* if the method is invoked with index 0,10 the method might return 10 search results, plus an
* additional 20 resources which matched a client's _include specification.
*
* @param theFromIndex The low index (inclusive) to return
* @param theToIndex The high index (exclusive) to return
* @return A list of resources. The size of this list must be at least <code>theToIndex - theFromIndex</code>.
*/
List<IResource> getResources(int theFromIndex, int theToIndex);
/**
* Optionally may be used to signal a preferred page size to the server, e.g. because
* the implementing code recognizes that the resources which will be returned by this
* implementation are expensive to load so a smaller page size should be used. The value
* returned by this method will only be used if the client has not explicitly requested
* a page size.
*
* @return Returns the preferred page size or <code>null</code>
*/
Integer preferredPageSize();
/**
* Returns the total number of results which match the given query (exclusive of any
* _include's or OperationOutcome)
*/
int size();
/**
* Returns the instant as of which this result was valid
*/
InstantDt getPublished();
}

View File

@ -23,11 +23,9 @@ package ca.uhn.fhir.rest.server;
import java.util.List;
import java.util.Set;
import ca.uhn.fhir.model.api.Include;
import org.hl7.fhir.instance.model.IBaseResource;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
/**
@ -41,11 +39,11 @@ public interface IVersionSpecificBundleFactory {
void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theCompleteUrl, Integer theTotalResults, BundleTypeEnum theBundleType);
void initializeBundleFromBundleProvider(RestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint,
int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType, Set<Include> theIncludes);
int theOffset, Integer theCount, String theSearchId, BundleTypeEnum theBundleType, Set<Include> theIncludes);
Bundle getDstu1Bundle();
IBaseResource getResourceBundle();
IResource getResourceBundle();
void initializeBundleFromResourceList(String theAuthor, List<IResource> theResult, String theServerBase, String theCompleteUrl, int theTotalResults, BundleTypeEnum theBundleType);

View File

@ -0,0 +1,98 @@
package ca.uhn.fhir.rest.server;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.IBaseResource;
/**
* Used by {@link IBundleProvider} to provide a single page worth of results.
*
* If the server chooses to, it may return a different number of matching results to the number that the user requested.
* For example, if the client requested 100 results but the server decided to return only 10 (perhaps because they were
* very large), this value should be set to 10. Note that this count refers only to resources which are included in the
* indexes provided to {@link IBundleProvider#getResources(int, int)}, so it should not reflect any additional results
* added to the response as a result of _include parameters, OperationOutcome's etc.
*/
public class ResponseResourceList {
/**
* Singleton unmodifiable empty list
*/
public static final ResponseResourceList EMPTY = new EmptyResponseResourceList();
private List<IBaseResource> myIncludeResults;
private List<IBaseResource> myMatchResults;
/**
* Adds an "include" results. Include results are results which are added as a result of <code>_include</code>
* directives in search requests.
*/
public void addIncludeResults(IBaseResource theIncludeResult) {
if (myIncludeResults == null) {
myIncludeResults = new ArrayList<IBaseResource>();
}
myIncludeResults.add(theIncludeResult);
}
/**
* Adds a "match" result. A match result is a result added as a direct result of the operation in question. E.g. for
* a search invocation a match result would be a result which directly matched the search criteria. For a history
* invocation it would be a historical version of a resource or the current version.
*/
public void addMatchResult(IBaseResource theResource) {
Validate.notNull(theResource, "theResource must not be null");
if (myMatchResults == null) {
myMatchResults = new ArrayList<IBaseResource>();
}
myMatchResults.add(theResource);
}
public List<IBaseResource> getIncludeResults() {
return myIncludeResults;
}
public List<IBaseResource> getMatchResults() {
return myMatchResults;
}
/**
* Sets the "include" results. Include results are results which are added as a result of <code>_include</code>
* directives in search requests.
*/
public void setIncludeResults(List<IBaseResource> theIncludeResults) {
myIncludeResults = theIncludeResults;
}
/**
* Sets the "match" results. A match result is a result added as a direct result of the operation in question. E.g.
* for a search invocation a match result would be a result which directly matched the search criteria. For a
* history invocation it would be a historical version of a resource or the current version.
*/
public void setMatchResults(List<IBaseResource> theMatchResults) {
myMatchResults = theMatchResults;
}
private static final class EmptyResponseResourceList extends ResponseResourceList {
@Override
public void addIncludeResults(IBaseResource theIncludeResult) {
throw new UnsupportedOperationException();
}
@Override
public void addMatchResult(IBaseResource theResource) {
throw new UnsupportedOperationException();
}
@Override
public void setIncludeResults(List<IBaseResource> theIncludeResults) {
throw new UnsupportedOperationException();
}
@Override
public void setMatchResults(List<IBaseResource> theMatchResults) {
throw new UnsupportedOperationException();
}
}
}

View File

@ -59,5 +59,10 @@ public class SimpleBundleProvider implements IBundleProvider {
public InstantDt getPublished() {
return InstantDt.withCurrentTime();
}
@Override
public Integer preferredPageSize() {
return null;
}
}

View File

@ -494,6 +494,11 @@ public abstract class BaseFhirDao implements IDao {
public int size() {
return tuples.size();
}
@Override
public Integer preferredPageSize() {
return null;
}
};
}

View File

@ -659,6 +659,18 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
return new HashSet<Long>(q.getResultList());
}
private List<IResource> addResourcesAsIncludesById(List<IResource> theListToPopulate, Set<IdDt> includePids, List<IResource> resources) {
if (!includePids.isEmpty()) {
ourLog.info("Loading {} included resources", includePids.size());
resources = loadResourcesById(includePids);
for (IResource next : resources) {
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(next, BundleEntrySearchModeEnum.INCLUDE);
}
theListToPopulate.addAll(resources);
}
return resources;
}
@Override
public void addTag(IdDt theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
StopWatch w = new StopWatch();
@ -713,50 +725,6 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
return doCreate(theResource, theIfNoneExist, thePerformIndexing);
}
private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing) {
StopWatch w = new StopWatch();
ResourceTable entity = new ResourceTable();
entity.setResourceType(toResourceName(theResource));
if (isNotBlank(theIfNoneExist)) {
Set<Long> match = processMatchUrl(theIfNoneExist, myResourceType);
if (match.size() > 1) {
String msg = getContext().getLocalizer().getMessage(BaseFhirDao.class, "transactionOperationWithMultipleMatchFailure", "CREATE", theIfNoneExist, match.size());
throw new PreconditionFailedException(msg);
} else if (match.size() == 1) {
Long pid = match.iterator().next();
entity = myEntityManager.find(ResourceTable.class, pid);
return toMethodOutcome(entity, theResource).setCreated(false);
}
}
if (theResource.getId().isEmpty() == false) {
if (isValidPid(theResource.getId())) {
throw new UnprocessableEntityException(
"This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID");
}
createForcedIdIfNeeded(entity, theResource.getId());
if (entity.getForcedId() != null) {
try {
translateForcedIdToPid(theResource.getId());
throw new UnprocessableEntityException(getContext().getLocalizer().getMessage(BaseFhirResourceDao.class, "duplicateCreateForcedId", theResource.getId().getIdPart()));
} catch (ResourceNotFoundException e) {
// good, this ID doesn't exist so we can create it
}
}
}
updateEntity(theResource, entity, false, null, thePerformIndexing, true);
DaoMethodOutcome outcome = toMethodOutcome(entity, theResource).setCreated(true);
notifyWriteCompleted();
ourLog.info("Processed create on {} in {}ms", myResourceName, w.getMillisAndRestart());
return outcome;
}
private Predicate createCompositeParamPart(CriteriaBuilder builder, Root<ResourceTable> from, RuntimeSearchParam left, IQueryParameterType leftValue) {
Predicate retVal = null;
switch (left.getParamType()) {
@ -979,6 +947,50 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
return toMethodOutcome(savedEntity, null);
}
private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing) {
StopWatch w = new StopWatch();
ResourceTable entity = new ResourceTable();
entity.setResourceType(toResourceName(theResource));
if (isNotBlank(theIfNoneExist)) {
Set<Long> match = processMatchUrl(theIfNoneExist, myResourceType);
if (match.size() > 1) {
String msg = getContext().getLocalizer().getMessage(BaseFhirDao.class, "transactionOperationWithMultipleMatchFailure", "CREATE", theIfNoneExist, match.size());
throw new PreconditionFailedException(msg);
} else if (match.size() == 1) {
Long pid = match.iterator().next();
entity = myEntityManager.find(ResourceTable.class, pid);
return toMethodOutcome(entity, theResource).setCreated(false);
}
}
if (theResource.getId().isEmpty() == false) {
if (isValidPid(theResource.getId())) {
throw new UnprocessableEntityException(
"This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID");
}
createForcedIdIfNeeded(entity, theResource.getId());
if (entity.getForcedId() != null) {
try {
translateForcedIdToPid(theResource.getId());
throw new UnprocessableEntityException(getContext().getLocalizer().getMessage(BaseFhirResourceDao.class, "duplicateCreateForcedId", theResource.getId().getIdPart()));
} catch (ResourceNotFoundException e) {
// good, this ID doesn't exist so we can create it
}
}
}
updateEntity(theResource, entity, false, null, thePerformIndexing, true);
DaoMethodOutcome outcome = toMethodOutcome(entity, theResource).setCreated(true);
notifyWriteCompleted();
ourLog.info("Processed create on {} in {}ms", myResourceName, w.getMillisAndRestart());
return outcome;
}
@Override
public TagList getAllResourceTags() {
StopWatch w = new StopWatch();
@ -987,6 +999,8 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
return tags;
}
protected abstract List<Object> getIncludeValues(FhirTerser theTerser, Include theInclude, IResource theResource, RuntimeResourceDefinition theResourceDef);
public Class<T> getResourceType() {
return myResourceType;
}
@ -1092,6 +1106,11 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
return retVal;
}
@Override
public Integer preferredPageSize() {
return null;
}
@Override
public int size() {
return count;
@ -1140,6 +1159,159 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
}
}
protected void loadReverseIncludes(List<Long> theMatches, Set<Include> theRevIncludes) {
if (theMatches.size() == 0) {
return;
}
HashSet<Long> pidsToInclude = new HashSet<Long>();
for (Include nextInclude : theRevIncludes) {
boolean matchAll = "*".equals(nextInclude.getValue());
if (matchAll) {
String sql = "SELECT r FROM ResourceLink r WHERE r.myTargetResourcePid IN (:target_pids)";
TypedQuery<ResourceLink> q = myEntityManager.createQuery(sql, ResourceLink.class);
q.setParameter("target_pids", theMatches);
List<ResourceLink> results = q.getResultList();
for (ResourceLink resourceLink : results) {
pidsToInclude.add(resourceLink.getSourceResourcePid());
}
} else {
int colonIdx = nextInclude.getValue().indexOf(':');
if (colonIdx < 2) {
continue;
}
String resType = nextInclude.getValue().substring(0, colonIdx);
RuntimeResourceDefinition def = getContext().getResourceDefinition(resType);
if (def == null) {
ourLog.warn("Unknown resource type in _revinclude=" + nextInclude.getValue());
continue;
}
String paramName = nextInclude.getValue().substring(colonIdx + 1);
RuntimeSearchParam param = def.getSearchParam(paramName);
if (param == null) {
ourLog.warn("Unknown param name in _revinclude=" + nextInclude.getValue());
continue;
}
for (String nextPath : param.getPathsSplit()) {
String sql = "SELECT r FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r.myTargetResourcePid IN (:target_pids)";
TypedQuery<ResourceLink> q = myEntityManager.createQuery(sql, ResourceLink.class);
q.setParameter("src_path", nextPath);
q.setParameter("target_pids", theMatches);
List<ResourceLink> results = q.getResultList();
for (ResourceLink resourceLink : results) {
pidsToInclude.add(resourceLink.getSourceResourcePid());
}
}
}
}
theMatches.addAll(pidsToInclude);
}
@Override
public MetaDt metaAddOperation(IdDt theResourceId, MetaDt theMetaAdd) {
StopWatch w = new StopWatch();
BaseHasResource entity = readEntity(theResourceId);
if (entity == null) {
throw new ResourceNotFoundException(theResourceId);
}
List<TagDefinition> tags = toTagList(theMetaAdd);
//@formatter:off
for (TagDefinition nextDef : tags) {
boolean hasTag = false;
for (BaseTag next : new ArrayList<BaseTag>(entity.getTags())) {
if (ObjectUtil.equals(next.getTag().getTagType(), nextDef.getTagType()) &&
ObjectUtil.equals(next.getTag().getSystem(), nextDef.getSystem()) &&
ObjectUtil.equals(next.getTag().getCode(), nextDef.getCode())) {
hasTag = true;
break;
}
}
if (!hasTag) {
entity.setHasTags(true);
TagDefinition def = getTag(nextDef.getTagType(), nextDef.getSystem(), nextDef.getCode(), nextDef.getDisplay());
BaseTag newEntity = entity.addTag(def);
myEntityManager.persist(newEntity);
}
}
//@formatter:on
myEntityManager.merge(entity);
notifyWriteCompleted();
ourLog.info("Processed metaAddOperation on {} in {}ms", new Object[] { theResourceId, w.getMillisAndRestart() });
return metaGetOperation(theResourceId);
}
@Override
public MetaDt metaDeleteOperation(IdDt theResourceId, MetaDt theMetaDel) {
StopWatch w = new StopWatch();
BaseHasResource entity = readEntity(theResourceId);
if (entity == null) {
throw new ResourceNotFoundException(theResourceId);
}
List<TagDefinition> tags = toTagList(theMetaDel);
//@formatter:off
for (TagDefinition nextDef : tags) {
for (BaseTag next : new ArrayList<BaseTag>(entity.getTags())) {
if (ObjectUtil.equals(next.getTag().getTagType(), nextDef.getTagType()) &&
ObjectUtil.equals(next.getTag().getSystem(), nextDef.getSystem()) &&
ObjectUtil.equals(next.getTag().getCode(), nextDef.getCode())) {
myEntityManager.remove(next);
entity.getTags().remove(next);
}
}
}
//@formatter:on
if (entity.getTags().isEmpty()) {
entity.setHasTags(false);
}
myEntityManager.merge(entity);
ourLog.info("Processed metaDeleteOperation on {} in {}ms", new Object[] { theResourceId.getValue(), w.getMillisAndRestart() });
return metaGetOperation(theResourceId);
}
@Override
public MetaDt metaGetOperation() {
String sql = "SELECT d FROM TagDefinition d WHERE d.myId IN (SELECT DISTINCT t.myTagId FROM ResourceTag t WHERE t.myResourceType = :res_type)";
TypedQuery<TagDefinition> q = myEntityManager.createQuery(sql, TagDefinition.class);
q.setParameter("res_type", myResourceName);
List<TagDefinition> tagDefinitions = q.getResultList();
MetaDt retVal = super.toMetaDt(tagDefinitions);
return retVal;
}
@Override
public MetaDt metaGetOperation(IdDt theId) {
Long pid = super.translateForcedIdToPid(theId);
String sql = "SELECT d FROM TagDefinition d WHERE d.myId IN (SELECT DISTINCT t.myTagId FROM ResourceTag t WHERE t.myResourceType = :res_type AND t.myResourceId = :res_id)";
TypedQuery<TagDefinition> q = myEntityManager.createQuery(sql, TagDefinition.class);
q.setParameter("res_type", myResourceName);
q.setParameter("res_id", pid);
List<TagDefinition> tagDefinitions = q.getResultList();
MetaDt retVal = super.toMetaDt(tagDefinitions);
return retVal;
}
@PostConstruct
public void postConstruct() {
RuntimeResourceDefinition def = getContext().getResourceDefinition(myResourceType);
@ -1341,7 +1513,6 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
}
IBundleProvider retVal = new IBundleProvider() {
@Override
public InstantDt getPublished() {
return now;
@ -1422,6 +1593,11 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
});
}
@Override
public Integer preferredPageSize() {
return theParams.getCount();
}
@Override
public int size() {
return pids.size();
@ -1433,77 +1609,11 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
return retVal;
}
private List<IResource> addResourcesAsIncludesById(List<IResource> theListToPopulate, Set<IdDt> includePids, List<IResource> resources) {
if (!includePids.isEmpty()) {
ourLog.info("Loading {} included resources", includePids.size());
resources = loadResourcesById(includePids);
for (IResource next : resources) {
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(next, BundleEntrySearchModeEnum.INCLUDE);
}
theListToPopulate.addAll(resources);
}
return resources;
}
protected void loadReverseIncludes(List<Long> theMatches, Set<Include> theRevIncludes) {
if (theMatches.size() == 0) {
return;
}
HashSet<Long> pidsToInclude = new HashSet<Long>();
for (Include nextInclude : theRevIncludes) {
boolean matchAll = "*".equals(nextInclude.getValue());
if (matchAll) {
String sql = "SELECT r FROM ResourceLink r WHERE r.myTargetResourcePid IN (:target_pids)";
TypedQuery<ResourceLink> q = myEntityManager.createQuery(sql, ResourceLink.class);
q.setParameter("target_pids", theMatches);
List<ResourceLink> results = q.getResultList();
for (ResourceLink resourceLink : results) {
pidsToInclude.add(resourceLink.getSourceResourcePid());
}
} else {
int colonIdx = nextInclude.getValue().indexOf(':');
if (colonIdx < 2) {
continue;
}
String resType = nextInclude.getValue().substring(0, colonIdx);
RuntimeResourceDefinition def = getContext().getResourceDefinition(resType);
if (def == null) {
ourLog.warn("Unknown resource type in _revinclude=" + nextInclude.getValue());
continue;
}
String paramName = nextInclude.getValue().substring(colonIdx + 1);
RuntimeSearchParam param = def.getSearchParam(paramName);
if (param == null) {
ourLog.warn("Unknown param name in _revinclude=" + nextInclude.getValue());
continue;
}
for (String nextPath : param.getPathsSplit()) {
String sql = "SELECT r FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r.myTargetResourcePid IN (:target_pids)";
TypedQuery<ResourceLink> q = myEntityManager.createQuery(sql, ResourceLink.class);
q.setParameter("src_path", nextPath);
q.setParameter("target_pids", theMatches);
List<ResourceLink> results = q.getResultList();
for (ResourceLink resourceLink : results) {
pidsToInclude.add(resourceLink.getSourceResourcePid());
}
}
}
}
theMatches.addAll(pidsToInclude);
}
@Override
public IBundleProvider search(String theParameterName, IQueryParameterType theValue) {
return search(Collections.singletonMap(theParameterName, theValue));
}
protected abstract List<Object> getIncludeValues(FhirTerser theTerser, Include theInclude, IResource theResource, RuntimeResourceDefinition theResourceDef);
@Override
public Set<Long> searchForIds(Map<String, IQueryParameterType> theParams) {
SearchParameterMap map = new SearchParameterMap();
@ -1708,6 +1818,22 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
return qp;
}
private ArrayList<TagDefinition> toTagList(MetaDt theMeta) {
ArrayList<TagDefinition> retVal = new ArrayList<TagDefinition>();
for (CodingDt next : theMeta.getTag()) {
retVal.add(new TagDefinition(TagTypeEnum.TAG, next.getSystem(), next.getCode(), next.getDisplay()));
}
for (CodingDt next : theMeta.getSecurity()) {
retVal.add(new TagDefinition(TagTypeEnum.SECURITY_LABEL, next.getSystem(), next.getCode(), next.getDisplay()));
}
for (UriDt next : theMeta.getProfile()) {
retVal.add(new TagDefinition(TagTypeEnum.PROFILE, BaseFhirDao.NS_JPA_PROFILE, next.getValue(), null));
}
return retVal;
}
@Override
public DaoMethodOutcome update(T theResource) {
return update(theResource, null);
@ -1788,121 +1914,4 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
}
}
@Override
public MetaDt metaGetOperation() {
String sql = "SELECT d FROM TagDefinition d WHERE d.myId IN (SELECT DISTINCT t.myTagId FROM ResourceTag t WHERE t.myResourceType = :res_type)";
TypedQuery<TagDefinition> q = myEntityManager.createQuery(sql, TagDefinition.class);
q.setParameter("res_type", myResourceName);
List<TagDefinition> tagDefinitions = q.getResultList();
MetaDt retVal = super.toMetaDt(tagDefinitions);
return retVal;
}
@Override
public MetaDt metaGetOperation(IdDt theId) {
Long pid = super.translateForcedIdToPid(theId);
String sql = "SELECT d FROM TagDefinition d WHERE d.myId IN (SELECT DISTINCT t.myTagId FROM ResourceTag t WHERE t.myResourceType = :res_type AND t.myResourceId = :res_id)";
TypedQuery<TagDefinition> q = myEntityManager.createQuery(sql, TagDefinition.class);
q.setParameter("res_type", myResourceName);
q.setParameter("res_id", pid);
List<TagDefinition> tagDefinitions = q.getResultList();
MetaDt retVal = super.toMetaDt(tagDefinitions);
return retVal;
}
@Override
public MetaDt metaDeleteOperation(IdDt theResourceId, MetaDt theMetaDel) {
StopWatch w = new StopWatch();
BaseHasResource entity = readEntity(theResourceId);
if (entity == null) {
throw new ResourceNotFoundException(theResourceId);
}
List<TagDefinition> tags = toTagList(theMetaDel);
//@formatter:off
for (TagDefinition nextDef : tags) {
for (BaseTag next : new ArrayList<BaseTag>(entity.getTags())) {
if (ObjectUtil.equals(next.getTag().getTagType(), nextDef.getTagType()) &&
ObjectUtil.equals(next.getTag().getSystem(), nextDef.getSystem()) &&
ObjectUtil.equals(next.getTag().getCode(), nextDef.getCode())) {
myEntityManager.remove(next);
entity.getTags().remove(next);
}
}
}
//@formatter:on
if (entity.getTags().isEmpty()) {
entity.setHasTags(false);
}
myEntityManager.merge(entity);
ourLog.info("Processed metaDeleteOperation on {} in {}ms", new Object[] { theResourceId.getValue(), w.getMillisAndRestart() });
return metaGetOperation(theResourceId);
}
@Override
public MetaDt metaAddOperation(IdDt theResourceId, MetaDt theMetaAdd) {
StopWatch w = new StopWatch();
BaseHasResource entity = readEntity(theResourceId);
if (entity == null) {
throw new ResourceNotFoundException(theResourceId);
}
List<TagDefinition> tags = toTagList(theMetaAdd);
//@formatter:off
for (TagDefinition nextDef : tags) {
boolean hasTag = false;
for (BaseTag next : new ArrayList<BaseTag>(entity.getTags())) {
if (ObjectUtil.equals(next.getTag().getTagType(), nextDef.getTagType()) &&
ObjectUtil.equals(next.getTag().getSystem(), nextDef.getSystem()) &&
ObjectUtil.equals(next.getTag().getCode(), nextDef.getCode())) {
hasTag = true;
break;
}
}
if (!hasTag) {
entity.setHasTags(true);
TagDefinition def = getTag(nextDef.getTagType(), nextDef.getSystem(), nextDef.getCode(), nextDef.getDisplay());
BaseTag newEntity = entity.addTag(def);
myEntityManager.persist(newEntity);
}
}
//@formatter:on
myEntityManager.merge(entity);
notifyWriteCompleted();
ourLog.info("Processed metaAddOperation on {} in {}ms", new Object[] { theResourceId, w.getMillisAndRestart() });
return metaGetOperation(theResourceId);
}
private ArrayList<TagDefinition> toTagList(MetaDt theMeta) {
ArrayList<TagDefinition> retVal = new ArrayList<TagDefinition>();
for (CodingDt next : theMeta.getTag()) {
retVal.add(new TagDefinition(TagTypeEnum.TAG, next.getSystem(), next.getCode(), next.getDisplay()));
}
for (CodingDt next : theMeta.getSecurity()) {
retVal.add(new TagDefinition(TagTypeEnum.SECURITY_LABEL, next.getSystem(), next.getCode(), next.getDisplay()));
}
for (UriDt next : theMeta.getProfile()) {
retVal.add(new TagDefinition(TagTypeEnum.PROFILE, BaseFhirDao.NS_JPA_PROFILE, next.getValue(), null));
}
return retVal;
}
}

View File

@ -15,6 +15,7 @@ import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
@ -50,6 +51,7 @@ import ca.uhn.fhir.model.dstu.resource.Practitioner;
import ca.uhn.fhir.model.dstu2.composite.PeriodDt;
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
import ca.uhn.fhir.model.dstu2.resource.Condition;
import ca.uhn.fhir.model.dstu2.resource.DiagnosticOrder;
import ca.uhn.fhir.model.dstu2.resource.DocumentManifest;
import ca.uhn.fhir.model.dstu2.resource.DocumentReference;
@ -62,7 +64,9 @@ import ca.uhn.fhir.model.dstu2.resource.Parameters;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.valueset.EncounterClassEnum;
import ca.uhn.fhir.model.dstu2.valueset.EncounterStateEnum;
import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
@ -127,15 +131,15 @@ public class ResourceProviderDstu2Test {
HttpPost post = new HttpPost(ourServerBase + "/Patient");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
assertEquals(201, response.getStatusLine().getStatusCode());
assertThat(response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(), startsWith(ourServerBase + "/Patient/"));
assertThat(response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(), endsWith("/_history/1"));
assertThat(response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(), not(containsString("1777")));
} finally {
response.close();
}
@ -144,7 +148,7 @@ public class ResourceProviderDstu2Test {
@Test
public void testCreateResourceConditional() throws IOException {
String methodName = "testCreateResourceConditional";
Patient pt = new Patient();
pt.addName().addFamily(methodName);
String resource = ourFhirCtx.newXmlParser().encodeResourceToString(pt);
@ -161,7 +165,7 @@ public class ResourceProviderDstu2Test {
} finally {
response.close();
}
post = new HttpPost(ourServerBase + "/Patient");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
post.addHeader(Constants.HEADER_IF_NONE_EXIST, "Patient?name=" + methodName);
@ -173,13 +177,13 @@ public class ResourceProviderDstu2Test {
} finally {
response.close();
}
}
@Test
public void testUpdateResourceConditional() throws IOException {
String methodName = "testUpdateResourceConditional";
Patient pt = new Patient();
pt.addName().addFamily(methodName);
String resource = ourFhirCtx.newXmlParser().encodeResourceToString(pt);
@ -196,7 +200,7 @@ public class ResourceProviderDstu2Test {
} finally {
response.close();
}
HttpPut put = new HttpPut(ourServerBase + "/Patient?name=" + methodName);
put.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
response = ourHttpClient.execute(put);
@ -208,13 +212,13 @@ public class ResourceProviderDstu2Test {
} finally {
response.close();
}
}
@Test
public void testDeleteResourceConditional() throws IOException {
String methodName = "testDeleteResourceConditional";
Patient pt = new Patient();
pt.addName().addFamily(methodName);
String resource = ourFhirCtx.newXmlParser().encodeResourceToString(pt);
@ -231,7 +235,7 @@ public class ResourceProviderDstu2Test {
} finally {
response.close();
}
HttpDelete delete = new HttpDelete(ourServerBase + "/Patient?name=" + methodName);
response = ourHttpClient.execute(delete);
try {
@ -239,7 +243,7 @@ public class ResourceProviderDstu2Test {
} finally {
response.close();
}
HttpGet read = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart());
response = ourHttpClient.execute(read);
try {
@ -257,7 +261,7 @@ public class ResourceProviderDstu2Test {
@Test
public void testReadAllInstancesOfType() throws Exception {
Patient pat;
pat = new Patient();
pat.addIdentifier().setSystem("urn:system").setValue("testReadAllInstancesOfType_01");
ourClient.create().resource(pat).prettyPrint().encodedXml().execute().getId();
@ -277,11 +281,10 @@ public class ResourceProviderDstu2Test {
}
}
@Test
public void testSearchWithInclude() throws Exception {
Organization org = new Organization();
org.addIdentifier().setSystem("urn:system:rpdstu2").setValue( "testSearchWithInclude01");
org.addIdentifier().setSystem("urn:system:rpdstu2").setValue("testSearchWithInclude01");
IdDt orgId = ourClient.create().resource(org).prettyPrint().encodedXml().execute().getId();
Patient pat = new Patient();
@ -298,7 +301,7 @@ public class ResourceProviderDstu2Test {
.prettyPrint()
.execute();
//@formatter:on
assertEquals(2, found.size());
assertEquals(Patient.class, found.getEntries().get(0).getResource().getClass());
assertEquals(BundleEntrySearchModeEnum.MATCH, found.getEntries().get(0).getSearchMode().getValueAsEnum());
@ -311,16 +314,16 @@ public class ResourceProviderDstu2Test {
@Test
public void testEverythingOperation() throws Exception {
String methodName = "testEverythingOperation";
Organization org1 = new Organization();
org1.setName(methodName + "1");
IdDt orgId1 = ourClient.create().resource(org1).execute().getId();
Patient p = new Patient();
p.addName().addFamily(methodName);
p.getManagingOrganization().setReference(orgId1);
IdDt patientId = ourClient.create().resource(p).execute().getId();
Organization org2 = new Organization();
org2.setName(methodName + "1");
IdDt orgId2 = ourClient.create().resource(org2).execute().getId();
@ -328,31 +331,30 @@ public class ResourceProviderDstu2Test {
Device dev = new Device();
dev.setModel(methodName);
dev.getOwner().setReference(orgId2);
IdDt devId = ourClient.create().resource(dev).execute().getId();
IdDt devId = ourClient.create().resource(dev).execute().getId();
Observation obs = new Observation();
obs.getSubject().setReference(patientId);
obs.getDevice().setReference(devId);
IdDt obsId = ourClient.create().resource(obs).execute().getId();
Encounter enc = new Encounter();
enc.getPatient().setReference(patientId);
IdDt encId = ourClient.create().resource(enc).execute().getId();
Parameters output = ourClient.operation().onInstance(patientId).named("everything").withNoParameters(Parameters.class).execute();
ca.uhn.fhir.model.dstu2.resource.Bundle b = (ca.uhn.fhir.model.dstu2.resource.Bundle) output.getParameterFirstRep().getResource();
Set<IdDt> ids = new HashSet<IdDt>();
for (Entry next : b.getEntry()) {
ids.add(next.getResource().getId());
}
assertThat(ids, containsInAnyOrder(patientId, devId, obsId, encId, orgId1, orgId2));
// _revinclude's are counted but not _include's
assertEquals(3, b.getTotal().intValue());
ourLog.info(ids.toString());
}
@ -360,33 +362,101 @@ public class ResourceProviderDstu2Test {
* See #147
*/
@Test
public void testEverythingDoesnRepeatPatient() throws Exception {
public void testEverythingDoesntRepeatPatient() throws Exception {
ca.uhn.fhir.model.dstu2.resource.Bundle b;
b = ourFhirCtx.newJsonParser().parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, new InputStreamReader(ResourceProviderDstu2Test.class.getResourceAsStream("/bug147-bundle.json")));
ca.uhn.fhir.model.dstu2.resource.Bundle resp = ourClient.transaction().withBundle(b).execute();
ourLog.info(ourFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp));
IdDt patientId = new IdDt(resp.getEntry().get(1).getTransactionResponse().getLocation());
assertEquals("Patient", patientId.getResourceType());
Parameters output = ourClient.operation().onInstance(patientId).named("everything").withNoParameters(Parameters.class).execute();
b = (ca.uhn.fhir.model.dstu2.resource.Bundle) output.getParameterFirstRep().getResource();
List<IdDt> ids = new ArrayList<IdDt>();
boolean dupes = false;
for (Entry next : b.getEntry()) {
IdDt toAdd = next.getResource().getId().toUnqualifiedVersionless();
dupes = dupes | ids.contains(toAdd);
for (Entry next : resp.getEntry()) {
IdDt toAdd = new IdDt(next.getTransactionResponse().getLocation()).toUnqualifiedVersionless();
ids.add(toAdd);
}
ourLog.info(ids.toString());
assertFalse(ids.toString(), dupes);
ourLog.info("Created: " + ids.toString());
IdDt patientId = new IdDt(resp.getEntry().get(1).getTransactionResponse().getLocation());
assertEquals("Patient", patientId.getResourceType());
{
Parameters output = ourClient.operation().onInstance(patientId).named("everything").withNoParameters(Parameters.class).execute();
b = (ca.uhn.fhir.model.dstu2.resource.Bundle) output.getParameterFirstRep().getResource();
ids = new ArrayList<IdDt>();
boolean dupes = false;
for (Entry next : b.getEntry()) {
IdDt toAdd = next.getResource().getId().toUnqualifiedVersionless();
dupes = dupes | ids.contains(toAdd);
ids.add(toAdd);
}
ourLog.info("$everything: " + ids.toString());
assertFalse(ids.toString(), dupes);
/*
* Condition/11 is the 11th resource and the default page size is 10 so we don't show the 11th.
*/
assertThat(ids.toString(), not(containsString("Condition")));
}
/*
* Now try with a size specified
*/
{
Parameters input = new Parameters();
input.addParameter().setName(Constants.PARAM_COUNT).setValue(new IntegerDt(100));
Parameters output = ourClient.operation().onInstance(patientId).named("everything").withParameters(input).execute();
b = (ca.uhn.fhir.model.dstu2.resource.Bundle) output.getParameterFirstRep().getResource();
ids = new ArrayList<IdDt>();
boolean dupes = false;
for (Entry next : b.getEntry()) {
IdDt toAdd = next.getResource().getId().toUnqualifiedVersionless();
dupes = dupes | ids.contains(toAdd);
ids.add(toAdd);
}
ourLog.info("$everything: " + ids.toString());
assertFalse(ids.toString(), dupes);
assertThat(ids.toString(), containsString("Condition"));
}
}
/**
* See #148
*/
@Test
public void testEverythingIncludesCondition() throws Exception {
ca.uhn.fhir.model.dstu2.resource.Bundle b = new ca.uhn.fhir.model.dstu2.resource.Bundle();
Patient p = new Patient();
p.setId("1");
b.addEntry().setResource(p).getTransaction().setMethod(HTTPVerbEnum.POST);
Condition c = new Condition();
c.getPatient().setReference("Patient/1");
b.addEntry().setResource(c).getTransaction().setMethod(HTTPVerbEnum.POST);
ca.uhn.fhir.model.dstu2.resource.Bundle resp = ourClient.transaction().withBundle(b).execute();
ourLog.info(ourFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp));
IdDt patientId = new IdDt(resp.getEntry().get(1).getTransactionResponse().getLocation());
assertEquals("Patient", patientId.getResourceType());
Parameters output = ourClient.operation().onInstance(patientId).named("everything").withNoParameters(Parameters.class).execute();
b = (ca.uhn.fhir.model.dstu2.resource.Bundle) output.getParameterFirstRep().getResource();
List<IdDt> ids = new ArrayList<IdDt>();
for (Entry next : b.getEntry()) {
IdDt toAdd = next.getResource().getId().toUnqualifiedVersionless();
ids.add(toAdd);
}
assertThat(ids.toString(), containsString("Patient/"));
assertThat(ids.toString(), containsString("Condition/"));
}
@Test
public void testCountParam() throws Exception {
// NB this does not get used- The paging provider has its own limits built in
@ -435,7 +505,7 @@ public class ResourceProviderDstu2Test {
public void testDocumentManifestResources() throws Exception {
ourFhirCtx.getResourceDefinition(Practitioner.class);
ourFhirCtx.getResourceDefinition(ca.uhn.fhir.model.dstu.resource.DocumentManifest.class);
IGenericClient client = ourClient;
int initialSize = client.search().forResource(DocumentManifest.class).execute().size();
@ -477,7 +547,7 @@ public class ResourceProviderDstu2Test {
int initialSize = client.search().forResource(DiagnosticOrder.class).execute().size();
DiagnosticOrder res = new DiagnosticOrder();
res.addIdentifier().setSystem("urn:foo").setValue( "123");
res.addIdentifier().setSystem("urn:foo").setValue("123");
client.create().resource(res).execute();
@ -737,7 +807,7 @@ public class ResourceProviderDstu2Test {
RestfulServer restServer = new RestfulServer();
ourFhirCtx = FhirContext.forDstu2();
restServer.setFhirContext(ourFhirCtx);
ourServerBase = "http://localhost:" + port + "/fhir/context";
ourAppCtx = new ClassPathXmlApplicationContext("hapi-fhir-server-resourceproviders-dstu2.xml", "fhir-jpabase-spring-test-config.xml");
@ -771,7 +841,7 @@ public class ResourceProviderDstu2Test {
ourFhirCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
ourClient = ourFhirCtx.newRestfulGenericClient(ourServerBase);
ourClient.registerInterceptor(new LoggingInterceptor(true));
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);

View File

@ -24,7 +24,11 @@
"entry":
[
{
"transaction": { "method":"POST" },
"transaction":
{
"method": "POST"
},
"resource":
{
"resourceType": "Patient",
@ -114,7 +118,11 @@
},
{
"transaction": { "method":"POST" },
"transaction":
{
"method": "POST"
},
"resource":
{
"resourceType": "Encounter",
@ -148,7 +156,11 @@
},
{
"transaction": { "method":"POST" },
"transaction":
{
"method": "POST"
},
"resource":
{
"resourceType": "ClinicalImpression",
@ -193,7 +205,11 @@
},
{
"transaction": { "method":"POST" },
"transaction":
{
"method": "POST"
},
"resource":
{
"resourceType": "ClinicalImpression",
@ -238,7 +254,11 @@
},
{
"transaction": { "method":"POST" },
"transaction":
{
"method": "POST"
},
"resource":
{
"resourceType": "Encounter",
@ -267,7 +287,11 @@
},
{
"transaction": { "method":"POST" },
"transaction":
{
"method": "POST"
},
"resource":
{
"resourceType": "ClinicalImpression",
@ -311,7 +335,11 @@
},
{
"transaction": { "method":"POST" },
"transaction":
{
"method": "POST"
},
"resource":
{
"resourceType": "Encounter",
@ -340,7 +368,11 @@
},
{
"transaction": { "method":"POST" },
"transaction":
{
"method": "POST"
},
"resource":
{
"resourceType": "ClinicalImpression",
@ -384,7 +416,11 @@
},
{
"transaction": { "method":"POST" },
"transaction":
{
"method": "POST"
},
"resource":
{
"resourceType": "Observation",
@ -417,7 +453,11 @@
},
{
"transaction": { "method":"POST" },
"transaction":
{
"method": "POST"
},
"resource":
{
"resourceType": "Observation",
@ -447,7 +487,50 @@
{
"mode": "match"
}
}
},
{
"transaction":
{
"method": "POST"
},
"resource":
{
"resourceType": "Condition",
"id": "20443",
"meta":
{
"versionId": "1",
"lastUpdated": "2015-04-03T22:56:49.886-04:00"
},
"text":
{
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">pharyngitis working </div>"
},
"patient":
{
"reference": "Patient/1702",
"display": "Eve Everywoman "
},
"code":
{
"coding":
[
{
"system": "http://loinc.org",
"code": "28397-8",
"display": "pharyngitis"
}
],
"text": "pharyngitis"
}
}
}
]
}

View File

@ -35,7 +35,7 @@
<property name="persistenceUnitName" value="FHIR_UT" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true" />
<property name="showSql" value="false" />
<property name="generateDdl" value="true" />
<!-- <property name="databasePlatform" value="org.hibernate.dialect.HSQLDialect" /> -->
<property name="databasePlatform" value="org.hibernate.dialect.DerbyTenSevenDialect" />

View File

@ -268,7 +268,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
}
@Override
public IBaseResource getResourceBundle() {
public IResource getResourceBundle() {
return myBundle;
}

View File

@ -123,6 +123,9 @@
the main focus resource if it was referred to in a deep chain. Thanks
to David Hay for reporting!
</action>
<action type="add" issue="148">
JPA Server $everything operation now allows a _count parameter
</action>
</release>
<release version="0.9" date="2015-Mar-14">
<action type="add">