001package org.hl7.fhir.dstu3.utils.client;
002
003
004
005
006
007/*
008  Copyright (c) 2011+, HL7, Inc.
009  All rights reserved.
010
011  Redistribution and use in source and binary forms, with or without modification, 
012  are permitted provided that the following conditions are met:
013
014 * Redistributions of source code must retain the above copyright notice, this 
015     list of conditions and the following disclaimer.
016 * Redistributions in binary form must reproduce the above copyright notice, 
017     this list of conditions and the following disclaimer in the documentation 
018     and/or other materials provided with the distribution.
019 * Neither the name of HL7 nor the names of its contributors may be used to 
020     endorse or promote products derived from this software without specific 
021     prior written permission.
022
023  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
024  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
025  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
026  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
027  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
028  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
029  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
030  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
031  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
032  POSSIBILITY OF SUCH DAMAGE.
033
034 */
035
036
037import java.io.ByteArrayOutputStream;
038import java.io.IOException;
039import java.io.InputStream;
040import java.io.OutputStreamWriter;
041import java.io.UnsupportedEncodingException;
042import java.net.HttpURLConnection;
043import java.net.MalformedURLException;
044import java.net.URI;
045import java.net.URLConnection;
046import java.nio.charset.StandardCharsets;
047import java.text.ParseException;
048import java.text.SimpleDateFormat;
049import java.util.ArrayList;
050import java.util.Calendar;
051import java.util.Date;
052import java.util.List;
053import java.util.Locale;
054import java.util.Map;
055
056import org.apache.commons.codec.binary.Base64;
057import org.apache.commons.io.IOUtils;
058import org.apache.commons.lang3.StringUtils;
059import org.apache.http.Header;
060import org.apache.http.HttpEntityEnclosingRequest;
061import org.apache.http.HttpHost;
062import org.apache.http.HttpRequest;
063import org.apache.http.HttpResponse;
064import org.apache.http.client.HttpClient;
065import org.apache.http.client.methods.HttpDelete;
066import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
067import org.apache.http.client.methods.HttpGet;
068import org.apache.http.client.methods.HttpOptions;
069import org.apache.http.client.methods.HttpPost;
070import org.apache.http.client.methods.HttpPut;
071import org.apache.http.client.methods.HttpUriRequest;
072import org.apache.http.conn.params.ConnRoutePNames;
073import org.apache.http.entity.ByteArrayEntity;
074import org.apache.http.impl.client.DefaultHttpClient;
075import org.apache.http.params.HttpConnectionParams;
076import org.apache.http.params.HttpParams;
077import org.hl7.fhir.dstu3.formats.IParser;
078import org.hl7.fhir.dstu3.formats.IParser.OutputStyle;
079import org.hl7.fhir.dstu3.formats.JsonParser;
080import org.hl7.fhir.dstu3.formats.XmlParser;
081import org.hl7.fhir.dstu3.model.Bundle;
082import org.hl7.fhir.dstu3.model.OperationOutcome;
083import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity;
084import org.hl7.fhir.dstu3.model.OperationOutcome.OperationOutcomeIssueComponent;
085import org.hl7.fhir.dstu3.model.Resource;
086import org.hl7.fhir.dstu3.model.ResourceType;
087import org.hl7.fhir.dstu3.utils.ResourceUtilities;
088import org.hl7.fhir.utilities.ToolingClientLogger;
089import org.hl7.fhir.utilities.Utilities;
090
091/**
092 * Helper class handling lower level HTTP transport concerns.
093 * TODO Document methods.
094 * @author Claude Nanjo
095 */
096public class ClientUtils {
097
098  public static final String DEFAULT_CHARSET = "UTF-8";
099  public static final String HEADER_LOCATION = "location";
100  private static boolean debugging = false;
101  public static final int TIMEOUT_SOCKET = 5000;
102  public static final int TIMEOUT_CONNECT = 1000;
103
104  private HttpHost proxy;
105  private int timeout = TIMEOUT_SOCKET;
106  private String username;
107  private String password;
108  private ToolingClientLogger logger;
109  private int retryCount;
110  private HttpClient httpclient;
111
112  public HttpHost getProxy() {
113    return proxy;
114  }
115
116  public void setProxy(HttpHost proxy) {
117    this.proxy = proxy;
118  }
119
120  public int getTimeout() {
121    return timeout;
122  }
123
124  public void setTimeout(int timeout) {
125    this.timeout = timeout;
126  }
127
128  public String getUsername() {
129    return username;
130  }
131
132  public void setUsername(String username) {
133    this.username = username;
134  }
135
136  public String getPassword() {
137    return password;
138  }
139
140  public void setPassword(String password) {
141    this.password = password;
142  }
143
144  public <T extends Resource> ResourceRequest<T> issueOptionsRequest(URI optionsUri, String resourceFormat, String message, int timeout) {
145    HttpOptions options = new HttpOptions(optionsUri);
146    return issueResourceRequest(resourceFormat, options, message, timeout);
147  }
148
149  public <T extends Resource> ResourceRequest<T> issueGetResourceRequest(URI resourceUri, String resourceFormat, String message, int timeout) {
150    HttpGet httpget = new HttpGet(resourceUri);
151    return issueResourceRequest(resourceFormat, httpget, message, timeout);
152  }
153
154  public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat, List<Header> headers, String message, int timeout) {
155    HttpPut httpPut = new HttpPut(resourceUri);
156    return issueResourceRequest(resourceFormat, httpPut, payload, headers, message, timeout);
157  }
158
159  public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat, String message, int timeout) {
160    HttpPut httpPut = new HttpPut(resourceUri);
161    return issueResourceRequest(resourceFormat, httpPut, payload, null, message, timeout);
162  }
163
164  public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri, byte[] payload, String resourceFormat, List<Header> headers, String message, int timeout) {
165    HttpPost httpPost = new HttpPost(resourceUri);
166    return issueResourceRequest(resourceFormat, httpPost, payload, headers, message, timeout);
167  }
168
169
170  public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri, byte[] payload, String resourceFormat, String message, int timeout) {
171    return issuePostRequest(resourceUri, payload, resourceFormat, null, message, timeout);
172  }
173
174  public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) {
175    HttpGet httpget = new HttpGet(resourceUri);
176    configureFhirRequest(httpget, resourceFormat);
177    HttpResponse response = sendRequest(httpget);
178    return unmarshalReference(response, resourceFormat);
179  }
180
181  private void setAuth(HttpRequest httpget) {
182    if (password != null) {
183      try {
184        byte[] b = Base64.encodeBase64((username+":"+password).getBytes("ASCII"));
185        String b64 = new String(b, StandardCharsets.US_ASCII);
186        httpget.setHeader("Authorization", "Basic " + b64);
187      } catch (UnsupportedEncodingException e) {
188      }
189    }
190  }
191
192  public Bundle postBatchRequest(URI resourceUri, byte[] payload, String resourceFormat, String message, int timeout) {
193    HttpPost httpPost = new HttpPost(resourceUri);
194    configureFhirRequest(httpPost, resourceFormat);
195    HttpResponse response = sendPayload(httpPost, payload, proxy, message, timeout);
196    return unmarshalFeed(response, resourceFormat);
197  }
198
199  public boolean issueDeleteRequest(URI resourceUri) {
200    HttpDelete deleteRequest = new HttpDelete(resourceUri);
201    HttpResponse response = sendRequest(deleteRequest);
202    int responseStatusCode = response.getStatusLine().getStatusCode();
203    boolean deletionSuccessful = false;
204    if(responseStatusCode == 204) {
205      deletionSuccessful = true;
206    }
207    return deletionSuccessful;
208  }
209
210  /***********************************************************
211   * Request/Response Helper methods
212   ***********************************************************/
213
214  protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request, String message, int timeout) {
215    return issueResourceRequest(resourceFormat, request, null, message, timeout);
216  }
217
218  /**
219   * @param resourceFormat
220   * @param options
221   * @return
222   */
223  protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request, byte[] payload, String message, int timeout) {
224    return issueResourceRequest(resourceFormat, request, payload, null, message, timeout);
225  }
226
227  /**
228   * @param resourceFormat
229   * @param options
230   * @return
231   */
232  protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request, byte[] payload, List<Header> headers, String message, int timeout) {
233    configureFhirRequest(request, resourceFormat, headers);
234    HttpResponse response = null;
235    if(request instanceof HttpEntityEnclosingRequest && payload != null) {
236      response = sendPayload((HttpEntityEnclosingRequestBase)request, payload, proxy, message, timeout);
237    } else if (request instanceof HttpEntityEnclosingRequest && payload == null){
238      throw new EFhirClientException("PUT and POST requests require a non-null payload");
239    } else {
240      response = sendRequest(request);
241    }
242    T resource = unmarshalReference(response, resourceFormat);
243    return new ResourceRequest<T>(resource, response.getStatusLine().getStatusCode(), getLocationHeader(response));
244  }
245
246
247  /**
248   * Method adds required request headers.
249   * TODO handle JSON request as well.
250   * 
251   * @param request
252   */
253  protected void configureFhirRequest(HttpRequest request, String format) {
254    configureFhirRequest(request, format, null);
255  }
256
257  /**
258   * Method adds required request headers.
259   * TODO handle JSON request as well.
260   * 
261   * @param request
262   */
263  protected void configureFhirRequest(HttpRequest request, String format, List<Header> headers) {
264    request.addHeader("User-Agent", "Java FHIR Client for FHIR");
265
266    if (format != null) {               
267      request.addHeader("Accept",format);
268      request.addHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET);
269    }
270    request.addHeader("Accept-Charset", DEFAULT_CHARSET);
271    if(headers != null) {
272      for(Header header : headers) {
273        request.addHeader(header);
274      }
275    }
276    setAuth(request);
277  }
278
279  /**
280   * Method posts request payload
281   * 
282   * @param request
283   * @param payload
284   * @return
285   */
286  @SuppressWarnings({ "resource", "deprecation" })
287  protected HttpResponse sendPayload(HttpEntityEnclosingRequestBase request, byte[] payload, HttpHost proxy, String message, int timeout) {
288    HttpResponse response = null;
289    boolean ok = false;
290    long t = System.currentTimeMillis();
291    int tryCount = 0;
292    while (!ok) {
293      try {
294        tryCount++;
295        if (httpclient == null) {
296          makeClient(proxy);
297        }
298        HttpParams params = httpclient.getParams();
299        HttpConnectionParams.setSoTimeout(params, timeout < 1 ? this.timeout : timeout * 1000);
300        request.setEntity(new ByteArrayEntity(payload));
301        log(request);
302        response = httpclient.execute(request);
303        ok = true;
304      } catch(IOException ioe) {
305        System.out.println(ioe.getMessage()+" ("+(System.currentTimeMillis()-t)+"ms / "+Utilities.describeSize(payload.length)+" for "+message+")");
306        if (tryCount <= retryCount || (tryCount < 3 && ioe instanceof org.apache.http.conn.ConnectTimeoutException)) {
307          ok = false;
308          try {
309            Thread.sleep(100);
310          } catch (InterruptedException e) {
311          }
312        } else {
313          if (tryCount > 1) {
314            System.out.println("Giving up: "+ioe.getMessage()+" (R3 / "+(System.currentTimeMillis()-t)+"ms / "+Utilities.describeSize(payload.length)+" for "+message+")");
315          }
316          throw new EFhirClientException("Error sending HTTP Post/Put Payload: "+ioe.getMessage(), ioe);
317        }
318      }
319    }
320    return response;
321  }
322
323  @SuppressWarnings("deprecation")
324  public void makeClient(HttpHost proxy) {
325    httpclient = new DefaultHttpClient();
326    HttpParams params = httpclient.getParams();
327    HttpConnectionParams.setConnectionTimeout(params, TIMEOUT_CONNECT);
328    HttpConnectionParams.setSoTimeout(params, timeout);
329    HttpConnectionParams.setSoKeepalive(params, true);
330    if(proxy != null) {
331      httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
332    }
333  }
334
335  /**
336   * 
337   * @param request
338   * @param payload
339   * @return
340   */
341  protected HttpResponse sendRequest(HttpUriRequest request) {
342    HttpResponse response = null;
343    try {
344      if (httpclient == null) {
345        makeClient(proxy);
346      }
347      response = httpclient.execute(request);
348    } catch(IOException ioe) {
349      if (ClientUtils.debugging ) {
350        ioe.printStackTrace();
351      }
352      throw new EFhirClientException("Error sending Http Request: "+ioe.getMessage(), ioe);
353    }
354    return response;
355  }
356
357  /**
358   * Unmarshals a resource from the response stream.
359   * 
360   * @param response
361   * @return
362   */
363  @SuppressWarnings("unchecked")
364  protected <T extends Resource> T unmarshalReference(HttpResponse response, String format) {
365    T resource = null;
366    OperationOutcome error = null;
367    byte[] cnt = log(response);
368    if (cnt != null) {
369      try {
370        resource = (T)getParser(format).parse(cnt);
371        if (resource instanceof OperationOutcome && hasError((OperationOutcome)resource)) {
372          error = (OperationOutcome) resource;
373        }
374      } catch(IOException ioe) {
375        throw new EFhirClientException("Error reading Http Response: "+ioe.getMessage(), ioe);
376      } catch(Exception e) {
377        throw new EFhirClientException("Error parsing response message: "+e.getMessage(), e);
378      }
379    }
380    if(error != null) {
381      throw new EFhirClientException("Error from server: "+ResourceUtilities.getErrorDescription(error), error);
382    }
383    return resource;
384  }
385
386  /**
387   * Unmarshals Bundle from response stream.
388   * 
389   * @param response
390   * @return
391   */
392  protected Bundle unmarshalFeed(HttpResponse response, String format) {
393    Bundle feed = null;
394    byte[] cnt = log(response);
395    String contentType = response.getHeaders("Content-Type")[0].getValue();
396    OperationOutcome error = null;
397    try {
398      if (cnt != null) {
399        if(contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) || contentType.contains("text/xml+fhir")) {
400          Resource rf = getParser(format).parse(cnt);
401          if (rf instanceof Bundle)
402            feed = (Bundle) rf;
403          else if (rf instanceof OperationOutcome && hasError((OperationOutcome) rf)) {
404            error = (OperationOutcome) rf;
405          } else {
406            throw new EFhirClientException("Error reading server response: a resource was returned instead");
407          }
408        }
409      }
410    } catch(IOException ioe) {
411      throw new EFhirClientException("Error reading Http Response", ioe);
412    } catch(Exception e) {
413      throw new EFhirClientException("Error parsing response message", e);
414    }
415    if(error != null) {
416      throw new EFhirClientException("Error from server: "+ResourceUtilities.getErrorDescription(error), error);
417    }
418    return feed;
419  }
420
421  private boolean hasError(OperationOutcome oo) {
422    for (OperationOutcomeIssueComponent t : oo.getIssue())
423      if (t.getSeverity() == IssueSeverity.ERROR || t.getSeverity() == IssueSeverity.FATAL)
424        return true;
425    return false;
426  }
427
428  protected String getLocationHeader(HttpResponse response) {
429    String location = null;
430    if(response.getHeaders("location").length > 0) {//TODO Distinguish between both cases if necessary
431      location = response.getHeaders("location")[0].getValue();
432    } else if(response.getHeaders("content-location").length > 0) {
433      location = response.getHeaders("content-location")[0].getValue();
434    }
435    return location;
436  }
437
438
439  /*****************************************************************
440   * Client connection methods
441   * ***************************************************************/
442
443  public HttpURLConnection buildConnection(URI baseServiceUri, String tail) {
444    try {
445      HttpURLConnection client = (HttpURLConnection) baseServiceUri.resolve(tail).toURL().openConnection();
446      return client;
447    } catch(MalformedURLException mue) {
448      throw new EFhirClientException("Invalid Service URL", mue);
449    } catch(IOException ioe) {
450      throw new EFhirClientException("Unable to establish connection to server: " + baseServiceUri.toString() + tail, ioe);
451    }
452  }
453
454  public HttpURLConnection buildConnection(URI baseServiceUri, ResourceType resourceType, String id) {
455    return buildConnection(baseServiceUri, ResourceAddress.buildRelativePathFromResourceType(resourceType, id));
456  }
457
458  /******************************************************************
459   * Other general helper methods
460   * ****************************************************************/
461
462
463  public  <T extends Resource>  byte[] getResourceAsByteArray(T resource, boolean pretty, boolean isJson) {
464    ByteArrayOutputStream baos = null;
465    byte[] byteArray = null;
466    try {
467      baos = new ByteArrayOutputStream();
468      IParser parser = null;
469      if(isJson) {
470        parser = new JsonParser();
471      } else {
472        parser = new XmlParser();
473      }
474      parser.setOutputStyle(pretty ? OutputStyle.PRETTY : OutputStyle.NORMAL);
475      parser.compose(baos, resource);
476      baos.close();
477      byteArray =  baos.toByteArray();
478      baos.close();
479    } catch (Exception e) {
480      try{
481        baos.close();
482      }catch(Exception ex) {
483        throw new EFhirClientException("Error closing output stream", ex);
484      }
485      throw new EFhirClientException("Error converting output stream to byte array", e);
486    }
487    return byteArray;
488  }
489
490  public  byte[] getFeedAsByteArray(Bundle feed, boolean pretty, boolean isJson) {
491    ByteArrayOutputStream baos = null;
492    byte[] byteArray = null;
493    try {
494      baos = new ByteArrayOutputStream();
495      IParser parser = null;
496      if(isJson) {
497        parser = new JsonParser();
498      } else {
499        parser = new XmlParser();
500      }
501      parser.setOutputStyle(pretty ? OutputStyle.PRETTY : OutputStyle.NORMAL);
502      parser.compose(baos, feed);
503      baos.close();
504      byteArray =  baos.toByteArray();
505      baos.close();
506    } catch (Exception e) {
507      try{
508        baos.close();
509      }catch(Exception ex) {
510        throw new EFhirClientException("Error closing output stream", ex);
511      }
512      throw new EFhirClientException("Error converting output stream to byte array", e);
513    }
514    return byteArray;
515  }
516
517  public Calendar getLastModifiedResponseHeaderAsCalendarObject(URLConnection serverConnection) {
518    String dateTime = null;
519    try {
520      dateTime = serverConnection.getHeaderField("Last-Modified");
521      SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", new Locale("en", "US"));
522      Date lastModifiedTimestamp = format.parse(dateTime);
523      Calendar calendar=Calendar.getInstance();
524      calendar.setTime(lastModifiedTimestamp);
525      return calendar;
526    } catch(ParseException pe) {
527      throw new EFhirClientException("Error parsing Last-Modified response header " + dateTime, pe);
528    }
529  }
530
531  protected IParser getParser(String format) {
532    if(StringUtils.isBlank(format)) {
533      format = ResourceFormat.RESOURCE_XML.getHeader();
534    }
535    if(format.equalsIgnoreCase("json") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_JSON.getHeader()) || format.equalsIgnoreCase(ResourceFormat.RESOURCE_JSON.getHeader())) {
536      return new JsonParser();
537    } else if(format.equalsIgnoreCase("xml") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_XML.getHeader()) || format.equalsIgnoreCase(ResourceFormat.RESOURCE_XML.getHeader())) {
538      return new XmlParser();
539    } else {
540      throw new EFhirClientException("Invalid format: " + format);
541    }
542  }
543
544  public Bundle issuePostFeedRequest(URI resourceUri, Map<String, String> parameters, String resourceName, Resource resource, String resourceFormat) throws IOException {
545    HttpPost httppost = new HttpPost(resourceUri);
546    String boundary = "----WebKitFormBoundarykbMUo6H8QaUnYtRy";
547    httppost.addHeader("Content-Type", "multipart/form-data; boundary="+boundary);
548    httppost.addHeader("Accept", resourceFormat);
549    configureFhirRequest(httppost, null);
550    HttpResponse response = sendPayload(httppost, encodeFormSubmission(parameters, resourceName, resource, boundary));
551    return unmarshalFeed(response, resourceFormat);
552  }
553
554  private byte[] encodeFormSubmission(Map<String, String> parameters, String resourceName, Resource resource, String boundary) throws IOException {
555    ByteArrayOutputStream b = new ByteArrayOutputStream();
556    OutputStreamWriter w = new OutputStreamWriter(b, "UTF-8");  
557    for (String name : parameters.keySet()) {
558      w.write("--");
559      w.write(boundary);
560      w.write("\r\nContent-Disposition: form-data; name=\""+name+"\"\r\n\r\n");
561      w.write(parameters.get(name)+"\r\n");
562    }
563    w.write("--");
564    w.write(boundary);
565    w.write("\r\nContent-Disposition: form-data; name=\""+resourceName+"\"\r\n\r\n");
566    w.close(); 
567    JsonParser json = new JsonParser();
568    json.setOutputStyle(OutputStyle.NORMAL);
569    json.compose(b, resource);
570    b.close();
571    w = new OutputStreamWriter(b, "UTF-8");  
572    w.write("\r\n--");
573    w.write(boundary);
574    w.write("--");
575    w.close();
576    return b.toByteArray();
577  }
578
579  /**
580   * Method posts request payload
581   * 
582   * @param request
583   * @param payload
584   * @return
585   */
586  protected HttpResponse sendPayload(HttpEntityEnclosingRequestBase request, byte[] payload) {
587    HttpResponse response = null;
588    try {
589      log(request);
590      if (httpclient == null) {
591        makeClient(proxy);
592      }
593      request.setEntity(new ByteArrayEntity(payload));
594      response = httpclient.execute(request);
595      log(response);
596    } catch(IOException ioe) {
597      throw new EFhirClientException("Error sending HTTP Post/Put Payload: "+ioe.getMessage(), ioe);
598    }
599    return response;
600  }
601
602  private void log(HttpUriRequest request) {
603    if (logger != null) {
604      List<String> headers = new ArrayList<>();
605      for (Header h : request.getAllHeaders()) {
606        headers.add(h.toString());
607      }
608      logger.logRequest(request.getMethod(), request.getURI().toString(), headers, null);
609    }    
610  }
611  private void log(HttpEntityEnclosingRequestBase request)  {
612    if (logger != null) {
613      List<String> headers = new ArrayList<>();
614      for (Header h : request.getAllHeaders()) {
615        headers.add(h.toString());
616      }
617      byte[] cnt = null;
618      InputStream s;
619      try {
620        s = request.getEntity().getContent();
621        cnt = IOUtils.toByteArray(s);
622        s.close();
623      } catch (Exception e) {
624      }
625      logger.logRequest(request.getMethod(), request.getURI().toString(), headers, cnt);
626    }    
627  }  
628
629  private byte[] log(HttpResponse response) {
630    byte[] cnt = null;
631    try {
632      InputStream s = response.getEntity().getContent();
633      cnt = IOUtils.toByteArray(s);
634      s.close();
635    } catch (Exception e) {
636    }
637    if (logger != null) {
638      List<String> headers = new ArrayList<>();
639      for (Header h : response.getAllHeaders()) {
640        headers.add(h.toString());
641      }
642      logger.logResponse(response.getStatusLine().toString(), headers, cnt);
643    }
644    return cnt;
645  }
646
647  public ToolingClientLogger getLogger() {
648    return logger;
649  }
650
651  public void setLogger(ToolingClientLogger logger) {
652    this.logger = logger;
653  }
654
655
656  /**
657   * Used for debugging
658   * 
659   * @param instream
660   * @return
661   */
662  protected String writeInputStreamAsString(InputStream instream) {
663    String value = null;
664    try {
665      value = IOUtils.toString(instream, "UTF-8");
666      System.out.println(value);
667
668    } catch(IOException ioe) {
669      //Do nothing
670    }
671    return value;
672  }
673
674  public int getRetryCount() {
675    return retryCount;
676  }
677
678  public void setRetryCount(int retryCount) {
679    this.retryCount = retryCount;
680  }
681
682
683}