Skip to content

Commit

Permalink
Fix #168 - Client conformance check should use any registered client
Browse files Browse the repository at this point in the history
interceptors
  • Loading branch information
jamesagnew committed Apr 30, 2015
1 parent b68ce95 commit cb7d948
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 62 deletions.
Expand Up @@ -153,10 +153,14 @@ <T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, Ba
return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse);
}

void forceConformanceCheck() {
myFactory.validateServerBase(myUrlBase, myClient, this);
}

<T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint, boolean theLogRequestAndResponse) {

if (!myDontValidateConformance) {
myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient);
myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient, this);
}

// TODO: handle non 2xx status codes by throwing the correct exception,
Expand Down Expand Up @@ -441,4 +445,8 @@ public static Reader createReaderFromResponse(HttpResponse theResponse) throws I
return reader;
}

public List<IClientInterceptor> getInterceptors() {
return Collections.unmodifiableList(myInterceptors);
}

}
Expand Up @@ -140,7 +140,7 @@ public GenericClient(FhirContext theContext, HttpClient theHttpClient, String th
super(theHttpClient, theServerBase, theFactory);
myContext = theContext;
}

@Override
public BaseConformance conformance() {
HttpGetClientInvocation invocation = MethodUtil.createConformanceInvocation();
Expand All @@ -156,6 +156,11 @@ public BaseConformance conformance() {
return resp;
}

@Override
public void forceConformanceCheck() {
super.forceConformanceCheck();
}

@Override
public ICreate create() {
return new CreateInternal();
Expand Down
Expand Up @@ -34,6 +34,8 @@
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
import ca.uhn.fhir.rest.client.exceptions.FhirClientInnapropriateForServerException;
import ca.uhn.fhir.rest.gclient.ICreate;
import ca.uhn.fhir.rest.gclient.IDelete;
import ca.uhn.fhir.rest.gclient.IGetPage;
Expand Down Expand Up @@ -100,6 +102,16 @@ public interface IGenericClient extends IRestfulClient {
@Deprecated
MethodOutcome delete(Class<? extends IResource> theType, String theId);

/**
* Force the client to fetch the server's conformance statement and validate that it is appropriate for this client.
*
* @throws FhirClientConnectionException
* if the conformance statement cannot be read, or if the client
* @throws FhirClientInnapropriateForServerException
* If the conformance statement indicates that the server is inappropriate for this client (e.g. it implements the wrong version of FHIR)
*/
void forceConformanceCheck() throws FhirClientConnectionException;

/**
* Fluent method for the "get tags" operation
*/
Expand All @@ -114,17 +126,15 @@ public interface IGenericClient extends IRestfulClient {
* Implementation of the "history instance" method.
*
* @param theType
* The type of resource to return the history for, or
* <code>null</code> to search for history across all resources
* The type of resource to return the history for, or <code>null</code> to search for history across all resources
* @param theId
* The ID of the resource to return the history for, or <code>null</code> to search for all resource
* instances. Note that if this param is not null, <code>theType</code> must also not be null
* The ID of the resource to return the history for, or <code>null</code> to search for all resource instances. Note that if this param is not null, <code>theType</code> must also not
* be null
* @param theSince
* If not null, request that the server only return resources updated since this time
* @param theLimit
* If not null, request that the server return no more than this number of resources. Note that the
* server may return less even if more are available, but should not return more according to the FHIR
* specification.
* If not null, request that the server return no more than this number of resources. Note that the server may return less even if more are available, but should not return more
* according to the FHIR specification.
* @return A bundle containing returned resources
* @deprecated As of 0.9, use the fluent {@link #history()} method instead
*/
Expand All @@ -135,49 +145,46 @@ public interface IGenericClient extends IRestfulClient {
* Implementation of the "history instance" method.
*
* @param theType
* The type of resource to return the history for, or
* <code>null</code> to search for history across all resources
* The type of resource to return the history for, or <code>null</code> to search for history across all resources
* @param theId
* The ID of the resource to return the history for, or <code>null</code> to search for all resource
* instances. Note that if this param is not null, <code>theType</code> must also not be null
* The ID of the resource to return the history for, or <code>null</code> to search for all resource instances. Note that if this param is not null, <code>theType</code> must also not
* be null
* @param theSince
* If not null, request that the server only return resources updated since this time
* @param theLimit
* If not null, request that the server return no more than this number of resources. Note that the
* server may return less even if more are available, but should not return more according to the FHIR
* specification.
* If not null, request that the server return no more than this number of resources. Note that the server may return less even if more are available, but should not return more
* according to the FHIR specification.
* @return A bundle containing returned resources
* @deprecated As of 0.9, use the fluent {@link #history()} method instead
*/
@Deprecated
<T extends IResource> Bundle history(Class<T> theType, String theId, DateTimeDt theSince, Integer theLimit);

// /**
// * Implementation of the "instance read" method. This method will only ever do a "read" for the latest version of a
// * given resource instance, even if the ID passed in contains a version. If you wish to request a specific version
// * of a resource (the "vread" operation), use {@link #vread(Class, IdDt)} instead.
// * <p>
// * Note that if an absolute resource ID is passed in (i.e. a URL containing a protocol and host as well as the
// * resource type and ID) the server base for the client will be ignored, and the URL passed in will be queried.
// * </p>
// *
// * @param theType
// * The type of resource to load
// * @param theId
// * The ID to load, including the resource ID and the resource version ID. Valid values include
// * "Patient/123/_history/222", or "http://example.com/fhir/Patient/123/_history/222"
// * @return The resource
// */
// <T extends IBaseResource> T read(Class<T> theType, IdDt theId);

/**
* Loads the previous/next bundle of resources from a paged set, using the link specified in the "link type=next"
* tag within the atom bundle.
* Loads the previous/next bundle of resources from a paged set, using the link specified in the "link type=next" tag within the atom bundle.
*
* @see Bundle#getLinkNext()
*/
IGetPage loadPage();

// /**
// * Implementation of the "instance read" method. This method will only ever do a "read" for the latest version of a
// * given resource instance, even if the ID passed in contains a version. If you wish to request a specific version
// * of a resource (the "vread" operation), use {@link #vread(Class, IdDt)} instead.
// * <p>
// * Note that if an absolute resource ID is passed in (i.e. a URL containing a protocol and host as well as the
// * resource type and ID) the server base for the client will be ignored, and the URL passed in will be queried.
// * </p>
// *
// * @param theType
// * The type of resource to load
// * @param theId
// * The ID to load, including the resource ID and the resource version ID. Valid values include
// * "Patient/123/_history/222", or "http://example.com/fhir/Patient/123/_history/222"
// * @return The resource
// */
// <T extends IBaseResource> T read(Class<T> theType, IdDt theId);

/**
* Implementation of the FHIR "extended operations" action
*/
Expand Down Expand Up @@ -220,8 +227,7 @@ public interface IGenericClient extends IRestfulClient {
IResource read(UriDt theUrl);

/**
* Register a new interceptor for this client. An interceptor can be used to add additional logging, or add security
* headers, or pre-process responses, etc.
* Register a new interceptor for this client. An interceptor can be used to add additional logging, or add security headers, or pre-process responses, etc.
*/
void registerInterceptor(IClientInterceptor theInterceptor);

Expand Down Expand Up @@ -250,8 +256,8 @@ public interface IGenericClient extends IRestfulClient {
Bundle search(UriDt theUrl);

/**
* If set to <code>true</code>, the client will log all requests and all responses. This is probably not a good
* production setting since it will result in a lot of extra logging, but it can be useful for troubleshooting.
* If set to <code>true</code>, the client will log all requests and all responses. This is probably not a good production setting since it will result in a lot of extra logging, but it can be
* useful for troubleshooting.
*
* @param theLogRequestAndResponse
* Should requests and responses be logged
Expand All @@ -268,17 +274,15 @@ public interface IGenericClient extends IRestfulClient {
*
* @param theResources
* The resources to create/update in a single transaction
* @return A list of resource stubs (<b>these will not be fully populated</b>) containing IDs and other
* {@link IResource#getResourceMetadata() metadata}
* @return A list of resource stubs (<b>these will not be fully populated</b>) containing IDs and other {@link IResource#getResourceMetadata() metadata}
* @deprecated Use {@link #transaction()}
*
*/
@Deprecated
List<IResource> transaction(List<IResource> theResources);

/**
* Remove an intercaptor that was previously registered using
* {@link IRestfulClient#registerInterceptor(IClientInterceptor)}
* Remove an intercaptor that was previously registered using {@link IRestfulClient#registerInterceptor(IClientInterceptor)}
*/
void unregisterInterceptor(IClientInterceptor theInterceptor);

Expand Down Expand Up @@ -319,18 +323,16 @@ public interface IGenericClient extends IRestfulClient {
MethodOutcome validate(IResource theResource);

/**
* Implementation of the "instance vread" method. Note that this method expects <code>theId</code> to contain a
* resource ID as well as a version ID, and will fail if it does not.
* Implementation of the "instance vread" method. Note that this method expects <code>theId</code> to contain a resource ID as well as a version ID, and will fail if it does not.
* <p>
* Note that if an absolute resource ID is passed in (i.e. a URL containing a protocol and host as well as the
* resource type and ID) the server base for the client will be ignored, and the URL passed in will be queried.
* Note that if an absolute resource ID is passed in (i.e. a URL containing a protocol and host as well as the resource type and ID) the server base for the client will be ignored, and the URL
* passed in will be queried.
* </p>
*
* @param theType
* The type of resource to load
* @param theId
* The ID to load, including the resource ID and the resource version ID. Valid values include
* "Patient/123/_history/222", or "http://example.com/fhir/Patient/123/_history/222"
* The ID to load, including the resource ID and the resource version ID. Valid values include "Patient/123/_history/222", or "http://example.com/fhir/Patient/123/_history/222"
* @return The resource
*/
<T extends IBaseResource> T vread(Class<T> theType, IdDt theId);
Expand Down
Expand Up @@ -50,6 +50,7 @@
import ca.uhn.fhir.model.base.resource.BaseConformance;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
import ca.uhn.fhir.rest.client.exceptions.FhirClientInnapropriateForServerException;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.Constants;

Expand Down Expand Up @@ -193,25 +194,29 @@ public synchronized IGenericClient newGenericClient(String theServerBase) {
/**
* 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 + "/";
}
public void validateServerBaseIfConfiguredToDoSo(String theServerBase, HttpClient theHttpClient, BaseClient theClient) {
String serverBase = normalizeBaseUrlForMap(theServerBase);

switch (myServerValidationMode) {
case NEVER:
break;
case ONCE:
if (!myValidatedServerBaseUrls.contains(serverBase)) {
validateServerBase(serverBase, theHttpClient);
myValidatedServerBaseUrls.add(serverBase);
validateServerBase(serverBase, theHttpClient, theClient);
}
break;
}

}

private String normalizeBaseUrlForMap(String theServerBase) {
String serverBase = theServerBase;
if (!serverBase.endsWith("/")) {
serverBase = serverBase + "/";
}
return serverBase;
}

@Override
public synchronized void setConnectionRequestTimeout(int theConnectionRequestTimeout) {
myConnectionRequestTimeout = theConnectionRequestTimeout;
Expand Down Expand Up @@ -267,9 +272,12 @@ public synchronized void setSocketTimeout(int theSocketTimeout) {
myHttpClient = null;
}

private void validateServerBase(String theServerBase, HttpClient theHttpClient) {
void validateServerBase(String theServerBase, HttpClient theHttpClient, BaseClient theClient) {

GenericClient client = new GenericClient(myContext, theHttpClient, theServerBase, this);
for (IClientInterceptor interceptor : theClient.getInterceptors()) {
client.registerInterceptor(interceptor);
}
client.setDontValidateConformance(true);

BaseConformance conformance;
Expand Down Expand Up @@ -299,9 +307,12 @@ private void validateServerBase(String theServerBase, HttpClient theHttpClient)
if (serverFhirVersionEnum != null) {
FhirVersionEnum contextFhirVersion = myContext.getVersion().getVersion();
if (!contextFhirVersion.isEquivalentTo(serverFhirVersionEnum)) {
throw new FhirClientConnectionException(myContext.getLocalizer().getMessage(RestfulClientFactory.class, "wrongVersionInConformance", theServerBase + Constants.URL_TOKEN_METADATA, serverFhirVersionString, serverFhirVersionEnum, contextFhirVersion));
throw new FhirClientInnapropriateForServerException(myContext.getLocalizer().getMessage(RestfulClientFactory.class, "wrongVersionInConformance", theServerBase + Constants.URL_TOKEN_METADATA, serverFhirVersionString, serverFhirVersionEnum, contextFhirVersion));
}
}

myValidatedServerBaseUrls.add(normalizeBaseUrlForMap(theServerBase));

}

@Override
Expand Down
@@ -0,0 +1,46 @@
package ca.uhn.fhir.rest.client.exceptions;

/*
* #%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 ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;

/**
* This exception will be thrown by FHIR clients if the client attempts to
* communicate with a server which is a valid FHIR server but is incompatible
* with this client for some reason.
*/
public class FhirClientInnapropriateForServerException extends BaseServerResponseException {

private static final long serialVersionUID = 1L;

public FhirClientInnapropriateForServerException(Throwable theCause) {
super(0, theCause);
}

public FhirClientInnapropriateForServerException(String theMessage, Throwable theCause) {
super(0, theMessage, theCause);
}

public FhirClientInnapropriateForServerException(String theMessage) {
super(0, theMessage);
}

}

0 comments on commit cb7d948

Please sign in to comment.