Correctly populate Bundle.entry.fullUrl on Hl7OrgDstu2 servers

This commit is contained in:
jamesagnew 2016-04-14 06:59:43 -04:00
parent 8a37ed3b59
commit 81dec23faf
4 changed files with 202 additions and 69 deletions

View File

@ -0,0 +1,120 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.HumanName;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
public class SearchWithGenericListDstu3Test {
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forDstu3();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchWithGenericListDstu3Test.class);
private static int ourPort;
private static Server ourServer;
private static String ourLastMethod;
@Before
public void before() {
ourLastMethod = null;
}
/**
* See #291
*/
@Test
public void testSearch() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo&_pretty=true");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("searchByIdentifier", ourLastMethod);
assertThat(responseContent, containsString("<family value=\"FAMILY\""));
assertThat(responseContent, containsString("<fullUrl value=\"http://localhost:" + ourPort + "/Patient/1\"/>"));
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.setPagingProvider(new FifoMemoryPagingProvider(10));
servlet.setResourceProviders(patientProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static class DummyPatientResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> getResourceType() {
return Patient.class;
}
//@formatter:off
@SuppressWarnings("rawtypes")
@Search()
public List searchByIdentifier(
@RequiredParam(name=Patient.SP_IDENTIFIER) TokenParam theIdentifier) {
ourLastMethod = "searchByIdentifier";
ArrayList<Patient> retVal = new ArrayList<Patient>();
retVal.add((Patient) new Patient().addName(new HumanName().addFamily("FAMILY")).setId("1"));
return retVal;
}
//@formatter:on
}
}

View File

