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.util.List;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.DateTimeType;
import org.hl7.fhir.dstu3.model.Extension;
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.Patient;
import org.hl7.fhir.dstu3.model.StringType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.CustomTypeDstu3Test.MyCustomPatient;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.client.IGenericClient;
@ -33,7 +36,31 @@ client.create().resource(custPatient).execute();
custPatient = client.read().resource(MyPatient.class).withId("123").execute();
//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
}
public void customTypeDeclared() {

View File

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

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.parser;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.List;
import java.util.Set;
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 ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
@ -62,7 +64,7 @@ public interface IParser {
* @return An encoded tag list
*/
String encodeTagListToString(TagList theTagList);
/**
* Encodes a tag list, as defined in the <a href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR
* Specification</a>.
@ -73,7 +75,7 @@ public interface IParser {
* The writer to encode to
*/
void encodeTagListToWriter(TagList theTagList, Writer theWriter) throws IOException;
/**
* See {@link #setEncodeElements(Set)}
*/
@ -95,6 +97,14 @@ public interface IParser {
*/
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
*
@ -289,6 +299,25 @@ public interface IParser {
*/
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
* 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");
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);
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 + "'");
}
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)) {
state.enteringNewElement(null, "Bundle");
} else {
@ -1420,7 +1420,7 @@ public class JsonParser extends BaseParser implements IParser {
assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType");
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);
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 List<String> myComments = new ArrayList<String>(2);
private FhirContext myContext;
private IParserErrorHandler myErrorHandler;
private boolean myJsonMode;
private final FhirContext myContext;
private final IParserErrorHandler myErrorHandler;
private final boolean myJsonMode;
private T myObject;
private BaseState myState;
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;
myJsonMode = theJsonMode;
myErrorHandler = theErrorHandler;
@ -247,8 +249,8 @@ class ParserState<T> {
myState.xmlEvent(theNextEvent);
}
static ParserState<Bundle> getPreAtomInstance(FhirContext theContext, Class<? extends IBaseResource> theResourceType, boolean theJsonMode, IParserErrorHandler theErrorHandler) throws DataFormatException {
ParserState<Bundle> retVal = new ParserState<Bundle>(theContext, theJsonMode, theErrorHandler);
static ParserState<Bundle> getPreAtomInstance(IParser theParser, FhirContext theContext, Class<? extends IBaseResource> theResourceType, boolean theJsonMode, IParserErrorHandler theErrorHandler) throws DataFormatException {
ParserState<Bundle> retVal = new ParserState<Bundle>(theParser, theContext, theJsonMode, theErrorHandler);
if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
retVal.push(retVal.new PreAtomState(theResourceType));
} else {
@ -261,8 +263,8 @@ class ParserState<T> {
* @param theResourceType
* May be null
*/
static <T extends IBaseResource> ParserState<T> getPreResourceInstance(Class<T> theResourceType, FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) throws DataFormatException {
ParserState<T> retVal = new ParserState<T>(theContext, theJsonMode, theErrorHandler);
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>(theParser, theContext, theJsonMode, theErrorHandler);
if (theResourceType == null) {
if (theContext.getVersion().getVersion().isRi()) {
retVal.push(retVal.new PreResourceStateHl7Org(theResourceType));
@ -279,8 +281,8 @@ class ParserState<T> {
return retVal;
}
static ParserState<TagList> getPreTagListInstance(FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) {
ParserState<TagList> retVal = new ParserState<TagList>(theContext, theJsonMode, theErrorHandler);
static ParserState<TagList> getPreTagListInstance(IParser theParser, FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) {
ParserState<TagList> retVal = new ParserState<TagList>(theParser, theContext, theJsonMode, theErrorHandler);
retVal.push(retVal.new PreTagListState());
return retVal;
}
@ -1997,7 +1999,18 @@ class ParserState<T> {
public void enteringNewElement(String theNamespaceUri, String theLocalPart) throws DataFormatException {
BaseRuntimeElementDefinition<?> definition;
if (myResourceType == null) {
definition = myContext.getResourceDefinition(myParentVersion, theLocalPart);
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);
}
if ((definition == null)) {
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) {
ParserState<Bundle> parserState = ParserState.getPreAtomInstance(myContext, theResourceType, false, getErrorHandler());
ParserState<Bundle> parserState = ParserState.getPreAtomInstance(this, myContext, theResourceType, false, getErrorHandler());
return doXmlLoop(theStreamReader, parserState);
}
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);
}
@ -1093,7 +1093,7 @@ public class XmlParser extends BaseParser implements IParser {
public TagList parseTagList(Reader 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);
}

View File

@ -107,6 +107,13 @@ public abstract class BaseClient implements IRestfulClient {
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() {
myFactory.validateServerBase(myUrlBase, myClient, this);
}
@ -189,39 +196,39 @@ public abstract class BaseClient implements IRestfulClient {
IHttpRequest httpRequest;
IHttpResponse response = null;
try {
Map<String, List<String>> params = createExtraParams();
Map<String, List<String>> params = createExtraParams();
if (theEncoding == EncodingEnum.XML) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
} else if (theEncoding == EncodingEnum.JSON) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
}
if (theEncoding == EncodingEnum.XML) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
} else if (theEncoding == EncodingEnum.JSON) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
}
if (theSummaryMode != null) {
params.put(Constants.PARAM_SUMMARY, Collections.singletonList(theSummaryMode.getCode()));
} else if (mySummary != null) {
params.put(Constants.PARAM_SUMMARY, Collections.singletonList(mySummary.getCode()));
}
if (theSummaryMode != null) {
params.put(Constants.PARAM_SUMMARY, Collections.singletonList(theSummaryMode.getCode()));
} else if (mySummary != null) {
params.put(Constants.PARAM_SUMMARY, Collections.singletonList(mySummary.getCode()));
}
if (thePrettyPrint == Boolean.TRUE) {
params.put(Constants.PARAM_PRETTY, Collections.singletonList(Constants.PARAM_PRETTY_VALUE_TRUE));
}
if (thePrettyPrint == Boolean.TRUE) {
params.put(Constants.PARAM_PRETTY, Collections.singletonList(Constants.PARAM_PRETTY_VALUE_TRUE));
}
if (theSubsetElements != null && theSubsetElements.isEmpty() == false) {
params.put(Constants.PARAM_ELEMENTS, Collections.singletonList(StringUtils.join(theSubsetElements, ',')));
}
if (theSubsetElements != null && theSubsetElements.isEmpty() == false) {
params.put(Constants.PARAM_ELEMENTS, Collections.singletonList(StringUtils.join(theSubsetElements, ',')));
}
EncodingEnum encoding = getEncoding();
if (theEncoding != null) {
encoding = theEncoding;
}
EncodingEnum encoding = getEncoding();
if (theEncoding != null) {
encoding = theEncoding;
}
httpRequest = clientInvocation.asHttpRequest(myUrlBase, params, encoding, thePrettyPrint);
if (theLogRequestAndResponse) {
ourLog.info("Client invoking: {}", httpRequest);
String body = httpRequest.getRequestBodyFromStream();
if(body != null) {
if (body != null) {
ourLog.info("Client request body: {}", body);
}
}
@ -337,7 +344,7 @@ public abstract class BaseClient implements IRestfulClient {
throw new FhirClientConnectionException(e);
} catch (IOException e) {
throw new FhirClientConnectionException(e);
} catch(RuntimeException e) {
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new FhirClientConnectionException(e);
@ -449,28 +456,47 @@ public abstract class BaseClient implements IRestfulClient {
Validate.notNull(theInterceptor, "Interceptor can not be null");
myInterceptors.remove(theInterceptor);
}
@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, null, false);
return invokeClient(getFhirContext(), binding, clientInvocation, null, false, false, null, null);
static ArrayList<Class<? extends IBaseResource>> toTypeList(Class<? extends IBaseResource> thePreferResponseType) {
ArrayList<Class<? extends IBaseResource>> preferResponseTypes = null;
if (thePreferResponseType != null) {
preferResponseTypes = new ArrayList<Class<? extends IBaseResource>>(1);
preferResponseTypes.add(thePreferResponseType);
}
return preferResponseTypes;
}
protected final class ResourceResponseHandler<T extends IBaseResource> implements IClientResponseHandler<T> {
private boolean myAllowHtmlResponse;
private IIdType myId;
private Class<T> myType;
private List<Class<? extends IBaseResource>> myPreferResponseTypes;
private Class<T> myReturnType;
public ResourceResponseHandler(Class<T> theType, IIdType theId) {
myType = theType;
myId = theId;
public ResourceResponseHandler() {
this(null);
}
public ResourceResponseHandler(Class<T> theType, IIdType theId, boolean theAllowHtmlResponse) {
myType = theType;
public ResourceResponseHandler(Class<T> theReturnType) {
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;
myPreferResponseTypes = thePreferResponseTypes;
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 {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
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);
}
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
}
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);
@ -493,7 +522,7 @@ public abstract class BaseClient implements IRestfulClient {
@SuppressWarnings("unchecked")
private T readHtmlResponse(Reader theResponseReader) {
RuntimeResourceDefinition resDef = getFhirContext().getResourceDefinition(myType);
RuntimeResourceDefinition resDef = getFhirContext().getResourceDefinition(myReturnType);
IBaseResource instance = resDef.newInstance();
BaseRuntimeChildDefinition textChild = resDef.getChildByName("text");
BaseRuntimeElementCompositeDefinition<?> textElement = (BaseRuntimeElementCompositeDefinition<?>) textChild.getChildByName("text");
@ -511,6 +540,10 @@ public abstract class BaseClient implements IRestfulClient {
divChild.getMutator().addValue(textInstance, divInstance);
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")
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);
return resp;
}
@ -263,7 +263,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
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) {
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 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 Boolean myPrettyPrint;
private boolean myQueryLogRequestAndResponse;
@ -925,7 +958,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override
public Object execute() {
ResourceResponseHandler binding = new ResourceResponseHandler(myType.getImplementingClass(), null);
ResourceResponseHandler binding = new ResourceResponseHandler(myType.getImplementingClass());
HttpGetClientInvocation invocation = MethodUtil.createConformanceInvocation(getFhirContext());
return super.invoke(null, binding, invocation);
}
@ -963,7 +996,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
if (myBundleType == null) {
binding = new BundleResponseHandler(null);
} else {
binding = new ResourceResponseHandler(myBundleType, null);
binding = new ResourceResponseHandler(myBundleType, getPreferResponseTypes());
}
HttpSimpleGetClientInvocation invocation = new HttpSimpleGetClientInvocation(myContext, myUrl);
@ -1087,7 +1120,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
IClientResponseHandler handler;
if (myReturnType != null) {
handler = new ResourceResponseHandler(myReturnType, null);
handler = new ResourceResponseHandler(myReturnType, getPreferResponseTypes(myType));
} else {
handler = new BundleResponseHandler(null);
}
@ -1391,9 +1424,9 @@ public class GenericClient extends BaseClient implements IGenericClient {
BaseHttpClientInvocation invocation = OperationMethodBinding.createOperationInvocation(myContext, resourceName, id, myOperationName, myParameters, myUseHttpGet);
IClientResponseHandler handler;
handler = new ResourceResponseHandler(null, null);
ResourceResponseHandler handler = new ResourceResponseHandler();
handler.setPreferResponseTypes(getPreferResponseTypes(myType));
Object retVal = invoke(null, handler, invocation);
if (myContext.getResourceDefinition((IBaseResource) retVal).getName().equals("Parameters")) {
return retVal;
@ -1705,7 +1738,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
throws BaseServerResponseException {
if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) {
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);
IVersionSpecificBundleFactory bundleFactory = myContext.newBundleFactory();
bundleFactory.initializeWithBundleResource(response);
@ -1804,7 +1837,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
IClientResponseHandler<? extends IBase> binding;
if (myReturnBundleType != null) {
binding = new ResourceResponseHandler(myReturnBundleType, null);
binding = new ResourceResponseHandler(myReturnBundleType, getPreferResponseTypes(myResourceType));
} else {
binding = new BundleResponseHandler(myResourceType);
}
@ -2070,13 +2103,13 @@ public class GenericClient extends BaseClient implements IGenericClient {
BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myResources, myContext);
return (T) invoke(params, binding, invocation);
} else if (myBaseBundle != null) {
ResourceResponseHandler binding = new ResourceResponseHandler(myBaseBundle.getClass(), null);
ResourceResponseHandler binding = new ResourceResponseHandler(myBaseBundle.getClass(), getPreferResponseTypes());
BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myBaseBundle, myContext);
return (T) invoke(params, binding, invocation);
} else if (myRawBundle != null) {
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 (MethodUtil.detectEncodingNoDefault(myRawBundle) != getParamEncoding()) {

View File

@ -1,5 +1,9 @@
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;
/*
@ -49,6 +53,27 @@ public interface IClientExecutable<T extends IClientExecutable<?,?>, Y> {
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
*/

View File

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

View File

@ -10,7 +10,9 @@ import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.io.IOUtils;
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.BasicStatusLine;
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.OperationOutcome;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@ -34,6 +39,8 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
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.rest.server.Constants;
import ca.uhn.fhir.util.VersionUtil;
@ -88,6 +95,163 @@ public class GenericClientDstu3Test {
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
public void testUserAgentForBinary() throws Exception {
IParser p = ourCtx.newXmlParser();

View File

@ -185,6 +185,29 @@
<param name="file" value="examples/src/main/java/example/ExtensionsDstu3.java" />
</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>
Sometimes you may not know in advance exactly which
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,
you can declare the "primary" type for a given profile.
</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">
<param name="id" value="customTypeClientDeclared" />
<param name="file" value="examples/src/main/java/example/ExtensionsDstu3.java" />