Fix #299 - Don't crash if the client receives extensions in Bundle.entry.search

This commit is contained in:
jamesagnew 2016-02-28 19:38:54 -05:00
parent c7d3f39457
commit ed5bffba9e
6 changed files with 231 additions and 19 deletions

View File

@ -2117,16 +2117,19 @@ class ParserState<T> {
public PreResourceStateHapi(BundleEntry theEntry, Class<? extends IBaseResource> theResourceType) {
super(theResourceType);
myEntry = theEntry;
assert theResourceType == null || IResource.class.isAssignableFrom(theResourceType);
}
public PreResourceStateHapi(Class<? extends IBaseResource> theResourceType) {
super(theResourceType);
assert theResourceType == null || IResource.class.isAssignableFrom(theResourceType);
}
public PreResourceStateHapi(Object theTarget, IMutator theMutator, Class<? extends IBaseResource> theResourceType) {
super(theResourceType);
myTarget = theTarget;
myMutator = theMutator;
assert theResourceType == null || IResource.class.isAssignableFrom(theResourceType);
}
@Override

View File

@ -25,21 +25,23 @@ import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
@ -62,6 +64,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.ReflectionUtil;
import ca.uhn.fhir.util.UrlUtil;
@ -157,32 +160,40 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
switch (getReturnType()) {
case BUNDLE: {
Bundle bundle;
if (myResourceType != null) {
bundle = parser.parseBundle(myResourceType, theResponseReader);
Bundle dstu1bundle = null;
IBaseBundle dstu2bundle = null;
List<IBaseResource> listOfResources = null;
if (getMethodReturnType() == MethodReturnTypeEnum.BUNDLE || getContext().getVersion().getVersion() == FhirVersionEnum.DSTU1) {
if (myResourceType != null) {
dstu1bundle = parser.parseBundle(myResourceType, theResponseReader);
} else {
dstu1bundle = parser.parseBundle(theResponseReader);
}
} else {
bundle = parser.parseBundle(theResponseReader);
Class<? extends IBaseResource> type = getContext().getResourceDefinition("Bundle").getImplementingClass();
dstu2bundle = (IBaseBundle) parser.parseResource(type, theResponseReader);
listOfResources = BundleUtil.toListOfResources(getContext(), dstu2bundle);
}
switch (getMethodReturnType()) {
case BUNDLE:
return bundle;
return dstu1bundle;
case BUNDLE_RESOURCE:
return dstu2bundle;
case LIST_OF_RESOURCES:
List<IResource> listOfResources;
if (myResourceListCollectionType != null) {
listOfResources = new ArrayList<IResource>();
for (IResource next : bundle.toListOfResources()) {
for (Iterator<IBaseResource> iter = listOfResources.iterator(); iter.hasNext(); ) {
IBaseResource next = iter.next();
if (!myResourceListCollectionType.isAssignableFrom(next.getClass())) {
ourLog.debug("Not returning resource of type {} because it is not a subclass or instance of {}", next.getClass(), myResourceListCollectionType);
continue;
iter.remove();
}
listOfResources.add(next);
}
} else {
listOfResources = bundle.toListOfResources();
}
return listOfResources;
case RESOURCE:
List<IResource> list = bundle.toListOfResources();
List<IResource> list = dstu1bundle.toListOfResources();
if (list.size() == 0) {
return null;
} else if (list.size() == 1) {

View File

@ -37,6 +37,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
@ -130,7 +131,11 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
@Override
public ReturnTypeEnum getReturnType() {
return ReturnTypeEnum.BUNDLE;
// if (getContext().getVersion().getVersion() == FhirVersionEnum.DSTU1) {
return ReturnTypeEnum.BUNDLE;
// } else {
// return ReturnTypeEnum.RESOURCE;
// }
}
@Override

View File

@ -0,0 +1,41 @@
package ca.uhn.fhir.util;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
/**
* Fetch resources from a bundle
*/
public class BundleUtil {
/**
* Extract all of the resources from a given bundle
*/
public static List<IBaseResource> toListOfResources(FhirContext theContext, IBaseBundle theBundle) {
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
for (IBase nextEntry : entries) {
for (IBase next : resourceChild.getAccessor().getValues(nextEntry)) {
retVal.add((IBaseResource) next);
}
}
return retVal;
}
}

View File

@ -1,6 +1,5 @@
package ca.uhn.fhir.rest.client;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -27,10 +26,8 @@ import org.mockito.stubbing.Answer;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.Parameters;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.valueset.IssueTypeEnum;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.Constants;
@ -39,7 +36,6 @@ public class ClientWithProfileDstu2Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ClientWithProfileDstu2Test.class);
private FhirContext ourCtx;
private HttpClient ourHttpClient;
private HttpResponse ourHttpResponse;
@Before

View File

@ -0,0 +1,156 @@
package ca.uhn.fhir.rest.client;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.InputStream;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.util.List;
import org.apache.commons.io.input.ReaderInputStream;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicStatusLine;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Extension;
import org.hl7.fhir.dstu3.model.Location;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.Count;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.Constants;
public class SearchClientDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchClientDstu3Test.class);
private FhirContext ourCtx;
private HttpClient ourHttpClient;
private HttpResponse ourHttpResponse;
@Before
public void before() {
ourCtx = FhirContext.forDstu3();
ourHttpClient = mock(HttpClient.class, new ReturnsDeepStubs());
ourCtx.getRestfulClientFactory().setHttpClient(ourHttpClient);
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
}
/**
* See #299
*/
@Test
public void testListResponseWithSearchExtension() throws Exception {
final String response = createBundleWithSearchExtension();
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(ourHttpClient.execute(capt.capture())).thenReturn(ourHttpResponse);
when(ourHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(ourHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(ourHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(response), Charset.forName("UTF-8"));
}
});
ILocationClient client = ourCtx.newRestfulClient(ILocationClient.class, "http://localhost:8081/hapi-fhir/fhir");
List<Location> matches = client.getMatches(new StringParam("smith"), 100);
assertEquals(1, matches.size());
assertEquals("Sample Clinic", matches.get(0).getName());
HttpGet value = (HttpGet) capt.getValue();
assertEquals("http://localhost:8081/hapi-fhir/fhir/Location?_query=match&name=smith&_count=100", value.getURI().toString());
}
/**
* See #299
*/
@Test
public void testBundleResponseWithSearchExtension() throws Exception {
final String response = createBundleWithSearchExtension();
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(ourHttpClient.execute(capt.capture())).thenReturn(ourHttpResponse);
when(ourHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(ourHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(ourHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(response), Charset.forName("UTF-8"));
}
});
ILocationClient client = ourCtx.newRestfulClient(ILocationClient.class, "http://localhost:8081/hapi-fhir/fhir");
Bundle matches = client.getMatchesReturnBundle(new StringParam("smith"), 100);
assertEquals(1, matches.getEntry().size());
BundleEntryComponent entry = matches.getEntry().get(0);
assertEquals("Sample Clinic", ((Location)entry.getResource()).getName());
List<Extension> ext = entry.getSearch().getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/algorithmic-match");
assertEquals(1, ext.size());
HttpGet value = (HttpGet) capt.getValue();
assertEquals("http://localhost:8081/hapi-fhir/fhir/Location?_query=match&name=smith&_count=100", value.getURI().toString());
}
private String createBundleWithSearchExtension() {
//@formatter:off
final String response = "<Bundle xmlns=\"http://hl7.org/fhir\">"
+ "<id value=\"f61f6ddc-95e8-4ef9-a4cd-17c79bbb74f3\"></id>"
+ "<meta><lastUpdated value=\"2016-02-19T12:04:02.616-05:00\"></lastUpdated></meta>"
+ "<type value=\"searchset\"></type>"
+ "<link><relation value=\"self\"></relation><url value=\"http://localhost:8081/hapi-fhir/fhir/Location?name=Sample+Clinic&amp;_query=match\"></url></link>"
+ "<entry>"
+ "<resource>"
+ "<Location xmlns=\"http://hl7.org/fhir\">"
+ "<id value=\"1\"></id>"
+ "<name value=\"Sample Clinic\"></name>"
+ "</Location>"
+ "</resource>"
+ "<search>"
+ "<extension url=\"http://hl7.org/fhir/StructureDefinition/algorithmic-match\">"
+ "<valueCode value=\"probable\"></valueCode>"
+ "</extension>"
+ "<score value=\"0.8000000000000000444089209850062616169452667236328125\">"
+ "</score>"
+ "</search>"
+ "</entry>"
+ "</Bundle>";
//@formatter:on
return response;
}
public interface ILocationClient extends IRestfulClient {
@Search(queryName = "match")
public List<Location> getMatches(final @RequiredParam(name = Location.SP_NAME) StringParam name, final @Count Integer count);
@Search(queryName = "match", type=Location.class)
public Bundle getMatchesReturnBundle(final @RequiredParam(name = Location.SP_NAME) StringParam name, final @Count Integer count);
}
}