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