Fix #315 - Use custom types for searches and other client operations

when requested
This commit is contained in:
James Agnew 2016-03-24 11:09:41 +01:00
parent 1adfc4b4d9
commit c6f06548fc
12 changed files with 434 additions and 72 deletions

View File

@ -3,14 +3,17 @@ package example;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.DateTimeType; import org.hl7.fhir.dstu3.model.DateTimeType;
import org.hl7.fhir.dstu3.model.Extension; import org.hl7.fhir.dstu3.model.Extension;
import org.hl7.fhir.dstu3.model.HumanName; import org.hl7.fhir.dstu3.model.HumanName;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Identifier.IdentifierUse; import org.hl7.fhir.dstu3.model.Identifier.IdentifierUse;
import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.StringType;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.CustomTypeDstu3Test.MyCustomPatient;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.rest.client.IGenericClient;
@ -33,6 +36,30 @@ client.create().resource(custPatient).execute();
custPatient = client.read().resource(MyPatient.class).withId("123").execute(); custPatient = client.read().resource(MyPatient.class).withId("123").execute();
//END SNIPPET: customTypeClientSimple //END SNIPPET: customTypeClientSimple
//START SNIPPET: customTypeClientSearch
// Perform the search using the custom type
Bundle bundle = client
.search()
.forResource(MyPatient.class)
.returnBundle(Bundle.class)
.execute();
// Entries in the return bundle will use the given type
MyPatient pat0 = (MyPatient) bundle.getEntry().get(0).getResource();
//END SNIPPET: customTypeClientSearch
//START SNIPPET: customTypeClientSearch2
//Perform the search using the custom type
bundle = client
.history()
.onInstance(new IdType("Patient/123"))
.andReturnBundle(Bundle.class)
.preferResponseType(MyPatient.class)
.execute();
//Entries in the return bundle will use the given type
MyPatient historyPatient0 = (MyPatient) bundle.getEntry().get(0).getResource();
//END SNIPPET: customTypeClientSearch2
} }

View File

