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