Fix #315 - Use custom types for searches and other client operations
when requested
This commit is contained in:
parent
1adfc4b4d9
commit
c6f06548fc
|
@ -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,6 +36,30 @@ 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
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = 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");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,8 +1424,8 @@ 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")) {
|
||||
|
@ -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()) {
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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" />
|
||||
|
|
Loading…
Reference in New Issue