ResourceReferenceDt#loadResource(IRestfulClient) did not
use the client's read functionality, so it did not handle JSON responses or use interceptors. Thanks to JT for reporting!
This commit is contained in:
parent
e93f7542a5
commit
eea406e10e
|
@ -1,48 +1,23 @@
|
||||||
package ca.uhn.fhir.model.base.composite;
|
package ca.uhn.fhir.model.base.composite;
|
||||||
|
|
||||||
/*
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
* #%L
|
|
||||||
* HAPI FHIR - Core Library
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2015 University Health Network
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Reader;
|
|
||||||
|
|
||||||
import org.apache.http.HttpResponse;
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
|
||||||
import org.apache.http.client.methods.HttpGet;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseDatatype;
|
import org.hl7.fhir.instance.model.api.IBaseDatatype;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
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.FhirContext;
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.model.api.BaseIdentifiableElement;
|
import ca.uhn.fhir.model.api.BaseIdentifiableElement;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
import ca.uhn.fhir.parser.IParser;
|
|
||||||
import ca.uhn.fhir.rest.client.BaseClient;
|
|
||||||
import ca.uhn.fhir.rest.client.api.IRestfulClient;
|
import ca.uhn.fhir.rest.client.api.IRestfulClient;
|
||||||
|
|
||||||
public abstract class BaseResourceReferenceDt extends BaseIdentifiableElement implements IBaseDatatype, IBaseReference {
|
public abstract class BaseResourceReferenceDt extends BaseIdentifiableElement implements IBaseDatatype, IBaseReference {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseResourceReferenceDt.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseResourceReferenceDt.class);
|
||||||
private IBaseResource myResource;
|
private IBaseResource myResource;
|
||||||
|
|
||||||
|
@ -95,42 +70,27 @@ public abstract class BaseResourceReferenceDt extends BaseIdentifiableElement im
|
||||||
* HTTP client to retrieve the resource unless it has already been loaded, or was a contained resource in which case
|
* HTTP client to retrieve the resource unless it has already been loaded, or was a contained resource in which case
|
||||||
* it is simply returned.
|
* it is simply returned.
|
||||||
*/
|
*/
|
||||||
public IBaseResource loadResource(IRestfulClient theClient) throws IOException {
|
public IBaseResource loadResource(IRestfulClient theClient) {
|
||||||
if (myResource != null) {
|
if (myResource != null) {
|
||||||
return myResource;
|
return myResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
IdDt resourceId = getReference();
|
IdDt resourceId = getReference();
|
||||||
if (resourceId == null) {
|
if (resourceId == null || isBlank(resourceId.getValue())) {
|
||||||
throw new IllegalStateException("Reference has no resource ID defined");
|
throw new IllegalStateException("Reference has no resource ID defined");
|
||||||
}
|
}
|
||||||
|
if (isBlank(resourceId.getBaseUrl()) || isBlank(resourceId.getResourceType())) {
|
||||||
|
throw new IllegalStateException("Reference is not complete (must be in the form [baseUrl]/[resource type]/[resource ID]) - Reference is: " + resourceId.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
String resourceUrl = resourceId.getValue();
|
String resourceUrl = resourceId.getValue();
|
||||||
|
|
||||||
ourLog.debug("Loading resource at URL: {}", resourceUrl);
|
ourLog.debug("Loading resource at URL: {}", resourceUrl);
|
||||||
|
|
||||||
HttpClient httpClient = theClient.getHttpClient();
|
RuntimeResourceDefinition definition = theClient.getFhirContext().getResourceDefinition(resourceId.getResourceType());
|
||||||
FhirContext context = theClient.getFhirContext();
|
Class<? extends IBaseResource> resourceType = definition.getImplementingClass();
|
||||||
|
myResource = theClient.fetchResourceFromUrl(resourceType, resourceUrl);
|
||||||
if (!resourceUrl.startsWith("http")) {
|
myResource.setId(resourceUrl);
|
||||||
resourceUrl = theClient.getServerBase() + resourceUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpGet get = new HttpGet(resourceUrl);
|
|
||||||
HttpResponse response = httpClient.execute(get);
|
|
||||||
try {
|
|
||||||
// TODO: choose appropriate parser based on response CT
|
|
||||||
IParser parser = context.newXmlParser();
|
|
||||||
|
|
||||||
Reader responseReader = BaseClient.createReaderFromResponse(response);
|
|
||||||
myResource = parser.parseResource(responseReader);
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
if (response instanceof CloseableHttpResponse) {
|
|
||||||
((CloseableHttpResponse) response).close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return myResource;
|
return myResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,21 +46,34 @@ import org.apache.http.client.HttpClient;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpRequestBase;
|
import org.apache.http.client.methods.HttpRequestBase;
|
||||||
import org.apache.http.entity.ContentType;
|
import org.apache.http.entity.ContentType;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBase;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||||
|
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
||||||
|
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
|
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
import ca.uhn.fhir.parser.IParser;
|
import ca.uhn.fhir.parser.IParser;
|
||||||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
import ca.uhn.fhir.rest.api.SummaryEnum;
|
||||||
import ca.uhn.fhir.rest.client.api.IRestfulClient;
|
import ca.uhn.fhir.rest.client.api.IRestfulClient;
|
||||||
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
|
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
|
||||||
|
import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException;
|
||||||
|
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
|
||||||
|
import ca.uhn.fhir.rest.method.HttpGetClientInvocation;
|
||||||
import ca.uhn.fhir.rest.method.IClientResponseHandler;
|
import ca.uhn.fhir.rest.method.IClientResponseHandler;
|
||||||
import ca.uhn.fhir.rest.method.IClientResponseHandlerHandlesBinary;
|
import ca.uhn.fhir.rest.method.IClientResponseHandlerHandlesBinary;
|
||||||
|
import ca.uhn.fhir.rest.method.MethodUtil;
|
||||||
import ca.uhn.fhir.rest.server.Constants;
|
import ca.uhn.fhir.rest.server.Constants;
|
||||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||||
|
|
||||||
public abstract class BaseClient implements IRestfulClient {
|
public abstract class BaseClient implements IRestfulClient {
|
||||||
|
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseClient.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseClient.class);
|
||||||
|
|
||||||
private final HttpClient myClient;
|
private final HttpClient myClient;
|
||||||
|
@ -103,8 +116,8 @@ public abstract class BaseClient implements IRestfulClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the encoding that will be used on requests. Default is <code>null</code>, which means the client will not explicitly request an encoding. (This is standard behaviour according to the
|
* Returns the encoding that will be used on requests. Default is <code>null</code>, which means the client will not
|
||||||
* FHIR specification)
|
* explicitly request an encoding. (This is standard behaviour according to the FHIR specification)
|
||||||
*/
|
*/
|
||||||
public EncodingEnum getEncoding() {
|
public EncodingEnum getEncoding() {
|
||||||
return myEncoding;
|
return myEncoding;
|
||||||
|
@ -137,8 +150,9 @@ public abstract class BaseClient implements IRestfulClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note that this is currently a non-standard flag (_pretty) which is supported only by
|
* Returns the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note
|
||||||
* HAPI based servers (and any other servers which might implement it).
|
* that this is currently a non-standard flag (_pretty) which is supported only by HAPI based servers (and any other
|
||||||
|
* servers which might implement it).
|
||||||
*/
|
*/
|
||||||
public Boolean getPrettyPrint() {
|
public Boolean getPrettyPrint() {
|
||||||
return myPrettyPrint;
|
return myPrettyPrint;
|
||||||
|
@ -168,8 +182,7 @@ public abstract class BaseClient implements IRestfulClient {
|
||||||
return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse, null, null);
|
return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
<T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint,
|
<T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint, boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set<String> theSubsetElements) {
|
||||||
boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set<String> theSubsetElements) {
|
|
||||||
|
|
||||||
if (!myDontValidateConformance) {
|
if (!myDontValidateConformance) {
|
||||||
myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient, this);
|
myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient, this);
|
||||||
|
@ -198,7 +211,7 @@ public abstract class BaseClient implements IRestfulClient {
|
||||||
params.put(Constants.PARAM_PRETTY, Collections.singletonList(Constants.PARAM_PRETTY_VALUE_TRUE));
|
params.put(Constants.PARAM_PRETTY, Collections.singletonList(Constants.PARAM_PRETTY_VALUE_TRUE));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theSubsetElements != null && theSubsetElements.isEmpty()== false) {
|
if (theSubsetElements != null && theSubsetElements.isEmpty() == false) {
|
||||||
params.put(Constants.PARAM_ELEMENTS, Collections.singletonList(StringUtils.join(theSubsetElements, ',')));
|
params.put(Constants.PARAM_ELEMENTS, Collections.singletonList(StringUtils.join(theSubsetElements, ',')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,8 +379,9 @@ public abstract class BaseClient implements IRestfulClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note that this is currently a non-standard flag (_pretty) which is supported only by
|
* Returns the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note
|
||||||
* HAPI based servers (and any other servers which might implement it).
|
* that this is currently a non-standard flag (_pretty) which is supported only by HAPI based servers (and any other
|
||||||
|
* servers which might implement it).
|
||||||
*/
|
*/
|
||||||
public boolean isPrettyPrint() {
|
public boolean isPrettyPrint() {
|
||||||
return Boolean.TRUE.equals(myPrettyPrint);
|
return Boolean.TRUE.equals(myPrettyPrint);
|
||||||
|
@ -397,7 +411,8 @@ public abstract class BaseClient implements IRestfulClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is an internal part of the HAPI API and may change, use with caution. If you want to disable the loading of conformance statements, use
|
* This method is an internal part of the HAPI API and may change, use with caution. If you want to disable the
|
||||||
|
* loading of conformance statements, use
|
||||||
* {@link IRestfulClientFactory#setServerValidationModeEnum(ServerValidationModeEnum)}
|
* {@link IRestfulClientFactory#setServerValidationModeEnum(ServerValidationModeEnum)}
|
||||||
*/
|
*/
|
||||||
public void setDontValidateConformance(boolean theDontValidateConformance) {
|
public void setDontValidateConformance(boolean theDontValidateConformance) {
|
||||||
|
@ -405,8 +420,9 @@ public abstract class BaseClient implements IRestfulClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the encoding that will be used on requests. Default is <code>null</code>, which means the client will not explicitly request an encoding. (This is perfectly acceptable behaviour according
|
* Sets the encoding that will be used on requests. Default is <code>null</code>, which means the client will not
|
||||||
* to the FHIR specification. In this case, the server will choose which encoding to return, and the client can handle either XML or JSON)
|
* explicitly request an encoding. (This is perfectly acceptable behaviour according to the FHIR specification. In
|
||||||
|
* this case, the server will choose which encoding to return, and the client can handle either XML or JSON)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setEncoding(EncodingEnum theEncoding) {
|
public void setEncoding(EncodingEnum theEncoding) {
|
||||||
|
@ -436,8 +452,9 @@ public abstract class BaseClient implements IRestfulClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note that this is currently a non-standard flag (_pretty) which is supported only by
|
* Sets the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note
|
||||||
* HAPI based servers (and any other servers which might implement it).
|
* that this is currently a non-standard flag (_pretty) which is supported only by HAPI based servers (and any other
|
||||||
|
* servers which might implement it).
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setPrettyPrint(Boolean thePrettyPrint) {
|
public void setPrettyPrint(Boolean thePrettyPrint) {
|
||||||
|
@ -477,4 +494,67 @@ public abstract class BaseClient implements IRestfulClient {
|
||||||
return reader;
|
return reader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends IBaseResource> T fetchResourceFromUrl(Class<T> theResourceType, String theUrl) {
|
||||||
|
BaseHttpClientInvocation clientInvocation = new HttpGetClientInvocation(theUrl);
|
||||||
|
ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theResourceType, null, false);
|
||||||
|
return invokeClient(getFhirContext(), binding, clientInvocation, null, false, false, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final class ResourceResponseHandler<T extends IBaseResource> implements IClientResponseHandler<T> {
|
||||||
|
|
||||||
|
private boolean myAllowHtmlResponse;
|
||||||
|
private IIdType myId;
|
||||||
|
private Class<T> myType;
|
||||||
|
|
||||||
|
public ResourceResponseHandler(Class<T> theType, IIdType theId) {
|
||||||
|
myType = theType;
|
||||||
|
myId = theId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceResponseHandler(Class<T> theType, IIdType theId, boolean theAllowHtmlResponse) {
|
||||||
|
myType = theType;
|
||||||
|
myId = theId;
|
||||||
|
myAllowHtmlResponse = theAllowHtmlResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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) {
|
||||||
|
return readHtmlResponse(theResponseReader);
|
||||||
|
}
|
||||||
|
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
|
||||||
|
}
|
||||||
|
IParser parser = respType.newParser(getFhirContext());
|
||||||
|
T retVal = parser.parseResource(myType, theResponseReader);
|
||||||
|
|
||||||
|
MethodUtil.parseClientRequestResourceHeaders(myId, theHeaders, retVal);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private T readHtmlResponse(Reader theResponseReader) {
|
||||||
|
RuntimeResourceDefinition resDef = getFhirContext().getResourceDefinition(myType);
|
||||||
|
IBaseResource instance = resDef.newInstance();
|
||||||
|
BaseRuntimeChildDefinition textChild = resDef.getChildByName("text");
|
||||||
|
BaseRuntimeElementCompositeDefinition<?> textElement = (BaseRuntimeElementCompositeDefinition<?>) textChild.getChildByName("text");
|
||||||
|
IBase textInstance = textElement.newInstance();
|
||||||
|
textChild.getMutator().addValue(instance, textInstance);
|
||||||
|
|
||||||
|
BaseRuntimeChildDefinition divChild = textElement.getChildByName("div");
|
||||||
|
BaseRuntimeElementDefinition<?> divElement = divChild.getChildByName("div");
|
||||||
|
IPrimitiveType<?> divInstance = (IPrimitiveType<?>) divElement.newInstance();
|
||||||
|
try {
|
||||||
|
divInstance.setValueAsString(IOUtils.toString(theResponseReader));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InvalidResponseException(400, "Failed to process HTML response from server: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
divChild.getMutator().addValue(textInstance, divInstance);
|
||||||
|
return (T) instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
@ -57,6 +58,7 @@ class ClientInvocationHandlerFactory {
|
||||||
myMethodToLambda.put(theClientType.getMethod("registerInterceptor", IClientInterceptor.class), new RegisterInterceptorLambda());
|
myMethodToLambda.put(theClientType.getMethod("registerInterceptor", IClientInterceptor.class), new RegisterInterceptorLambda());
|
||||||
myMethodToLambda.put(theClientType.getMethod("unregisterInterceptor", IClientInterceptor.class), new UnregisterInterceptorLambda());
|
myMethodToLambda.put(theClientType.getMethod("unregisterInterceptor", IClientInterceptor.class), new UnregisterInterceptorLambda());
|
||||||
myMethodToLambda.put(theClientType.getMethod("setSummary", SummaryEnum.class), new SetSummaryLambda());
|
myMethodToLambda.put(theClientType.getMethod("setSummary", SummaryEnum.class), new SetSummaryLambda());
|
||||||
|
myMethodToLambda.put(theClientType.getMethod("fetchResourceFromUrl", Class.class, String.class), new FetchResourceFromUrlLambda());
|
||||||
|
|
||||||
} catch (NoSuchMethodException e) {
|
} catch (NoSuchMethodException e) {
|
||||||
throw new ConfigurationException("Failed to find methods on client. This is a HAPI bug!", e);
|
throw new ConfigurationException("Failed to find methods on client. This is a HAPI bug!", e);
|
||||||
|
@ -86,6 +88,17 @@ class ClientInvocationHandlerFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FetchResourceFromUrlLambda implements ILambda {
|
||||||
|
@Override
|
||||||
|
public Object handle(ClientInvocationHandler theTarget, Object[] theArgs) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Class<? extends IBaseResource> type = (Class<? extends IBaseResource>) theArgs[0];
|
||||||
|
String url = (String) theArgs[1];
|
||||||
|
|
||||||
|
return theTarget.fetchResourceFromUrl(type, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SetEncodingLambda implements ILambda {
|
class SetEncodingLambda implements ILambda {
|
||||||
@Override
|
@Override
|
||||||
public Object handle(ClientInvocationHandler theTarget, Object[] theArgs) {
|
public Object handle(ClientInvocationHandler theTarget, Object[] theArgs) {
|
||||||
|
|
|
@ -50,7 +50,6 @@ 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 org.hl7.fhir.instance.model.api.IPrimitiveType;
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.BaseRuntimeChildDatatypeDefinition;
|
|
||||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||||
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
||||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
||||||
|
@ -74,7 +73,6 @@ import ca.uhn.fhir.parser.IParser;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
import ca.uhn.fhir.rest.api.PreferReturnEnum;
|
import ca.uhn.fhir.rest.api.PreferReturnEnum;
|
||||||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
import ca.uhn.fhir.rest.api.SummaryEnum;
|
||||||
import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException;
|
|
||||||
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
|
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
|
||||||
import ca.uhn.fhir.rest.gclient.IClientExecutable;
|
import ca.uhn.fhir.rest.gclient.IClientExecutable;
|
||||||
import ca.uhn.fhir.rest.gclient.ICreate;
|
import ca.uhn.fhir.rest.gclient.ICreate;
|
||||||
|
@ -1719,62 +1717,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class ResourceResponseHandler<T extends IBaseResource> implements IClientResponseHandler<T> {
|
|
||||||
|
|
||||||
private boolean myAllowHtmlResponse;
|
|
||||||
private IIdType myId;
|
|
||||||
private Class<T> myType;
|
|
||||||
|
|
||||||
public ResourceResponseHandler(Class<T> theType, IIdType theId) {
|
|
||||||
myType = theType;
|
|
||||||
myId = theId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResourceResponseHandler(Class<T> theType, IIdType theId, boolean theAllowHtmlResponse) {
|
|
||||||
myType = theType;
|
|
||||||
myId = theId;
|
|
||||||
myAllowHtmlResponse = theAllowHtmlResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
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) {
|
|
||||||
return readHtmlResponse(theResponseReader);
|
|
||||||
}
|
|
||||||
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
|
|
||||||
}
|
|
||||||
IParser parser = respType.newParser(myContext);
|
|
||||||
T retVal = parser.parseResource(myType, theResponseReader);
|
|
||||||
|
|
||||||
MethodUtil.parseClientRequestResourceHeaders(myId, theHeaders, retVal);
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private T readHtmlResponse(Reader theResponseReader) {
|
|
||||||
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(myType);
|
|
||||||
IBaseResource instance = resDef.newInstance();
|
|
||||||
BaseRuntimeChildDefinition textChild = resDef.getChildByName("text");
|
|
||||||
BaseRuntimeElementCompositeDefinition<?> textElement = (BaseRuntimeElementCompositeDefinition<?>) textChild.getChildByName("text");
|
|
||||||
IBase textInstance = textElement.newInstance();
|
|
||||||
textChild.getMutator().addValue(instance, textInstance);
|
|
||||||
|
|
||||||
BaseRuntimeChildDefinition divChild = textElement.getChildByName("div");
|
|
||||||
BaseRuntimeElementDefinition<?> divElement = divChild.getChildByName("div");
|
|
||||||
IPrimitiveType<?> divInstance = (IPrimitiveType<?>) divElement.newInstance();
|
|
||||||
try {
|
|
||||||
divInstance.setValueAsString(IOUtils.toString(theResponseReader));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new InvalidResponseException(400, "Failed to process HTML response from server: " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
divChild.getMutator().addValue(textInstance, divInstance);
|
|
||||||
return (T) instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
private class SearchInternal extends BaseClientExecutable<IQuery<Object>, Object>implements IQuery<Object>, IUntypedQuery {
|
private class SearchInternal extends BaseClientExecutable<IQuery<Object>, Object>implements IQuery<Object>, IUntypedQuery {
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.rest.client.api;
|
||||||
|
|
||||||
|
|
||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
import ca.uhn.fhir.rest.api.SummaryEnum;
|
||||||
|
@ -30,6 +31,17 @@ import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||||
|
|
||||||
public interface IRestfulClient {
|
public interface IRestfulClient {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the contents at the given URL and parse them as a resource. This
|
||||||
|
* method could be used as a low level implementation of a read/vread/search
|
||||||
|
* operation.
|
||||||
|
*
|
||||||
|
* @param theResourceType The resource type to parse
|
||||||
|
* @param theUrl The URL to load
|
||||||
|
* @return The parsed resource
|
||||||
|
*/
|
||||||
|
<T extends IBaseResource> T fetchResourceFromUrl(Class<T> theResourceType, String theUrl);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the FHIR context associated with this client
|
* Returns the FHIR context associated with this client
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,210 @@
|
||||||
|
package ca.uhn.fhir.model.primitive;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
import org.apache.commons.io.input.ReaderInputStream;
|
||||||
|
import org.apache.http.Header;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.ProtocolVersion;
|
||||||
|
import org.apache.http.client.ClientProtocolException;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.apache.http.message.BasicHeader;
|
||||||
|
import org.apache.http.message.BasicStatusLine;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||||
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
|
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||||
|
import ca.uhn.fhir.rest.client.ServerValidationModeEnum;
|
||||||
|
import ca.uhn.fhir.rest.client.api.IRestfulClient;
|
||||||
|
import ca.uhn.fhir.rest.server.Constants;
|
||||||
|
|
||||||
|
public class BaseResourceReferenceDtTest {
|
||||||
|
|
||||||
|
private static FhirContext ourCtx;
|
||||||
|
|
||||||
|
private HttpClient myHttpClient;
|
||||||
|
private HttpResponse myHttpResponse;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
ourCtx = FhirContext.forDstu2();
|
||||||
|
myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs());
|
||||||
|
ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient);
|
||||||
|
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
|
||||||
|
myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArgumentCaptor<HttpUriRequest> fixtureJson() throws IOException, ClientProtocolException {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addName().addFamily("FAM");
|
||||||
|
final String input = ourCtx.newJsonParser().encodeResourceToString(patient);
|
||||||
|
|
||||||
|
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_JSON + "; charset=UTF-8"));
|
||||||
|
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LAST_MODIFIED, "Sat, 20 Jun 2015 19:32:17 GMT") });
|
||||||
|
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
|
||||||
|
@Override
|
||||||
|
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||||
|
return new ReaderInputStream(new StringReader(input), Charset.forName("UTF-8"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return capt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArgumentCaptor<HttpUriRequest> fixtureXml() throws IOException, ClientProtocolException {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addName().addFamily("FAM");
|
||||||
|
final String input = ourCtx.newXmlParser().encodeResourceToString(patient);
|
||||||
|
|
||||||
|
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.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LAST_MODIFIED, "Sat, 20 Jun 2015 19:32:17 GMT") });
|
||||||
|
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
|
||||||
|
@Override
|
||||||
|
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||||
|
return new ReaderInputStream(new StringReader(input), Charset.forName("UTF-8"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return capt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadResourceFromAnnotationClientJson() throws Exception {
|
||||||
|
ArgumentCaptor<HttpUriRequest> capt = fixtureJson();
|
||||||
|
|
||||||
|
IClientType client = ourCtx.newRestfulClient(IClientType.class, "http://example.com/fhir");
|
||||||
|
|
||||||
|
ResourceReferenceDt ref = new ResourceReferenceDt();
|
||||||
|
ref.setReference("http://domain2.example.com/base/Patient/123");
|
||||||
|
Patient response = (Patient) ref.loadResource(client);
|
||||||
|
|
||||||
|
assertEquals("http://domain2.example.com/base/Patient/123", capt.getAllValues().get(0).getURI().toASCIIString());
|
||||||
|
assertEquals("FAM", response.getName().get(0).getFamily().get(0).getValue());
|
||||||
|
assertEquals("http://domain2.example.com/base/Patient/123", response.getId().getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testErrors() {
|
||||||
|
IClientType client = ourCtx.newRestfulClient(IClientType.class, "http://example.com/fhir");
|
||||||
|
|
||||||
|
try {
|
||||||
|
new ResourceReferenceDt().loadResource(client);
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
assertEquals("Reference has no resource ID defined", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
new ResourceReferenceDt("123").loadResource(client);
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
assertEquals("Reference is not complete (must be in the form [baseUrl]/[resource type]/[resource ID]) - Reference is: 123", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
new ResourceReferenceDt("Patient/123").loadResource(client);
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
assertEquals("Reference is not complete (must be in the form [baseUrl]/[resource type]/[resource ID]) - Reference is: Patient/123", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
new ResourceReferenceDt("http://foo/123123").loadResource(client);
|
||||||
|
fail();
|
||||||
|
} catch (DataFormatException e) {
|
||||||
|
assertEquals("Unknown resource name \"Foo\" (this name is not known in FHIR version \"DSTU2\")", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
new ResourceReferenceDt("http://foo/Sometype/123123").loadResource(client);
|
||||||
|
fail();
|
||||||
|
} catch (DataFormatException e) {
|
||||||
|
assertEquals("Unknown resource name \"Sometype\" (this name is not known in FHIR version \"DSTU2\")", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnAlreadyLoadedInstance() throws ClientProtocolException, IOException {
|
||||||
|
ArgumentCaptor<HttpUriRequest> capt = fixtureJson();
|
||||||
|
IClientType client = ourCtx.newRestfulClient(IClientType.class, "http://example4.com/fhir");
|
||||||
|
|
||||||
|
Patient pat = new Patient();
|
||||||
|
|
||||||
|
ResourceReferenceDt ref = new ResourceReferenceDt();
|
||||||
|
ref.setReference("http://domain2.example.com/base/Patient/123");
|
||||||
|
ref.setResource(pat);
|
||||||
|
Patient response = (Patient) ref.loadResource(client);
|
||||||
|
|
||||||
|
assertEquals(0, capt.getAllValues().size());
|
||||||
|
assertSame(pat, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadResourceFromAnnotationClientXml() throws Exception {
|
||||||
|
ArgumentCaptor<HttpUriRequest> capt = fixtureXml();
|
||||||
|
|
||||||
|
IClientType client = ourCtx.newRestfulClient(IClientType.class, "http://example3.com/fhir");
|
||||||
|
|
||||||
|
ResourceReferenceDt ref = new ResourceReferenceDt();
|
||||||
|
ref.setReference("http://domain2.example.com/base/Patient/123");
|
||||||
|
Patient response = (Patient) ref.loadResource(client);
|
||||||
|
|
||||||
|
assertEquals("FAM", response.getName().get(0).getFamily().get(0).getValue());
|
||||||
|
assertEquals("http://domain2.example.com/base/Patient/123", capt.getAllValues().get(0).getURI().toASCIIString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadResourceFromGenericClientJson() throws Exception {
|
||||||
|
ArgumentCaptor<HttpUriRequest> capt = fixtureJson();
|
||||||
|
|
||||||
|
IGenericClient client = ourCtx.newRestfulGenericClient("http://example1.com/fhir");
|
||||||
|
|
||||||
|
ResourceReferenceDt ref = new ResourceReferenceDt();
|
||||||
|
ref.setReference("http://domain2.example.com/base/Patient/123");
|
||||||
|
Patient response = (Patient) ref.loadResource(client);
|
||||||
|
|
||||||
|
assertEquals("http://domain2.example.com/base/Patient/123", capt.getAllValues().get(0).getURI().toASCIIString());
|
||||||
|
assertEquals("FAM", response.getName().get(0).getFamily().get(0).getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadResourceFromGenericClientXml() throws Exception {
|
||||||
|
ArgumentCaptor<HttpUriRequest> capt = fixtureXml();
|
||||||
|
|
||||||
|
IGenericClient client = ourCtx.newRestfulGenericClient("http://example2.com/fhir");
|
||||||
|
|
||||||
|
ResourceReferenceDt ref = new ResourceReferenceDt();
|
||||||
|
ref.setReference("http://domain2.example.com/base/Patient/123");
|
||||||
|
Patient response = (Patient) ref.loadResource(client);
|
||||||
|
|
||||||
|
assertEquals("http://domain2.example.com/base/Patient/123", capt.getAllValues().get(0).getURI().toASCIIString());
|
||||||
|
assertEquals("FAM", response.getName().get(0).getFamily().get(0).getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IClientType extends IRestfulClient {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -188,6 +188,12 @@
|
||||||
by calling the actual implementing method in the server (previously
|
by calling the actual implementing method in the server (previously
|
||||||
the call was simulated, which meant that many features did not work)
|
the call was simulated, which meant that many features did not work)
|
||||||
</action>
|
</action>
|
||||||
|
<action type="fix">
|
||||||
|
ResourceReferenceDt#loadResource(IRestfulClient) did not
|
||||||
|
use the client's read functionality, so it did not
|
||||||
|
handle JSON responses or use interceptors. Thanks to
|
||||||
|
JT for reporting!
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="1.2" date="2015-09-18">
|
<release version="1.2" date="2015-09-18">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
|
Loading…
Reference in New Issue