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