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.net.URI;
033import java.net.URISyntaxException;
034import java.util.HashMap;
035import java.util.List;
036import java.util.Map;
037
038import org.apache.http.Header;
039import org.apache.http.HttpHost;
040import org.hl7.fhir.dstu2.model.Bundle;
041import org.hl7.fhir.dstu2.model.Coding;
042import org.hl7.fhir.dstu2.model.ConceptMap;
043import org.hl7.fhir.dstu2.model.Conformance;
044import org.hl7.fhir.dstu2.model.OperationOutcome;
045import org.hl7.fhir.dstu2.model.Parameters;
046import org.hl7.fhir.dstu2.model.Parameters.ParametersParameterComponent;
047import org.hl7.fhir.dstu2.model.PrimitiveType;
048import org.hl7.fhir.dstu2.model.Resource;
049import org.hl7.fhir.dstu2.model.StringType;
050import org.hl7.fhir.dstu2.model.ValueSet;
051import org.hl7.fhir.utilities.FHIRBaseToolingClient;
052import org.hl7.fhir.utilities.ToolingClientLogger;
053import org.hl7.fhir.utilities.Utilities;
054
055/**
056 * Very Simple RESTful client. This is purely for use in the standalone tools
057 * jar packages. It doesn't support many features, only what the tools need.
058 * 
059 * To use, initialize class and set base service URI as follows:
060 * 
061 * <pre>
062 * <code>
063 * FHIRSimpleClient fhirClient = new FHIRSimpleClient();
064 * fhirClient.initialize("http://my.fhir.domain/myServiceRoot");
065 * </code>
066 * </pre>
067 * 
068 * Default Accept and Content-Type headers are application/xml+fhir and
069 * application/j+fhir.
070 * 
071 * These can be changed by invoking the following setter functions:
072 * 
073 * <pre>
074 * <code>
075 * setPreferredResourceFormat()
076 * setPreferredFeedFormat()
077 * </code>
078 * </pre>
079 * 
080 * TODO Review all sad paths.
081 * 
082 * @author Claude Nanjo
083 *
084 */
085public class FHIRToolingClient extends FHIRBaseToolingClient {
086
087  public static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssK";
088  public static final String DATE_FORMAT = "yyyy-MM-dd";
089  public static final String hostKey = "http.proxyHost";
090  public static final String portKey = "http.proxyPort";
091
092  private String base;
093  private ResourceAddress resourceAddress;
094  private ResourceFormat preferredResourceFormat;
095  private HttpHost proxy;
096  private int maxResultSetSize = -1;// _count
097  private Conformance conf;
098  private ClientUtils utils = new ClientUtils();
099  private int useCount;
100
101  // Pass enpoint for client - URI
102  public FHIRToolingClient(String baseServiceUrl, String userAgent) throws URISyntaxException {
103    preferredResourceFormat = ResourceFormat.RESOURCE_XML;
104    utils.setUserAgent(userAgent);
105    detectProxy();
106    initialize(baseServiceUrl);
107  }
108
109  public FHIRToolingClient(String baseServiceUrl, String userAgent, String username, String password)
110      throws URISyntaxException {
111    preferredResourceFormat = ResourceFormat.RESOURCE_XML;
112    utils.setUserAgent(userAgent);
113    utils.setUsername(username);
114    utils.setPassword(password);
115    detectProxy();
116    initialize(baseServiceUrl);
117  }
118
119  public void configureProxy(String proxyHost, int proxyPort) {
120    utils.setProxy(new HttpHost(proxyHost, proxyPort));
121  }
122
123  public void detectProxy() {
124    String host = System.getenv(hostKey);
125    String port = System.getenv(portKey);
126
127    if (host == null) {
128      host = System.getProperty(hostKey);
129    }
130
131    if (port == null) {
132      port = System.getProperty(portKey);
133    }
134
135    if (host != null && port != null) {
136      this.configureProxy(host, Integer.parseInt(port));
137    }
138  }
139
140  public void initialize(String baseServiceUrl) throws URISyntaxException {
141    base = baseServiceUrl;
142    resourceAddress = new ResourceAddress(baseServiceUrl);
143    this.maxResultSetSize = -1;
144    checkConformance();
145  }
146
147  private void checkConformance() {
148    try {
149      conf = getConformanceStatementQuick();
150    } catch (Throwable e) {
151    }
152  }
153
154  public String getPreferredResourceFormat() {
155    return preferredResourceFormat.getHeader();
156  }
157
158  public void setPreferredResourceFormat(ResourceFormat resourceFormat) {
159    preferredResourceFormat = resourceFormat;
160  }
161
162  public int getMaximumRecordCount() {
163    return maxResultSetSize;
164  }
165
166  public void setMaximumRecordCount(int maxResultSetSize) {
167    this.maxResultSetSize = maxResultSetSize;
168  }
169
170  public Conformance getConformanceStatement() throws EFhirClientException {
171    if (conf != null)
172      return conf;
173    return getConformanceStatement(false);
174  }
175
176  public Conformance getConformanceStatement(boolean useOptionsVerb) {
177    Conformance conformance = null;
178    try {
179      if (useOptionsVerb) {
180        conformance = (Conformance) utils
181            .issueOptionsRequest(resourceAddress.getBaseServiceUri(), withVer(getPreferredResourceFormat(), "1.0"), timeoutNormal)
182            .getReference();// TODO fix this
183      } else {
184        conformance = (Conformance) utils.issueGetResourceRequest(resourceAddress.resolveMetadataUri(false),
185            withVer(getPreferredResourceFormat(), "1.0"), timeoutNormal).getReference();
186      }
187    } catch (Exception e) {
188      handleException("An error has occurred while trying to fetch the server's conformance statement", e);
189    }
190    return conformance;
191  }
192
193  public Conformance getConformanceStatementQuick() throws EFhirClientException {
194    if (conf != null)
195      return conf;
196    return getConformanceStatementQuick(false);
197  }
198
199  public Conformance getConformanceStatementQuick(boolean useOptionsVerb) {
200    Conformance conformance = null;
201    try {
202      if (useOptionsVerb) {
203        conformance = (Conformance) utils
204            .issueOptionsRequest(resourceAddress.getBaseServiceUri(), withVer(getPreferredResourceFormat(), "1.0"), timeoutNormal)
205            .getReference();// TODO fix this
206      } else {
207        conformance = (Conformance) utils.issueGetResourceRequest(resourceAddress.resolveMetadataUri(true),
208            withVer(getPreferredResourceFormat(), "1.0"), timeoutNormal).getReference();
209      }
210    } catch (Exception e) {
211      handleException("An error has occurred while trying to fetch the server's conformance statement", e);
212    }
213    return conformance;
214  }
215
216  public <T extends Resource> T read(Class<T> resourceClass, String id) {// TODO Change this to AddressableResource
217    recordUse();
218    ResourceRequest<T> result = null;
219    try {
220      result = utils.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
221          withVer(getPreferredResourceFormat(), "1.0"), timeoutNormal);
222      result.addErrorStatus(410);// gone
223      result.addErrorStatus(404);// unknown
224      result.addSuccessStatus(200);// Only one for now
225      if (result.isUnsuccessfulRequest()) {
226        throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
227            (OperationOutcome) result.getPayload());
228      }
229    } catch (Exception e) {
230      handleException("An error has occurred while trying to read this resource", e);
231    }
232    return result.getPayload();
233  }
234
235  public <T extends Resource> T vread(Class<T> resourceClass, String id, String version) {
236    recordUse();
237    ResourceRequest<T> result = null;
238    try {
239      result = utils.issueGetResourceRequest(
240          resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version),
241          withVer(getPreferredResourceFormat(), "1.0"), timeoutNormal);
242      result.addErrorStatus(410);// gone
243      result.addErrorStatus(404);// unknown
244      result.addErrorStatus(405);// unknown
245      result.addSuccessStatus(200);// Only one for now
246      if (result.isUnsuccessfulRequest()) {
247        throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
248            (OperationOutcome) result.getPayload());
249      }
250    } catch (Exception e) {
251      handleException("An error has occurred while trying to read this version of the resource", e);
252    }
253    return result.getPayload();
254  }
255
256  // GET
257  // fhir/ValueSet?url=http://hl7.org/fhir/ValueSet/clinical-findings&version=0.8
258
259  public <T extends Resource> T getCanonical(Class<T> resourceClass, String canonicalURL) {
260    recordUse();
261    ResourceRequest<T> result = null;
262    try {
263      result = utils.issueGetResourceRequest(
264          resourceAddress.resolveGetUriFromResourceClassAndCanonical(resourceClass, canonicalURL),
265          withVer(getPreferredResourceFormat(), "1.0"), timeoutNormal);
266      result.addErrorStatus(410);// gone
267      result.addErrorStatus(404);// unknown
268      result.addErrorStatus(405);// unknown
269      result.addSuccessStatus(200);// Only one for now
270      if (result.isUnsuccessfulRequest()) {
271        throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
272            (OperationOutcome) result.getPayload());
273      }
274    } catch (Exception e) {
275      handleException("An error has occurred while trying to read this version of the resource", e);
276    }
277    Bundle bnd = (Bundle) result.getPayload();
278    if (bnd.getEntry().size() == 0)
279      throw new EFhirClientException("No matching resource found for canonical URL '" + canonicalURL + "'");
280    if (bnd.getEntry().size() > 1)
281      throw new EFhirClientException("Multiple matching resources found for canonical URL '" + canonicalURL + "'");
282    return (T) bnd.getEntry().get(0).getResource();
283  }
284
285  public Resource update(Resource resource) {
286    recordUse();
287    ResourceRequest<Resource> result = null;
288    try {
289      List<Header> headers = null;
290      result = utils.issuePutRequest(
291          resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()),
292          utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())),
293          withVer(getPreferredResourceFormat(), "1.0"), headers, timeoutOperation);
294      result.addErrorStatus(410);// gone
295      result.addErrorStatus(404);// unknown
296      result.addErrorStatus(405);
297      result.addErrorStatus(422);// Unprocessable Entity
298      result.addSuccessStatus(200);
299      result.addSuccessStatus(201);
300      if (result.isUnsuccessfulRequest()) {
301        throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
302            (OperationOutcome) result.getPayload());
303      }
304    } catch (Exception e) {
305      throw new EFhirClientException("An error has occurred while trying to update this resource", e);
306    }
307    // TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader
308    // is returned with an operationOutcome would be returned (and not the resource
309    // also) we make another read
310    try {
311      OperationOutcome operationOutcome = (OperationOutcome) result.getPayload();
312      ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress
313          .parseCreateLocation(result.getLocation());
314      return this.vread(resource.getClass(), resVersionedIdentifier.getId(), resVersionedIdentifier.getVersionId());
315    } catch (ClassCastException e) {
316      // if we fall throught we have the correct type already in the create
317    }
318
319    return result.getPayload();
320  }
321
322  public <T extends Resource> T update(Class<T> resourceClass, T resource, String id) {
323    recordUse();
324    ResourceRequest<T> result = null;
325    try {
326      List<Header> headers = null;
327      result = utils.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
328          utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())),
329          withVer(getPreferredResourceFormat(), "1.0"), headers, timeoutOperation);
330      result.addErrorStatus(410);// gone
331      result.addErrorStatus(404);// unknown
332      result.addErrorStatus(405);
333      result.addErrorStatus(422);// Unprocessable Entity
334      result.addSuccessStatus(200);
335      result.addSuccessStatus(201);
336      if (result.isUnsuccessfulRequest()) {
337        throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
338            (OperationOutcome) result.getPayload());
339      }
340    } catch (Exception e) {
341      throw new EFhirClientException("An error has occurred while trying to update this resource", e);
342    }
343    // TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader
344    // is returned with an operationOutcome would be returned (and not the resource
345    // also) we make another read
346    try {
347      OperationOutcome operationOutcome = (OperationOutcome) result.getPayload();
348      ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress
349          .parseCreateLocation(result.getLocation());
350      return this.vread(resourceClass, resVersionedIdentifier.getId(), resVersionedIdentifier.getVersionId());
351    } catch (ClassCastException e) {
352      // if we fall throught we have the correct type already in the create
353    }
354
355    return result.getPayload();
356  }
357
358//      
359//      public <T extends Resource> boolean delete(Class<T> resourceClass, String id) {
360//              try {
361//                      return utils.issueDeleteRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), proxy);
362//              } catch(Exception e) {
363//                      throw new EFhirClientException("An error has occurred while trying to delete this resource", e);
364//              }
365//
366//      }
367
368//      
369//      public <T extends Resource> OperationOutcome create(Class<T> resourceClass, T resource) {
370//        ResourceRequest<T> resourceRequest = null;
371//        try {
372//          List<Header> headers = null;
373//          resourceRequest = utils.issuePostRequest(resourceAddress.resolveGetUriFromResourceClass(resourceClass),utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"), headers, proxy);
374//          resourceRequest.addSuccessStatus(201);
375//          if(resourceRequest.isUnsuccessfulRequest()) {
376//            throw new EFhirClientException("Server responded with HTTP error code " + resourceRequest.getHttpStatus(), (OperationOutcome)resourceRequest.getPayload());
377//          }
378//        } catch(Exception e) {
379//          handleException("An error has occurred while trying to create this resource", e);
380//        }
381//        OperationOutcome operationOutcome = null;;
382//        try {
383//          operationOutcome = (OperationOutcome)resourceRequest.getPayload();
384//          ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = 
385//              ResourceAddress.parseCreateLocation(resourceRequest.getLocation());
386//          OperationOutcomeIssueComponent issue = operationOutcome.addIssue();
387//          issue.setSeverity(IssueSeverity.INFORMATION);
388//          issue.setUserData(ResourceAddress.ResourceVersionedIdentifier.class.toString(),
389//              resVersionedIdentifier);
390//          return operationOutcome;
391//        } catch(ClassCastException e) {
392//          // some server (e.g. grahams) returns the resource directly
393//          operationOutcome = new OperationOutcome();
394//          OperationOutcomeIssueComponent issue = operationOutcome.addIssue();
395//          issue.setSeverity(IssueSeverity.INFORMATION);
396//          issue.setUserData(ResourceRequest.class.toString(),
397//              resourceRequest.getPayload());
398//          return operationOutcome;
399//        }     
400//      }
401
402//      
403//      public <T extends Resource> Bundle history(Calendar lastUpdate, Class<T> resourceClass, String id) {
404//              Bundle history = null;
405//              try {
406//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, lastUpdate, maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
407//              } catch (Exception e) {
408//                      handleException("An error has occurred while trying to retrieve history information for this resource", e);
409//              }
410//              return history;
411//      }
412
413//      
414//      public <T extends Resource> Bundle history(Date lastUpdate, Class<T> resourceClass, String id) {
415//              Bundle history = null;
416//              try {
417//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, lastUpdate, maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
418//              } catch (Exception e) {
419//                      handleException("An error has occurred while trying to retrieve history information for this resource", e);
420//              }
421//              return history;
422//      }
423//
424//      
425//      public <T extends Resource> Bundle history(Calendar lastUpdate, Class<T> resourceClass) {
426//              Bundle history = null;
427//              try {
428//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, lastUpdate, maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
429//              } catch (Exception e) {
430//                      handleException("An error has occurred while trying to retrieve history information for this resource type", e);
431//              }
432//              return history;
433//      }
434//      
435//      
436//      public <T extends Resource> Bundle history(Date lastUpdate, Class<T> resourceClass) {
437//              Bundle history = null;
438//              try {
439//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, lastUpdate, maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
440//              } catch (Exception e) {
441//                      handleException("An error has occurred while trying to retrieve history information for this resource type", e);
442//              }
443//              return history;
444//      }
445//      
446//      
447//      public <T extends Resource> Bundle history(Class<T> resourceClass) {
448//              Bundle history = null;
449//              try {
450//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
451//              } catch (Exception e) {
452//                      handleException("An error has occurred while trying to retrieve history information for this resource type", e);
453//              }
454//              return history;
455//      }
456//      
457//      
458//      public <T extends Resource> Bundle history(Class<T> resourceClass, String id) {
459//              Bundle history = null;
460//              try {
461//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
462//              } catch (Exception e) {
463//                      handleException("An error has occurred while trying to retrieve history information for this resource", e);
464//              }
465//              return history;
466//      }
467//
468//      
469//      public <T extends Resource> Bundle history(Date lastUpdate) {
470//              Bundle history = null;
471//              try {
472//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(lastUpdate, maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
473//              } catch (Exception e) {
474//                      handleException("An error has occurred while trying to retrieve history since last update",e);
475//              }
476//              return history;
477//      }
478//
479//      
480//      public <T extends Resource> Bundle history(Calendar lastUpdate) {
481//              Bundle history = null;
482//              try {
483//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(lastUpdate, maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
484//              } catch (Exception e) {
485//                      handleException("An error has occurred while trying to retrieve history since last update",e);
486//              }
487//              return history;
488//      }
489//
490//      
491//      public <T extends Resource> Bundle history() {
492//              Bundle history = null;
493//              try {
494//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(maxResultSetSize), withVer(getPreferredResourceFormat(), "1.0"), proxy);
495//              } catch (Exception e) {
496//                      handleException("An error has occurred while trying to retrieve history since last update",e);
497//              }
498//              return history;
499//      }
500//
501//      
502//      public <T extends Resource> Bundle search(Class<T> resourceClass, Map<String, String> parameters) {
503//              Bundle searchResults = null;
504//              try {
505//                      searchResults = utils.issueGetFeedRequest(resourceAddress.resolveSearchUri(resourceClass, parameters), withVer(getPreferredResourceFormat(), "1.0"), proxy);
506//              } catch (Exception e) {
507//                      handleException("Error performing search with parameters " + parameters, e);
508//              }
509//              return searchResults;
510//      }
511//      
512//  
513//  public <T extends Resource> Bundle searchPost(Class<T> resourceClass, T resource, Map<String, String> parameters) {
514//    Bundle searchResults = null;
515//    try {
516//      searchResults = utils.issuePostFeedRequest(resourceAddress.resolveSearchUri(resourceClass, new HashMap<String, String>()), parameters, "src", resource, getPreferredResourceFormat());
517//    } catch (Exception e) {
518//      handleException("Error performing search with parameters " + parameters, e);
519//    }
520//    return searchResults;
521//  }
522
523  public <T extends Resource> Parameters operateType(Class<T> resourceClass, String name, Parameters params) {
524    recordUse();
525    boolean complex = false;
526    for (ParametersParameterComponent p : params.getParameter())
527      complex = complex || !(p.getValue() instanceof PrimitiveType);
528    Parameters searchResults = null;
529    String ps = "";
530    if (!complex)
531      for (ParametersParameterComponent p : params.getParameter())
532        if (p.getValue() instanceof PrimitiveType)
533          ps += p.getName() + "=" + Utilities.encodeUri(((PrimitiveType) p.getValue()).asStringValue()) + "&";
534    ResourceRequest<T> result;
535    if (complex)
536      result = utils.issuePostRequest(resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps),
537          utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())),
538          withVer(getPreferredResourceFormat(), "1.0"), timeoutLong);
539    else
540      result = utils.issueGetResourceRequest(resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps),
541          withVer(getPreferredResourceFormat(), "1.0"), timeoutLong);
542    result.addErrorStatus(410);// gone
543    result.addErrorStatus(404);// unknown
544    result.addSuccessStatus(200);// Only one for now
545    if (result.isUnsuccessfulRequest())
546      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
547          (OperationOutcome) result.getPayload());
548    if (result.getPayload() instanceof Parameters)
549      return (Parameters) result.getPayload();
550    else {
551      Parameters p_out = new Parameters();
552      p_out.addParameter().setName("return").setResource(result.getPayload());
553      return p_out;
554    }
555  }
556
557  public Bundle transaction(Bundle batch) {
558    recordUse();
559    Bundle transactionResult = null;
560    try {
561      transactionResult = utils.postBatchRequest(resourceAddress.getBaseServiceUri(),
562          utils.getFeedAsByteArray(batch, false, isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"),
563          timeoutNormal + batch.getEntry().size());
564    } catch (Exception e) {
565      handleException("An error occurred trying to process this transaction request", e);
566    }
567    return transactionResult;
568  }
569
570  @SuppressWarnings("unchecked")
571  public <T extends Resource> OperationOutcome validate(Class<T> resourceClass, T resource, String id) {
572    recordUse();
573    ResourceRequest<T> result = null;
574    try {
575      result = utils.issuePostRequest(resourceAddress.resolveValidateUri(resourceClass, id),
576          utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())),
577          withVer(getPreferredResourceFormat(), "1.0"), 3);
578      result.addErrorStatus(400);// gone
579      result.addErrorStatus(422);// Unprocessable Entity
580      result.addSuccessStatus(200);// OK
581      if (result.isUnsuccessfulRequest()) {
582        throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
583            (OperationOutcome) result.getPayload());
584      }
585    } catch (Exception e) {
586      handleException("An error has occurred while trying to validate this resource", e);
587    }
588    return (OperationOutcome) result.getPayload();
589  }
590
591  /*
592   * change to meta operations
593   * 
594   * public List<Coding> getAllTags() { TagListRequest result = null; try { result
595   * = utils.issueGetRequestForTagList(resourceAddress.resolveGetAllTags(),
596   * withVer(getPreferredResourceFormat(), "1.0"), null, proxy); } catch (Exception e) {
597   * handleException("An error has occurred while trying to retrieve all tags",
598   * e); } return result.getPayload(); }
599   * 
600   * 
601   * public <T extends Resource> List<Coding> getAllTagsForResourceType(Class<T>
602   * resourceClass) { TagListRequest result = null; try { result =
603   * utils.issueGetRequestForTagList(resourceAddress.
604   * resolveGetAllTagsForResourceType(resourceClass),
605   * withVer(getPreferredResourceFormat(), "1.0"), null, proxy); } catch (Exception e) {
606   * handleException("An error has occurred while trying to retrieve tags for this resource type"
607   * , e); } return result.getPayload(); }
608   * 
609   * 
610   * public <T extends Resource> List<Coding> getTagsForReference(Class<T>
611   * resource, String id) { TagListRequest result = null; try { result =
612   * utils.issueGetRequestForTagList(resourceAddress.resolveGetTagsForReference(
613   * resource, id), withVer(getPreferredResourceFormat(), "1.0"), null, proxy); } catch (Exception
614   * e) {
615   * handleException("An error has occurred while trying to retrieve tags for this resource"
616   * , e); } return result.getPayload(); }
617   * 
618   * 
619   * public <T extends Resource> List<Coding> getTagsForResourceVersion(Class<T>
620   * resource, String id, String versionId) { TagListRequest result = null; try {
621   * result = utils.issueGetRequestForTagList(resourceAddress.
622   * resolveGetTagsForResourceVersion(resource, id, versionId),
623   * withVer(getPreferredResourceFormat(), "1.0"), null, proxy); } catch (Exception e) {
624   * handleException("An error has occurred while trying to retrieve tags for this resource version"
625   * , e); } return result.getPayload(); }
626   * 
627   * // // public <T extends Resource> boolean deleteTagsForReference(Class<T>
628   * resourceClass, String id) { // try { // return
629   * utils.issueDeleteRequest(resourceAddress.resolveGetTagsForReference(
630   * resourceClass, id), proxy); // } catch(Exception e) { //
631   * handleException("An error has occurred while trying to retrieve tags for this resource version"
632   * , e); // throw new
633   * EFhirClientException("An error has occurred while trying to delete this resource"
634   * , e); // } // // } // // // public <T extends Resource> boolean
635   * deleteTagsForResourceVersion(Class<T> resourceClass, String id, List<Coding>
636   * tags, String version) { // try { // return
637   * utils.issueDeleteRequest(resourceAddress.resolveGetTagsForResourceVersion(
638   * resourceClass, id, version), proxy); // } catch(Exception e) { //
639   * handleException("An error has occurred while trying to retrieve tags for this resource version"
640   * , e); // throw new
641   * EFhirClientException("An error has occurred while trying to delete this resource"
642   * , e); // } // }
643   * 
644   * 
645   * public <T extends Resource> List<Coding> createTags(List<Coding> tags,
646   * Class<T> resourceClass, String id) { TagListRequest request = null; try {
647   * request =
648   * utils.issuePostRequestForTagList(resourceAddress.resolveGetTagsForReference(
649   * resourceClass, id),utils.getTagListAsByteArray(tags, false,
650   * isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"), null,
651   * proxy); request.addSuccessStatus(201); request.addSuccessStatus(200);
652   * if(request.isUnsuccessfulRequest()) { throw new
653   * EFhirClientException("Server responded with HTTP error code " +
654   * request.getHttpStatus()); } } catch(Exception e) {
655   * handleException("An error has occurred while trying to set tags for this resource"
656   * , e); } return request.getPayload(); }
657   * 
658   * 
659   * public <T extends Resource> List<Coding> createTags(List<Coding> tags,
660   * Class<T> resourceClass, String id, String version) { TagListRequest request =
661   * null; try { request = utils.issuePostRequestForTagList(resourceAddress.
662   * resolveGetTagsForResourceVersion(resourceClass, id,
663   * version),utils.getTagListAsByteArray(tags, false,
664   * isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"), null,
665   * proxy); request.addSuccessStatus(201); request.addSuccessStatus(200);
666   * if(request.isUnsuccessfulRequest()) { throw new
667   * EFhirClientException("Server responded with HTTP error code " +
668   * request.getHttpStatus()); } } catch(Exception e) {
669   * handleException("An error has occurred while trying to set the tags for this resource version"
670   * , e); } return request.getPayload(); }
671   * 
672   * 
673   * public <T extends Resource> List<Coding> deleteTags(List<Coding> tags,
674   * Class<T> resourceClass, String id, String version) { TagListRequest request =
675   * null; try { request = utils.issuePostRequestForTagList(resourceAddress.
676   * resolveDeleteTagsForResourceVersion(resourceClass, id,
677   * version),utils.getTagListAsByteArray(tags, false,
678   * isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"), null,
679   * proxy); request.addSuccessStatus(201); request.addSuccessStatus(200);
680   * if(request.isUnsuccessfulRequest()) { throw new
681   * EFhirClientException("Server responded with HTTP error code " +
682   * request.getHttpStatus()); } } catch(Exception e) {
683   * handleException("An error has occurred while trying to delete the tags for this resource version"
684   * , e); } return request.getPayload(); }
685   */
686
687  /**
688   * Helper method to prevent nesting of previously thrown EFhirClientExceptions
689   * 
690   * @param e
691   * @throws EFhirClientException
692   */
693  protected void handleException(String message, Exception e) throws EFhirClientException {
694    if (e instanceof EFhirClientException) {
695      throw (EFhirClientException) e;
696    } else {
697      throw new EFhirClientException(message, e);
698    }
699  }
700
701  /**
702   * Helper method to determine whether desired resource representation is Json or
703   * XML.
704   * 
705   * @param format
706   * @return
707   */
708  protected boolean isJson(String format) {
709    boolean isJson = false;
710    if (format.toLowerCase().contains("json")) {
711      isJson = true;
712    }
713    return isJson;
714  }
715
716  public Bundle fetchFeed(String url) {
717    recordUse();
718    Bundle feed = null;
719    try {
720      feed = utils.issueGetFeedRequest(new URI(url), getPreferredResourceFormat());
721    } catch (Exception e) {
722      handleException("An error has occurred while trying to retrieve history since last update", e);
723    }
724    return feed;
725  }
726
727  public Parameters lookupCode(Map<String, String> params) {
728    recordUse();
729    ResourceRequest<Resource> result = utils.issueGetResourceRequest(
730        resourceAddress.resolveOperationUri(ValueSet.class, "lookup", params), withVer(getPreferredResourceFormat(), "1.0"),
731        timeoutNormal);
732    result.addErrorStatus(410);// gone
733    result.addErrorStatus(404);// unknown
734    result.addErrorStatus(405);
735    result.addErrorStatus(422);// Unprocessable Entity
736    result.addSuccessStatus(200);
737    result.addSuccessStatus(201);
738    if (result.isUnsuccessfulRequest()) {
739      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
740          (OperationOutcome) result.getPayload());
741    }
742    return (Parameters) result.getPayload();
743  }
744
745  public Parameters lookupCode(Parameters p) {
746    recordUse();
747    ResourceRequest<Resource> result = utils.issuePostRequest(
748        resourceAddress.resolveOperationUri(ValueSet.class, "lookup"), 
749        utils.getResourceAsByteArray(p, false, isJson(getPreferredResourceFormat())),
750        withVer(getPreferredResourceFormat(), "1.0"), 
751        timeoutNormal);
752    result.addErrorStatus(410);// gone
753    result.addErrorStatus(404);// unknown
754    result.addErrorStatus(405);
755    result.addErrorStatus(422);// Unprocessable Entity
756    result.addSuccessStatus(200);
757    result.addSuccessStatus(201);
758    if (result.isUnsuccessfulRequest()) {
759      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
760          (OperationOutcome) result.getPayload());
761    }
762    return (Parameters) result.getPayload();
763  }
764
765  public Parameters translate(Parameters p) {
766    recordUse();
767    ResourceRequest<Resource> result = utils.issuePostRequest(
768        resourceAddress.resolveOperationUri(ConceptMap.class, "translate"), 
769        utils.getResourceAsByteArray(p, false, isJson(getPreferredResourceFormat())),
770        withVer(getPreferredResourceFormat(), "1.0"), 
771        timeoutNormal);
772    result.addErrorStatus(410);// gone
773    result.addErrorStatus(404);// unknown
774    result.addErrorStatus(405);
775    result.addErrorStatus(422);// Unprocessable Entity
776    result.addSuccessStatus(200);
777    result.addSuccessStatus(201);
778    if (result.isUnsuccessfulRequest()) {
779      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
780          (OperationOutcome) result.getPayload());
781    }
782    return (Parameters) result.getPayload();
783  }
784
785  public ValueSet expandValueset(ValueSet source, Parameters expParams) {
786    recordUse();
787    List<Header> headers = null;
788    Parameters p = expParams == null ? new Parameters() : expParams.copy();
789    p.addParameter().setName("valueSet").setResource(source);
790    ResourceRequest<Resource> result = utils.issuePostRequest(
791        resourceAddress.resolveOperationUri(ValueSet.class, "expand"),
792        utils.getResourceAsByteArray(p, false, isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"),
793        headers, 4);
794    result.addErrorStatus(410); // gone
795    result.addErrorStatus(404); // unknown
796    result.addErrorStatus(405);
797    result.addErrorStatus(422); // Unprocessable Entity
798    result.addSuccessStatus(200);
799    result.addSuccessStatus(201);
800    if (result.isUnsuccessfulRequest()) {
801      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
802          (OperationOutcome) result.getPayload());
803    }
804    return (ValueSet) result.getPayload();
805  }
806
807  public String getAddress() {
808    return base;
809  }
810
811  public ConceptMap initializeClosure(String name) {
812    recordUse();
813    Parameters params = new Parameters();
814    params.addParameter().setName("name").setValue(new StringType(name));
815    List<Header> headers = null;
816    ResourceRequest<Resource> result = utils.issuePostRequest(
817        resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
818        utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"),
819        headers, timeoutNormal);
820    result.addErrorStatus(410);// gone
821    result.addErrorStatus(404);// unknown
822    result.addErrorStatus(405);
823    result.addErrorStatus(422);// Unprocessable Entity
824    result.addSuccessStatus(200);
825    result.addSuccessStatus(201);
826    if (result.isUnsuccessfulRequest()) {
827      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
828          (OperationOutcome) result.getPayload());
829    }
830    return (ConceptMap) result.getPayload();
831  }
832
833  public ConceptMap updateClosure(String name, Coding coding) {
834    recordUse();
835    Parameters params = new Parameters();
836    params.addParameter().setName("name").setValue(new StringType(name));
837    params.addParameter().setName("concept").setValue(coding);
838    List<Header> headers = null;
839    ResourceRequest<Resource> result = utils.issuePostRequest(
840        resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
841        utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"),
842        headers, timeoutOperation);
843    result.addErrorStatus(410);// gone
844    result.addErrorStatus(404);// unknown
845    result.addErrorStatus(405);
846    result.addErrorStatus(422);// Unprocessable Entity
847    result.addSuccessStatus(200);
848    result.addSuccessStatus(201);
849    if (result.isUnsuccessfulRequest()) {
850      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
851          (OperationOutcome) result.getPayload());
852    }
853    return (ConceptMap) result.getPayload();
854  }
855
856  public int getTimeout() {
857    return utils.getTimeout();
858  }
859
860  public void setTimeout(int timeout) {
861    utils.setTimeout(timeout);
862  }
863
864  public String getUsername() {
865    return utils.getUsername();
866  }
867
868  public void setUsername(String username) {
869    utils.setUsername(username);
870  }
871
872  public String getPassword() {
873    return utils.getPassword();
874  }
875
876  public void setPassword(String password) {
877    utils.setPassword(password);
878  }
879
880  public Parameters getTerminologyCapabilities() {
881    return (Parameters) utils
882        .issueGetResourceRequest(resourceAddress.resolveMetadataTxCaps(), withVer(getPreferredResourceFormat(), "1.0"), timeoutNormal)
883        .getReference();
884  }
885
886  public org.hl7.fhir.utilities.ToolingClientLogger getLogger() {
887    return utils.getLogger();
888  }
889
890  public void setLogger(ToolingClientLogger logger) {
891    utils.setLogger(logger);
892  }
893
894  public int getRetryCount() {
895    return utils.getRetryCount();
896  }
897
898  public void setRetryCount(int retryCount) {
899    utils.setRetryCount(retryCount);
900  }
901
902  public String getUserAgent() {
903    return utils.getUserAgent();
904  }
905
906  public void setUserAgent(String userAgent) {
907    utils.setUserAgent(userAgent);
908  }
909
910  public String getServerVersion() {
911    return conf == null ? null : conf.getSoftware().getVersion();
912  }
913
914  public void setContentLanguage(String lang) {
915    utils.setContentLanguage(lang);
916  }
917
918  public void setAcceptLanguage(String lang) {
919    utils.setAcceptLanguage(lang);
920  }
921
922  public int getUseCount() {
923    return useCount;
924  }
925
926  private void recordUse() {
927    useCount++;    
928  }
929
930  public Bundle search(String type, String criteria) {
931    recordUse();
932    return fetchFeed(Utilities.pathURL(base, type+criteria));
933  }
934 
935}