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

View File

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

View File

@ -56,6 +56,11 @@ public class BundleProviders {
public InstantDt getPublished() { public InstantDt getPublished() {
return published; 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.Set;
import java.util.UUID; 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.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.IBaseResource;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition; 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.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome; import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.ResourceReferenceInfo;
public class Dstu1BundleFactory implements IVersionSpecificBundleFactory { public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
@ -253,7 +256,7 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
} }
@Override @Override
public IBaseResource getResourceBundle() { public IResource getResourceBundle() {
return null; return null;
} }

View File

@ -28,14 +28,37 @@ import ca.uhn.fhir.model.primitive.InstantDt;
public interface IBundleProvider { 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 theFromIndex The low index (inclusive) to return
* @param theToIndex The high index (exclusive) 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>. * @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); 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(); int size();
/**
* Returns the instant as of which this result was valid
*/
InstantDt getPublished(); InstantDt getPublished();
} }

View File

@ -23,11 +23,9 @@ package ca.uhn.fhir.rest.server;
import java.util.List; import java.util.List;
import java.util.Set; 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.Bundle;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.valueset.BundleTypeEnum; 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 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, 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(); Bundle getDstu1Bundle();
IBaseResource getResourceBundle(); IResource getResourceBundle();
void initializeBundleFromResourceList(String theAuthor, List<IResource> theResult, String theServerBase, String theCompleteUrl, int theTotalResults, BundleTypeEnum theBundleType); 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

@ -60,4 +60,9 @@ public class SimpleBundleProvider implements IBundleProvider {
return InstantDt.withCurrentTime(); return InstantDt.withCurrentTime();
} }
@Override
public Integer preferredPageSize() {
return null;
}
} }

View File

