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