@ -72,76 +72,78 @@ public class Dstu2Hl7OrgBundleFactory implements IVersionSpecificBundleFactory {
} }
private void addResourcesForSearch(List<? extends IBaseResource> theResult) { private void addResourcesForSearch(List<? extends IBaseResource> theResult) {
List<IBaseResource> includedResources = new ArrayList<IBaseResource>(); List<IBaseResource> includedResources = new ArrayList<IBaseResource>();
Set<IIdType> addedResourceIds = new HashSet<IIdType>(); Set<IIdType> addedResourceIds = new HashSet<IIdType>();
for (IBaseResource next : theResult) { for (IBaseResource next : theResult) {
if (next.getIdElement().isEmpty() == false) { if (next.getIdElement().isEmpty() == false) {
addedResourceIds.add(next.getIdElement()); addedResourceIds.add(next.getIdElement());
} }
} }
for (IBaseResource nextBaseRes : theResult) { for (IBaseResource nextBaseRes : theResult) {
IDomainResource next = (IDomainResource) nextBaseRes; IDomainResource next = (IDomainResource) nextBaseRes;
Set<String> containedIds = new HashSet<String>(); Set<String> containedIds = new HashSet<String>();
for (IBaseResource nextContained : next.getContained()) { for (IBaseResource nextContained : next.getContained()) {
if (nextContained.getIdElement().isEmpty() == false) { if (nextContained.getIdElement().isEmpty() == false) {
containedIds.add(nextContained.getIdElement().getValue()); containedIds.add(nextContained.getIdElement().getValue());
} }
} }
List<IBaseReference> references = myContext.newTerser().getAllPopulatedChildElementsOfType(next, IBaseReference.class); List<IBaseReference> references = myContext.newTerser().getAllPopulatedChildElementsOfType(next,
do { IBaseReference.class);
List<IBaseResource> addedResourcesThisPass = new ArrayList<IBaseResource>(); do {
List<IBaseResource> addedResourcesThisPass = new ArrayList<IBaseResource>();
for (IBaseReference nextRef : references) { for (IBaseReference nextRef : references) {
IBaseResource nextRes = (IBaseResource) nextRef.getResource(); IBaseResource nextRes = (IBaseResource) nextRef.getResource();
if (nextRes != null) { if (nextRes != null) {
if (nextRes.getIdElement().hasIdPart()) { if (nextRes.getIdElement().hasIdPart()) {
if (containedIds.contains(nextRes.getIdElement().getValue())) { if (containedIds.contains(nextRes.getIdElement().getValue())) {
// Don't add contained IDs as top level resources // Don't add contained IDs as top level resources
continue; continue;
} }
IIdType id = nextRes.getIdElement(); IIdType id = nextRes.getIdElement();
if (id.hasResourceType() == false) { if (id.hasResourceType() == false) {
String resName = myContext.getResourceDefinition(nextRes).getName(); String resName = myContext.getResourceDefinition(nextRes).getName();
id = id.withResourceType(resName); id = id.withResourceType(resName);
} }
if (!addedResourceIds.contains(id)) { if (!addedResourceIds.contains(id)) {
addedResourceIds.add(id); addedResourceIds.add(id);
addedResourcesThisPass.add(nextRes); addedResourcesThisPass.add(nextRes);
} }
} }
} }
} }
// Linked resources may themselves have linked resources // Linked resources may themselves have linked resources
references = new ArrayList<IBaseReference>(); references = new ArrayList<IBaseReference>();
for (IBaseResource iResource : addedResourcesThisPass) { for (IBaseResource iResource : addedResourcesThisPass) {
List<IBaseReference> newReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(iResource, IBaseReference.class); List<IBaseReference> newReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(iResource,
references.addAll(newReferences); IBaseReference.class);
} references.addAll(newReferences);
}
includedResources.addAll(addedResourcesThisPass); includedResources.addAll(addedResourcesThisPass);
} while (references.isEmpty() == false); } while (references.isEmpty() == false);
BundleEntryComponent entry = myBundle.addEntry().setResource((Resource) next); BundleEntryComponent entry = myBundle.addEntry().setResource((Resource) next);
populateBundleEntryFullUrl(next, entry); populateBundleEntryFullUrl(next, entry);
} }
/* /*
* Actually add the resources to the bundle * Actually add the resources to the bundle
*/ */
for (IBaseResource next : includedResources) { for (IBaseResource next : includedResources) {
BundleEntryComponent entry = myBundle.addEntry(); BundleEntryComponent entry = myBundle.addEntry();
entry.setResource((Resource) next).getSearch().setMode(SearchEntryMode.INCLUDE); entry.setResource((Resource) next).getSearch().setMode(SearchEntryMode.INCLUDE);
populateBundleEntryFullUrl(next, entry); populateBundleEntryFullUrl(next, entry);
} }
} }
private void populateBundleEntryFullUrl(IBaseResource next, BundleEntryComponent entry) { private void populateBundleEntryFullUrl(IBaseResource next, BundleEntryComponent entry) {
if (next.getIdElement().hasBaseUrl()) { if (next.getIdElement().hasBaseUrl()) {
@ -230,8 +232,10 @@ public class Dstu2Hl7OrgBundleFactory implements IVersionSpecificBundleFactory {
} while (references.isEmpty() == false); } while (references.isEmpty() == false);
BundleEntryComponent entry = myBundle.addEntry().setResource((Resource) next); BundleEntryComponent entry = myBundle.addEntry().setResource((Resource) next);
populateBundleEntryFullUrl(next, entry);
// BundleEntrySearchModeEnum searchMode = ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(next); // BundleEntrySearchModeEnum searchMode =
// ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(next);
// if (searchMode != null) { // if (searchMode != null) {
// entry.getSearch().getModeElement().setValue(searchMode.getCode()); // entry.getSearch().getModeElement().setValue(searchMode.getCode());
// } // }
@ -352,8 +356,9 @@ public class Dstu2Hl7OrgBundleFactory implements IVersionSpecificBundleFactory {
if (searchId != null) { if (searchId != null) {
if (theOffset + numToReturn < theResult.size()) { if (theOffset + numToReturn < theResult.size()) {
myBundle.addLink().setRelation(Constants.LINK_NEXT).setUrl(RestfulServerUtils.createPagingLink(theIncludes, myBundle.addLink().setRelation(Constants.LINK_NEXT)
theServerBase, searchId, theOffset + numToReturn, numToReturn, theResponseEncoding, thePrettyPrint, theBundleType)); .setUrl(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, theOffset + numToReturn,
numToReturn, theResponseEncoding, thePrettyPrint, theBundleType));
} }
if (theOffset > 0) { if (theOffset > 0) {
int start = Math.max(0, theOffset - limit); int start = Math.max(0, theOffset - limit);

View File

@ -17,24 +17,25 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.HumanName;
import org.hl7.fhir.instance.model.Patient;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.util.PortUtil; import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
public class SearchWithGenericListHl7OrgDstu2Test { public class SearchWithGenericListHl7OrgDstu2Test {
private static CloseableHttpClient ourClient; private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forDstu2(); private static FhirContext ourCtx = FhirContext.forDstu2Hl7Org();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchWithGenericListHl7OrgDstu2Test.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchWithGenericListHl7OrgDstu2Test.class);
private static int ourPort; private static int ourPort;
private static Server ourServer; private static Server ourServer;
@ -62,10 +63,13 @@ public class SearchWithGenericListHl7OrgDstu2Test {
} }
@AfterClass @AfterClass
public static void afterClass() throws Exception { public static void afterClassClearContext() throws Exception {
ourServer.stop(); ourServer.stop();
} TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass @BeforeClass
public static void beforeClass() throws Exception { public static void beforeClass() throws Exception {
@ -94,7 +98,7 @@ public class SearchWithGenericListHl7OrgDstu2Test {
public static class DummyPatientResourceProvider implements IResourceProvider { public static class DummyPatientResourceProvider implements IResourceProvider {
@Override @Override
public Class<? extends IResource> getResourceType() { public Class<? extends IBaseResource> getResourceType() {
return Patient.class; return Patient.class;
} }
@ -105,7 +109,7 @@ public class SearchWithGenericListHl7OrgDstu2Test {
@RequiredParam(name=Patient.SP_IDENTIFIER) TokenParam theIdentifier) { @RequiredParam(name=Patient.SP_IDENTIFIER) TokenParam theIdentifier) {
ourLastMethod = "searchByIdentifier"; ourLastMethod = "searchByIdentifier";
ArrayList<Patient> retVal = new ArrayList<Patient>(); ArrayList<Patient> retVal = new ArrayList<Patient>();
retVal.add((Patient) new Patient().addName(new HumanNameDt().addFamily("FAMILY")).setId("1")); retVal.add((Patient) new Patient().addName(new HumanName().addFamily("FAMILY")).setId("1"));
return retVal; return retVal;
} }
//@formatter:on //@formatter:on

View File

@ -412,6 +412,10 @@
Extensions were placed before any other content, which is incorrect (several Extensions were placed before any other content, which is incorrect (several
elements come first: meta, text, etc.) elements come first: meta, text, etc.)
</action> </action>
<action type="fix">
In server implementations, the Bundle.entry.fullUrl was not getting correctly
populated on Hl7OrgDstu2 servers. Thanks to Christian Ohr for reporting!
</action>
</release> </release>
<release version="1.4" date="2016-02-04"> <release version="1.4" date="2016-02-04">
<action type="add"> <action type="add">