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) {
List<IBaseResource> includedResources = new ArrayList<IBaseResource>();
Set<IIdType> addedResourceIds = new HashSet<IIdType>();
List<IBaseResource> includedResources = new ArrayList<IBaseResource>();
Set<IIdType> addedResourceIds = new HashSet<IIdType>();
for (IBaseResource next : theResult) {
if (next.getIdElement().isEmpty() == false) {
addedResourceIds.add(next.getIdElement());
}
}
for (IBaseResource next : theResult) {
if (next.getIdElement().isEmpty() == false) {
addedResourceIds.add(next.getIdElement());
}
}
for (IBaseResource nextBaseRes : theResult) {
IDomainResource next = (IDomainResource) nextBaseRes;
Set<String> containedIds = new HashSet<String>();
for (IBaseResource nextContained : next.getContained()) {
if (nextContained.getIdElement().isEmpty() == false) {
containedIds.add(nextContained.getIdElement().getValue());
}
}
for (IBaseResource nextBaseRes : theResult) {
IDomainResource next = (IDomainResource) nextBaseRes;
Set<String> containedIds = new HashSet<String>();
for (IBaseResource nextContained : next.getContained()) {
if (nextContained.getIdElement().isEmpty() == false) {
containedIds.add(nextContained.getIdElement().getValue());
}
}
List<IBaseReference> references = myContext.newTerser().getAllPopulatedChildElementsOfType(next, IBaseReference.class);
do {
List<IBaseResource> addedResourcesThisPass = new ArrayList<IBaseResource>();
List<IBaseReference> references = myContext.newTerser().getAllPopulatedChildElementsOfType(next,
IBaseReference.class);
do {
List<IBaseResource> addedResourcesThisPass = new ArrayList<IBaseResource>();
for (IBaseReference nextRef : references) {
IBaseResource nextRes = (IBaseResource) nextRef.getResource();
if (nextRes != null) {
if (nextRes.getIdElement().hasIdPart()) {
if (containedIds.contains(nextRes.getIdElement().getValue())) {
// Don't add contained IDs as top level resources
continue;
}
for (IBaseReference nextRef : references) {
IBaseResource nextRes = (IBaseResource) nextRef.getResource();
if (nextRes != null) {
if (nextRes.getIdElement().hasIdPart()) {
if (containedIds.contains(nextRes.getIdElement().getValue())) {
// Don't add contained IDs as top level resources
continue;
}
IIdType id = nextRes.getIdElement();
if (id.hasResourceType() == false) {
String resName = myContext.getResourceDefinition(nextRes).getName();
id = id.withResourceType(resName);
}
IIdType id = nextRes.getIdElement();
if (id.hasResourceType() == false) {
String resName = myContext.getResourceDefinition(nextRes).getName();
id = id.withResourceType(resName);
}
if (!addedResourceIds.contains(id)) {
addedResourceIds.add(id);
addedResourcesThisPass.add(nextRes);
}
if (!addedResourceIds.contains(id)) {
addedResourceIds.add(id);
addedResourcesThisPass.add(nextRes);
}
}
}
}
}
}
}
// Linked resources may themselves have linked resources
references = new ArrayList<IBaseReference>();
for (IBaseResource iResource : addedResourcesThisPass) {
List<IBaseReference> newReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(iResource, IBaseReference.class);
references.addAll(newReferences);
}
// Linked resources may themselves have linked resources
references = new ArrayList<IBaseReference>();
for (IBaseResource iResource : addedResourcesThisPass) {
List<IBaseReference> newReferences = myContext.newTerser().getAllPopulatedChildElementsOfType(iResource,
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);
}
}
/*
* Actually add the resources to the bundle
*/
for (IBaseResource next : includedResources) {
BundleEntryComponent entry = myBundle.addEntry();
/*
* Actually add the resources to the bundle
*/
for (IBaseResource next : includedResources) {
BundleEntryComponent entry = myBundle.addEntry();
entry.setResource((Resource) next).getSearch().setMode(SearchEntryMode.INCLUDE);
populateBundleEntryFullUrl(next, entry);
}
}
}
}
private void populateBundleEntryFullUrl(IBaseResource next, BundleEntryComponent entry) {
if (next.getIdElement().hasBaseUrl()) {
@ -230,8 +232,10 @@ public class Dstu2Hl7OrgBundleFactory implements IVersionSpecificBundleFactory {
} while (references.isEmpty() == false);
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) {
// entry.getSearch().getModeElement().setValue(searchMode.getCode());
// }
@ -352,8 +356,9 @@ public class Dstu2Hl7OrgBundleFactory implements IVersionSpecificBundleFactory {
if (searchId != null) {
if (theOffset + numToReturn < theResult.size()) {
myBundle.addLink().setRelation(Constants.LINK_NEXT).setUrl(RestfulServerUtils.createPagingLink(theIncludes,
theServerBase, searchId, theOffset + numToReturn, numToReturn, theResponseEncoding, thePrettyPrint, theBundleType));
myBundle.addLink().setRelation(Constants.LINK_NEXT)
.setUrl(RestfulServerUtils.createPagingLink(theIncludes, theServerBase, searchId, theOffset + numToReturn,
numToReturn, theResponseEncoding, thePrettyPrint, theBundleType));
}
if (theOffset > 0) {
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.servlet.ServletHandler;
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.Before;
import org.junit.BeforeClass;
import org.junit.Test;
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.Search;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
public class SearchWithGenericListHl7OrgDstu2Test {
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 int ourPort;
private static Server ourServer;
@ -62,10 +63,13 @@ public class SearchWithGenericListHl7OrgDstu2Test {
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
@ -94,7 +98,7 @@ public class SearchWithGenericListHl7OrgDstu2Test {
public static class DummyPatientResourceProvider implements IResourceProvider {
@Override
public Class<? extends IResource> getResourceType() {
public Class<? extends IBaseResource> getResourceType() {
return Patient.class;
}
@ -105,7 +109,7 @@ public class SearchWithGenericListHl7OrgDstu2Test {
@RequiredParam(name=Patient.SP_IDENTIFIER) TokenParam theIdentifier) {
ourLastMethod = "searchByIdentifier";
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;
}
//@formatter:on

View File

@ -412,6 +412,10 @@
Extensions were placed before any other content, which is incorrect (several
elements come first: meta, text, etc.)
</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 version="1.4" date="2016-02-04">
<action type="add">