This commit is contained in:
jamesagnew 2015-03-09 19:29:54 -04:00
parent 2ca0b5e85b
commit 102f509120
19 changed files with 561 additions and 66 deletions

View File

@ -27,6 +27,10 @@ import ca.uhn.fhir.model.base.resource.ResourceMetadataMap;
import ca.uhn.fhir.model.primitive.CodeDt;
import ca.uhn.fhir.model.primitive.IdDt;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* This interface is the parent interface for all FHIR Resource definition
* classes. Classes implementing this interface should be annotated
@ -39,8 +43,11 @@ import ca.uhn.fhir.model.primitive.IdDt;
* </p>
*/
public interface IResource extends ICompositeElement, org.hl7.fhir.instance.model.IBaseResource {
public static final Include INCLUDE_ALL = new Include("*");
public static final Set<Include> WILDCARD_ALL_SET = new HashSet<Include>(Arrays.asList(INCLUDE_ALL));
/**
/**
* Returns the contained resource list for this resource.
* <p>
* Usage note: HAPI will generally populate and use the resources from this

View File

@ -26,12 +26,9 @@ import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.*;
import ca.uhn.fhir.model.api.Include;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.IBaseResource;
@ -126,7 +123,41 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
return myParameters;
}
public Object getProvider() {
public Set<Include> getRequestIncludesFromParams(Object[] params) {
if (params == null || params.length == 0)
return null;
int index = 0;
boolean match = false;
for (IParameter parameter : myParameters) {
if (parameter instanceof IncludeParameter) {
match = true;
break;
}
index++;
}
if (!match)
return null;
if (index >= params.length) {
ourLog.warn("index out of parameter range (should never happen");
return null;
}
if (params[index] instanceof Set) {
return (Set<Include>)params[index];
}
if (params[index] instanceof Iterable) {
Set includes = new HashSet<Include>();
for (Object o : (Iterable)params[index]) {
if (o instanceof Include) {
includes.add((Include) o);
}
}
return includes;
}
ourLog.warn("include params wasn't Set or Iterable, it was {}", params[index].getClass());
return null;
}
public Object getProvider() {
return myProvider;
}

View File

@ -34,6 +34,7 @@ import java.util.Set;
import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.model.api.Include;
import org.hl7.fhir.instance.model.IBaseResource;
import ca.uhn.fhir.context.ConfigurationException;
@ -282,10 +283,12 @@ 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);
IBundleProvider result = (IBundleProvider) resultObj;
IVersionSpecificBundleFactory bundleFactory = theServer.getFhirContext().newBundleFactory();
bundleFactory.initializeBundleFromBundleProvider(theServer, result, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, 0, count, null, getResponseBundleType());
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

@ -0,0 +1,41 @@
package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.util.ResourceReferenceInfo;
import java.util.Set;
/**
* Created by Bill de Beaubien on 3/4/2015.
*
* Controls how bundles decide whether referenced resources should be included
*/
public enum BundleInclusionRule {
/**
* Decision is based on whether the resource's Include is in the IncludeSet (e.g. DiagnosticReport.result). Note that
* the resource has to be populated to be included.
*
* This is the default behavior
*/
BASED_ON_INCLUDES {
@Override
public boolean shouldIncludeReferencedResource(ResourceReferenceInfo theReferenceInfo, Set<Include> theIncludes) {
return theReferenceInfo.matchesIncludeSet(theIncludes);
}
},
/**
* Decision is based on whether the resource reference is set to a populated resource (in which case its included) or just
* an id (in which case it's not included)
*
* This is the original HAPI behavior
*/
BASED_ON_RESOURCE_PRESENCE {
@Override
public boolean shouldIncludeReferencedResource(ResourceReferenceInfo theReferenceInfo, Set<Include> theIncludes) {
return true;
}
};
public abstract boolean shouldIncludeReferencedResource(ResourceReferenceInfo theReferenceInfo, Set<Include> theIncludes);
}

View File

@ -26,6 +26,8 @@ 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;
@ -33,10 +35,6 @@ 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.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.primitive.IdDt;
@ -56,7 +54,7 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
@Override
public void addResourcesToBundle(List<IResource> theResult, BundleTypeEnum theBundleType, String theServerBase) {
public void addResourcesToBundle(List<IResource> theResult, BundleTypeEnum theBundleType, String theServerBase, BundleInclusionRule theBundleInclusionRule, Set<Include> theIncludes) {
if (myBundle == null) {
myBundle = new Bundle();
}
@ -88,13 +86,16 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
} else {
ourLog.trace("No narrative generator specified");
}
List<BaseResourceReferenceDt> references = myContext.newTerser().getAllPopulatedChildElementsOfType(next, BaseResourceReferenceDt.class);
List<ResourceReferenceInfo> references = myContext.newTerser().getAllResourceReferences(next);
do {
List<IResource> addedResourcesThisPass = new ArrayList<IResource>();
for (BaseResourceReferenceDt nextRef : references) {
IResource nextRes = nextRef.getResource();
for (ResourceReferenceInfo nextRefInfo : references) {
if (!theBundleInclusionRule.shouldIncludeReferencedResource(nextRefInfo, theIncludes))
continue;
IResource nextRes = nextRefInfo.getResourceReference().getResource();
if (nextRes != null) {
if (nextRes.getId().hasIdPart()) {
if (containedIds.contains(nextRes.getId().getValue())) {
@ -116,20 +117,18 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
}
}
}
includedResources.addAll(addedResourcesThisPass);
// Linked resources may themselves have linked resources
references = new ArrayList<BaseResourceReferenceDt>();
references = new ArrayList<ResourceReferenceInfo>();
for (IResource iResource : addedResourcesThisPass) {
List<BaseResourceReferenceDt> newReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(iResource, BaseResourceReferenceDt.class);
List<ResourceReferenceInfo> newReferences = myContext.newTerser().getAllResourceReferences(iResource);
references.addAll(newReferences);
}
includedResources.addAll(addedResourcesThisPass);
} while (references.isEmpty() == false);
myBundle.addResource(next, myContext, theServerBase);
}
/*
@ -146,8 +145,8 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
}
@Override
public void initializeBundleFromBundleProvider(RestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType) {
@Override
public 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 numToReturn;
String searchId = null;
List<IResource> resourceList;
@ -195,7 +194,7 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory {
}
}
addResourcesToBundle(resourceList, theBundleType, theServerBase);
addResourcesToBundle(resourceList, theBundleType, theServerBase, theServer.getBundleInclusionRule(), theIncludes);
addRootPropertiesToBundle(null, theServerBase, theCompleteUrl, theResult.size(), theBundleType);
myBundle.setPublished(theResult.getPublished());

View File

@ -21,7 +21,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;
@ -30,12 +32,12 @@ import ca.uhn.fhir.model.valueset.BundleTypeEnum;
public interface IVersionSpecificBundleFactory {
void addResourcesToBundle(List<IResource> theResult, BundleTypeEnum theBundleType, String theServerBase);
void addResourcesToBundle(List<IResource> theResult, BundleTypeEnum theBundleType, String theServerBase, BundleInclusionRule theBundleInclusionRule, Set<Include> theIncludes);
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);
int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType, Set<Include> theIncludes);
Bundle getDstu1Bundle();

View File

@ -27,14 +27,7 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.*;
import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
@ -80,7 +73,7 @@ public class RestfulServer extends HttpServlet {
* Default setting for {@link #setETagSupport(ETagSupportEnum) ETag Support}: {@link ETagSupportEnum#ENABLED}
*/
public static final ETagSupportEnum DEFAULT_ETAG_SUPPORT = ETagSupportEnum.ENABLED;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);
private static final long serialVersionUID = 1L;
private AddProfileTagEnum myAddProfileTag;
@ -99,12 +92,13 @@ public class RestfulServer extends HttpServlet {
private String myServerName = "HAPI FHIR Server";
/** This is configurable but by default we just use HAPI version */
private String myServerVersion = VersionUtil.getVersion();
private BundleInclusionRule myBundleInclusionRule = BundleInclusionRule.BASED_ON_INCLUDES;
private boolean myStarted;
private boolean myUseBrowserFriendlyContentTypes;
/**
/**
* Constructor
*/
public RestfulServer() {
@ -460,7 +454,8 @@ public class RestfulServer extends HttpServlet {
boolean respondGzip = theRequest.isRespondGzip();
IVersionSpecificBundleFactory bundleFactory = myFhirContext.newBundleFactory();
bundleFactory.initializeBundleFromBundleProvider(this, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, start, count, thePagingAction, null);
bundleFactory.initializeBundleFromBundleProvider(this, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, start, count, thePagingAction,
null, IResource.WILDCARD_ALL_SET);
Bundle bundle = bundleFactory.getDstu1Bundle();
if (bundle != null) {
@ -1089,4 +1084,17 @@ public class RestfulServer extends HttpServlet {
return valueOf(NarrativeModeEnum.class, theCode.toUpperCase());
}
}
public BundleInclusionRule getBundleInclusionRule() {
return myBundleInclusionRule;
}
/**
* set how bundle factory should decide whether referenced resources should be included in bundles
*
* @param theBundleInclusionRule - inclusion rule (@see BundleInclusionRule for behaviors)
*/
public void setBundleInclusionRule(BundleInclusionRule theBundleInclusionRule) {
myBundleInclusionRule = theBundleInclusionRule;
}
}

View File

@ -114,7 +114,39 @@ public class FhirTerser {
return retVal;
}
private BaseRuntimeChildDefinition getDefinition(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, List<String> theSubList) {
public <T extends IBase> List<ResourceReferenceInfo> getAllResourceReferences(final IBaseResource theResource) {
final ArrayList<ResourceReferenceInfo> retVal = new ArrayList<ResourceReferenceInfo>();
BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
visit(theResource, null, def, new IModelVisitor() {
@SuppressWarnings("unchecked")
@Override
public void acceptElement(IBase theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) {
if (theElement == null || theElement.isEmpty()) {
return;
}
String name = null;
if (theChildDefinition != null) {
name = theChildDefinition.getElementName();
}
if (BaseResourceReferenceDt.class.isAssignableFrom(theElement.getClass())) {
retVal.add(new ResourceReferenceInfo(theResource, name, (BaseResourceReferenceDt)theElement));
}
}
@SuppressWarnings("unchecked")
@Override
public void acceptUndeclaredExtension(ISupportsUndeclaredExtensions theContainingElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition,
ExtensionDt theNextExt) {
String name = null;
if (theChildDefinition != null) {
name = theChildDefinition.getElementName();
}
if (theNextExt.getValue() != null && BaseResourceReferenceDt.class.isAssignableFrom(theNextExt.getValue().getClass())) {
retVal.add(new ResourceReferenceInfo(theResource, name, (BaseResourceReferenceDt)theNextExt.getValue()));
}
}
});
return retVal; } private BaseRuntimeChildDefinition getDefinition(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, List<String> theSubList) {
BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(theSubList.get(0));
if (theSubList.size() == 1) {

View File

@ -0,0 +1,47 @@
package ca.uhn.fhir.util;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import org.hl7.fhir.instance.model.IBaseResource;
import java.util.Set;
/**
* Created by Bill de Beaubien on 2/26/2015.
*/
public class ResourceReferenceInfo {
private String myOwningResource;
private String myName;
private BaseResourceReferenceDt myResource;
public ResourceReferenceInfo(IBaseResource theOwningResource, String theName, BaseResourceReferenceDt theResource) {
myOwningResource = theOwningResource.getClass().getAnnotation(ResourceDef.class).name();
myName = theName;
myResource = theResource;
}
public String getName() {
return myName;
}
public BaseResourceReferenceDt getResourceReference() {
return myResource;
}
public boolean matchesIncludeSet(Set<Include> theIncludes) {
if (theIncludes == null)
return false;
for (Include include : theIncludes) {
if (matchesInclude(include))
return true;
}
return false;
}
public boolean matchesInclude(Include theInclude) {
if (theInclude.getValue() == "*")
return true;
return (theInclude.getValue().equals(myOwningResource + "." + myName));
}
}

View File

@ -35,7 +35,7 @@ public class JpaServerDemo extends RestfulServer {
*
* If you want to use DSTU1 instead, change the following line, and change the 2 occurrences of dstu2 in web.xml to dstu1
*/
FhirVersionEnum fhirVersion = FhirVersionEnum.DSTU2;
FhirVersionEnum fhirVersion = FhirVersionEnum.DSTU1;
setFhirContext(new FhirContext(fhirVersion));
// Get the spring context from the web container (it's declared in web.xml)

View File

@ -9,7 +9,7 @@
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:hapi-fhir-server-resourceproviders-dstu2.xml
classpath:hapi-fhir-server-resourceproviders-dstu1.xml
/WEB-INF/hapi-fhir-server-database-config.xml
/WEB-INF/hapi-fhir-server-config.xml
/WEB-INF/hapi-fhir-tester-application-context.xml
@ -41,7 +41,7 @@
</init-param>
<init-param>
<param-name>FhirVersion</param-name>
<param-value>DSTU2</param-value>
<param-value>DSTU1</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

View File

@ -0,0 +1,163 @@
package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.context.FhirContext;
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.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu.resource.*;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.*;
import static org.junit.Assert.assertEquals;
/**
* Created by Bill de Beaubien on 3/3/2015.
*/
public class Dstu1BundleFactoryTest {
private static FhirContext ourCtx;
private List<IResource> myResourceList;
private Dstu1BundleFactory myBundleFactory;
@BeforeClass
public static void beforeClass() throws Exception {
ourCtx = new FhirContext(Patient.class);
}
@Before
public void setUp() throws Exception {
// DiagnosticReport
// Performer(practitioner)
// Subject(patient)
// Result(observation)
// Specimen(specimen1)
// Subject(patient)
// Collector(practitioner)
// Subject(patient)
// Performer(practitioner)
// Specimen(specimen2)
DiagnosticReport diagnosticReport = new DiagnosticReport();
diagnosticReport.setId("DiagnosticReport/1");
Observation observation = new Observation();
observation.setId("Observation/1");
Specimen specimen1 = new Specimen();
specimen1.setId("Specimen/1");
Specimen specimen2 = new Specimen();
specimen2.setId("Specimen/2");
Practitioner practitioner = new Practitioner();
practitioner.setId("Practitioner/1");
Patient patient = new Patient();
patient.setId("Patient/1");
diagnosticReport.setPerformer(new ResourceReferenceDt(practitioner));
diagnosticReport.setSubject(new ResourceReferenceDt(patient));
diagnosticReport.setResult(Arrays.asList(new ResourceReferenceDt[]{new ResourceReferenceDt(observation)}));
diagnosticReport.setSpecimen(Arrays.asList(new ResourceReferenceDt(specimen2)));
observation.setSpecimen(new ResourceReferenceDt(specimen1));
observation.setSubject(new ResourceReferenceDt(patient));
observation.setPerformer(Arrays.asList(new ResourceReferenceDt[]{new ResourceReferenceDt(practitioner)}));
specimen1.setSubject(new ResourceReferenceDt(patient));
specimen1.getCollection().setCollector(new ResourceReferenceDt(practitioner));
myResourceList = Arrays.asList(new IResource[]{diagnosticReport});
myBundleFactory = new Dstu1BundleFactory(ourCtx);
}
@Test
public void whenIncludeIsAsterisk_bundle_shouldContainAllReferencedResources() throws Exception {
Bundle bundle = makeBundle(BundleInclusionRule.BASED_ON_INCLUDES, includes("*"));
assertEquals(6, bundle.getEntries().size());
assertEquals(2, numberOfEntriesOfType(bundle, Specimen.class));
}
@Test
public void whenIncludeIsNull_bundle_shouldOnlyContainPrimaryResource() throws Exception {
Bundle bundle = makeBundle(BundleInclusionRule.BASED_ON_INCLUDES, null);
assertEquals(1, bundle.getEntries().size());
assertEquals(1, numberOfEntriesOfType(bundle, DiagnosticReport.class));
}
@Test
public void whenIncludeIsDiagnosticReportSubject_bundle_shouldIncludePatient() throws Exception {
Bundle bundle = makeBundle(BundleInclusionRule.BASED_ON_INCLUDES, includes(DiagnosticReport.INCLUDE_SUBJECT.getValue()));
assertEquals(2, bundle.getEntries().size());
assertEquals(1, numberOfEntriesOfType(bundle, DiagnosticReport.class));
assertEquals(1, numberOfEntriesOfType(bundle, Patient.class));
}
@Test
public void whenAChainedResourceIsIncludedAndItsParentIsAlsoIncluded_bundle_shouldContainTheChainedResource() throws Exception {
Bundle bundle = makeBundle(BundleInclusionRule.BASED_ON_INCLUDES, includes(DiagnosticReport.INCLUDE_RESULT.getValue(), Observation.INCLUDE_SPECIMEN.getValue()));
assertEquals(3, bundle.getEntries().size());
assertEquals(1, numberOfEntriesOfType(bundle, DiagnosticReport.class));
assertEquals(1, numberOfEntriesOfType(bundle, Observation.class));
assertEquals(1, numberOfEntriesOfType(bundle, Specimen.class));
List<Specimen> specimens = getResourcesOfType(bundle, Specimen.class);
assertEquals(1, specimens.size());
assertEquals("1", specimens.get(0).getId().getIdPart());
}
@Test
public void whenAChainedResourceIsIncludedButItsParentIsNot_bundle_shouldNotContainTheChainedResource() throws Exception {
Bundle bundle = makeBundle(BundleInclusionRule.BASED_ON_INCLUDES, includes(Observation.INCLUDE_SPECIMEN.getValue()));
assertEquals(1, bundle.getEntries().size());
assertEquals(1, numberOfEntriesOfType(bundle, DiagnosticReport.class));
}
@Test
public void whenBundleInclusionRuleSetToResourcePresence_bundle_shouldContainAllResources() throws Exception {
Bundle bundle = makeBundle(BundleInclusionRule.BASED_ON_RESOURCE_PRESENCE, null);
assertEquals(6, bundle.getEntries().size());
assertEquals(2, numberOfEntriesOfType(bundle, Specimen.class));
}
Bundle makeBundle(BundleInclusionRule theBundleInclusionRule, Set<Include> theIncludes) {
myBundleFactory.addResourcesToBundle(myResourceList, null, "http://foo", theBundleInclusionRule, theIncludes);
return myBundleFactory.getDstu1Bundle();
}
private Set<Include> includes(String... includes) {
Set<Include> includeSet = new HashSet<Include>();
for (String include : includes) {
includeSet.add(new Include(include));
}
return includeSet;
}
private <T extends IResource> int numberOfEntriesOfType(Bundle theBundle, Class<T> theResourceClass) {
int count = 0;
for (BundleEntry entry : theBundle.getEntries()) {
if (theResourceClass.isAssignableFrom(entry.getResource().getClass()))
count++;
}
return count;
}
private <T extends IResource> List<T> getResourcesOfType(Bundle theBundle, Class<T> theResourceClass) {
List<T> resources = new ArrayList<T>();
for (BundleEntry entry : theBundle.getEntries()) {
if (theResourceClass.isAssignableFrom(entry.getResource().getClass())) {
resources.add((T) entry.getResource());
}
}
return resources;
}
}

View File

@ -264,6 +264,7 @@ public class IncludeTest {
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer();
servlet.setFhirContext(ourCtx);
servlet.setBundleInclusionRule(BundleInclusionRule.BASED_ON_RESOURCE_PRESENCE);
servlet.setResourceProviders(patientProvider, new DummyDiagnosticReportResourceProvider());
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");

View File

@ -299,6 +299,7 @@ public class ReferenceParameterTest {
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer();
servlet.setBundleInclusionRule(BundleInclusionRule.BASED_ON_RESOURCE_PRESENCE);
ourCtx = servlet.getFhirContext();
servlet.setResourceProviders(patientProvider, new DummyOrganizationResourceProvider(), new DummyLocationResourceProvider());
ServletHolder servletHolder = new ServletHolder(servlet);

View File

@ -1059,7 +1059,9 @@ public class RestfulServerMethodTest {
dummyServer.addResourceProvider(profProvider);
ServletHolder servletHolder = new ServletHolder(ourRestfulServer);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourRestfulServer.setBundleInclusionRule(BundleInclusionRule.BASED_ON_RESOURCE_PRESENCE);
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);

View File

@ -282,6 +282,7 @@ public class ServerFeaturesTest {
ServletHandler proxyHandler = new ServletHandler();
servlet = new RestfulServer();
servlet.setResourceProviders(patientProvider);
servlet.setBundleInclusionRule(BundleInclusionRule.BASED_ON_RESOURCE_PRESENCE);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);

View File

@ -28,6 +28,9 @@ import java.util.List;
import java.util.Set;
import java.util.UUID;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.server.*;
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;
@ -47,14 +50,6 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.server.AddProfileTagEnum;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.IPagingProvider;
import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.RestfulServerUtils;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
@ -68,7 +63,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
}
@Override
public void addResourcesToBundle(List<IResource> theResult, BundleTypeEnum theBundleType, String theServerBase) {
public void addResourcesToBundle(List<IResource> theResult, BundleTypeEnum theBundleType, String theServerBase, BundleInclusionRule theBundleInclusionRule, Set<Include> theIncludes) {
if (myBundle == null) {
myBundle = new Bundle();
}
@ -101,12 +96,15 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
ourLog.trace("No narrative generator specified");
}
List<BaseResourceReferenceDt> references = myContext.newTerser().getAllPopulatedChildElementsOfType(next, BaseResourceReferenceDt.class);
List<ResourceReferenceInfo> references = myContext.newTerser().getAllResourceReferences(next);
do {
List<IResource> addedResourcesThisPass = new ArrayList<IResource>();
for (BaseResourceReferenceDt nextRef : references) {
IResource nextRes = nextRef.getResource();
for (ResourceReferenceInfo nextRefInfo : references) {
if (!theBundleInclusionRule.shouldIncludeReferencedResource(nextRefInfo, theIncludes))
continue;
IResource nextRes = nextRefInfo.getResourceReference().getResource();
if (nextRes != null) {
if (nextRes.getId().hasIdPart()) {
if (containedIds.contains(nextRes.getId().getValue())) {
@ -129,15 +127,14 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
}
}
includedResources.addAll(addedResourcesThisPass);
// Linked resources may themselves have linked resources
references = new ArrayList<BaseResourceReferenceDt>();
references = new ArrayList<ResourceReferenceInfo>();
for (IResource iResource : addedResourcesThisPass) {
List<BaseResourceReferenceDt> newReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(iResource, BaseResourceReferenceDt.class);
List<ResourceReferenceInfo> newReferences = myContext.newTerser().getAllResourceReferences(iResource);
references.addAll(newReferences);
}
includedResources.addAll(addedResourcesThisPass);
} while (references.isEmpty() == false);
Entry entry = myBundle.addEntry().setResource(next);
@ -157,7 +154,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
}
@Override
@Override
public void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theCompleteUrl, Integer theTotalResults, BundleTypeEnum theBundleType) {
if (myBundle.getId().isEmpty()) {
@ -197,7 +194,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
}
@Override
public void initializeBundleFromBundleProvider(RestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType) {
public 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 numToReturn;
String searchId = null;
List<IResource> resourceList;
@ -245,7 +242,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory {
}
}
addResourcesToBundle(resourceList, theBundleType, theServerBase);
addResourcesToBundle(resourceList, theBundleType, theServerBase, theServer.getBundleInclusionRule(), theIncludes);
addRootPropertiesToBundle(null, theServerBase, theCompleteUrl, theResult.size(), theBundleType);
if (theServer.getPagingProvider() != null) {

View File

@ -280,6 +280,7 @@ public class IncludeTest {
RestfulServer servlet = new RestfulServer();
servlet.setFhirContext(ourCtx);
servlet.setResourceProviders(patientProvider, new DummyDiagnosticReportResourceProvider());
servlet.setBundleInclusionRule(BundleInclusionRule.BASED_ON_RESOURCE_PRESENCE);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);

View File

@ -0,0 +1,159 @@
package ca.uhn.fhir.rest.server.provider.dstu2;
import ca.uhn.fhir.rest.server.BundleInclusionRule;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu2.resource.*;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.*;
import static org.junit.Assert.assertEquals;
public class Dstu2BundleFactoryTest {
private static FhirContext ourCtx;
private List<IResource> myResourceList;
private Dstu2BundleFactory myBundleFactory;
@BeforeClass
public static void beforeClass() throws Exception {
ourCtx = new FhirContext(Patient.class);
}
@Before
public void setUp() throws Exception {
// DiagnosticReport
// Performer(practitioner)
// Subject(patient)
// Result(observation)
// Specimen(specimen1)
// Subject(patient)
// Collector(practitioner)
// Subject(patient)
// Performer(practitioner)
// Specimen(specimen2)
DiagnosticReport diagnosticReport = new DiagnosticReport();
diagnosticReport.setId("DiagnosticReport/1");
Observation observation = new Observation();
observation.setId("Observation/1");
Specimen specimen1 = new Specimen();
specimen1.setId("Specimen/1");
Specimen specimen2 = new Specimen();
specimen2.setId("Specimen/2");
Practitioner practitioner = new Practitioner();
practitioner.setId("Practitioner/1");
Patient patient = new Patient();
patient.setId("Patient/1");
diagnosticReport.setPerformer(new ResourceReferenceDt(practitioner));
diagnosticReport.setSubject(new ResourceReferenceDt(patient));
diagnosticReport.setResult(Arrays.asList(new ResourceReferenceDt[]{new ResourceReferenceDt(observation)}));
diagnosticReport.setSpecimen(Arrays.asList(new ResourceReferenceDt(specimen2)));
observation.setSpecimen(new ResourceReferenceDt(specimen1));
observation.setSubject(new ResourceReferenceDt(patient));
observation.setPerformer(Arrays.asList(new ResourceReferenceDt[]{new ResourceReferenceDt(practitioner)}));
specimen1.setSubject(new ResourceReferenceDt(patient));
specimen1.getCollection().setCollector(new ResourceReferenceDt(practitioner));
myResourceList = Arrays.asList(new IResource[]{diagnosticReport});
myBundleFactory = new Dstu2BundleFactory(ourCtx);
}
@Test
public void whenIncludeIsAsterisk_bundle_shouldContainAllReferencedResources() throws Exception {
Bundle bundle = makeBundle(BundleInclusionRule.BASED_ON_INCLUDES, includes("*"));
assertEquals(6, bundle.getEntry().size());
assertEquals(2, numberOfEntriesOfType(bundle, Specimen.class));
}
@Test
public void whenIncludeIsNull_bundle_shouldOnlyContainPrimaryResource() throws Exception {
Bundle bundle = makeBundle(BundleInclusionRule.BASED_ON_INCLUDES, null);
assertEquals(1, bundle.getEntry().size());
assertEquals(1, numberOfEntriesOfType(bundle, DiagnosticReport.class));
}
@Test
public void whenIncludeIsDiagnosticReportSubject_bundle_shouldIncludePatient() throws Exception {
Bundle bundle = makeBundle(BundleInclusionRule.BASED_ON_INCLUDES, includes(DiagnosticReport.INCLUDE_SUBJECT.getValue()));
assertEquals(2, bundle.getEntry().size());
assertEquals(1, numberOfEntriesOfType(bundle, DiagnosticReport.class));
assertEquals(1, numberOfEntriesOfType(bundle, Patient.class));
}
@Test
public void whenAChainedResourceIsIncludedAndItsParentIsAlsoIncluded_bundle_shouldContainTheChainedResource() throws Exception {
Bundle bundle = makeBundle(BundleInclusionRule.BASED_ON_INCLUDES, includes(DiagnosticReport.INCLUDE_RESULT.getValue(), Observation.INCLUDE_SPECIMEN.getValue()));
assertEquals(3, bundle.getEntry().size());
assertEquals(1, numberOfEntriesOfType(bundle, DiagnosticReport.class));
assertEquals(1, numberOfEntriesOfType(bundle, Observation.class));
assertEquals(1, numberOfEntriesOfType(bundle, Specimen.class));
List<Specimen> specimens = getResourcesOfType(bundle, Specimen.class);
assertEquals(1, specimens.size());
assertEquals("1", specimens.get(0).getId().getIdPart());
}
@Test
public void whenAChainedResourceIsIncludedButItsParentIsNot_bundle_shouldNotContainTheChainedResource() throws Exception {
Bundle bundle = makeBundle(BundleInclusionRule.BASED_ON_INCLUDES, includes(Observation.INCLUDE_SPECIMEN.getValue()));
assertEquals(1, bundle.getEntry().size());
assertEquals(1, numberOfEntriesOfType(bundle, DiagnosticReport.class));
}
@Test
public void whenBundleInclusionRuleSetToResourcePresence_bundle_shouldContainAllResources() throws Exception {
Bundle bundle = makeBundle(BundleInclusionRule.BASED_ON_RESOURCE_PRESENCE, null);
assertEquals(6, bundle.getEntry().size());
assertEquals(2, numberOfEntriesOfType(bundle, Specimen.class));
}
Bundle makeBundle(BundleInclusionRule theBundleInclusionRule, Set<Include> theIncludes) {
myBundleFactory.addResourcesToBundle(myResourceList, null, "http://foo", theBundleInclusionRule, theIncludes);
return (Bundle) myBundleFactory.getResourceBundle();
}
private Set<Include> includes(String... includes) {
Set<Include> includeSet = new HashSet<Include>();
for (String include : includes) {
includeSet.add(new Include(include));
}
return includeSet;
}
private <T extends IResource> int numberOfEntriesOfType(Bundle theBundle, Class<T> theResourceClass) {
int count = 0;
for (Bundle.Entry entry : theBundle.getEntry()) {
if (theResourceClass.isAssignableFrom(entry.getResource().getClass()))
count++;
}
return count;
}
private <T extends IResource> List<T> getResourcesOfType(Bundle theBundle, Class<T> theResourceClass) {
List<T> resources = new ArrayList<T>();
for (Bundle.Entry entry : theBundle.getEntry()) {
if (theResourceClass.isAssignableFrom(entry.getResource().getClass())) {
resources.add((T) entry.getResource());
}
}
return resources;
}
}