@ -88,6 +88,7 @@ public abstract class BaseParser implements IParser {
private IIdType myEncodeForceResourceId; private IIdType myEncodeForceResourceId;
private IParserErrorHandler myErrorHandler; private IParserErrorHandler myErrorHandler;
private boolean myOmitResourceId; private boolean myOmitResourceId;
private List<Class<? extends IBaseResource>> myPreferTypes;
private String myServerBaseUrl; private String myServerBaseUrl;
private boolean myStripVersionsFromReferences = true; private boolean myStripVersionsFromReferences = true;
private boolean mySummaryMode; private boolean mySummaryMode;
@ -437,6 +438,11 @@ public abstract class BaseParser implements IParser {
return tags; return tags;
} }
@Override
public List<Class<? extends IBaseResource>> getPreferTypes() {
return myPreferTypes;
}
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
protected <T extends IPrimitiveType<String>> List<T> getProfileTagsForEncoding(IBaseResource theResource, List<T> theProfiles) { protected <T extends IPrimitiveType<String>> List<T> getProfileTagsForEncoding(IBaseResource theResource, List<T> theProfiles) {
switch (myContext.getAddProfileTagWhenEncoding()) { switch (myContext.getAddProfileTagWhenEncoding()) {
@ -717,6 +723,11 @@ public abstract class BaseParser implements IParser {
return this; return this;
} }
@Override
public void setPreferTypes(List<Class<? extends IBaseResource>> thePreferTypes) {
myPreferTypes = thePreferTypes;
}
@Override @Override
public IParser setServerBaseUrl(String theUrl) { public IParser setServerBaseUrl(String theUrl) {
myServerBaseUrl = isNotBlank(theUrl) ? theUrl : null; myServerBaseUrl = isNotBlank(theUrl) ? theUrl : null;

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.parser;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.io.Writer; import java.io.Writer;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IAnyResource;
@ -30,6 +31,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
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.TagList; import ca.uhn.fhir.model.api.TagList;
@ -95,6 +97,14 @@ public interface IParser {
*/ */
EncodingEnum getEncoding(); EncodingEnum getEncoding();
/**
* Gets the preferred types, as set using {@link #setPreferTypes(List)}
*
* @return Returns the preferred types, or <code>null</code>
* @see #setPreferTypes(List)
*/
List<Class<? extends IBaseResource>> getPreferTypes();
/** /**
* Returns true if resource IDs should be omitted * Returns true if resource IDs should be omitted
* *
@ -289,6 +299,25 @@ public interface IParser {
*/ */
IParser setParserErrorHandler(IParserErrorHandler theErrorHandler); IParser setParserErrorHandler(IParserErrorHandler theErrorHandler);
/**
* If set, when parsing resources the parser will try to use the given types when possible, in
* the order that they are provided (from highest to lowest priority). For example, if a custom
* type which declares to implement the Patient resource is passed in here, and the
* parser is parsing a Bundle containing a Patient resource, the parser will use the given
* custom type.
* <p>
* This feature is related to, but not the same as the
* {@link FhirContext#setDefaultTypeForProfile(String, Class)} feature.
* <code>setDefaultTypeForProfile</code> is used to specify a type to be used
* when a resource explicitly declares support for a given profile. This
* feature specifies a type to be used irrespective of the profile declaration
* in the metadata statement.
* </p>
*
* @param thePreferTypes The preferred types, or <code>null</code>
*/
void setPreferTypes(List<Class<? extends IBaseResource>> thePreferTypes);
/** /**
* Sets the "pretty print" flag, meaning that the parser will encode resources with human-readable spacing and * Sets the "pretty print" flag, meaning that the parser will encode resources with human-readable spacing and
* newlines between elements instead of condensing output as much as possible. * newlines between elements instead of condensing output as much as possible.

View File

@ -220,7 +220,7 @@ public class JsonParser extends BaseParser implements IParser {
assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType"); assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType");
String resourceType = ((JsonString) resourceTypeObj).getString(); String resourceType = ((JsonString) resourceTypeObj).getString();
ParserState<? extends IBaseResource> state = ParserState.getPreResourceInstance(theResourceType, myContext, true, getErrorHandler()); ParserState<? extends IBaseResource> state = ParserState.getPreResourceInstance(this, theResourceType, myContext, true, getErrorHandler());
state.enteringNewElement(null, resourceType); state.enteringNewElement(null, resourceType);
parseChildren(object, state); parseChildren(object, state);
@ -1097,7 +1097,7 @@ public class JsonParser extends BaseParser implements IParser {
throw new DataFormatException("Trying to parse bundle but found resourceType other than 'Bundle'. Found: '" + resourceType + "'"); throw new DataFormatException("Trying to parse bundle but found resourceType other than 'Bundle'. Found: '" + resourceType + "'");
} }
ParserState<Bundle> state = ParserState.getPreAtomInstance(myContext, theResourceType, true, getErrorHandler()); ParserState<Bundle> state = ParserState.getPreAtomInstance(this, myContext, theResourceType, true, getErrorHandler());
if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) {
state.enteringNewElement(null, "Bundle"); state.enteringNewElement(null, "Bundle");
} else { } else {
@ -1420,7 +1420,7 @@ public class JsonParser extends BaseParser implements IParser {
assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType"); assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType");
String resourceType = ((JsonString) resourceTypeObj).getString(); String resourceType = ((JsonString) resourceTypeObj).getString();
ParserState<TagList> state = ParserState.getPreTagListInstance(myContext, true, getErrorHandler()); ParserState<TagList> state = ParserState.getPreTagListInstance(this, myContext, true, getErrorHandler());
state.enteringNewElement(null, resourceType); state.enteringNewElement(null, resourceType);
parseChildren(object, state); parseChildren(object, state);

View File

@ -91,14 +91,16 @@ class ParserState<T> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ParserState.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ParserState.class);
private List<String> myComments = new ArrayList<String>(2); private List<String> myComments = new ArrayList<String>(2);
private FhirContext myContext; private final FhirContext myContext;
private IParserErrorHandler myErrorHandler; private final IParserErrorHandler myErrorHandler;
private boolean myJsonMode; private final boolean myJsonMode;
private T myObject; private T myObject;
private BaseState myState; private BaseState myState;
private IBase myPreviousElement; private IBase myPreviousElement;
private final IParser myParser;
private ParserState(FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) { private ParserState(IParser theParser, FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) {
myParser = theParser;
myContext = theContext; myContext = theContext;
myJsonMode = theJsonMode; myJsonMode = theJsonMode;
myErrorHandler = theErrorHandler; myErrorHandler = theErrorHandler;
@ -247,8 +249,8 @@ class ParserState<T> {
myState.xmlEvent(theNextEvent); myState.xmlEvent(theNextEvent);
} }
static ParserState<Bundle> getPreAtomInstance(FhirContext theContext, Class<? extends IBaseResource> theResourceType, boolean theJsonMode, IParserErrorHandler theErrorHandler) throws DataFormatException { static ParserState<Bundle> getPreAtomInstance(IParser theParser, FhirContext theContext, Class<? extends IBaseResource> theResourceType, boolean theJsonMode, IParserErrorHandler theErrorHandler) throws DataFormatException {
ParserState<Bundle> retVal = new ParserState<Bundle>(theContext, theJsonMode, theErrorHandler); ParserState<Bundle> retVal = new ParserState<Bundle>(theParser, theContext, theJsonMode, theErrorHandler);
if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) { if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
retVal.push(retVal.new PreAtomState(theResourceType)); retVal.push(retVal.new PreAtomState(theResourceType));
} else { } else {
@ -261,8 +263,8 @@ class ParserState<T> {
* @param theResourceType * @param theResourceType
* May be null * May be null
*/ */
static <T extends IBaseResource> ParserState<T> getPreResourceInstance(Class<T> theResourceType, FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) throws DataFormatException { static <T extends IBaseResource> ParserState<T> getPreResourceInstance(IParser theParser, Class<T> theResourceType, FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) throws DataFormatException {
ParserState<T> retVal = new ParserState<T>(theContext, theJsonMode, theErrorHandler); ParserState<T> retVal = new ParserState<T>(theParser, theContext, theJsonMode, theErrorHandler);
if (theResourceType == null) { if (theResourceType == null) {
if (theContext.getVersion().getVersion().isRi()) { if (theContext.getVersion().getVersion().isRi()) {
retVal.push(retVal.new PreResourceStateHl7Org(theResourceType)); retVal.push(retVal.new PreResourceStateHl7Org(theResourceType));
@ -279,8 +281,8 @@ class ParserState<T> {
return retVal; return retVal;
} }
static ParserState<TagList> getPreTagListInstance(FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) { static ParserState<TagList> getPreTagListInstance(IParser theParser, FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) {
ParserState<TagList> retVal = new ParserState<TagList>(theContext, theJsonMode, theErrorHandler); ParserState<TagList> retVal = new ParserState<TagList>(theParser, theContext, theJsonMode, theErrorHandler);
retVal.push(retVal.new PreTagListState()); retVal.push(retVal.new PreTagListState());
return retVal; return retVal;
} }
@ -1997,7 +1999,18 @@ class ParserState<T> {
public void enteringNewElement(String theNamespaceUri, String theLocalPart) throws DataFormatException { public void enteringNewElement(String theNamespaceUri, String theLocalPart) throws DataFormatException {
BaseRuntimeElementDefinition<?> definition; BaseRuntimeElementDefinition<?> definition;
if (myResourceType == null) { if (myResourceType == null) {
definition = null;
if (myParser.getPreferTypes() != null) {
for (Class<? extends IBaseResource> next : myParser.getPreferTypes()) {
RuntimeResourceDefinition nextDef = myContext.getResourceDefinition(next);
if (nextDef.getName().equals(theLocalPart)) {
definition = nextDef;
}
}
}
if (definition == null) {
definition = myContext.getResourceDefinition(myParentVersion, theLocalPart); definition = myContext.getResourceDefinition(myParentVersion, theLocalPart);
}
if ((definition == null)) { if ((definition == null)) {
throw new DataFormatException("Element '" + theLocalPart + "' is not a known resource type, expected a resource at this position"); throw new DataFormatException("Element '" + theLocalPart + "' is not a known resource type, expected a resource at this position");
} }

View File

@ -1080,12 +1080,12 @@ public class XmlParser extends BaseParser implements IParser {
} }
private Bundle parseBundle(XMLEventReader theStreamReader, Class<? extends IBaseResource> theResourceType) { private Bundle parseBundle(XMLEventReader theStreamReader, Class<? extends IBaseResource> theResourceType) {
ParserState<Bundle> parserState = ParserState.getPreAtomInstance(myContext, theResourceType, false, getErrorHandler()); ParserState<Bundle> parserState = ParserState.getPreAtomInstance(this, myContext, theResourceType, false, getErrorHandler());
return doXmlLoop(theStreamReader, parserState); return doXmlLoop(theStreamReader, parserState);
} }
private <T extends IBaseResource> T parseResource(Class<T> theResourceType, XMLEventReader theStreamReader) { private <T extends IBaseResource> T parseResource(Class<T> theResourceType, XMLEventReader theStreamReader) {
ParserState<T> parserState = ParserState.getPreResourceInstance(theResourceType, myContext, false, getErrorHandler()); ParserState<T> parserState = ParserState.getPreResourceInstance(this, theResourceType, myContext, false, getErrorHandler());
return doXmlLoop(theStreamReader, parserState); return doXmlLoop(theStreamReader, parserState);
} }
@ -1093,7 +1093,7 @@ public class XmlParser extends BaseParser implements IParser {
public TagList parseTagList(Reader theReader) { public TagList parseTagList(Reader theReader) {
XMLEventReader streamReader = createStreamReader(theReader); XMLEventReader streamReader = createStreamReader(theReader);
ParserState<TagList> parserState = ParserState.getPreTagListInstance(myContext, false, getErrorHandler()); ParserState<TagList> parserState = ParserState.getPreTagListInstance(this, myContext, false, getErrorHandler());
return doXmlLoop(streamReader, parserState); return doXmlLoop(streamReader, parserState);
} }

View File

@ -107,6 +107,13 @@ public abstract class BaseClient implements IRestfulClient {
return retVal; return retVal;
} }
@Override
public <T extends IBaseResource> T fetchResourceFromUrl(Class<T> theResourceType, String theUrl) {
BaseHttpClientInvocation clientInvocation = new HttpGetClientInvocation(getFhirContext(), theUrl);
ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theResourceType);
return invokeClient(getFhirContext(), binding, clientInvocation, null, false, false, null, null);
}
void forceConformanceCheck() { void forceConformanceCheck() {
myFactory.validateServerBase(myUrlBase, myClient, this); myFactory.validateServerBase(myUrlBase, myClient, this);
} }
@ -221,7 +228,7 @@ public abstract class BaseClient implements IRestfulClient {
if (theLogRequestAndResponse) { if (theLogRequestAndResponse) {
ourLog.info("Client invoking: {}", httpRequest); ourLog.info("Client invoking: {}", httpRequest);
String body = httpRequest.getRequestBodyFromStream(); String body = httpRequest.getRequestBodyFromStream();
if(body != null) { if (body != null) {
ourLog.info("Client request body: {}", body); ourLog.info("Client request body: {}", body);
} }
} }
@ -337,7 +344,7 @@ public abstract class BaseClient implements IRestfulClient {
throw new FhirClientConnectionException(e); throw new FhirClientConnectionException(e);
} catch (IOException e) { } catch (IOException e) {
throw new FhirClientConnectionException(e); throw new FhirClientConnectionException(e);
} catch(RuntimeException e) { } catch (RuntimeException e) {
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
throw new FhirClientConnectionException(e); throw new FhirClientConnectionException(e);
@ -449,28 +456,47 @@ public abstract class BaseClient implements IRestfulClient {
Validate.notNull(theInterceptor, "Interceptor can not be null"); Validate.notNull(theInterceptor, "Interceptor can not be null");
myInterceptors.remove(theInterceptor); myInterceptors.remove(theInterceptor);
} }
static ArrayList<Class<? extends IBaseResource>> toTypeList(Class<? extends IBaseResource> thePreferResponseType) {
@Override ArrayList<Class<? extends IBaseResource>> preferResponseTypes = null;
public <T extends IBaseResource> T fetchResourceFromUrl(Class<T> theResourceType, String theUrl) { if (thePreferResponseType != null) {
BaseHttpClientInvocation clientInvocation = new HttpGetClientInvocation(getFhirContext(), theUrl); preferResponseTypes = new ArrayList<Class<? extends IBaseResource>>(1);
ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theResourceType, null, false); preferResponseTypes.add(thePreferResponseType);
return invokeClient(getFhirContext(), binding, clientInvocation, null, false, false, null, null); }
return preferResponseTypes;
} }
protected final class ResourceResponseHandler<T extends IBaseResource> implements IClientResponseHandler<T> { protected final class ResourceResponseHandler<T extends IBaseResource> implements IClientResponseHandler<T> {
private boolean myAllowHtmlResponse; private boolean myAllowHtmlResponse;
private IIdType myId; private IIdType myId;
private Class<T> myType; private List<Class<? extends IBaseResource>> myPreferResponseTypes;
private Class<T> myReturnType;
public ResourceResponseHandler(Class<T> theType, IIdType theId) { public ResourceResponseHandler() {
myType = theType; this(null);
myId = theId;
} }
public ResourceResponseHandler(Class<T> theType, IIdType theId, boolean theAllowHtmlResponse) { public ResourceResponseHandler(Class<T> theReturnType) {
myType = theType; this(theReturnType, null, null);
}
public ResourceResponseHandler(Class<T> theReturnType, Class<? extends IBaseResource> thePreferResponseType, IIdType theId) {
this(theReturnType, thePreferResponseType, theId, false);
}
public ResourceResponseHandler(Class<T> theReturnType, Class<? extends IBaseResource> thePreferResponseType, IIdType theId, boolean theAllowHtmlResponse) {
this(theReturnType, toTypeList(thePreferResponseType), theId, theAllowHtmlResponse);
}
public ResourceResponseHandler(Class<T> theClass, List<Class<? extends IBaseResource>> thePreferResponseTypes) {
this(theClass, thePreferResponseTypes, null, false);
}
public ResourceResponseHandler(Class<T> theReturnType, List<Class<? extends IBaseResource>> thePreferResponseTypes, IIdType theId, boolean theAllowHtmlResponse) {
myReturnType = theReturnType;
myId = theId; myId = theId;
myPreferResponseTypes = thePreferResponseTypes;
myAllowHtmlResponse = theAllowHtmlResponse; myAllowHtmlResponse = theAllowHtmlResponse;
} }
@ -478,13 +504,16 @@ public abstract class BaseClient implements IRestfulClient {
public T invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException { public T invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType); EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) { if (respType == null) {
if (myAllowHtmlResponse && theResponseMimeType.toLowerCase().contains(Constants.CT_HTML) && myType != null) { if (myAllowHtmlResponse && theResponseMimeType.toLowerCase().contains(Constants.CT_HTML) && myReturnType != null) {
return readHtmlResponse(theResponseReader); return readHtmlResponse(theResponseReader);
} }
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader); throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
} }
IParser parser = respType.newParser(getFhirContext()); IParser parser = respType.newParser(getFhirContext());
T retVal = parser.parseResource(myType, theResponseReader); if (myPreferResponseTypes != null) {
parser.setPreferTypes(myPreferResponseTypes);
}
T retVal = parser.parseResource(myReturnType, theResponseReader);
MethodUtil.parseClientRequestResourceHeaders(myId, theHeaders, retVal); MethodUtil.parseClientRequestResourceHeaders(myId, theHeaders, retVal);
@ -493,7 +522,7 @@ public abstract class BaseClient implements IRestfulClient {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private T readHtmlResponse(Reader theResponseReader) { private T readHtmlResponse(Reader theResponseReader) {
RuntimeResourceDefinition resDef = getFhirContext().getResourceDefinition(myType); RuntimeResourceDefinition resDef = getFhirContext().getResourceDefinition(myReturnType);
IBaseResource instance = resDef.newInstance(); IBaseResource instance = resDef.newInstance();
BaseRuntimeChildDefinition textChild = resDef.getChildByName("text"); BaseRuntimeChildDefinition textChild = resDef.getChildByName("text");
BaseRuntimeElementCompositeDefinition<?> textElement = (BaseRuntimeElementCompositeDefinition<?>) textChild.getChildByName("text"); BaseRuntimeElementCompositeDefinition<?> textElement = (BaseRuntimeElementCompositeDefinition<?>) textChild.getChildByName("text");
@ -511,6 +540,10 @@ public abstract class BaseClient implements IRestfulClient {
divChild.getMutator().addValue(textInstance, divInstance); divChild.getMutator().addValue(textInstance, divInstance);
return (T) instance; return (T) instance;
} }
public void setPreferResponseTypes(List<Class<? extends IBaseResource>> thePreferResponseTypes) {
myPreferResponseTypes = thePreferResponseTypes;
}
} }
} }

View File

@ -182,7 +182,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Class<BaseConformance> conformance = (Class<BaseConformance>) myContext.getResourceDefinition("Conformance").getImplementingClass(); Class<BaseConformance> conformance = (Class<BaseConformance>) myContext.getResourceDefinition("Conformance").getImplementingClass();
ResourceResponseHandler<? extends BaseConformance> binding = new ResourceResponseHandler<BaseConformance>(conformance, null); ResourceResponseHandler<? extends BaseConformance> binding = new ResourceResponseHandler<BaseConformance>(conformance);
BaseConformance resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse); BaseConformance resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse);
return resp; return resp;
} }
@ -263,7 +263,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
} }
boolean allowHtmlResponse = (theSummary == SummaryEnum.TEXT) || (theSummary == null && getSummary() == SummaryEnum.TEXT); boolean allowHtmlResponse = (theSummary == SummaryEnum.TEXT) || (theSummary == null && getSummary() == SummaryEnum.TEXT);
ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theType, id, allowHtmlResponse); ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theType, (Class<? extends IBaseResource>)null, id, allowHtmlResponse);
if (theNotModifiedHandler == null) { if (theNotModifiedHandler == null) {
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements); return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements);
@ -596,6 +596,39 @@ public class GenericClient extends BaseClient implements IGenericClient {
} }
private abstract class BaseClientExecutable<T extends IClientExecutable<?, ?>, Y> implements IClientExecutable<T, Y> { private abstract class BaseClientExecutable<T extends IClientExecutable<?, ?>, Y> implements IClientExecutable<T, Y> {
private List<Class<? extends IBaseResource>> myPreferResponseTypes;
public List<Class<? extends IBaseResource>> getPreferResponseTypes() {
return myPreferResponseTypes;
}
public List<Class<? extends IBaseResource>> getPreferResponseTypes(Class<? extends IBaseResource> theDefault) {
if (myPreferResponseTypes != null) {
return myPreferResponseTypes;
} else {
return toTypeList(theDefault);
}
}
@SuppressWarnings("unchecked")
@Override
public T preferResponseType(Class<? extends IBaseResource> theClass) {
myPreferResponseTypes = null;
if (theClass != null) {
myPreferResponseTypes = new ArrayList<Class<? extends IBaseResource>>();
myPreferResponseTypes.add(theClass);
}
return (T) this;
}
@SuppressWarnings("unchecked")
@Override
public T preferResponseTypes(List<Class<? extends IBaseResource>> theClass) {
myPreferResponseTypes = theClass;
return (T) this;
}
protected EncodingEnum myParamEncoding; protected EncodingEnum myParamEncoding;
protected Boolean myPrettyPrint; protected Boolean myPrettyPrint;
private boolean myQueryLogRequestAndResponse; private boolean myQueryLogRequestAndResponse;
@ -925,7 +958,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override @Override
public Object execute() { public Object execute() {
ResourceResponseHandler binding = new ResourceResponseHandler(myType.getImplementingClass(), null); ResourceResponseHandler binding = new ResourceResponseHandler(myType.getImplementingClass());
HttpGetClientInvocation invocation = MethodUtil.createConformanceInvocation(getFhirContext()); HttpGetClientInvocation invocation = MethodUtil.createConformanceInvocation(getFhirContext());
return super.invoke(null, binding, invocation); return super.invoke(null, binding, invocation);
} }
@ -963,7 +996,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
if (myBundleType == null) { if (myBundleType == null) {
binding = new BundleResponseHandler(null); binding = new BundleResponseHandler(null);
} else { } else {
binding = new ResourceResponseHandler(myBundleType, null); binding = new ResourceResponseHandler(myBundleType, getPreferResponseTypes());
} }
HttpSimpleGetClientInvocation invocation = new HttpSimpleGetClientInvocation(myContext, myUrl); HttpSimpleGetClientInvocation invocation = new HttpSimpleGetClientInvocation(myContext, myUrl);
@ -1087,7 +1120,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
IClientResponseHandler handler; IClientResponseHandler handler;
if (myReturnType != null) { if (myReturnType != null) {
handler = new ResourceResponseHandler(myReturnType, null); handler = new ResourceResponseHandler(myReturnType, getPreferResponseTypes(myType));
} else { } else {
handler = new BundleResponseHandler(null); handler = new BundleResponseHandler(null);
} }
@ -1391,8 +1424,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
BaseHttpClientInvocation invocation = OperationMethodBinding.createOperationInvocation(myContext, resourceName, id, myOperationName, myParameters, myUseHttpGet); BaseHttpClientInvocation invocation = OperationMethodBinding.createOperationInvocation(myContext, resourceName, id, myOperationName, myParameters, myUseHttpGet);
IClientResponseHandler handler; ResourceResponseHandler handler = new ResourceResponseHandler();
handler = new ResourceResponseHandler(null, null); handler.setPreferResponseTypes(getPreferResponseTypes(myType));
Object retVal = invoke(null, handler, invocation); Object retVal = invoke(null, handler, invocation);
if (myContext.getResourceDefinition((IBaseResource) retVal).getName().equals("Parameters")) { if (myContext.getResourceDefinition((IBaseResource) retVal).getName().equals("Parameters")) {
@ -1705,7 +1738,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
throws BaseServerResponseException { throws BaseServerResponseException {
if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) {
Class<? extends IBaseResource> bundleType = myContext.getResourceDefinition("Bundle").getImplementingClass(); Class<? extends IBaseResource> bundleType = myContext.getResourceDefinition("Bundle").getImplementingClass();
ResourceResponseHandler<IBaseResource> handler = new ResourceResponseHandler<IBaseResource>((Class<IBaseResource>) bundleType, null); ResourceResponseHandler<IBaseResource> handler = new ResourceResponseHandler<IBaseResource>((Class<IBaseResource>) bundleType);
IBaseResource response = handler.invokeClient(theResponseMimeType, theResponseReader, theResponseStatusCode, theHeaders); IBaseResource response = handler.invokeClient(theResponseMimeType, theResponseReader, theResponseStatusCode, theHeaders);
IVersionSpecificBundleFactory bundleFactory = myContext.newBundleFactory(); IVersionSpecificBundleFactory bundleFactory = myContext.newBundleFactory();
bundleFactory.initializeWithBundleResource(response); bundleFactory.initializeWithBundleResource(response);
@ -1804,7 +1837,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
IClientResponseHandler<? extends IBase> binding; IClientResponseHandler<? extends IBase> binding;
if (myReturnBundleType != null) { if (myReturnBundleType != null) {
binding = new ResourceResponseHandler(myReturnBundleType, null); binding = new ResourceResponseHandler(myReturnBundleType, getPreferResponseTypes(myResourceType));
} else { } else {
binding = new BundleResponseHandler(myResourceType); binding = new BundleResponseHandler(myResourceType);
} }
@ -2070,13 +2103,13 @@ public class GenericClient extends BaseClient implements IGenericClient {
BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myResources, myContext); BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myResources, myContext);
return (T) invoke(params, binding, invocation); return (T) invoke(params, binding, invocation);
} else if (myBaseBundle != null) { } else if (myBaseBundle != null) {
ResourceResponseHandler binding = new ResourceResponseHandler(myBaseBundle.getClass(), null); ResourceResponseHandler binding = new ResourceResponseHandler(myBaseBundle.getClass(), getPreferResponseTypes());
BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myBaseBundle, myContext); BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myBaseBundle, myContext);
return (T) invoke(params, binding, invocation); return (T) invoke(params, binding, invocation);
} else if (myRawBundle != null) { } else if (myRawBundle != null) {
StringResponseHandler binding = new StringResponseHandler(); StringResponseHandler binding = new StringResponseHandler();
/* /*
* If the user has explicitly requested a given encoding, we may need to reencode the raw string * If the user has explicitly requested a given encoding, we may need to re-encode the raw string
*/ */
if (getParamEncoding() != null) { if (getParamEncoding() != null) {
if (MethodUtil.detectEncodingNoDefault(myRawBundle) != getParamEncoding()) { if (MethodUtil.detectEncodingNoDefault(myRawBundle) != getParamEncoding()) {

View File

@ -1,5 +1,9 @@
package ca.uhn.fhir.rest.gclient; package ca.uhn.fhir.rest.gclient;
import java.util.List;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.api.SummaryEnum;
/* /*
@ -49,6 +53,27 @@ public interface IClientExecutable<T extends IClientExecutable<?,?>, Y> {
T prettyPrint(); T prettyPrint();
/**
* Explicitly specify a custom structure type to attempt to use when parsing the response. This
* is useful for invocations where the response is a Bundle/Parameters containing nested resources,
* and you want to use specific custom structures for those nested resources.
* <p>
* See <a href="https://jamesagnew.github.io/hapi-fhir/doc_extensions.html">Profiles and Extensions</a> for more information on using custom structures
* </p>
*/
T preferResponseType(Class<? extends IBaseResource> theType);
/**
* Explicitly specify a list of custom structure types to attempt to use (in order from most to
* least preferred) when parsing the response. This
* is useful for invocations where the response is a Bundle/Parameters containing nested resources,
* and you want to use specific custom structures for those nested resources.
* <p>
* See <a href="https://jamesagnew.github.io/hapi-fhir/doc_extensions.html">Profiles and Extensions</a> for more information on using custom structures
* </p>
*/
T preferResponseTypes(List<Class<? extends IBaseResource>> theTypes);
/** /**
* Request that the server modify the response using the <code>_summary</code> param * Request that the server modify the response using the <code>_summary</code> param
*/ */

View File

@ -38,7 +38,7 @@ public class CustomTypeDstu3Test {
} }
private String createBundle(String... theResources) { public static String createBundle(String... theResources) {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
b.append("<Bundle xmlns=\"http://hl7.org/fhir\">\n"); b.append("<Bundle xmlns=\"http://hl7.org/fhir\">\n");
for (String next : theResources) { for (String next : theResources) {
@ -52,7 +52,7 @@ public class CustomTypeDstu3Test {
return b.toString(); return b.toString();
} }
private String createResource(boolean theWithProfile) { public static String createResource(boolean theWithProfile) {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
b.append("<Patient xmlns=\"http://hl7.org/fhir\">\n"); b.append("<Patient xmlns=\"http://hl7.org/fhir\">\n");
if (theWithProfile) { if (theWithProfile) {
@ -120,7 +120,6 @@ public class CustomTypeDstu3Test {
@Test @Test
public void parseBundleWithResourceDirective() { public void parseBundleWithResourceDirective() {
String input = createBundle(createResource(false), createResource(true)); String input = createBundle(createResource(false), createResource(true));
FhirContext ctx = FhirContext.forDstu3(); FhirContext ctx = FhirContext.forDstu3();

View File

@ -10,7 +10,9 @@ import static org.mockito.Mockito.when;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.ReaderInputStream; import org.apache.commons.io.input.ReaderInputStream;
@ -22,9 +24,12 @@ import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicStatusLine; import org.apache.http.message.BasicStatusLine;
import org.hl7.fhir.dstu3.model.Binary; import org.hl7.fhir.dstu3.model.Binary;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Conformance; import org.hl7.fhir.dstu3.model.Conformance;
import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
@ -34,6 +39,8 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.CustomTypeDstu3Test;
import ca.uhn.fhir.parser.CustomTypeDstu3Test.MyCustomPatient;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.util.VersionUtil; import ca.uhn.fhir.util.VersionUtil;
@ -88,6 +95,163 @@ public class GenericClientDstu3Test {
validateUserAgent(capt); validateUserAgent(capt);
} }
@Test
public void testExplicitCustomTypeSearch() throws Exception {
final String respString = CustomTypeDstu3Test.createBundle(CustomTypeDstu3Test.createResource(false));
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
//@formatter:off
Bundle resp = client
.search()
.forResource(CustomTypeDstu3Test.MyCustomPatient.class)
.returnBundle(Bundle.class)
.execute();
//@formatter:on
assertEquals(1, resp.getEntry().size());
assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getEntry().get(0).getResource().getClass());
assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).getURI().toASCIIString());
}
@Test
public void testExplicitCustomTypeHistoryType() throws Exception {
final String respString = CustomTypeDstu3Test.createBundle(CustomTypeDstu3Test.createResource(false));
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
//@formatter:off
Bundle resp = client
.history()
.onType(CustomTypeDstu3Test.MyCustomPatient.class)
.andReturnBundle(Bundle.class)
.execute();
//@formatter:on
assertEquals(1, resp.getEntry().size());
assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getEntry().get(0).getResource().getClass());
assertEquals("http://example.com/fhir/Patient/_history", capt.getAllValues().get(0).getURI().toASCIIString());
}
@Test
public void testExplicitCustomTypeLoadPage() throws Exception {
final String respString = CustomTypeDstu3Test.createBundle(CustomTypeDstu3Test.createResource(false));
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
Bundle bundle = new Bundle();
bundle.addLink().setRelation("next").setUrl("http://foo/next");
//@formatter:off
Bundle resp = client
.loadPage()
.next(bundle)
.preferResponseType(MyCustomPatient.class)
.execute();
//@formatter:on
assertEquals(1, resp.getEntry().size());
assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getEntry().get(0).getResource().getClass());
assertEquals("http://foo/next", capt.getAllValues().get(0).getURI().toASCIIString());
//@formatter:off
resp = client
.loadPage()
.next(bundle)
.preferResponseTypes(toTypeList(MyCustomPatient.class))
.execute();
//@formatter:on
assertEquals(1, resp.getEntry().size());
assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getEntry().get(0).getResource().getClass());
assertEquals("http://foo/next", capt.getAllValues().get(0).getURI().toASCIIString());
}
@Test
public void testExplicitCustomTypeOperation() throws Exception {
Parameters param = new Parameters();
Patient patient = new Patient();
patient.addName().addFamily("FOO");
param.addParameter().setName("foo").setResource(patient);
final String respString = ourCtx.newXmlParser().encodeResourceToString(param);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
//@formatter:off
Parameters resp = client
.operation()
.onServer()
.named("foo")
.withNoParameters(Parameters.class)
.preferResponseType(MyCustomPatient.class)
.execute();
//@formatter:on
assertEquals(1, resp.getParameter().size());
assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getParameter().get(0).getResource().getClass());
assertEquals("http://example.com/fhir/$foo", capt.getAllValues().get(0).getURI().toASCIIString());
//@formatter:off
resp = client
.operation()
.onType(MyCustomPatient.class)
.named("foo")
.withNoParameters(Parameters.class)
.execute();
//@formatter:on
assertEquals(1, resp.getParameter().size());
assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getParameter().get(0).getResource().getClass());
assertEquals("http://example.com/fhir/Patient/$foo", capt.getAllValues().get(1).getURI().toASCIIString());
}
private List<Class<? extends IBaseResource>> toTypeList(Class<? extends IBaseResource> theClass) {
ArrayList<Class<? extends IBaseResource>> retVal = new ArrayList<Class<? extends IBaseResource>>();
retVal.add(theClass);
return retVal;
}
@Test @Test
public void testUserAgentForBinary() throws Exception { public void testUserAgentForBinary() throws Exception {
IParser p = ourCtx.newXmlParser(); IParser p = ourCtx.newXmlParser();

View File

@ -185,6 +185,29 @@
<param name="file" value="examples/src/main/java/example/ExtensionsDstu3.java" /> <param name="file" value="examples/src/main/java/example/ExtensionsDstu3.java" />
</macro> </macro>
<p>
You may also explicitly use custom types in searches and other
operations which return resources.
</p>
<macro name="snippet">
<param name="id" value="customTypeClientSearch" />
<param name="file" value="examples/src/main/java/example/ExtensionsDstu3.java" />
</macro>
<p>
You can also explicitly declare a preferred response resource custom
type. This is useful for some operations that do not otherwise
declare their resource types in the method signature.
</p>
<macro name="snippet">
<param name="id" value="customTypeClientSearch2" />
<param name="file" value="examples/src/main/java/example/ExtensionsDstu3.java" />
</macro>
</subsection>
<subsection name="Using Multiple Custom Types in a Client">
<p> <p>
Sometimes you may not know in advance exactly which Sometimes you may not know in advance exactly which
type you will be receiving. For example, there are Patient resources type you will be receiving. For example, there are Patient resources
@ -192,6 +215,11 @@
aren't sure which profile you will get back for a specific read, aren't sure which profile you will get back for a specific read,
you can declare the "primary" type for a given profile. you can declare the "primary" type for a given profile.
</p> </p>
<p>
This is declared at the FhirContext level, and will apply to any
clients created from this context (including clients created before
the default was set).
</p>
<macro name="snippet"> <macro name="snippet">
<param name="id" value="customTypeClientDeclared" /> <param name="id" value="customTypeClientDeclared" />
<param name="file" value="examples/src/main/java/example/ExtensionsDstu3.java" /> <param name="file" value="examples/src/main/java/example/ExtensionsDstu3.java" />