Fix #108 - Don't check the server version from the client until the first actual request
This commit is contained in:
parent
595f8f524f
commit
7540203836
|
@ -287,6 +287,14 @@ public class FhirContext {
|
|||
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.
|
||||
*
|
||||
|
@ -486,8 +494,4 @@ public class FhirContext {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
public IVersionSpecificBundleFactory newBundleFactory() {
|
||||
return myVersion.newBundleFactory(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -67,13 +67,16 @@ public abstract class BaseClient {
|
|||
private HttpResponse myLastResponse;
|
||||
private String myLastResponseBody;
|
||||
private Boolean myPrettyPrint = false;
|
||||
|
||||
private final RestfulClientFactory myFactory;
|
||||
private final String myUrlBase;
|
||||
|
||||
BaseClient(HttpClient theClient, String theUrlBase) {
|
||||
private boolean myDontValidateConformance;
|
||||
|
||||
BaseClient(HttpClient theClient, String theUrlBase, RestfulClientFactory theFactory) {
|
||||
super();
|
||||
myClient = theClient;
|
||||
myUrlBase = theUrlBase;
|
||||
myFactory = theFactory;
|
||||
}
|
||||
|
||||
protected Map<String, List<String>> createExtraParams() {
|
||||
|
@ -130,7 +133,21 @@ public abstract class BaseClient {
|
|||
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) {
|
||||
|
||||
if (!myDontValidateConformance) {
|
||||
myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient);
|
||||
}
|
||||
|
||||
// TODO: handle non 2xx status codes by throwing the correct exception,
|
||||
// and ensure it's passed upwards
|
||||
HttpRequestBase httpRequest;
|
||||
|
|
|
@ -37,13 +37,13 @@ class ClientInvocationHandler extends BaseClient implements InvocationHandler {
|
|||
private FhirContext myContext;
|
||||
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) {
|
||||
super(theClient, theUrlBase);
|
||||
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, theFactory);
|
||||
|
||||
myContext =theContext;
|
||||
myContext = theContext;
|
||||
myMethodToReturnValue = theMethodToReturnValue;
|
||||
myBindings = theBindings;
|
||||
myMethodToLambda=theMethodToLambda;
|
||||
myMethodToLambda = theMethodToLambda;
|
||||
}
|
||||
|
||||
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?");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -67,8 +67,8 @@ class ClientInvocationHandlerFactory {
|
|||
myBindings.put(theMethod, theBinding);
|
||||
}
|
||||
|
||||
ClientInvocationHandler newInvocationHandler() {
|
||||
return new ClientInvocationHandler(myClient, myContext, myUrlBase, myMethodToReturnValue, myBindings, myMethodToLambda);
|
||||
ClientInvocationHandler newInvocationHandler(RestfulClientFactory theRestfulClientFactory) {
|
||||
return new ClientInvocationHandler(myClient, myContext, myUrlBase, myMethodToReturnValue, myBindings, myMethodToLambda, theRestfulClientFactory);
|
||||
}
|
||||
|
||||
interface ILambda {
|
||||
|
|
|
@ -43,7 +43,6 @@ import org.hl7.fhir.instance.model.api.IBaseParameters;
|
|||
|
||||
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.FhirVersionEnum;
|
||||
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.IParser;
|
||||
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.gclient.IClientExecutable;
|
||||
import ca.uhn.fhir.rest.gclient.ICreate;
|
||||
|
@ -128,21 +126,18 @@ import ca.uhn.fhir.util.ICallable;
|
|||
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_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 org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClient.class);
|
||||
private FhirContext myContext;
|
||||
|
||||
private HttpRequestBase myLastRequest;
|
||||
|
||||
private boolean myLogRequestAndResponse;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
super(theHttpClient, theServerBase);
|
||||
public GenericClient(FhirContext theContext, HttpClient theHttpClient, String theServerBase, RestfulClientFactory theFactory) {
|
||||
super(theHttpClient, theServerBase, theFactory);
|
||||
myContext = theContext;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ package ca.uhn.fhir.rest.client;
|
|||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
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 HttpHost myProxy;
|
||||
private ServerValidationModeEnum myServerValidationMode = DEFAULT_SERVER_VALIDATION_MODE;
|
||||
|
||||
private int mySocketTimeout = DEFAULT_SOCKET_TIMEOUT;
|
||||
|
||||
private Set<String> myValidatedServerBaseUrls = new HashSet<String>();
|
||||
private Set<String> myValidatedServerBaseUrls = Collections.synchronizedSet(new HashSet<String>());
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -169,11 +167,10 @@ public class RestfulClientFactory implements IRestfulClientFactory {
|
|||
throw new ConfigurationException(theClientType.getCanonicalName() + " is not an interface");
|
||||
}
|
||||
|
||||
HttpClient httpClient = getHttpClient();
|
||||
maybeValidateServerBase(theServerBase, httpClient);
|
||||
|
||||
|
||||
ClientInvocationHandlerFactory invocationHandler = myInvocationHandlers.get(theClientType);
|
||||
if (invocationHandler == null) {
|
||||
HttpClient httpClient = getHttpClient();
|
||||
invocationHandler = new ClientInvocationHandlerFactory(httpClient, myContext, theServerBase, theClientType);
|
||||
for (Method nextMethod : theClientType.getMethods()) {
|
||||
BaseMethodBinding<?> binding = BaseMethodBinding.bindMethod(nextMethod, myContext, null);
|
||||
|
@ -182,7 +179,7 @@ public class RestfulClientFactory implements IRestfulClientFactory {
|
|||
myInvocationHandlers.put(theClientType, invocationHandler);
|
||||
}
|
||||
|
||||
T proxy = instantiateProxy(theClientType, invocationHandler.newInvocationHandler());
|
||||
T proxy = instantiateProxy(theClientType, invocationHandler.newInvocationHandler(this));
|
||||
|
||||
return proxy;
|
||||
}
|
||||
|
@ -190,11 +187,13 @@ public class RestfulClientFactory implements IRestfulClientFactory {
|
|||
@Override
|
||||
public synchronized IGenericClient newGenericClient(String theServerBase) {
|
||||
HttpClient httpClient = getHttpClient();
|
||||
maybeValidateServerBase(theServerBase, httpClient);
|
||||
return new GenericClient(myContext, httpClient, theServerBase);
|
||||
return new GenericClient(myContext, httpClient, theServerBase, this);
|
||||
}
|
||||
|
||||
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;
|
||||
if (!serverBase.endsWith("/")) {
|
||||
serverBase = serverBase + "/";
|
||||
|
@ -270,7 +269,9 @@ public class RestfulClientFactory implements IRestfulClientFactory {
|
|||
|
||||
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;
|
||||
try {
|
||||
conformance = client.conformance();
|
||||
|
|
|
@ -30,6 +30,10 @@ import ca.uhn.fhir.model.api.Bundle;
|
|||
import ca.uhn.fhir.model.api.IResource;
|
||||
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 {
|
||||
|
||||
void addResourcesToBundle(List<IResource> theResult, BundleTypeEnum theBundleType, String theServerBase, BundleInclusionRule theBundleInclusionRule, Set<Include> theIncludes);
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package ca.uhn.fhir.rest.client;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
|
@ -23,9 +23,13 @@ import org.junit.Test;
|
|||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Matchers;
|
||||
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.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.server.Constants;
|
||||
|
||||
|
@ -35,6 +39,7 @@ public class ClientServerValidationTestDstu2 {
|
|||
private FhirContext myCtx;
|
||||
private HttpClient myHttpClient;
|
||||
private HttpResponse myHttpResponse;
|
||||
private boolean myFirstResponse;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
|
@ -43,27 +48,44 @@ public class ClientServerValidationTestDstu2 {
|
|||
|
||||
myCtx = FhirContext.forDstu2();
|
||||
myCtx.getRestfulClientFactory().setHttpClient(myHttpClient);
|
||||
myFirstResponse = true;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerReturnsAppropriateVersionForDstu2() throws Exception {
|
||||
Conformance conf = new Conformance();
|
||||
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);
|
||||
|
||||
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||
when(myHttpResponse.getEntity().getContent()).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);
|
||||
|
||||
myCtx.getRestfulClientFactory().setServerValidationModeEnum(ServerValidationModeEnum.ONCE);
|
||||
myCtx.newRestfulGenericClient("http://foo");
|
||||
myCtx.newRestfulGenericClient("http://foo");
|
||||
IGenericClient client = 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
|
||||
|
|
|
@ -171,12 +171,6 @@
|
|||
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
|
||||
</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>
|
||||
|
||||
</section>
|
||||
|
|
Loading…
Reference in New Issue