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