Fix #108 - Don't check the server version from the client until the first actual request

This commit is contained in:
jamesagnew 2015-03-12 22:15:15 -04:00
parent 595f8f524f
commit 7540203836
9 changed files with 82 additions and 46 deletions

View File

@ -287,6 +287,14 @@ public class FhirContext {
return myVersion; return myVersion;
} }
/**
* This method should be considered experimental and will likely change in future releases
* of HAPI. Use with caution!
*/
public IVersionSpecificBundleFactory newBundleFactory() {
return myVersion.newBundleFactory(this);
}
/** /**
* Create and return a new JSON parser. * Create and return a new JSON parser.
* *
@ -486,8 +494,4 @@ public class FhirContext {
return retVal; return retVal;
} }
public IVersionSpecificBundleFactory newBundleFactory() {
return myVersion.newBundleFactory(this);
}
} }

View File

@ -67,13 +67,16 @@ public abstract class BaseClient {
private HttpResponse myLastResponse; private HttpResponse myLastResponse;
private String myLastResponseBody; private String myLastResponseBody;
private Boolean myPrettyPrint = false; private Boolean myPrettyPrint = false;
private final RestfulClientFactory myFactory;
private final String myUrlBase; private final String myUrlBase;
BaseClient(HttpClient theClient, String theUrlBase) { private boolean myDontValidateConformance;
BaseClient(HttpClient theClient, String theUrlBase, RestfulClientFactory theFactory) {
super(); super();
myClient = theClient; myClient = theClient;
myUrlBase = theUrlBase; myUrlBase = theUrlBase;
myFactory = theFactory;
} }
protected Map<String, List<String>> createExtraParams() { protected Map<String, List<String>> createExtraParams() {
@ -130,7 +133,21 @@ public abstract class BaseClient {
return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse); return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse);
} }
/**
* This method is an internal part of the HAPI API andmay change, use with caution. If you
* want to disable the loading of conformance statements, use {@link IRestfulClientFactory#setServerValidationModeEnum(ServerValidationModeEnum)}
*/
public void setDontValidateConformance(boolean theDontValidateConformance) {
myDontValidateConformance = theDontValidateConformance;
}
<T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint, boolean theLogRequestAndResponse) { <T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint, boolean theLogRequestAndResponse) {
if (!myDontValidateConformance) {
myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient);
}
// TODO: handle non 2xx status codes by throwing the correct exception, // TODO: handle non 2xx status codes by throwing the correct exception,
// and ensure it's passed upwards // and ensure it's passed upwards
HttpRequestBase httpRequest; HttpRequestBase httpRequest;

View File

@ -37,13 +37,13 @@ class ClientInvocationHandler extends BaseClient implements InvocationHandler {
private FhirContext myContext; private FhirContext myContext;
private Map<Method, ILambda> myMethodToLambda; private Map<Method, ILambda> myMethodToLambda;
public ClientInvocationHandler(HttpClient theClient, FhirContext theContext, String theUrlBase, Map<Method, Object> theMethodToReturnValue, Map<Method, BaseMethodBinding<?>> theBindings, Map<Method, ILambda> theMethodToLambda) { public ClientInvocationHandler(HttpClient theClient, FhirContext theContext, String theUrlBase, Map<Method, Object> theMethodToReturnValue, Map<Method, BaseMethodBinding<?>> theBindings, Map<Method, ILambda> theMethodToLambda, RestfulClientFactory theFactory) {
super(theClient, theUrlBase); super(theClient, theUrlBase, theFactory);
myContext =theContext; myContext = theContext;
myMethodToReturnValue = theMethodToReturnValue; myMethodToReturnValue = theMethodToReturnValue;
myBindings = theBindings; myBindings = theBindings;
myMethodToLambda=theMethodToLambda; myMethodToLambda = theMethodToLambda;
} }
public void addBinding(Method theMethod, BaseMethodBinding<?> theBinding) { public void addBinding(Method theMethod, BaseMethodBinding<?> theBinding) {
@ -71,5 +71,4 @@ class ClientInvocationHandler extends BaseClient implements InvocationHandler {
throw new UnsupportedOperationException("The method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getSimpleName() + " has no handler. Did you forget to annotate it with a RESTful method annotation?"); throw new UnsupportedOperationException("The method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getSimpleName() + " has no handler. Did you forget to annotate it with a RESTful method annotation?");
} }
} }

View File

@ -67,8 +67,8 @@ class ClientInvocationHandlerFactory {
myBindings.put(theMethod, theBinding); myBindings.put(theMethod, theBinding);
} }
ClientInvocationHandler newInvocationHandler() { ClientInvocationHandler newInvocationHandler(RestfulClientFactory theRestfulClientFactory) {
return new ClientInvocationHandler(myClient, myContext, myUrlBase, myMethodToReturnValue, myBindings, myMethodToLambda); return new ClientInvocationHandler(myClient, myContext, myUrlBase, myMethodToReturnValue, myBindings, myMethodToLambda, theRestfulClientFactory);
} }
interface ILambda { interface ILambda {

View File

@ -43,7 +43,6 @@ import org.hl7.fhir.instance.model.api.IBaseParameters;
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.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
@ -61,7 +60,6 @@ import ca.uhn.fhir.model.primitive.UriDt;
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.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
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;
@ -128,21 +126,18 @@ import ca.uhn.fhir.util.ICallable;
public class GenericClient extends BaseClient implements IGenericClient { public class GenericClient extends BaseClient implements IGenericClient {
private static final String I18N_CANNOT_DETEMINE_RESOURCE_TYPE = "ca.uhn.fhir.rest.client.GenericClient.cannotDetermineResourceTypeFromUri"; private static final String I18N_CANNOT_DETEMINE_RESOURCE_TYPE = "ca.uhn.fhir.rest.client.GenericClient.cannotDetermineResourceTypeFromUri";
private static final String I18N_INCOMPLETE_URI_FOR_READ = "ca.uhn.fhir.rest.client.GenericClient.incompleteUriForRead"; private static final String I18N_INCOMPLETE_URI_FOR_READ = "ca.uhn.fhir.rest.client.GenericClient.incompleteUriForRead";
private static final String I18N_NO_VERSION_ID_FOR_VREAD = "ca.uhn.fhir.rest.client.GenericClient.noVersionIdForVread"; private static final String I18N_NO_VERSION_ID_FOR_VREAD = "ca.uhn.fhir.rest.client.GenericClient.noVersionIdForVread";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClient.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClient.class);
private FhirContext myContext; private FhirContext myContext;
private HttpRequestBase myLastRequest; private HttpRequestBase myLastRequest;
private boolean myLogRequestAndResponse; private boolean myLogRequestAndResponse;
/** /**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change! * For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
*/ */
public GenericClient(FhirContext theContext, HttpClient theHttpClient, String theServerBase) { public GenericClient(FhirContext theContext, HttpClient theHttpClient, String theServerBase, RestfulClientFactory theFactory) {
super(theHttpClient, theServerBase); super(theHttpClient, theServerBase, theFactory);
myContext = theContext; myContext = theContext;
} }

View File

@ -23,7 +23,7 @@ package ca.uhn.fhir.rest.client;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.Collection; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@ -63,10 +63,8 @@ public class RestfulClientFactory implements IRestfulClientFactory {
private Map<Class<? extends IRestfulClient>, ClientInvocationHandlerFactory> myInvocationHandlers = new HashMap<Class<? extends IRestfulClient>, ClientInvocationHandlerFactory>(); private Map<Class<? extends IRestfulClient>, ClientInvocationHandlerFactory> myInvocationHandlers = new HashMap<Class<? extends IRestfulClient>, ClientInvocationHandlerFactory>();
private HttpHost myProxy; private HttpHost myProxy;
private ServerValidationModeEnum myServerValidationMode = DEFAULT_SERVER_VALIDATION_MODE; private ServerValidationModeEnum myServerValidationMode = DEFAULT_SERVER_VALIDATION_MODE;
private int mySocketTimeout = DEFAULT_SOCKET_TIMEOUT; private int mySocketTimeout = DEFAULT_SOCKET_TIMEOUT;
private Set<String> myValidatedServerBaseUrls = Collections.synchronizedSet(new HashSet<String>());
private Set<String> myValidatedServerBaseUrls = new HashSet<String>();
/** /**
* Constructor * Constructor
@ -169,11 +167,10 @@ public class RestfulClientFactory implements IRestfulClientFactory {
throw new ConfigurationException(theClientType.getCanonicalName() + " is not an interface"); throw new ConfigurationException(theClientType.getCanonicalName() + " is not an interface");
} }
HttpClient httpClient = getHttpClient();
maybeValidateServerBase(theServerBase, httpClient);
ClientInvocationHandlerFactory invocationHandler = myInvocationHandlers.get(theClientType); ClientInvocationHandlerFactory invocationHandler = myInvocationHandlers.get(theClientType);
if (invocationHandler == null) { if (invocationHandler == null) {
HttpClient httpClient = getHttpClient();
invocationHandler = new ClientInvocationHandlerFactory(httpClient, myContext, theServerBase, theClientType); invocationHandler = new ClientInvocationHandlerFactory(httpClient, myContext, theServerBase, theClientType);
for (Method nextMethod : theClientType.getMethods()) { for (Method nextMethod : theClientType.getMethods()) {
BaseMethodBinding<?> binding = BaseMethodBinding.bindMethod(nextMethod, myContext, null); BaseMethodBinding<?> binding = BaseMethodBinding.bindMethod(nextMethod, myContext, null);
@ -182,7 +179,7 @@ public class RestfulClientFactory implements IRestfulClientFactory {
myInvocationHandlers.put(theClientType, invocationHandler); myInvocationHandlers.put(theClientType, invocationHandler);
} }
T proxy = instantiateProxy(theClientType, invocationHandler.newInvocationHandler()); T proxy = instantiateProxy(theClientType, invocationHandler.newInvocationHandler(this));
return proxy; return proxy;
} }
@ -190,11 +187,13 @@ public class RestfulClientFactory implements IRestfulClientFactory {
@Override @Override
public synchronized IGenericClient newGenericClient(String theServerBase) { public synchronized IGenericClient newGenericClient(String theServerBase) {
HttpClient httpClient = getHttpClient(); HttpClient httpClient = getHttpClient();
maybeValidateServerBase(theServerBase, httpClient); return new GenericClient(myContext, httpClient, theServerBase, this);
return new GenericClient(myContext, httpClient, theServerBase);
} }
private void maybeValidateServerBase(String theServerBase, HttpClient theHttpClient) { /**
* This method is internal to HAPI - It may change in future versions, use with caution.
*/
public void validateServerBaseIfConfiguredToDoSo(String theServerBase, HttpClient theHttpClient) {
String serverBase = theServerBase; String serverBase = theServerBase;
if (!serverBase.endsWith("/")) { if (!serverBase.endsWith("/")) {
serverBase = serverBase + "/"; serverBase = serverBase + "/";
@ -270,7 +269,9 @@ public class RestfulClientFactory implements IRestfulClientFactory {
private void validateServerBase(String theServerBase, HttpClient theHttpClient) { private void validateServerBase(String theServerBase, HttpClient theHttpClient) {
GenericClient client = new GenericClient(myContext, theHttpClient, theServerBase); GenericClient client = new GenericClient(myContext, theHttpClient, theServerBase, this);
client.setDontValidateConformance(true);
BaseConformance conformance; BaseConformance conformance;
try { try {
conformance = client.conformance(); conformance = client.conformance();

View File

@ -30,6 +30,10 @@ import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.model.valueset.BundleTypeEnum;
/**
* This interface should be considered experimental and will likely change in future releases
* of HAPI. Use with caution!
*/
public interface IVersionSpecificBundleFactory { public interface IVersionSpecificBundleFactory {
void addResourcesToBundle(List<IResource> theResult, BundleTypeEnum theBundleType, String theServerBase, BundleInclusionRule theBundleInclusionRule, Set<Include> theIncludes); void addResourcesToBundle(List<IResource> theResult, BundleTypeEnum theBundleType, String theServerBase, BundleInclusionRule theBundleInclusionRule, Set<Include> theIncludes);

View File

@ -1,13 +1,13 @@
package ca.uhn.fhir.rest.client; package ca.uhn.fhir.rest.client;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat; import static org.junit.Assert.*;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.io.InputStream;
import java.io.StringReader; import java.io.StringReader;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@ -23,9 +23,13 @@ import org.junit.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Matchers; import org.mockito.Matchers;
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; 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.context.FhirContext;
import ca.uhn.fhir.model.dstu2.resource.Conformance; import ca.uhn.fhir.model.dstu2.resource.Conformance;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException; import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
@ -35,6 +39,7 @@ public class ClientServerValidationTestDstu2 {
private FhirContext myCtx; private FhirContext myCtx;
private HttpClient myHttpClient; private HttpClient myHttpClient;
private HttpResponse myHttpResponse; private HttpResponse myHttpResponse;
private boolean myFirstResponse;
@Before @Before
public void before() { public void before() {
@ -43,27 +48,44 @@ public class ClientServerValidationTestDstu2 {
myCtx = FhirContext.forDstu2(); myCtx = FhirContext.forDstu2();
myCtx.getRestfulClientFactory().setHttpClient(myHttpClient); myCtx.getRestfulClientFactory().setHttpClient(myHttpClient);
myFirstResponse = true;
} }
@Test @Test
public void testServerReturnsAppropriateVersionForDstu2() throws Exception { public void testServerReturnsAppropriateVersionForDstu2() throws Exception {
Conformance conf = new Conformance(); Conformance conf = new Conformance();
conf.setFhirVersion("0.4.0"); conf.setFhirVersion("0.4.0");
String msg = myCtx.newXmlParser().encodeResourceToString(conf); final String confResource = myCtx.newXmlParser().encodeResourceToString(conf);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); 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().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
if (myFirstResponse) {
myFirstResponse=false;
return new ReaderInputStream(new StringReader(confResource), Charset.forName("UTF-8"));
} else {
return new ReaderInputStream(new StringReader(myCtx.newXmlParser().encodeResourceToString(new Patient())), Charset.forName("UTF-8"));
}
}});
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
myCtx.getRestfulClientFactory().setServerValidationModeEnum(ServerValidationModeEnum.ONCE); myCtx.getRestfulClientFactory().setServerValidationModeEnum(ServerValidationModeEnum.ONCE);
myCtx.newRestfulGenericClient("http://foo"); IGenericClient client = myCtx.newRestfulGenericClient("http://foo");
myCtx.newRestfulGenericClient("http://foo");
// don't load the conformance until the first time the client is actually used
assertTrue(myFirstResponse);
client.read(new UriDt("http://foo/Patient/123"));
assertFalse(myFirstResponse);
myCtx.newRestfulGenericClient("http://foo").read(new UriDt("http://foo/Patient/123"));
myCtx.newRestfulGenericClient("http://foo").read(new UriDt("http://foo/Patient/123"));
verify(myHttpClient, times(1)).execute(Matchers.any(HttpUriRequest.class)); // Conformance only loaded once, then 3 reads
verify(myHttpClient, times(4)).execute(Matchers.any(HttpUriRequest.class));
} }
@Test @Test

View File

@ -171,12 +171,6 @@
<param name="file" value="examples/src/main/java/example/ServletExamples.java" /> <param name="file" value="examples/src/main/java/example/ServletExamples.java" />
</macro> </macro>
<p>
This interceptor will then produce output similar to the following:
</p>
<source><![CDATA[2014-09-04 02:37:30.030 Source[127.0.0.1] Operation[vread Patient/1667/_history/1] UA[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.94 Safari/537.36] Params[?_format=json]
2014-09-04 03:30:00.443 Source[127.0.0.1] Operation[search-type Organization] UA[Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)] Params[]]]></source>
</subsection> </subsection>
</section> </section>