ca.uhn.fhir.rest.client.RestfulClientFactory.java Source code

Java tutorial

Introduction

Here is the source code for ca.uhn.fhir.rest.client.RestfulClientFactory.java

Source

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<Class<? extends IRestfulClient>, ClientInvocationHandlerFactory> myInvocationHandlers = new HashMap<Class<? extends IRestfulClient>, ClientInvocationHandlerFactory>();
    private ServerValidationModeEnum myServerValidationMode = DEFAULT_SERVER_VALIDATION_MODE;
    private int mySocketTimeout = DEFAULT_SOCKET_TIMEOUT;
    private Set<String> myValidatedServerBaseUrls = Collections.synchronizedSet(new HashSet<String>());
    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 extends IRestfulClient> T instantiateProxy(Class<T> 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 extends IRestfulClient> T newClient(Class<T> 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 
     * <code>super.validateConfigured()</code>
     */
    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();

}