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