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