001package org.hl7.fhir.r4.utils.client;
002
003/*-
004 * #%L
005 * org.hl7.fhir.r4
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.r4.model.Bundle;
063import org.hl7.fhir.r4.model.CapabilityStatement;
064import org.hl7.fhir.r4.model.CodeSystem;
065import org.hl7.fhir.r4.model.Coding;
066import org.hl7.fhir.r4.model.ConceptMap;
067import org.hl7.fhir.r4.model.OperationOutcome;
068import org.hl7.fhir.r4.model.Parameters;
069import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
070import org.hl7.fhir.r4.model.PrimitiveType;
071import org.hl7.fhir.r4.model.Resource;
072import org.hl7.fhir.r4.model.StringType;
073import org.hl7.fhir.r4.model.TerminologyCapabilities;
074import org.hl7.fhir.r4.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 TerminologyCapabilities getTerminologyCapabilities() {
185    return (TerminologyCapabilities) utils.issueGetResourceRequest(resourceAddress.resolveMetadataTxCaps(), getPreferredResourceFormat()).getReference();
186        }
187        
188        public CapabilityStatement getCapabilitiesStatement() {
189          CapabilityStatement conformance = null;
190                try {
191                conformance = (CapabilityStatement)utils.issueGetResourceRequest(resourceAddress.resolveMetadataUri(false), getPreferredResourceFormat()).getReference();
192                } catch(Exception e) {
193                        handleException("An error has occurred while trying to fetch the server's conformance statement", e);
194                }
195                return conformance;
196        }
197        
198  public CapabilityStatement getCapabilitiesStatementQuick() throws EFhirClientException {
199    if (capabilities != null)
200      return capabilities;
201    try {
202      capabilities = (CapabilityStatement)utils.issueGetResourceRequest(resourceAddress.resolveMetadataUri(true), getPreferredResourceFormat()).getReference();
203    } catch(Exception e) {
204      handleException("An error has occurred while trying to fetch the server's conformance statement", e);
205    }
206    return capabilities;
207  }
208  
209        public <T extends Resource> T read(Class<T> resourceClass, String id) {//TODO Change this to AddressableResource
210                ResourceRequest<T> result = null;
211                try {
212                        result = utils.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), getPreferredResourceFormat());
213                        result.addErrorStatus(410);//gone
214                        result.addErrorStatus(404);//unknown
215                        result.addSuccessStatus(200);//Only one for now
216                        if(result.isUnsuccessfulRequest()) {
217                                throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
218                        }
219                } catch (Exception e) {
220                        handleException("An error has occurred while trying to read this resource", e);
221                }
222                return result.getPayload();
223        }
224
225        public <T extends Resource> T vread(Class<T> resourceClass, String id, String version) {
226                ResourceRequest<T> result = null;
227                try {
228                        result = utils.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version), getPreferredResourceFormat());
229                        result.addErrorStatus(410);//gone
230                        result.addErrorStatus(404);//unknown
231                        result.addErrorStatus(405);//unknown
232                        result.addSuccessStatus(200);//Only one for now
233                        if(result.isUnsuccessfulRequest()) {
234                                throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
235                        }
236                } catch (Exception e) {
237                        handleException("An error has occurred while trying to read this version of the resource", e);
238                }
239                return result.getPayload();
240        }
241        
242        // GET fhir/ValueSet?url=http://hl7.org/fhir/ValueSet/clinical-findings&version=0.8
243
244  public <T extends Resource> T getCanonical(Class<T> resourceClass, String canonicalURL) {
245    ResourceRequest<T> result = null;
246    try {
247      result = utils.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndCanonical(resourceClass, canonicalURL), getPreferredResourceFormat());
248      result.addErrorStatus(410);//gone
249      result.addErrorStatus(404);//unknown
250      result.addErrorStatus(405);//unknown
251      result.addSuccessStatus(200);//Only one for now
252      if(result.isUnsuccessfulRequest()) {
253        throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
254      }
255    } catch (Exception e) {
256      handleException("An error has occurred while trying to read this version of the resource", e);
257    }
258    Bundle bnd = (Bundle) result.getPayload();
259    if (bnd.getEntry().size() == 0)
260      throw new EFhirClientException("No matching resource found for canonical URL '"+canonicalURL+"'");
261    if (bnd.getEntry().size() > 1)
262      throw new EFhirClientException("Multiple matching resources found for canonical URL '"+canonicalURL+"'");
263    return (T) bnd.getEntry().get(0).getResource();
264  }
265  
266        
267  public Resource update(Resource resource) {
268    ResourceRequest<Resource> result = null;
269    try {
270      List<Header> headers = null;
271      result = utils.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()),utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers);
272      result.addErrorStatus(410);//gone
273      result.addErrorStatus(404);//unknown
274      result.addErrorStatus(405);
275      result.addErrorStatus(422);//Unprocessable Entity
276      result.addSuccessStatus(200);
277      result.addSuccessStatus(201);
278      if(result.isUnsuccessfulRequest()) {
279        throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
280      }
281    } catch(Exception e) {
282      throw new EFhirClientException("An error has occurred while trying to update this resource", e);
283    }
284    // TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader is returned with an operationOutcome would be returned (and not  the resource also) we make another read
285    try {
286      OperationOutcome operationOutcome = (OperationOutcome)result.getPayload();
287      ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress.parseCreateLocation(result.getLocation());
288      return this.vread(resource.getClass(), resVersionedIdentifier.getId(),resVersionedIdentifier.getVersionId());
289    } catch(ClassCastException e) {
290      // if we fall throught we have the correct type already in the create
291    }
292
293    return result.getPayload();
294  }
295
296        public <T extends Resource> T update(Class<T> resourceClass, T resource, String id) {
297                ResourceRequest<T> result = null;
298                try {
299                        List<Header> headers = null;
300                        result = utils.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers);
301                        result.addErrorStatus(410);//gone
302                        result.addErrorStatus(404);//unknown
303                        result.addErrorStatus(405);
304                        result.addErrorStatus(422);//Unprocessable Entity
305                        result.addSuccessStatus(200);
306                        result.addSuccessStatus(201);
307                        if(result.isUnsuccessfulRequest()) {
308                                throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
309                        }
310                } catch(Exception e) {
311                        throw new EFhirClientException("An error has occurred while trying to update this resource", e);
312                }
313                // TODO oe 26.1.2015 could be made nicer if only OperationOutcome       locationheader is returned with an operationOutcome would be returned (and not  the resource also) we make another read
314                try {
315                  OperationOutcome operationOutcome = (OperationOutcome)result.getPayload();
316                  ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress.parseCreateLocation(result.getLocation());
317                  return this.vread(resourceClass, resVersionedIdentifier.getId(),resVersionedIdentifier.getVersionId());
318                } catch(ClassCastException e) {
319                  // if we fall throught we have the correct type already in the create
320                }
321
322                return result.getPayload();
323        }
324
325//      
326//      public <T extends Resource> boolean delete(Class<T> resourceClass, String id) {
327//              try {
328//                      return utils.issueDeleteRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), proxy);
329//              } catch(Exception e) {
330//                      throw new EFhirClientException("An error has occurred while trying to delete this resource", e);
331//              }
332//
333//      }
334
335//      
336//      public <T extends Resource> OperationOutcome create(Class<T> resourceClass, T resource) {
337//        ResourceRequest<T> resourceRequest = null;
338//        try {
339//          List<Header> headers = null;
340//          resourceRequest = utils.issuePostRequest(resourceAddress.resolveGetUriFromResourceClass(resourceClass),utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, proxy);
341//          resourceRequest.addSuccessStatus(201);
342//          if(resourceRequest.isUnsuccessfulRequest()) {
343//            throw new EFhirClientException("Server responded with HTTP error code " + resourceRequest.getHttpStatus(), (OperationOutcome)resourceRequest.getPayload());
344//          }
345//        } catch(Exception e) {
346//          handleException("An error has occurred while trying to create this resource", e);
347//        }
348//        OperationOutcome operationOutcome = null;;
349//        try {
350//          operationOutcome = (OperationOutcome)resourceRequest.getPayload();
351//          ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = 
352//              ResourceAddress.parseCreateLocation(resourceRequest.getLocation());
353//          OperationOutcomeIssueComponent issue = operationOutcome.addIssue();
354//          issue.setSeverity(IssueSeverity.INFORMATION);
355//          issue.setUserData(ResourceAddress.ResourceVersionedIdentifier.class.toString(),
356//              resVersionedIdentifier);
357//          return operationOutcome;
358//        } catch(ClassCastException e) {
359//          // some server (e.g. grahams) returns the resource directly
360//          operationOutcome = new OperationOutcome();
361//          OperationOutcomeIssueComponent issue = operationOutcome.addIssue();
362//          issue.setSeverity(IssueSeverity.INFORMATION);
363//          issue.setUserData(ResourceRequest.class.toString(),
364//              resourceRequest.getPayload());
365//          return operationOutcome;
366//        }     
367//      }
368
369//      
370//      public <T extends Resource> Bundle history(Calendar lastUpdate, Class<T> resourceClass, String id) {
371//              Bundle history = null;
372//              try {
373//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy);
374//              } catch (Exception e) {
375//                      handleException("An error has occurred while trying to retrieve history information for this resource", e);
376//              }
377//              return history;
378//      }
379
380//      
381//      public <T extends Resource> Bundle history(Date lastUpdate, Class<T> resourceClass, String id) {
382//              Bundle history = null;
383//              try {
384//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy);
385//              } catch (Exception e) {
386//                      handleException("An error has occurred while trying to retrieve history information for this resource", e);
387//              }
388//              return history;
389//      }
390//
391//      
392//      public <T extends Resource> Bundle history(Calendar lastUpdate, Class<T> resourceClass) {
393//              Bundle history = null;
394//              try {
395//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy);
396//              } catch (Exception e) {
397//                      handleException("An error has occurred while trying to retrieve history information for this resource type", e);
398//              }
399//              return history;
400//      }
401//      
402//      
403//      public <T extends Resource> Bundle history(Date lastUpdate, Class<T> resourceClass) {
404//              Bundle history = null;
405//              try {
406//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy);
407//              } catch (Exception e) {
408//                      handleException("An error has occurred while trying to retrieve history information for this resource type", e);
409//              }
410//              return history;
411//      }
412//      
413//      
414//      public <T extends Resource> Bundle history(Class<T> resourceClass) {
415//              Bundle history = null;
416//              try {
417//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, maxResultSetSize), getPreferredResourceFormat(), proxy);
418//              } catch (Exception e) {
419//                      handleException("An error has occurred while trying to retrieve history information for this resource type", e);
420//              }
421//              return history;
422//      }
423//      
424//      
425//      public <T extends Resource> Bundle history(Class<T> resourceClass, String id) {
426//              Bundle history = null;
427//              try {
428//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, maxResultSetSize), getPreferredResourceFormat(), proxy);
429//              } catch (Exception e) {
430//                      handleException("An error has occurred while trying to retrieve history information for this resource", e);
431//              }
432//              return history;
433//      }
434//
435//      
436//      public <T extends Resource> Bundle history(Date lastUpdate) {
437//              Bundle history = null;
438//              try {
439//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy);
440//              } catch (Exception e) {
441//                      handleException("An error has occurred while trying to retrieve history since last update",e);
442//              }
443//              return history;
444//      }
445//
446//      
447//      public <T extends Resource> Bundle history(Calendar lastUpdate) {
448//              Bundle history = null;
449//              try {
450//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy);
451//              } catch (Exception e) {
452//                      handleException("An error has occurred while trying to retrieve history since last update",e);
453//              }
454//              return history;
455//      }
456//
457//      
458//      public <T extends Resource> Bundle history() {
459//              Bundle history = null;
460//              try {
461//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(maxResultSetSize), getPreferredResourceFormat(), proxy);
462//              } catch (Exception e) {
463//                      handleException("An error has occurred while trying to retrieve history since last update",e);
464//              }
465//              return history;
466//      }
467//
468//      
469//      public <T extends Resource> Bundle search(Class<T> resourceClass, Map<String, String> parameters) {
470//              Bundle searchResults = null;
471//              try {
472//                      searchResults = utils.issueGetFeedRequest(resourceAddress.resolveSearchUri(resourceClass, parameters), getPreferredResourceFormat(), proxy);
473//              } catch (Exception e) {
474//                      handleException("Error performing search with parameters " + parameters, e);
475//              }
476//              return searchResults;
477//      }
478//      
479//  
480//  public <T extends Resource> Bundle searchPost(Class<T> resourceClass, T resource, Map<String, String> parameters) {
481//    Bundle searchResults = null;
482//    try {
483//      searchResults = utils.issuePostFeedRequest(resourceAddress.resolveSearchUri(resourceClass, new HashMap<String, String>()), parameters, "src", resource, getPreferredResourceFormat());
484//    } catch (Exception e) {
485//      handleException("Error performing search with parameters " + parameters, e);
486//    }
487//    return searchResults;
488//  }
489        
490        
491  public <T extends Resource> Parameters operateType(Class<T> resourceClass, String name, Parameters params) {
492        boolean complex = false;
493        for (ParametersParameterComponent p : params.getParameter())
494                complex = complex || !(p.getValue() instanceof PrimitiveType);
495        Parameters searchResults = null;
496                        String ps = "";
497                try {
498      if (!complex)
499                        for (ParametersParameterComponent p : params.getParameter())
500                        if (p.getValue() instanceof PrimitiveType)
501                          ps += p.getName() + "=" + Utilities.encodeUri(((PrimitiveType) p.getValue()).asStringValue())+"&";
502                ResourceRequest<T> result;
503                if (complex)
504                        result = utils.issuePostRequest(resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps), utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat());
505                else 
506                        result = utils.issueGetResourceRequest(resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps), getPreferredResourceFormat());
507                        result.addErrorStatus(410);//gone
508                        result.addErrorStatus(404);//unknown
509                        result.addSuccessStatus(200);//Only one for now
510                        if(result.isUnsuccessfulRequest()) 
511                                throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
512                if (result.getPayload() instanceof Parameters)
513                        return (Parameters) result.getPayload();
514                else {
515                        Parameters p_out = new Parameters();
516                        p_out.addParameter().setName("return").setResource(result.getPayload());
517                        return p_out;
518                }
519                } catch (Exception e) {
520                        handleException("Error performing operation '"+name+"' with parameters " + ps, e);              
521                }
522                return null;
523  }
524
525  
526        public Bundle transaction(Bundle batch) {
527                Bundle transactionResult = null;
528                try {
529                        transactionResult = utils.postBatchRequest(resourceAddress.getBaseServiceUri(), utils.getFeedAsByteArray(batch, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat());
530                } catch (Exception e) {
531                        handleException("An error occurred trying to process this transaction request", e);
532                }
533                return transactionResult;
534        }
535        
536        @SuppressWarnings("unchecked")
537        
538        public <T extends Resource> OperationOutcome validate(Class<T> resourceClass, T resource, String id) {
539                ResourceRequest<T> result = null;
540                try {
541                        result = utils.issuePostRequest(resourceAddress.resolveValidateUri(resourceClass, id), utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat());
542                        result.addErrorStatus(400);//gone
543                        result.addErrorStatus(422);//Unprocessable Entity
544                        result.addSuccessStatus(200);//OK
545                        if(result.isUnsuccessfulRequest()) {
546                                throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
547                        }
548                } catch(Exception e) {
549                        handleException("An error has occurred while trying to validate this resource", e);
550                }
551                return (OperationOutcome)result.getPayload();
552        }
553        
554        /* change to meta operations
555        
556        public List<Coding> getAllTags() {
557                TagListRequest result = null;
558                try {
559                        result = utils.issueGetRequestForTagList(resourceAddress.resolveGetAllTags(), getPreferredResourceFormat(), null, proxy);
560                } catch (Exception e) {
561                        handleException("An error has occurred while trying to retrieve all tags", e);
562                }
563                return result.getPayload();
564        }
565        
566        
567        public <T extends Resource> List<Coding> getAllTagsForResourceType(Class<T> resourceClass) {
568                TagListRequest result = null;
569                try {
570                        result = utils.issueGetRequestForTagList(resourceAddress.resolveGetAllTagsForResourceType(resourceClass), getPreferredResourceFormat(), null, proxy);
571                } catch (Exception e) {
572                        handleException("An error has occurred while trying to retrieve tags for this resource type", e);
573                }
574                return result.getPayload();
575        }
576        
577        
578        public <T extends Resource> List<Coding> getTagsForReference(Class<T> resource, String id) {
579                TagListRequest result = null;
580                try {
581                        result = utils.issueGetRequestForTagList(resourceAddress.resolveGetTagsForReference(resource, id), getPreferredResourceFormat(), null, proxy);
582                } catch (Exception e) {
583                        handleException("An error has occurred while trying to retrieve tags for this resource", e);
584                }
585                return result.getPayload();
586        }
587        
588        
589        public <T extends Resource> List<Coding> getTagsForResourceVersion(Class<T> resource, String id, String versionId) {
590                TagListRequest result = null;
591                try {
592                        result = utils.issueGetRequestForTagList(resourceAddress.resolveGetTagsForResourceVersion(resource, id, versionId), getPreferredResourceFormat(), null, proxy);
593                } catch (Exception e) {
594                        handleException("An error has occurred while trying to retrieve tags for this resource version", e);
595                }
596                return result.getPayload();
597        }
598        
599//      
600//      public <T extends Resource> boolean deleteTagsForReference(Class<T> resourceClass, String id) {
601//              try {
602//                      return utils.issueDeleteRequest(resourceAddress.resolveGetTagsForReference(resourceClass, id), proxy);
603//              } catch(Exception e) {
604//                      handleException("An error has occurred while trying to retrieve tags for this resource version", e);
605//                      throw new EFhirClientException("An error has occurred while trying to delete this resource", e);
606//              }
607//
608//      }
609//      
610//      
611//      public <T extends Resource> boolean deleteTagsForResourceVersion(Class<T> resourceClass, String id, List<Coding> tags, String version) {
612//              try {
613//                      return utils.issueDeleteRequest(resourceAddress.resolveGetTagsForResourceVersion(resourceClass, id, version), proxy);
614//              } catch(Exception e) {
615//                      handleException("An error has occurred while trying to retrieve tags for this resource version", e);
616//                      throw new EFhirClientException("An error has occurred while trying to delete this resource", e);
617//              }
618//      }
619        
620        
621        public <T extends Resource> List<Coding> createTags(List<Coding> tags, Class<T> resourceClass, String id) {
622                TagListRequest request = null;
623                try {
624                        request = utils.issuePostRequestForTagList(resourceAddress.resolveGetTagsForReference(resourceClass, id),utils.getTagListAsByteArray(tags, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), null, proxy);
625                        request.addSuccessStatus(201);
626                        request.addSuccessStatus(200);
627                        if(request.isUnsuccessfulRequest()) {
628                                throw new EFhirClientException("Server responded with HTTP error code " + request.getHttpStatus());
629                        }
630                } catch(Exception e) {
631                        handleException("An error has occurred while trying to set tags for this resource", e);
632                }
633                return request.getPayload();
634        }
635        
636        
637        public <T extends Resource> List<Coding> createTags(List<Coding> tags, Class<T> resourceClass, String id, String version) {
638                TagListRequest request = null;
639                try {
640                        request = utils.issuePostRequestForTagList(resourceAddress.resolveGetTagsForResourceVersion(resourceClass, id, version),utils.getTagListAsByteArray(tags, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), null, proxy);
641                        request.addSuccessStatus(201);
642                        request.addSuccessStatus(200);
643                        if(request.isUnsuccessfulRequest()) {
644                                throw new EFhirClientException("Server responded with HTTP error code " + request.getHttpStatus());
645                        }
646                } catch(Exception e) {
647                        handleException("An error has occurred while trying to set the tags for this resource version", e);
648                }
649                return request.getPayload();
650        }
651
652        
653        public <T extends Resource> List<Coding> deleteTags(List<Coding> tags, Class<T> resourceClass, String id, String version) {
654                TagListRequest request = null;
655                try {
656                        request = utils.issuePostRequestForTagList(resourceAddress.resolveDeleteTagsForResourceVersion(resourceClass, id, version),utils.getTagListAsByteArray(tags, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), null, proxy);
657                        request.addSuccessStatus(201);
658                        request.addSuccessStatus(200);
659                        if(request.isUnsuccessfulRequest()) {
660                                throw new EFhirClientException("Server responded with HTTP error code " + request.getHttpStatus());
661                        }
662                } catch(Exception e) {
663                        handleException("An error has occurred while trying to delete the tags for this resource version", e);
664                }
665                return request.getPayload();
666        }
667        */
668
669        /**
670         * Helper method to prevent nesting of previously thrown EFhirClientExceptions
671         * 
672         * @param e
673         * @throws EFhirClientException
674         */
675        protected void handleException(String message, Exception e) throws EFhirClientException {
676                if(e instanceof EFhirClientException) {
677                        throw (EFhirClientException)e;
678                } else {
679                        throw new EFhirClientException(message, e);
680                }
681        }
682        
683        /**
684         * Helper method to determine whether desired resource representation
685         * is Json or XML.
686         * 
687         * @param format
688         * @return
689         */
690        protected boolean isJson(String format) {
691                boolean isJson = false;
692                if(format.toLowerCase().contains("json")) {
693                        isJson = true;
694                }
695                return isJson;
696        }
697                
698  public Bundle fetchFeed(String url) {
699                Bundle feed = null;
700                try {
701                        feed = utils.issueGetFeedRequest(new URI(url), getPreferredResourceFormat());
702                } catch (Exception e) {
703                        handleException("An error has occurred while trying to retrieve history since last update",e);
704                }
705                return feed;
706  }
707  
708  public ValueSet expandValueset(ValueSet source, Parameters expParams) {
709    List<Header> headers = null;
710    Parameters p = expParams == null ? new Parameters() : expParams.copy();
711    p.addParameter().setName("valueSet").setResource(source);
712    ResourceRequest<Resource> result = utils.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand"), 
713        utils.getResourceAsByteArray(p, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers);
714    result.addErrorStatus(410);//gone
715    result.addErrorStatus(404);//unknown
716    result.addErrorStatus(405);
717    result.addErrorStatus(422);//Unprocessable Entity
718    result.addSuccessStatus(200);
719    result.addSuccessStatus(201);
720    if(result.isUnsuccessfulRequest()) {
721      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
722    }
723    return (ValueSet) result.getPayload();
724  }
725
726  
727  public Parameters lookupCode(Map<String, String> params) {
728    ResourceRequest<Resource> result = utils.issueGetResourceRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup", params), getPreferredResourceFormat());
729    result.addErrorStatus(410);//gone
730    result.addErrorStatus(404);//unknown
731    result.addErrorStatus(405);
732    result.addErrorStatus(422);//Unprocessable Entity
733    result.addSuccessStatus(200);
734    result.addSuccessStatus(201);
735    if(result.isUnsuccessfulRequest()) {
736      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
737    }
738    return (Parameters) result.getPayload();
739  }
740  public ValueSet expandValueset(ValueSet source, Parameters expParams, Map<String, String> params) {
741    List<Header> headers = null;
742    Parameters p = expParams == null ? new Parameters() : expParams.copy();
743    p.addParameter().setName("valueSet").setResource(source);
744    for (String n : params.keySet())
745      p.addParameter().setName(n).setValue(new StringType(params.get(n)));
746    ResourceRequest<Resource> result = utils.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand", params), 
747        utils.getResourceAsByteArray(p, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers);
748    result.addErrorStatus(410);//gone
749    result.addErrorStatus(404);//unknown
750    result.addErrorStatus(405);
751    result.addErrorStatus(422);//Unprocessable Entity
752    result.addSuccessStatus(200);
753    result.addSuccessStatus(201);
754    if(result.isUnsuccessfulRequest()) {
755      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
756    }
757    return (ValueSet) result.getPayload();
758  }
759  
760//  public ValueSet expandValueset(ValueSet source, ExpansionProfile profile, Map<String, String> params) {
761//    List<Header> headers = null;
762//    ResourceRequest<Resource> result = utils.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand", params), 
763//        utils.getResourceAsByteArray(source, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, proxy);
764//    result.addErrorStatus(410);//gone
765//    result.addErrorStatus(404);//unknown
766//    result.addErrorStatus(405);
767//    result.addErrorStatus(422);//Unprocessable Entity
768//    result.addSuccessStatus(200);
769//    result.addSuccessStatus(201);
770//    if(result.isUnsuccessfulRequest()) {
771//      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
772//    }
773//    return (ValueSet) result.getPayload();
774//  }
775  
776  
777  public String getAddress() {
778    return base;
779  }
780
781  public ConceptMap initializeClosure(String name) {
782    Parameters params = new Parameters();
783    params.addParameter().setName("name").setValue(new StringType(name));
784    List<Header> headers = null;
785    ResourceRequest<Resource> result = utils.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
786        utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers);
787    result.addErrorStatus(410);//gone
788    result.addErrorStatus(404);//unknown
789    result.addErrorStatus(405);
790    result.addErrorStatus(422);//Unprocessable Entity
791    result.addSuccessStatus(200);
792    result.addSuccessStatus(201);
793    if(result.isUnsuccessfulRequest()) {
794      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
795    }
796    return (ConceptMap) result.getPayload();
797  }
798
799  public ConceptMap updateClosure(String name, Coding coding) {
800    Parameters params = new Parameters();
801    params.addParameter().setName("name").setValue(new StringType(name));
802    params.addParameter().setName("concept").setValue(coding);
803    List<Header> headers = null;
804    ResourceRequest<Resource> result = utils.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
805        utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers);
806    result.addErrorStatus(410);//gone
807    result.addErrorStatus(404);//unknown
808    result.addErrorStatus(405);
809    result.addErrorStatus(422);//Unprocessable Entity
810    result.addSuccessStatus(200);
811    result.addSuccessStatus(201);
812    if(result.isUnsuccessfulRequest()) {
813      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
814    }
815    return (ConceptMap) result.getPayload();
816  }
817
818  public int getTimeout() {
819    return utils.getTimeout();
820  }
821
822  public void setTimeout(int timeout) {
823    utils.setTimeout(timeout);
824  }
825
826  public String getUsername() {
827    return utils.getUsername();
828  }
829
830  public void setUsername(String username) {
831    utils.setUsername(username);
832  }
833
834  public String getPassword() {
835    return utils.getPassword();
836  }
837
838  public void setPassword(String password) {
839    utils.setPassword(password);
840  }
841
842  public ToolingClientLogger getLogger() {
843    return utils.getLogger();
844  }
845
846  public void setLogger(ToolingClientLogger logger) {
847    utils.setLogger(logger);
848  }
849
850
851}