All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ca.uhn.fhir.rest.client.RestfulClientFactory Maven / Gradle / Ivy

There is a newer version: 7.6.1
Show newest version
package ca.uhn.fhir.rest.client;

/*
 * #%L
 * HAPI FHIR - Core Library
 * %%
 * Copyright (C) 2014 - 2016 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.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;

import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.client.api.IHttpClient;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
import ca.uhn.fhir.rest.client.exceptions.FhirClientInappropriateForServerException;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.util.FhirTerser;

/**
 * Base class for a REST client factory implementation
 */
public abstract class RestfulClientFactory implements IRestfulClientFactory {

	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulClientFactory.class);
	private int myConnectionRequestTimeout = DEFAULT_CONNECTION_REQUEST_TIMEOUT;
	private int myConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
	private FhirContext myContext;
	private Map, ClientInvocationHandlerFactory> myInvocationHandlers = new HashMap, ClientInvocationHandlerFactory>();
	private ServerValidationModeEnum myServerValidationMode = DEFAULT_SERVER_VALIDATION_MODE;
	private int mySocketTimeout = DEFAULT_SOCKET_TIMEOUT;
	private Set myValidatedServerBaseUrls = Collections.synchronizedSet(new HashSet());
	private String myProxyUsername;
	private String myProxyPassword;	
	private int myPoolMaxTotal = DEFAULT_POOL_MAX;
	private int myPoolMaxPerRoute = DEFAULT_POOL_MAX_PER_ROUTE;
	
	/**
	 * Constructor
	 */
	public RestfulClientFactory() {
	}

	/**
	 * Constructor
	 * 
	 * @param theFhirContext
	 *            The context
	 */
	public RestfulClientFactory(FhirContext theFhirContext) {
		myContext = theFhirContext;
	}

	@Override
	public int getConnectionRequestTimeout() {
		return myConnectionRequestTimeout;
	}

	@Override
	public int getConnectTimeout() {
		return myConnectTimeout;
	}

	/**
	 * Return the proxy username to authenticate with the HTTP proxy
	 * @param The proxy username
	 */	
	protected String getProxyUsername() {
		return myProxyUsername;
	}
	
	/**
	 * Return the proxy password to authenticate with the HTTP proxy
	 * @param The proxy password
	 */	
	protected String getProxyPassword() {
		return myProxyPassword;
	}

	@Override
	public void setProxyCredentials(String theUsername, String thePassword) {
		myProxyUsername=theUsername;
		myProxyPassword=thePassword;
	}
	
	@Override
	public ServerValidationModeEnum getServerValidationMode() {
		return myServerValidationMode;
	}

	@Override
	public int getSocketTimeout() {
		return mySocketTimeout;
	}

	@Override
	public int getPoolMaxTotal() {
		return myPoolMaxTotal;
	}

	@Override
	public int getPoolMaxPerRoute() {
		return myPoolMaxPerRoute;
	}

	@SuppressWarnings("unchecked")
	private  T instantiateProxy(Class theClientType, InvocationHandler theInvocationHandler) {
		T proxy = (T) Proxy.newProxyInstance(theClientType.getClassLoader(), new Class[] { theClientType }, theInvocationHandler);
		return proxy;
	}

	/**
	 * Instantiates a new client instance
	 * 
	 * @param theClientType
	 *            The client type, which is an interface type to be instantiated
	 * @param theServerBase
	 *            The URL of the base for the restful FHIR server to connect to
	 * @return A newly created client
	 * @throws ConfigurationException
	 *             If the interface type is not an interface
	 */
	@Override
	public synchronized  T newClient(Class theClientType, String theServerBase) {
		validateConfigured();
		
		if (!theClientType.isInterface()) {
			throw new ConfigurationException(theClientType.getCanonicalName() + " is not an interface");
		}
		
		ClientInvocationHandlerFactory invocationHandler = myInvocationHandlers.get(theClientType);
		if (invocationHandler == null) {
			IHttpClient httpClient = getHttpClient(theServerBase);
			invocationHandler = new ClientInvocationHandlerFactory(httpClient, myContext, theServerBase, theClientType);
			for (Method nextMethod : theClientType.getMethods()) {
				BaseMethodBinding binding = BaseMethodBinding.bindMethod(nextMethod, myContext, null);
				invocationHandler.addBinding(nextMethod, binding);
			}
			myInvocationHandlers.put(theClientType, invocationHandler);
		}

		T proxy = instantiateProxy(theClientType, invocationHandler.newInvocationHandler(this));

		return proxy;
	}

	/**
	 * Called automatically before the first use of this factory to ensure that
	 * the configuration is sane. Subclasses may override, but should also call 
	 * super.validateConfigured()
	 */
	protected void validateConfigured() {
		if (getFhirContext() == null) {
			throw new IllegalStateException(getClass().getSimpleName() + " does not have FhirContext defined. This must be set via " + getClass().getSimpleName() + "#setFhirContext(FhirContext)");
		}
	}

	@Override
	public synchronized IGenericClient newGenericClient(String theServerBase) {
		validateConfigured();
		IHttpClient httpClient = getHttpClient(theServerBase);
		return new GenericClient(myContext, httpClient, theServerBase, this);
	}

	@Override
	public void validateServerBaseIfConfiguredToDoSo(String theServerBase, IHttpClient theHttpClient, BaseClient theClient) {
		String serverBase = normalizeBaseUrlForMap(theServerBase);

		switch (getServerValidationMode()) {
		case NEVER:
			break;
		case ONCE:
			if (!myValidatedServerBaseUrls.contains(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;
		resetHttpClient();
	}

	@Override
	public synchronized void setConnectTimeout(int theConnectTimeout) {
		myConnectTimeout = theConnectTimeout;
		resetHttpClient();
	}

	/**
	 * Sets the context associated with this client factory. Must not be called more than once.
	 */
	public void setFhirContext(FhirContext theContext) {
		if (myContext != null && myContext != theContext) {
			throw new IllegalStateException("RestfulClientFactory instance is already associated with one FhirContext. RestfulClientFactory instances can not be shared.");
		}
		myContext = theContext;
	}
	
	/**
	 * Return the fhir context
	 * @return the fhir context 
	 */
	public FhirContext getFhirContext() {
		return myContext;
	}

	@Override
	public void setServerValidationMode(ServerValidationModeEnum theServerValidationMode) {
		Validate.notNull(theServerValidationMode, "theServerValidationMode may not be null");
		myServerValidationMode = theServerValidationMode;
	}

	@Override
	public synchronized void setSocketTimeout(int theSocketTimeout) {
		mySocketTimeout = theSocketTimeout;
		resetHttpClient();
	}

	@Override
	public synchronized void setPoolMaxTotal(int thePoolMaxTotal) {
		myPoolMaxTotal = thePoolMaxTotal;
		resetHttpClient();
	}

	@Override
	public synchronized void setPoolMaxPerRoute(int thePoolMaxPerRoute) {
		myPoolMaxPerRoute = thePoolMaxPerRoute;
		resetHttpClient();
	}

	@SuppressWarnings("unchecked")
	@Override
	public void validateServerBase(String theServerBase, IHttpClient theHttpClient, BaseClient theClient) {
		GenericClient client = new GenericClient(myContext, theHttpClient, theServerBase, this);
		client.setEncoding(theClient.getEncoding());
		for (IClientInterceptor interceptor : theClient.getInterceptors()) {
			client.registerInterceptor(interceptor);
		}
		client.setDontValidateConformance(true);
		
		IBaseResource conformance;
		try {
			String capabilityStatementResourceName = "CapabilityStatement";
			if (myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
				capabilityStatementResourceName = "Conformance";
			}
			
			@SuppressWarnings("rawtypes")
			Class implementingClass = myContext.getResourceDefinition(capabilityStatementResourceName).getImplementingClass();
			try {
				conformance = (IBaseResource) client.fetchConformance().ofType(implementingClass).execute();
			} catch (FhirClientConnectionException e) {
				if (!myContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3) && e.getCause() instanceof DataFormatException) {
					capabilityStatementResourceName = "Conformance";
					implementingClass = myContext.getResourceDefinition(capabilityStatementResourceName).getImplementingClass();
					conformance = (IBaseResource) client.fetchConformance().ofType(implementingClass).execute();
				} else {
					throw e;
				}
			}
		} catch (FhirClientConnectionException e) {
			String msg = myContext.getLocalizer().getMessage(RestfulClientFactory.class, "failedToRetrieveConformance", theServerBase + Constants.URL_TOKEN_METADATA);
			throw new FhirClientConnectionException(msg, e);
		}

		FhirTerser t = myContext.newTerser();
		String serverFhirVersionString = null;
		Object value = t.getSingleValueOrNull(conformance, "fhirVersion");
		if (value instanceof IPrimitiveType) {
			serverFhirVersionString = ((IPrimitiveType) value).getValueAsString();
		}
		FhirVersionEnum serverFhirVersionEnum = null;
		if (StringUtils.isBlank(serverFhirVersionString)) {
			// we'll be lenient and accept this
		} else {
			if (serverFhirVersionString.startsWith("0.80") || serverFhirVersionString.startsWith("0.0.8")) {
				serverFhirVersionEnum = FhirVersionEnum.DSTU1;
			} else if (serverFhirVersionString.startsWith("0.4")) {
				serverFhirVersionEnum = FhirVersionEnum.DSTU2;
			} else if (serverFhirVersionString.startsWith("0.5")) {
				serverFhirVersionEnum = FhirVersionEnum.DSTU2;
			} else {
				// we'll be lenient and accept this
				ourLog.debug("Server conformance statement indicates unknown FHIR version: {}", serverFhirVersionString);
			}
		}

		if (serverFhirVersionEnum != null) {
			FhirVersionEnum contextFhirVersion = myContext.getVersion().getVersion();
			if (!contextFhirVersion.isEquivalentTo(serverFhirVersionEnum)) {
				throw new FhirClientInappropriateForServerException(myContext.getLocalizer().getMessage(RestfulClientFactory.class, "wrongVersionInConformance", theServerBase + Constants.URL_TOKEN_METADATA, serverFhirVersionString, serverFhirVersionEnum, contextFhirVersion));
			}
		}
		
		myValidatedServerBaseUrls.add(normalizeBaseUrlForMap(theServerBase));

	}

	@Override
	public ServerValidationModeEnum getServerValidationModeEnum() {
		return getServerValidationMode();
	}

	@Override
	public void setServerValidationModeEnum(ServerValidationModeEnum theServerValidationMode) {
		setServerValidationMode(theServerValidationMode);
	}
	
	/**
	 * Get the http client for the given server base
	 * @param theServerBase the server base
	 * @return the http client
	 */
	protected abstract IHttpClient getHttpClient(String theServerBase);
	
	/**
	 * Reset the http client. This method is used when parameters have been set and a
	 * new http client needs to be created
	 */
	protected abstract void resetHttpClient();

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy