View Javadoc
1   package ca.uhn.fhir.rest.client;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import java.lang.reflect.InvocationHandler;
24  import java.lang.reflect.Method;
25  import java.lang.reflect.Proxy;
26  import java.util.HashMap;
27  import java.util.Map;
28  import java.util.concurrent.TimeUnit;
29  
30  import org.apache.http.HttpHost;
31  import org.apache.http.client.HttpClient;
32  import org.apache.http.client.config.RequestConfig;
33  import org.apache.http.impl.client.HttpClients;
34  import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
35  
36  import ca.uhn.fhir.context.ConfigurationException;
37  import ca.uhn.fhir.context.FhirContext;
38  import ca.uhn.fhir.rest.client.api.IRestfulClient;
39  import ca.uhn.fhir.rest.method.BaseMethodBinding;
40  
41  public class RestfulClientFactory implements IRestfulClientFactory {
42  
43  	private int myConnectionRequestTimeout = 10000;
44  	private int myConnectTimeout = 10000;
45  	private FhirContext myContext;
46  	private HttpClient myHttpClient;
47  	private Map<Class<? extends IRestfulClient>, ClientInvocationHandler> myInvocationHandlers = new HashMap<Class<? extends IRestfulClient>, ClientInvocationHandler>();
48  	private int mySocketTimeout = 10000;
49  	private HttpHost myProxy;
50  
51  	@Override
52  	public void setProxy(String theHost, Integer thePort) {
53  		if (theHost != null) {
54  			myProxy = new HttpHost(theHost, thePort, "http");
55  		} else {
56  			myProxy = null;
57  		}
58  	}
59  
60  	/**
61  	 * Constructor
62  	 * 
63  	 * @param theContext
64  	 *            The context
65  	 */
66  	public RestfulClientFactory(FhirContext theFhirContext) {
67  		myContext = theFhirContext;
68  	}
69  
70  	/**
71  	 * Constructor
72  	 */
73  	public RestfulClientFactory() {
74  	}
75  
76  	/**
77  	 * Sets the context associated with this client factory. Must not be called more than once.
78  	 */
79  	public void setFhirContext(FhirContext theContext) {
80  		if (myContext != null && myContext != theContext) {
81  			throw new IllegalStateException("RestfulClientFactory instance is already associated with one FhirContext. RestfulClientFactory instances can not be shared.");
82  		}
83  		myContext = theContext;
84  	}
85  
86  	public int getConnectionRequestTimeout() {
87  		return myConnectionRequestTimeout;
88  	}
89  
90  	public int getConnectTimeout() {
91  		return myConnectTimeout;
92  	}
93  
94  	@Override
95  	public synchronized HttpClient getHttpClient() {
96  		if (myHttpClient == null) {
97  
98  			PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
99  
100 			//@formatter:off
101 			RequestConfig defaultRequestConfig = RequestConfig.custom()
102 				    .setSocketTimeout(mySocketTimeout)
103 				    .setConnectTimeout(myConnectTimeout)
104 				    .setConnectionRequestTimeout(myConnectionRequestTimeout)
105 				    .setStaleConnectionCheckEnabled(true)
106 				    .setProxy(myProxy)
107 				    .build();
108 			
109 			myHttpClient = HttpClients.custom()
110 				.setConnectionManager(connectionManager)
111 				.setDefaultRequestConfig(defaultRequestConfig)
112 				.disableCookieManagement()
113 				.build();
114 			//@formatter:on
115 
116 		}
117 
118 		return myHttpClient;
119 	}
120 
121 	public int getSocketTimeout() {
122 		return mySocketTimeout;
123 	}
124 
125 	/**
126 	 * Instantiates a new client instance
127 	 * 
128 	 * @param theClientType
129 	 *            The client type, which is an interface type to be instantiated
130 	 * @param theServerBase
131 	 *            The URL of the base for the restful FHIR server to connect to
132 	 * @return A newly created client
133 	 * @throws ConfigurationException
134 	 *             If the interface type is not an interface
135 	 */
136 	@Override
137 	public synchronized <T extends IRestfulClient> T newClient(Class<T> theClientType, String theServerBase) {
138 		if (!theClientType.isInterface()) {
139 			throw new ConfigurationException(theClientType.getCanonicalName() + " is not an interface");
140 		}
141 
142 		HttpClient client = getHttpClient();
143 
144 		String serverBase = theServerBase;
145 		if (!serverBase.endsWith("/")) {
146 			serverBase = serverBase + "/";
147 		}
148 
149 		ClientInvocationHandler invocationHandler = myInvocationHandlers.get(theClientType);
150 		if (invocationHandler == null) {
151 			invocationHandler = new ClientInvocationHandler(client, myContext, serverBase, theClientType);
152 			for (Method nextMethod : theClientType.getMethods()) {
153 				BaseMethodBinding<?> binding = BaseMethodBinding.bindMethod(nextMethod, myContext, null);
154 				invocationHandler.addBinding(nextMethod, binding);
155 			}
156 			myInvocationHandlers.put(theClientType, invocationHandler);
157 		}
158 
159 		T proxy = instantiateProxy(theClientType, invocationHandler);
160 
161 		return proxy;
162 	}
163 
164 	@Override
165 	public IGenericClient newGenericClient(String theServerBase) {
166 		return new GenericClient(myContext, getHttpClient(), theServerBase);
167 	}
168 
169 	@Override
170 	public synchronized void setConnectionRequestTimeout(int theConnectionRequestTimeout) {
171 		myConnectionRequestTimeout = theConnectionRequestTimeout;
172 		myHttpClient = null;
173 	}
174 
175 	@Override
176 	public synchronized void setConnectTimeout(int theConnectTimeout) {
177 		myConnectTimeout = theConnectTimeout;
178 		myHttpClient = null;
179 	}
180 
181 	/**
182 	 * Sets the Apache HTTP client instance to be used by any new restful clients created by this factory. If set to
183 	 * <code>null</code>, which is the default, a new HTTP client with default settings will be created.
184 	 * 
185 	 * @param theHttpClient
186 	 *            An HTTP client instance to use, or <code>null</code>
187 	 */
188 	@Override
189 	public synchronized void setHttpClient(HttpClient theHttpClient) {
190 		myHttpClient = theHttpClient;
191 	}
192 
193 	@Override
194 	public synchronized void setSocketTimeout(int theSocketTimeout) {
195 		mySocketTimeout = theSocketTimeout;
196 		myHttpClient = null;
197 	}
198 
199 	@SuppressWarnings("unchecked")
200 	private <T extends IRestfulClient> T instantiateProxy(Class<T> theClientType, InvocationHandler theInvocationHandler) {
201 		T proxy = (T) Proxy.newProxyInstance(theClientType.getClassLoader(), new Class[] { theClientType }, theInvocationHandler);
202 		return proxy;
203 	}
204 
205 }