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