001package org.hl7.fhir.dstu2.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.dstu2.formats.IParser;
078import org.hl7.fhir.dstu2.formats.IParser.OutputStyle;
079import org.hl7.fhir.dstu2.formats.JsonParser;
080import org.hl7.fhir.dstu2.formats.XmlParser;
081import org.hl7.fhir.dstu2.model.Bundle;
082import org.hl7.fhir.dstu2.model.OperationOutcome;
083import org.hl7.fhir.dstu2.model.OperationOutcome.IssueSeverity;
084import org.hl7.fhir.dstu2.model.OperationOutcome.OperationOutcomeIssueComponent;
085import org.hl7.fhir.dstu2.model.Resource;
086import org.hl7.fhir.dstu2.model.ResourceType;
087import org.hl7.fhir.dstu2.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
102  private HttpHost proxy;
103  private int timeout = 5000;
104  private String username;
105  private String password;
106  private ToolingClientLogger logger;
107  private int retryCount;
108  private String userAgent;
109
110  public HttpHost getProxy() {
111    return proxy;
112  }
113
114  public void setProxy(HttpHost proxy) {
115    this.proxy = proxy;
116  }
117
118  public int getTimeout() {
119    return timeout;
120  }
121
122  public void setTimeout(int timeout) {
123    this.timeout = timeout;
124  }
125
126  public String getUsername() {
127    return username;
128  }
129
130  public void setUsername(String username) {
131    this.username = username;
132  }
133
134  public String getPassword() {
135    return password;
136  }
137
138  public void setPassword(String password) {
139    this.password = password;
140  }
141
142  public <T extends Resource> ResourceRequest<T> issueOptionsRequest(URI optionsUri, String resourceFormat, int timeoutLoading) {
143    HttpOptions options = new HttpOptions(optionsUri);
144    return issueResourceRequest(resourceFormat, options, timeoutLoading);
145  }
146
147  public <T extends Resource> ResourceRequest<T> issueGetResourceRequest(URI resourceUri, String resourceFormat, int timeoutLoading) {
148    HttpGet httpget = new HttpGet(resourceUri);
149    return issueResourceRequest(resourceFormat, httpget, timeoutLoading);
150  }
151
152  public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat, List<Header> headers, int timeoutLoading) {
153    HttpPut httpPut = new HttpPut(resourceUri);
154    return issueResourceRequest(resourceFormat, httpPut, payload, headers, timeoutLoading);
155  }
156
157  public <T extends Resource> ResourceRequest<T> issuePutRequest(URI resourceUri, byte[] payload, String resourceFormat, int timeoutLoading) {
158    HttpPut httpPut = new HttpPut(resourceUri);
159    return issueResourceRequest(resourceFormat, httpPut, payload, null, timeoutLoading);
160  }
161
162  public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri, byte[] payload, String resourceFormat, List<Header> headers, int timeoutLoading) {
163    HttpPost httpPost = new HttpPost(resourceUri);
164    return issueResourceRequest(resourceFormat, httpPost, payload, headers, timeoutLoading);
165  }
166
167
168  public <T extends Resource> ResourceRequest<T> issuePostRequest(URI resourceUri, byte[] payload, String resourceFormat, int timeoutLoading) {
169    return issuePostRequest(resourceUri, payload, resourceFormat, null, timeoutLoading);
170  }
171
172  public Bundle issueGetFeedRequest(URI resourceUri, String resourceFormat) {
173    HttpGet httpget = new HttpGet(resourceUri);
174    configureFhirRequest(httpget, resourceFormat);
175    HttpResponse response = sendRequest(httpget);
176    return unmarshalReference(response, resourceFormat);
177  }
178
179  private void setAuth(HttpRequest httpget) {
180    if (password != null) {
181      try {
182        byte[] b = Base64.encodeBase64((username+":"+password).getBytes("ASCII"));
183        String b64 = new String(b, StandardCharsets.US_ASCII);
184        httpget.setHeader("Authorization", "Basic " + b64);
185      } catch (UnsupportedEncodingException e) {
186      }
187    }
188  }
189
190  public Bundle postBatchRequest(URI resourceUri, byte[] payload, String resourceFormat, int timeoutLoading) {
191    HttpPost httpPost = new HttpPost(resourceUri);
192    configureFhirRequest(httpPost, resourceFormat);
193    HttpResponse response = sendPayload(httpPost, payload, proxy, timeoutLoading);
194    return unmarshalFeed(response, resourceFormat);
195  }
196
197  public boolean issueDeleteRequest(URI resourceUri) {
198    HttpDelete deleteRequest = new HttpDelete(resourceUri);
199    HttpResponse response = sendRequest(deleteRequest);
200    int responseStatusCode = response.getStatusLine().getStatusCode();
201    boolean deletionSuccessful = false;
202    if(responseStatusCode == 204) {
203      deletionSuccessful = true;
204    }
205    return deletionSuccessful;
206  }
207
208  /***********************************************************
209   * Request/Response Helper methods
210   ***********************************************************/
211
212  protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request, int timeoutLoading) {
213    return issueResourceRequest(resourceFormat, request, null, timeoutLoading);
214  }
215
216  /**
217   * @param resourceFormat
218   * @param options
219   * @return
220   */
221  protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request, byte[] payload, int timeoutLoading) {
222    return issueResourceRequest(resourceFormat, request, payload, null, timeoutLoading);
223  }
224
225  /**
226   * @param resourceFormat
227   * @param options
228   * @return
229   */
230  protected <T extends Resource> ResourceRequest<T> issueResourceRequest(String resourceFormat, HttpUriRequest request, byte[] payload, List<Header> headers, int timeoutLoading) {
231    configureFhirRequest(request, resourceFormat, headers);
232    HttpResponse response = null;
233    if(request instanceof HttpEntityEnclosingRequest && payload != null) {
234      response = sendPayload((HttpEntityEnclosingRequestBase)request, payload, proxy, timeoutLoading);
235    } else if (request instanceof HttpEntityEnclosingRequest && payload == null){
236      throw new EFhirClientException("PUT and POST requests require a non-null payload");
237    } else {
238      response = sendRequest(request);
239    }
240    T resource = unmarshalReference(response, resourceFormat);
241    return new ResourceRequest<T>(resource, response.getStatusLine().getStatusCode(), getLocationHeader(response));
242  }
243
244
245  /**
246   * Method adds required request headers.
247   * TODO handle JSON request as well.
248   * 
249   * @param request
250   */
251  protected void configureFhirRequest(HttpRequest request, String format) {
252    configureFhirRequest(request, format, null);
253  }
254
255  /**
256   * Method adds required request headers.
257   * TODO handle JSON request as well.
258   * 
259   * @param request
260   */
261  protected void configureFhirRequest(HttpRequest request, String format, List<Header> headers) {
262    if (!Utilities.noString(userAgent)) {
263      request.addHeader("User-Agent", userAgent);
264    }
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, int timeoutLoading) {
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        HttpClient httpclient = new DefaultHttpClient();
296        HttpParams params = httpclient.getParams();
297        HttpConnectionParams.setConnectionTimeout(params, timeout);
298        HttpConnectionParams.setSoTimeout(params, timeout * timeoutLoading);
299
300        if(proxy != null) {
301          httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
302        }
303        request.setEntity(new ByteArrayEntity(payload));
304        log(request);
305        response = httpclient.execute(request);
306        ok = true;
307      } catch(IOException ioe) {
308        System.out.println(ioe.getMessage()+" ("+(System.currentTimeMillis()-t)+"ms / "+Utilities.describeSize(payload.length)+")");
309        if (tryCount <= retryCount || (tryCount < 3 && ioe instanceof org.apache.http.conn.ConnectTimeoutException)) {
310          ok = false;
311        } else {
312          throw new EFhirClientException("Error sending HTTP Post/Put Payload: "+ioe.getMessage(), ioe);
313        }
314      }
315    }
316    return response;
317  }
318
319  /**
320   * 
321   * @param request
322   * @param payload
323   * @return
324   */
325  protected HttpResponse sendRequest(HttpUriRequest request) {
326    HttpResponse response = null;
327    try {
328      HttpClient httpclient = new DefaultHttpClient();
329      log(request);
330      HttpParams params = httpclient.getParams();
331      HttpConnectionParams.setConnectionTimeout(params, timeout);
332      HttpConnectionParams.setSoTimeout(params, timeout);
333      if(proxy != null) {
334        httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
335      }
336      response = httpclient.execute(request);
337    } catch(IOException ioe) {
338      if (ClientUtils.debugging ) {
339        ioe.printStackTrace();
340      }
341      throw new EFhirClientException("Error sending Http Request: "+ioe.getMessage(), ioe);
342    }
343    return response;
344  }
345
346  /**
347   * Unmarshals a resource from the response stream.
348   * 
349   * @param response
350   * @return
351   */
352  @SuppressWarnings("unchecked")
353  protected <T extends Resource> T unmarshalReference(HttpResponse response, String format) {
354    T resource = null;
355    OperationOutcome error = null;
356    byte[] cnt = log(response);
357    if (cnt != null) {
358      try {
359        resource = (T)getParser(format).parse(cnt);
360        if (resource instanceof OperationOutcome && hasError((OperationOutcome)resource)) {
361          error = (OperationOutcome) resource;
362        }
363      } catch(IOException ioe) {
364        throw new EFhirClientException("Error reading Http Response: "+ioe.getMessage(), ioe);
365      } catch(Exception e) {
366        throw new EFhirClientException("Error parsing response message: "+e.getMessage(), e);
367      }
368    }
369    if(error != null) {
370      throw new EFhirClientException("Error from server: "+ResourceUtilities.getErrorDescription(error), error);
371    }
372    return resource;
373  }
374
375  /**
376   * Unmarshals Bundle from response stream.
377   * 
378   * @param response
379   * @return
380   */
381  protected Bundle unmarshalFeed(HttpResponse response, String format) {
382    Bundle feed = null;
383    byte[] cnt = log(response);
384    String contentType = response.getHeaders("Content-Type")[0].getValue();
385    OperationOutcome error = null;
386    try {
387      if (cnt != null) {
388        if(contentType.contains(ResourceFormat.RESOURCE_XML.getHeader()) || contentType.contains("text/xml+fhir")) {
389          Resource rf = getParser(format).parse(cnt);
390          if (rf instanceof Bundle)
391            feed = (Bundle) rf;
392          else if (rf instanceof OperationOutcome && hasError((OperationOutcome) rf)) {
393            error = (OperationOutcome) rf;
394          } else {
395            throw new EFhirClientException("Error reading server response: a resource was returned instead");
396          }
397        }
398      }
399    } catch(IOException ioe) {
400      throw new EFhirClientException("Error reading Http Response", ioe);
401    } catch(Exception e) {
402      throw new EFhirClientException("Error parsing response message", e);
403    }
404    if(error != null) {
405      throw new EFhirClientException("Error from server: "+ResourceUtilities.getErrorDescription(error), error);
406    }
407    return feed;
408  }
409
410  private boolean hasError(OperationOutcome oo) {
411    for (OperationOutcomeIssueComponent t : oo.getIssue())
412      if (t.getSeverity() == IssueSeverity.ERROR || t.getSeverity() == IssueSeverity.FATAL)
413        return true;
414    return false;
415  }
416
417  protected String getLocationHeader(HttpResponse response) {
418    String location = null;
419    if(response.getHeaders("location").length > 0) {//TODO Distinguish between both cases if necessary
420      location = response.getHeaders("location")[0].getValue();
421    } else if(response.getHeaders("content-location").length > 0) {
422      location = response.getHeaders("content-location")[0].getValue();
423    }
424    return location;
425  }
426
427
428  /*****************************************************************
429   * Client connection methods
430   * ***************************************************************/
431
432  public HttpURLConnection buildConnection(URI baseServiceUri, String tail) {
433    try {
434      HttpURLConnection client = (HttpURLConnection) baseServiceUri.resolve(tail).toURL().openConnection();
435      return client;
436    } catch(MalformedURLException mue) {
437      throw new EFhirClientException("Invalid Service URL", mue);
438    } catch(IOException ioe) {
439      throw new EFhirClientException("Unable to establish connection to server: " + baseServiceUri.toString() + tail, ioe);
440    }
441  }
442
443  public HttpURLConnection buildConnection(URI baseServiceUri, ResourceType resourceType, String id) {
444    return buildConnection(baseServiceUri, ResourceAddress.buildRelativePathFromResourceType(resourceType, id));
445  }
446
447  /******************************************************************
448   * Other general helper methods
449   * ****************************************************************/
450
451
452  public  <T extends Resource>  byte[] getResourceAsByteArray(T resource, boolean pretty, boolean isJson) {
453    ByteArrayOutputStream baos = null;
454    byte[] byteArray = null;
455    try {
456      baos = new ByteArrayOutputStream();
457      IParser parser = null;
458      if(isJson) {
459        parser = new JsonParser();
460      } else {
461        parser = new XmlParser();
462      }
463      parser.setOutputStyle(pretty ? OutputStyle.PRETTY : OutputStyle.NORMAL);
464      parser.compose(baos, resource);
465      baos.close();
466      byteArray =  baos.toByteArray();
467      baos.close();
468    } catch (Exception e) {
469      try{
470        baos.close();
471      }catch(Exception ex) {
472        throw new EFhirClientException("Error closing output stream", ex);
473      }
474      throw new EFhirClientException("Error converting output stream to byte array", e);
475    }
476    return byteArray;
477  }
478
479  public  byte[] getFeedAsByteArray(Bundle feed, boolean pretty, boolean isJson) {
480    ByteArrayOutputStream baos = null;
481    byte[] byteArray = null;
482    try {
483      baos = new ByteArrayOutputStream();
484      IParser parser = null;
485      if(isJson) {
486        parser = new JsonParser();
487      } else {
488        parser = new XmlParser();
489      }
490      parser.setOutputStyle(pretty ? OutputStyle.PRETTY : OutputStyle.NORMAL);
491      parser.compose(baos, feed);
492      baos.close();
493      byteArray =  baos.toByteArray();
494      baos.close();
495    } catch (Exception e) {
496      try{
497        baos.close();
498      }catch(Exception ex) {
499        throw new EFhirClientException("Error closing output stream", ex);
500      }
501      throw new EFhirClientException("Error converting output stream to byte array", e);
502    }
503    return byteArray;
504  }
505
506  public Calendar getLastModifiedResponseHeaderAsCalendarObject(URLConnection serverConnection) {
507    String dateTime = null;
508    try {
509      dateTime = serverConnection.getHeaderField("Last-Modified");
510      SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", new Locale("en", "US"));
511      Date lastModifiedTimestamp = format.parse(dateTime);
512      Calendar calendar=Calendar.getInstance();
513      calendar.setTime(lastModifiedTimestamp);
514      return calendar;
515    } catch(ParseException pe) {
516      throw new EFhirClientException("Error parsing Last-Modified response header " + dateTime, pe);
517    }
518  }
519
520  protected IParser getParser(String format) {
521    if(StringUtils.isBlank(format)) {
522      format = ResourceFormat.RESOURCE_XML.getHeader();
523    }
524    if(format.equalsIgnoreCase("json") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_JSON.getHeader()) || format.equalsIgnoreCase(ResourceFormat.RESOURCE_JSON.getHeader())) {
525      return new JsonParser();
526    } else if(format.equalsIgnoreCase("xml") || format.equalsIgnoreCase(ResourceFormat.RESOURCE_XML.getHeader()) || format.equalsIgnoreCase(ResourceFormat.RESOURCE_XML.getHeader())) {
527      return new XmlParser();
528    } else {
529      throw new EFhirClientException("Invalid format: " + format);
530    }
531  }
532
533  public Bundle issuePostFeedRequest(URI resourceUri, Map<String, String> parameters, String resourceName, Resource resource, String resourceFormat) throws IOException {
534    HttpPost httppost = new HttpPost(resourceUri);
535    String boundary = "----WebKitFormBoundarykbMUo6H8QaUnYtRy";
536    httppost.addHeader("Content-Type", "multipart/form-data; boundary="+boundary);
537    httppost.addHeader("Accept", resourceFormat);
538    configureFhirRequest(httppost, null);
539    HttpResponse response = sendPayload(httppost, encodeFormSubmission(parameters, resourceName, resource, boundary));
540    return unmarshalFeed(response, resourceFormat);
541  }
542
543  private byte[] encodeFormSubmission(Map<String, String> parameters, String resourceName, Resource resource, String boundary) throws IOException {
544    ByteArrayOutputStream b = new ByteArrayOutputStream();
545    OutputStreamWriter w = new OutputStreamWriter(b, "UTF-8");  
546    for (String name : parameters.keySet()) {
547      w.write("--");
548      w.write(boundary);
549      w.write("\r\nContent-Disposition: form-data; name=\""+name+"\"\r\n\r\n");
550      w.write(parameters.get(name)+"\r\n");
551    }
552    w.write("--");
553    w.write(boundary);
554    w.write("\r\nContent-Disposition: form-data; name=\""+resourceName+"\"\r\n\r\n");
555    w.close(); 
556    JsonParser json = new JsonParser();
557    json.setOutputStyle(OutputStyle.NORMAL);
558    json.compose(b, resource);
559    b.close();
560    w = new OutputStreamWriter(b, "UTF-8");  
561    w.write("\r\n--");
562    w.write(boundary);
563    w.write("--");
564    w.close();
565    return b.toByteArray();
566  }
567
568  /**
569   * Method posts request payload
570   * 
571   * @param request
572   * @param payload
573   * @return
574   */
575  protected HttpResponse sendPayload(HttpEntityEnclosingRequestBase request, byte[] payload) {
576    HttpResponse response = null;
577    try {
578      log(request);
579      HttpClient httpclient = new DefaultHttpClient();
580      request.setEntity(new ByteArrayEntity(payload));
581      response = httpclient.execute(request);
582      log(response);
583    } catch(IOException ioe) {
584      throw new EFhirClientException("Error sending HTTP Post/Put Payload: "+ioe.getMessage(), ioe);
585    }
586    return response;
587  }
588
589  private void log(HttpUriRequest request) {
590    if (logger != null) {
591      List<String> headers = new ArrayList<>();
592      for (Header h : request.getAllHeaders()) {
593        headers.add(h.toString());
594      }
595      logger.logRequest(request.getMethod(), request.getURI().toString(), headers, null);
596    }    
597  }
598  private void log(HttpEntityEnclosingRequestBase request)  {
599    if (logger != null) {
600      List<String> headers = new ArrayList<>();
601      for (Header h : request.getAllHeaders()) {
602        headers.add(h.toString());
603      }
604      byte[] cnt = null;
605      InputStream s;
606      try {
607        s = request.getEntity().getContent();
608        cnt = IOUtils.toByteArray(s);
609        s.close();
610      } catch (Exception e) {
611      }
612      logger.logRequest(request.getMethod(), request.getURI().toString(), headers, cnt);
613    }    
614  }  
615
616  private byte[] log(HttpResponse response) {
617    byte[] cnt = null;
618    try {
619      InputStream s = response.getEntity().getContent();
620      cnt = IOUtils.toByteArray(s);
621      s.close();
622    } catch (Exception e) {
623    }
624    if (logger != null) {
625      List<String> headers = new ArrayList<>();
626      for (Header h : response.getAllHeaders()) {
627        headers.add(h.toString());
628      }
629      logger.logResponse(response.getStatusLine().toString(), headers, cnt);
630    }
631    return cnt;
632  }
633
634  public ToolingClientLogger getLogger() {
635    return logger;
636  }
637
638  public void setLogger(ToolingClientLogger logger) {
639    this.logger = logger;
640  }
641
642
643  /**
644   * Used for debugging
645   * 
646   * @param instream
647   * @return
648   */
649  protected String writeInputStreamAsString(InputStream instream) {
650    String value = null;
651    try {
652      value = IOUtils.toString(instream, "UTF-8");
653      System.out.println(value);
654
655    } catch(IOException ioe) {
656      //Do nothing
657    }
658    return value;
659  }
660
661  public int getRetryCount() {
662    return retryCount;
663  }
664
665  public void setRetryCount(int retryCount) {
666    this.retryCount = retryCount;
667  }
668
669  public String getUserAgent() {
670    return userAgent;
671  }
672
673  public void setUserAgent(String userAgent) {
674    this.userAgent = userAgent;
675  }
676
677
678}