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