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