1 package ca.uhn.fhir.rest.client;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 import java.io.ByteArrayInputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.io.Reader;
28 import java.io.StringReader;
29 import java.nio.charset.Charset;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.LinkedHashMap;
34 import java.util.List;
35 import java.util.Map;
36
37 import org.apache.commons.io.IOUtils;
38 import org.apache.commons.lang3.StringUtils;
39 import org.apache.commons.lang3.Validate;
40 import org.apache.http.Header;
41 import org.apache.http.HttpEntity;
42 import org.apache.http.HttpEntityEnclosingRequest;
43 import org.apache.http.HttpResponse;
44 import org.apache.http.client.HttpClient;
45 import org.apache.http.client.methods.CloseableHttpResponse;
46 import org.apache.http.client.methods.HttpRequestBase;
47 import org.apache.http.entity.ContentType;
48
49 import ca.uhn.fhir.context.FhirContext;
50 import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
51 import ca.uhn.fhir.parser.DataFormatException;
52 import ca.uhn.fhir.parser.IParser;
53 import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
54 import ca.uhn.fhir.rest.method.IClientResponseHandler;
55 import ca.uhn.fhir.rest.method.IClientResponseHandlerHandlesBinary;
56 import ca.uhn.fhir.rest.server.Constants;
57 import ca.uhn.fhir.rest.server.EncodingEnum;
58 import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
59
60 public abstract class BaseClient {
61 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseClient.class);
62
63 private final HttpClient myClient;
64 private EncodingEnum myEncoding = null;
65 private List<IClientInterceptor> myInterceptors = new ArrayList<IClientInterceptor>();
66 private boolean myKeepResponses = false;
67 private HttpResponse myLastResponse;
68 private String myLastResponseBody;
69 private Boolean myPrettyPrint = false;
70
71 private final String myUrlBase;
72
73 BaseClient(HttpClient theClient, String theUrlBase) {
74 super();
75 myClient = theClient;
76 myUrlBase = theUrlBase;
77 }
78
79 protected Map<String, List<String>> createExtraParams() {
80 HashMap<String, List<String>> retVal = new LinkedHashMap<String, List<String>>();
81
82 if (getEncoding() == EncodingEnum.XML) {
83 retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
84 } else if (getEncoding() == EncodingEnum.JSON) {
85 retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
86 }
87
88 if (isPrettyPrint()) {
89 retVal.put(Constants.PARAM_PRETTY, Collections.singletonList(Constants.PARAM_PRETTY_VALUE_TRUE));
90 }
91
92 return retVal;
93 }
94
95
96
97
98
99 public EncodingEnum getEncoding() {
100 return myEncoding;
101 }
102
103
104
105
106 public HttpResponse getLastResponse() {
107 return myLastResponse;
108 }
109
110
111
112
113 public String getLastResponseBody() {
114 return myLastResponseBody;
115 }
116
117 String getServerBase() {
118 return myUrlBase;
119 }
120
121 public String getUrlBase() {
122 return myUrlBase;
123 }
124
125 <T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation) {
126 return invokeClient(theContext, binding, clientInvocation, false);
127 }
128
129 <T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, boolean theLogRequestAndResponse) {
130 return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse);
131 }
132
133 <T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint, boolean theLogRequestAndResponse) {
134
135
136 HttpRequestBase httpRequest;
137 HttpResponse response;
138 try {
139 Map<String, List<String>> params = createExtraParams();
140
141 if (theEncoding == EncodingEnum.XML) {
142 params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
143 } else if (theEncoding == EncodingEnum.JSON) {
144 params.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
145 }
146 if (thePrettyPrint == Boolean.TRUE) {
147 params.put(Constants.PARAM_PRETTY, Collections.singletonList(Constants.PARAM_PRETTY_VALUE_TRUE));
148 }
149
150 EncodingEnum encoding = getEncoding();
151 if (theEncoding != null) {
152 encoding=theEncoding;
153 }
154
155 httpRequest = clientInvocation.asHttpRequest(myUrlBase, params, encoding);
156
157 if (theLogRequestAndResponse) {
158 ourLog.info("Client invoking: {}", httpRequest);
159 if (httpRequest instanceof HttpEntityEnclosingRequest) {
160 HttpEntity entity = ((HttpEntityEnclosingRequest) httpRequest).getEntity();
161 if (entity.isRepeatable()) {
162 String content = IOUtils.toString(entity.getContent());
163 ourLog.info("Client request body: {}", content);
164 }
165 }
166 }
167
168 for (IClientInterceptor nextInterceptor : myInterceptors) {
169 nextInterceptor.interceptRequest(httpRequest);
170 }
171
172 response = myClient.execute(httpRequest);
173
174 for (IClientInterceptor nextInterceptor : myInterceptors) {
175 nextInterceptor.interceptResponse(response);
176 }
177
178 } catch (DataFormatException e) {
179 throw new FhirClientConnectionException(e);
180 } catch (IOException e) {
181 throw new FhirClientConnectionException(e);
182 }
183
184 try {
185 ContentType ct = ContentType.get(response.getEntity());
186 String mimeType = ct != null ? ct.getMimeType() : null;
187
188 Map<String, List<String>> headers = new HashMap<String, List<String>>();
189 if (response.getAllHeaders() != null) {
190 for (Header next : response.getAllHeaders()) {
191 String name = next.getName().toLowerCase();
192 List<String> list = headers.get(name);
193 if (list == null) {
194 list = new ArrayList<String>();
195 headers.put(name, list);
196 }
197 list.add(next.getValue());
198 }
199 }
200
201 if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() > 299) {
202 String body = null;
203 Reader reader = null;
204 try {
205 reader = createReaderFromResponse(response);
206 body = IOUtils.toString(reader);
207 } catch (Exception e) {
208 ourLog.debug("Failed to read input stream", e);
209 } finally {
210 IOUtils.closeQuietly(reader);
211 }
212
213 String message = "HTTP " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase();
214 OperationOutcome oo=null;
215 if (Constants.CT_TEXT.equals(mimeType)) {
216 message = message + ": " + body;
217 } else {
218 EncodingEnum enc = EncodingEnum.forContentType(mimeType);
219 if (enc != null) {
220 IParser p = enc.newParser(theContext);
221 try {
222 oo = p.parseResource(OperationOutcome.class, body);
223 if (oo.getIssueFirstRep().getDetails().isEmpty()==false) {
224 message = message + ": " + oo.getIssueFirstRep().getDetails().getValue();
225 }
226 } catch (Exception e) {
227 ourLog.debug("Failed to process OperationOutcome response");
228 }
229 }
230 }
231
232 keepResponseAndLogIt(theLogRequestAndResponse, response, body);
233
234 BaseServerResponseException exception = BaseServerResponseException.newInstance(response.getStatusLine().getStatusCode(), message);
235 exception.setOperationOutcome(oo);
236
237 if (body != null) {
238 exception.setResponseBody(body);
239 }
240
241 throw exception;
242 }
243 if (binding instanceof IClientResponseHandlerHandlesBinary) {
244 IClientResponseHandlerHandlesBinary<T> handlesBinary = (IClientResponseHandlerHandlesBinary<T>) binding;
245 if (handlesBinary.isBinary()) {
246 InputStream reader = response.getEntity().getContent();
247 try {
248
249 if (ourLog.isTraceEnabled() || myKeepResponses || theLogRequestAndResponse) {
250 byte[] responseBytes = IOUtils.toByteArray(reader);
251 if (myKeepResponses) {
252 myLastResponse = response;
253 myLastResponseBody = null;
254 }
255 String message = "HTTP " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase();
256 if (theLogRequestAndResponse) {
257 ourLog.info("Client response: {} - {} bytes", message, responseBytes.length);
258 } else {
259 ourLog.trace("Client response: {} - {} bytes", message, responseBytes.length);
260 }
261 reader = new ByteArrayInputStream(responseBytes);
262 }
263
264 return handlesBinary.invokeClient(mimeType, reader, response.getStatusLine().getStatusCode(), headers);
265 } finally {
266 IOUtils.closeQuietly(reader);
267 }
268 }
269 }
270
271 Reader reader = createReaderFromResponse(response);
272
273 if (ourLog.isTraceEnabled() || myKeepResponses || theLogRequestAndResponse) {
274 String responseString = IOUtils.toString(reader);
275 keepResponseAndLogIt(theLogRequestAndResponse, response, responseString);
276 reader = new StringReader(responseString);
277 }
278
279 try {
280 return binding.invokeClient(mimeType, reader, response.getStatusLine().getStatusCode(), headers);
281 } finally {
282 IOUtils.closeQuietly(reader);
283 }
284
285 } catch (IllegalStateException e) {
286 throw new FhirClientConnectionException(e);
287 } catch (IOException e) {
288 throw new FhirClientConnectionException(e);
289 } finally {
290 if (response instanceof CloseableHttpResponse) {
291 try {
292 ((CloseableHttpResponse) response).close();
293 } catch (IOException e) {
294 ourLog.debug("Failed to close response", e);
295 }
296 }
297 }
298 }
299
300
301
302
303 public boolean isKeepResponses() {
304 return myKeepResponses;
305 }
306
307
308
309
310
311 public boolean isPrettyPrint() {
312 return Boolean.TRUE.equals(myPrettyPrint);
313 }
314
315
316
317
318
319 public Boolean getPrettyPrint() {
320 return myPrettyPrint;
321 }
322
323 private void keepResponseAndLogIt(boolean theLogRequestAndResponse, HttpResponse response, String responseString) {
324 if (myKeepResponses) {
325 myLastResponse = response;
326 myLastResponseBody = responseString;
327 }
328 if (theLogRequestAndResponse) {
329 String message = "HTTP " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase();
330 if (StringUtils.isNotBlank(responseString)) {
331 ourLog.info("Client response: {}\n{}", message, responseString);
332 } else {
333 ourLog.info("Client response: {}", message, responseString);
334 }
335 } else {
336 ourLog.trace("FHIR response:\n{}\n{}", response, responseString);
337 }
338 }
339
340 public void registerInterceptor(IClientInterceptor theInterceptor) {
341 Validate.notNull(theInterceptor, "Interceptor can not be null");
342 myInterceptors.add(theInterceptor);
343 }
344
345
346
347
348
349 public BaseClient setEncoding(EncodingEnum theEncoding) {
350 myEncoding = theEncoding;
351 return this;
352 }
353
354
355
356
357 public void setKeepResponses(boolean theKeepResponses) {
358 myKeepResponses = theKeepResponses;
359 }
360
361
362
363
364 public void setLastResponse(HttpResponse theLastResponse) {
365 myLastResponse = theLastResponse;
366 }
367
368
369
370
371 public void setLastResponseBody(String theLastResponseBody) {
372 myLastResponseBody = theLastResponseBody;
373 }
374
375
376
377
378
379 public BaseClient setPrettyPrint(Boolean thePrettyPrint) {
380 myPrettyPrint = thePrettyPrint;
381 return this;
382 }
383
384 public void unregisterInterceptor(IClientInterceptor theInterceptor) {
385 Validate.notNull(theInterceptor, "Interceptor can not be null");
386 myInterceptors.remove(theInterceptor);
387 }
388
389 public static Reader createReaderFromResponse(HttpResponse theResponse) throws IllegalStateException, IOException {
390 HttpEntity entity = theResponse.getEntity();
391 if (entity == null) {
392 return new StringReader("");
393 }
394 Charset charset = null;
395 if (entity.getContentType() != null && entity.getContentType().getElements() != null && entity.getContentType().getElements().length > 0) {
396 ContentType ct = ContentType.get(entity);
397 charset = ct.getCharset();
398 }
399 if (charset == null) {
400 ourLog.warn("Response did not specify a charset.");
401 charset = Charset.forName("UTF-8");
402 }
403
404 Reader reader = new InputStreamReader(theResponse.getEntity().getContent(), charset);
405 return reader;
406 }
407
408 }