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:
jamesagnew 2015-10-23 08:21:25 -04:00
parent e93f7542a5
commit eea406e10e
7 changed files with 356 additions and 133 deletions

View File

@ -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;
} }

View File

@ -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;
}
}
} }

View File

@ -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) {

View File

@ -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 {

View File

@ -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
*/ */

View File

@ -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 {
}
}

View File

@ -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">