@ -494,6 +494,11 @@ public abstract class BaseFhirDao implements IDao {
public int size() { public int size() {
return tuples.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()); 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 @Override
public void addTag(IdDt theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) { public void addTag(IdDt theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
StopWatch w = new StopWatch(); StopWatch w = new StopWatch();
@ -713,50 +725,6 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
return doCreate(theResource, theIfNoneExist, thePerformIndexing); 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) { private Predicate createCompositeParamPart(CriteriaBuilder builder, Root<ResourceTable> from, RuntimeSearchParam left, IQueryParameterType leftValue) {
Predicate retVal = null; Predicate retVal = null;
switch (left.getParamType()) { switch (left.getParamType()) {
@ -979,6 +947,50 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
return toMethodOutcome(savedEntity, null); 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 @Override
public TagList getAllResourceTags() { public TagList getAllResourceTags() {
StopWatch w = new StopWatch(); StopWatch w = new StopWatch();
@ -987,6 +999,8 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
return tags; return tags;
} }
protected abstract List<Object> getIncludeValues(FhirTerser theTerser, Include theInclude, IResource theResource, RuntimeResourceDefinition theResourceDef);
public Class<T> getResourceType() { public Class<T> getResourceType() {
return myResourceType; return myResourceType;
} }
@ -1092,6 +1106,11 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
return retVal; return retVal;
} }
@Override
public Integer preferredPageSize() {
return null;
}
@Override @Override
public int size() { public int size() {
return count; 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 @PostConstruct
public void postConstruct() { public void postConstruct() {
RuntimeResourceDefinition def = getContext().getResourceDefinition(myResourceType); RuntimeResourceDefinition def = getContext().getResourceDefinition(myResourceType);
@ -1341,7 +1513,6 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
} }
IBundleProvider retVal = new IBundleProvider() { IBundleProvider retVal = new IBundleProvider() {
@Override @Override
public InstantDt getPublished() { public InstantDt getPublished() {
return now; return now;
@ -1422,6 +1593,11 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
}); });
} }
@Override
public Integer preferredPageSize() {
return theParams.getCount();
}
@Override @Override
public int size() { public int size() {
return pids.size(); return pids.size();
@ -1433,77 +1609,11 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
return retVal; 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 @Override
public IBundleProvider search(String theParameterName, IQueryParameterType theValue) { public IBundleProvider search(String theParameterName, IQueryParameterType theValue) {
return search(Collections.singletonMap(theParameterName, theValue)); return search(Collections.singletonMap(theParameterName, theValue));
} }
protected abstract List<Object> getIncludeValues(FhirTerser theTerser, Include theInclude, IResource theResource, RuntimeResourceDefinition theResourceDef);
@Override @Override
public Set<Long> searchForIds(Map<String, IQueryParameterType> theParams) { public Set<Long> searchForIds(Map<String, IQueryParameterType> theParams) {
SearchParameterMap map = new SearchParameterMap(); SearchParameterMap map = new SearchParameterMap();
@ -1708,6 +1818,22 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
return qp; 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 @Override
public DaoMethodOutcome update(T theResource) { public DaoMethodOutcome update(T theResource) {
return update(theResource, null); 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.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils; 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.PeriodDt;
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt; import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; 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.DiagnosticOrder;
import ca.uhn.fhir.model.dstu2.resource.DocumentManifest; import ca.uhn.fhir.model.dstu2.resource.DocumentManifest;
import ca.uhn.fhir.model.dstu2.resource.DocumentReference; 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.resource.Patient;
import ca.uhn.fhir.model.dstu2.valueset.EncounterClassEnum; import ca.uhn.fhir.model.dstu2.valueset.EncounterClassEnum;
import ca.uhn.fhir.model.dstu2.valueset.EncounterStateEnum; 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.IdDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
@ -277,11 +281,10 @@ public class ResourceProviderDstu2Test {
} }
} }
@Test @Test
public void testSearchWithInclude() throws Exception { public void testSearchWithInclude() throws Exception {
Organization org = new Organization(); 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(); IdDt orgId = ourClient.create().resource(org).prettyPrint().encodedXml().execute().getId();
Patient pat = new Patient(); Patient pat = new Patient();
@ -347,7 +350,6 @@ public class ResourceProviderDstu2Test {
ids.add(next.getResource().getId()); ids.add(next.getResource().getId());
} }
assertThat(ids, containsInAnyOrder(patientId, devId, obsId, encId, orgId1, orgId2)); assertThat(ids, containsInAnyOrder(patientId, devId, obsId, encId, orgId1, orgId2));
// _revinclude's are counted but not _include's // _revinclude's are counted but not _include's
@ -360,10 +362,80 @@ public class ResourceProviderDstu2Test {
* See #147 * See #147
*/ */
@Test @Test
public void testEverythingDoesnRepeatPatient() throws Exception { public void testEverythingDoesntRepeatPatient() throws Exception {
ca.uhn.fhir.model.dstu2.resource.Bundle b; 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"))); 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();
List<IdDt> ids = new ArrayList<IdDt>();
for (Entry next : resp.getEntry()) {
IdDt toAdd = new IdDt(next.getTransactionResponse().getLocation()).toUnqualifiedVersionless();
ids.add(toAdd);
}
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(); ca.uhn.fhir.model.dstu2.resource.Bundle resp = ourClient.transaction().withBundle(b).execute();
ourLog.info(ourFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp)); ourLog.info(ourFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp));
@ -375,16 +447,14 @@ public class ResourceProviderDstu2Test {
b = (ca.uhn.fhir.model.dstu2.resource.Bundle) output.getParameterFirstRep().getResource(); b = (ca.uhn.fhir.model.dstu2.resource.Bundle) output.getParameterFirstRep().getResource();
List<IdDt> ids = new ArrayList<IdDt>(); List<IdDt> ids = new ArrayList<IdDt>();
boolean dupes = false;
for (Entry next : b.getEntry()) { for (Entry next : b.getEntry()) {
IdDt toAdd = next.getResource().getId().toUnqualifiedVersionless(); IdDt toAdd = next.getResource().getId().toUnqualifiedVersionless();
dupes = dupes | ids.contains(toAdd);
ids.add(toAdd); ids.add(toAdd);
} }
ourLog.info(ids.toString()); assertThat(ids.toString(), containsString("Patient/"));
assertThat(ids.toString(), containsString("Condition/"));
assertFalse(ids.toString(), dupes);
} }
@Test @Test
@ -477,7 +547,7 @@ public class ResourceProviderDstu2Test {
int initialSize = client.search().forResource(DiagnosticOrder.class).execute().size(); int initialSize = client.search().forResource(DiagnosticOrder.class).execute().size();
DiagnosticOrder res = new DiagnosticOrder(); DiagnosticOrder res = new DiagnosticOrder();
res.addIdentifier().setSystem("urn:foo").setValue( "123"); res.addIdentifier().setSystem("urn:foo").setValue("123");
client.create().resource(res).execute(); client.create().resource(res).execute();

View File

@ -24,7 +24,11 @@
"entry": "entry":
[ [
{ {
"transaction": { "method":"POST" }, "transaction":
{
"method": "POST"
},
"resource": "resource":
{ {
"resourceType": "Patient", "resourceType": "Patient",
@ -114,7 +118,11 @@
}, },
{ {
"transaction": { "method":"POST" }, "transaction":
{
"method": "POST"
},
"resource": "resource":
{ {
"resourceType": "Encounter", "resourceType": "Encounter",
@ -148,7 +156,11 @@
}, },
{ {
"transaction": { "method":"POST" }, "transaction":
{
"method": "POST"
},
"resource": "resource":
{ {
"resourceType": "ClinicalImpression", "resourceType": "ClinicalImpression",
@ -193,7 +205,11 @@
}, },
{ {
"transaction": { "method":"POST" }, "transaction":
{
"method": "POST"
},
"resource": "resource":
{ {
"resourceType": "ClinicalImpression", "resourceType": "ClinicalImpression",
@ -238,7 +254,11 @@
}, },
{ {
"transaction": { "method":"POST" }, "transaction":
{
"method": "POST"
},
"resource": "resource":
{ {
"resourceType": "Encounter", "resourceType": "Encounter",
@ -267,7 +287,11 @@
}, },
{ {
"transaction": { "method":"POST" }, "transaction":
{
"method": "POST"
},
"resource": "resource":
{ {
"resourceType": "ClinicalImpression", "resourceType": "ClinicalImpression",
@ -311,7 +335,11 @@
}, },
{ {
"transaction": { "method":"POST" }, "transaction":
{
"method": "POST"
},
"resource": "resource":
{ {
"resourceType": "Encounter", "resourceType": "Encounter",
@ -340,7 +368,11 @@
}, },
{ {
"transaction": { "method":"POST" }, "transaction":
{
"method": "POST"
},
"resource": "resource":
{ {
"resourceType": "ClinicalImpression", "resourceType": "ClinicalImpression",
@ -384,7 +416,11 @@
}, },
{ {
"transaction": { "method":"POST" }, "transaction":
{
"method": "POST"
},
"resource": "resource":
{ {
"resourceType": "Observation", "resourceType": "Observation",
@ -417,7 +453,11 @@
}, },
{ {
"transaction": { "method":"POST" }, "transaction":
{
"method": "POST"
},
"resource": "resource":
{ {
"resourceType": "Observation", "resourceType": "Observation",
@ -447,7 +487,50 @@
{ {
"mode": "match" "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="persistenceUnitName" value="FHIR_UT" />
<property name="jpaVendorAdapter"> <property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <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="generateDdl" value="true" />
<!-- <property name="databasePlatform" value="org.hibernate.dialect.HSQLDialect" /> --> <!-- <property name="databasePlatform" value="org.hibernate.dialect.HSQLDialect" /> -->
<property name="databasePlatform" value="org.hibernate.dialect.DerbyTenSevenDialect" /> <property name="databasePlatform" value="org.hibernate.dialect.DerbyTenSevenDialect" />

View File

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

View File

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