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, String userAgent) throws URISyntaxException {
104                preferredResourceFormat = ResourceFormat.RESOURCE_XML;
105    utils.setUserAgent(userAgent);
106    detectProxy();
107    initialize(baseServiceUrl);
108        }
109
110  public FHIRToolingClient(String baseServiceUrl, String userAgent, String username, String password) throws URISyntaxException {
111    preferredResourceFormat = ResourceFormat.RESOURCE_XML;
112    utils.setUserAgent(userAgent);
113    utils.setUsername(username);
114    utils.setPassword(password);
115    detectProxy();
116    initialize(baseServiceUrl);
117        }
118        
119        public void configureProxy(String proxyHost, int proxyPort) {
120                utils.setProxy(new HttpHost(proxyHost, proxyPort));
121        }
122
123  public void detectProxy() {
124                String host = System.getenv(hostKey);
125                String port = System.getenv(portKey);
126
127                if(host==null) {
128                        host = System.getProperty(hostKey);
129                }
130
131                if(port==null) {
132                        port = System.getProperty(portKey);
133                }
134
135                if(host!=null && port!=null) {
136                        this.configureProxy(host, Integer.parseInt(port));
137                }
138        }
139        
140        public void initialize(String baseServiceUrl)  throws URISyntaxException {
141          base = baseServiceUrl;
142                resourceAddress = new ResourceAddress(baseServiceUrl);
143                this.maxResultSetSize = -1;
144                checkConformance();
145        }
146        
147        private void checkConformance() {
148          try {
149      conf = getConformanceStatementQuick();
150          } catch (Throwable e) {
151          }
152   }
153
154  public String getPreferredResourceFormat() {
155    return preferredResourceFormat.getHeader();
156  }
157  
158        public void setPreferredResourceFormat(ResourceFormat resourceFormat) {
159                preferredResourceFormat = resourceFormat;
160        }
161        
162        public int getMaximumRecordCount() {
163                return maxResultSetSize;
164        }
165        
166        public void setMaximumRecordCount(int maxResultSetSize) {
167                this.maxResultSetSize = maxResultSetSize;
168        }
169        
170        public Conformance getConformanceStatement() throws EFhirClientException {
171                if (conf != null)
172                        return conf;
173                return getConformanceStatement(false);
174        }
175        
176        public Conformance getConformanceStatement(boolean useOptionsVerb) {
177                Conformance conformance = null;
178                try {
179                        if(useOptionsVerb) {
180                                conformance = (Conformance)utils.issueOptionsRequest(resourceAddress.getBaseServiceUri(), getPreferredResourceFormat(), TIMEOUT_NORMAL).getReference();//TODO fix this
181                        } else {
182                                conformance = (Conformance)utils.issueGetResourceRequest(resourceAddress.resolveMetadataUri(false), getPreferredResourceFormat(), TIMEOUT_NORMAL).getReference();
183                        }
184                } catch(Exception e) {
185                        handleException("An error has occurred while trying to fetch the server's conformance statement", e);
186                }
187                return conformance;
188        }
189        
190  public Conformance getConformanceStatementQuick() throws EFhirClientException {
191    if (conf != null)
192      return conf;
193    return getConformanceStatementQuick(false);
194  }
195  
196  public Conformance getConformanceStatementQuick(boolean useOptionsVerb) {
197    Conformance conformance = null;
198    try {
199      if(useOptionsVerb) {
200        conformance = (Conformance)utils.issueOptionsRequest(resourceAddress.getBaseServiceUri(), getPreferredResourceFormat(), TIMEOUT_NORMAL).getReference();//TODO fix this
201      } else {
202        conformance = (Conformance)utils.issueGetResourceRequest(resourceAddress.resolveMetadataUri(true), getPreferredResourceFormat(), TIMEOUT_NORMAL).getReference();
203      }
204    } catch(Exception e) {
205      handleException("An error has occurred while trying to fetch the server's conformance statement", e);
206    }
207    return conformance;
208  }
209  
210        public <T extends Resource> T read(Class<T> resourceClass, String id) {//TODO Change this to AddressableResource
211                ResourceRequest<T> result = null;
212                try {
213                        result = utils.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), getPreferredResourceFormat(), TIMEOUT_NORMAL);
214                        result.addErrorStatus(410);//gone
215                        result.addErrorStatus(404);//unknown
216                        result.addSuccessStatus(200);//Only one for now
217                        if(result.isUnsuccessfulRequest()) {
218                                throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
219                        }
220                } catch (Exception e) {
221                        handleException("An error has occurred while trying to read this resource", e);
222                }
223                return result.getPayload();
224        }
225
226        public <T extends Resource> T vread(Class<T> resourceClass, String id, String version) {
227                ResourceRequest<T> result = null;
228                try {
229                        result = utils.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version), getPreferredResourceFormat(), TIMEOUT_NORMAL);
230                        result.addErrorStatus(410);//gone
231                        result.addErrorStatus(404);//unknown
232                        result.addErrorStatus(405);//unknown
233                        result.addSuccessStatus(200);//Only one for now
234                        if(result.isUnsuccessfulRequest()) {
235                                throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
236                        }
237                } catch (Exception e) {
238                        handleException("An error has occurred while trying to read this version of the resource", e);
239                }
240                return result.getPayload();
241        }
242        
243        // GET fhir/ValueSet?url=http://hl7.org/fhir/ValueSet/clinical-findings&version=0.8
244
245  public <T extends Resource> T getCanonical(Class<T> resourceClass, String canonicalURL) {
246    ResourceRequest<T> result = null;
247    try {
248      result = utils.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndCanonical(resourceClass, canonicalURL), getPreferredResourceFormat(), TIMEOUT_NORMAL);
249      result.addErrorStatus(410);//gone
250      result.addErrorStatus(404);//unknown
251      result.addErrorStatus(405);//unknown
252      result.addSuccessStatus(200);//Only one for now
253      if(result.isUnsuccessfulRequest()) {
254        throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
255      }
256    } catch (Exception e) {
257      handleException("An error has occurred while trying to read this version of the resource", e);
258    }
259    Bundle bnd = (Bundle) result.getPayload();
260    if (bnd.getEntry().size() == 0)
261      throw new EFhirClientException("No matching resource found for canonical URL '"+canonicalURL+"'");
262    if (bnd.getEntry().size() > 1)
263      throw new EFhirClientException("Multiple matching resources found for canonical URL '"+canonicalURL+"'");
264    return (T) bnd.getEntry().get(0).getResource();
265  }
266  
267        
268  public Resource update(Resource resource) {
269    ResourceRequest<Resource> result = null;
270    try {
271      List<Header> headers = null;
272      result = utils.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()),utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, TIMEOUT_OPERATION);
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(resource.getClass(), 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        public <T extends Resource> T update(Class<T> resourceClass, T resource, String id) {
298                ResourceRequest<T> result = null;
299                try {
300                        List<Header> headers = null;
301                        result = utils.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, TIMEOUT_OPERATION);
302                        result.addErrorStatus(410);//gone
303                        result.addErrorStatus(404);//unknown
304                        result.addErrorStatus(405);
305                        result.addErrorStatus(422);//Unprocessable Entity
306                        result.addSuccessStatus(200);
307                        result.addSuccessStatus(201);
308                        if(result.isUnsuccessfulRequest()) {
309                                throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
310                        }
311                } catch(Exception e) {
312                        throw new EFhirClientException("An error has occurred while trying to update this resource", e);
313                }
314                // 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
315                try {
316                  OperationOutcome operationOutcome = (OperationOutcome)result.getPayload();
317                  ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress.parseCreateLocation(result.getLocation());
318                  return this.vread(resourceClass, resVersionedIdentifier.getId(),resVersionedIdentifier.getVersionId());
319                } catch(ClassCastException e) {
320                  // if we fall throught we have the correct type already in the create
321                }
322
323                return result.getPayload();
324        }
325
326//      
327//      public <T extends Resource> boolean delete(Class<T> resourceClass, String id) {
328//              try {
329//                      return utils.issueDeleteRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id), proxy);
330//              } catch(Exception e) {
331//                      throw new EFhirClientException("An error has occurred while trying to delete this resource", e);
332//              }
333//
334//      }
335
336//      
337//      public <T extends Resource> OperationOutcome create(Class<T> resourceClass, T resource) {
338//        ResourceRequest<T> resourceRequest = null;
339//        try {
340//          List<Header> headers = null;
341//          resourceRequest = utils.issuePostRequest(resourceAddress.resolveGetUriFromResourceClass(resourceClass),utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, proxy);
342//          resourceRequest.addSuccessStatus(201);
343//          if(resourceRequest.isUnsuccessfulRequest()) {
344//            throw new EFhirClientException("Server responded with HTTP error code " + resourceRequest.getHttpStatus(), (OperationOutcome)resourceRequest.getPayload());
345//          }
346//        } catch(Exception e) {
347//          handleException("An error has occurred while trying to create this resource", e);
348//        }
349//        OperationOutcome operationOutcome = null;;
350//        try {
351//          operationOutcome = (OperationOutcome)resourceRequest.getPayload();
352//          ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = 
353//              ResourceAddress.parseCreateLocation(resourceRequest.getLocation());
354//          OperationOutcomeIssueComponent issue = operationOutcome.addIssue();
355//          issue.setSeverity(IssueSeverity.INFORMATION);
356//          issue.setUserData(ResourceAddress.ResourceVersionedIdentifier.class.toString(),
357//              resVersionedIdentifier);
358//          return operationOutcome;
359//        } catch(ClassCastException e) {
360//          // some server (e.g. grahams) returns the resource directly
361//          operationOutcome = new OperationOutcome();
362//          OperationOutcomeIssueComponent issue = operationOutcome.addIssue();
363//          issue.setSeverity(IssueSeverity.INFORMATION);
364//          issue.setUserData(ResourceRequest.class.toString(),
365//              resourceRequest.getPayload());
366//          return operationOutcome;
367//        }     
368//      }
369
370//      
371//      public <T extends Resource> Bundle history(Calendar lastUpdate, Class<T> resourceClass, String id) {
372//              Bundle history = null;
373//              try {
374//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy);
375//              } catch (Exception e) {
376//                      handleException("An error has occurred while trying to retrieve history information for this resource", e);
377//              }
378//              return history;
379//      }
380
381//      
382//      public <T extends Resource> Bundle history(Date lastUpdate, Class<T> resourceClass, String id) {
383//              Bundle history = null;
384//              try {
385//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy);
386//              } catch (Exception e) {
387//                      handleException("An error has occurred while trying to retrieve history information for this resource", e);
388//              }
389//              return history;
390//      }
391//
392//      
393//      public <T extends Resource> Bundle history(Calendar lastUpdate, Class<T> resourceClass) {
394//              Bundle history = null;
395//              try {
396//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy);
397//              } catch (Exception e) {
398//                      handleException("An error has occurred while trying to retrieve history information for this resource type", e);
399//              }
400//              return history;
401//      }
402//      
403//      
404//      public <T extends Resource> Bundle history(Date lastUpdate, Class<T> resourceClass) {
405//              Bundle history = null;
406//              try {
407//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy);
408//              } catch (Exception e) {
409//                      handleException("An error has occurred while trying to retrieve history information for this resource type", e);
410//              }
411//              return history;
412//      }
413//      
414//      
415//      public <T extends Resource> Bundle history(Class<T> resourceClass) {
416//              Bundle history = null;
417//              try {
418//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceType(resourceClass, maxResultSetSize), getPreferredResourceFormat(), proxy);
419//              } catch (Exception e) {
420//                      handleException("An error has occurred while trying to retrieve history information for this resource type", e);
421//              }
422//              return history;
423//      }
424//      
425//      
426//      public <T extends Resource> Bundle history(Class<T> resourceClass, String id) {
427//              Bundle history = null;
428//              try {
429//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForResourceId(resourceClass, id, maxResultSetSize), getPreferredResourceFormat(), proxy);
430//              } catch (Exception e) {
431//                      handleException("An error has occurred while trying to retrieve history information for this resource", e);
432//              }
433//              return history;
434//      }
435//
436//      
437//      public <T extends Resource> Bundle history(Date lastUpdate) {
438//              Bundle history = null;
439//              try {
440//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy);
441//              } catch (Exception e) {
442//                      handleException("An error has occurred while trying to retrieve history since last update",e);
443//              }
444//              return history;
445//      }
446//
447//      
448//      public <T extends Resource> Bundle history(Calendar lastUpdate) {
449//              Bundle history = null;
450//              try {
451//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(lastUpdate, maxResultSetSize), getPreferredResourceFormat(), proxy);
452//              } catch (Exception e) {
453//                      handleException("An error has occurred while trying to retrieve history since last update",e);
454//              }
455//              return history;
456//      }
457//
458//      
459//      public <T extends Resource> Bundle history() {
460//              Bundle history = null;
461//              try {
462//                      history = utils.issueGetFeedRequest(resourceAddress.resolveGetHistoryForAllResources(maxResultSetSize), getPreferredResourceFormat(), proxy);
463//              } catch (Exception e) {
464//                      handleException("An error has occurred while trying to retrieve history since last update",e);
465//              }
466//              return history;
467//      }
468//
469//      
470//      public <T extends Resource> Bundle search(Class<T> resourceClass, Map<String, String> parameters) {
471//              Bundle searchResults = null;
472//              try {
473//                      searchResults = utils.issueGetFeedRequest(resourceAddress.resolveSearchUri(resourceClass, parameters), getPreferredResourceFormat(), proxy);
474//              } catch (Exception e) {
475//                      handleException("Error performing search with parameters " + parameters, e);
476//              }
477//              return searchResults;
478//      }
479//      
480//  
481//  public <T extends Resource> Bundle searchPost(Class<T> resourceClass, T resource, Map<String, String> parameters) {
482//    Bundle searchResults = null;
483//    try {
484//      searchResults = utils.issuePostFeedRequest(resourceAddress.resolveSearchUri(resourceClass, new HashMap<String, String>()), parameters, "src", resource, getPreferredResourceFormat());
485//    } catch (Exception e) {
486//      handleException("Error performing search with parameters " + parameters, e);
487//    }
488//    return searchResults;
489//  }
490        
491        
492  public <T extends Resource> Parameters operateType(Class<T> resourceClass, String name, Parameters params) {
493        boolean complex = false;
494        for (ParametersParameterComponent p : params.getParameter())
495                complex = complex || !(p.getValue() instanceof PrimitiveType);
496        Parameters searchResults = null;
497                        String ps = "";
498                try {
499      if (!complex)
500                        for (ParametersParameterComponent p : params.getParameter())
501                        if (p.getValue() instanceof PrimitiveType)
502                          ps += p.getName() + "=" + Utilities.encodeUri(((PrimitiveType) p.getValue()).asStringValue())+"&";
503                ResourceRequest<T> result;
504                if (complex)
505                        result = utils.issuePostRequest(resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps), utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), TIMEOUT_OPERATION_LONG);
506                else 
507                        result = utils.issueGetResourceRequest(resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps), getPreferredResourceFormat(), TIMEOUT_OPERATION_LONG);
508                        result.addErrorStatus(410);//gone
509                        result.addErrorStatus(404);//unknown
510                        result.addSuccessStatus(200);//Only one for now
511                        if(result.isUnsuccessfulRequest()) 
512                                throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
513                if (result.getPayload() instanceof Parameters)
514                        return (Parameters) result.getPayload();
515                else {
516                        Parameters p_out = new Parameters();
517                        p_out.addParameter().setName("return").setResource(result.getPayload());
518                        return p_out;
519                }
520                } catch (Exception e) {
521                        handleException("Error performing tx2 operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);                
522                }
523                return null;
524  }
525
526  
527        public Bundle transaction(Bundle batch) {
528                Bundle transactionResult = null;
529                try {
530                        transactionResult = utils.postBatchRequest(resourceAddress.getBaseServiceUri(), utils.getFeedAsByteArray(batch, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), TIMEOUT_NORMAL+batch.getEntry().size());
531                } catch (Exception e) {
532                        handleException("An error occurred trying to process this transaction request", e);
533                }
534                return transactionResult;
535        }
536        
537        @SuppressWarnings("unchecked")
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(), 3);
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) {
709    List<Header> headers = null;
710    ResourceRequest<Resource> result = utils.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand"),
711        utils.getResourceAsByteArray(source, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, TIMEOUT_OPERATION_LONG);
712    result.addErrorStatus(410);//gone
713    result.addErrorStatus(404);//unknown
714    result.addErrorStatus(405);
715    result.addErrorStatus(422);//Unprocessable Entity
716    result.addSuccessStatus(200);
717    result.addSuccessStatus(201);
718    if(result.isUnsuccessfulRequest()) {
719      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
720    }
721    return (ValueSet) result.getPayload();
722  }
723
724  
725  public Parameters lookupCode(Map<String, String> params) {
726    ResourceRequest<Resource> result = utils.issueGetResourceRequest(resourceAddress.resolveOperationUri(ValueSet.class, "lookup", params), getPreferredResourceFormat(), TIMEOUT_NORMAL);
727    result.addErrorStatus(410);//gone
728    result.addErrorStatus(404);//unknown
729    result.addErrorStatus(405);
730    result.addErrorStatus(422);//Unprocessable Entity
731    result.addSuccessStatus(200);
732    result.addSuccessStatus(201);
733    if(result.isUnsuccessfulRequest()) {
734      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
735    }
736    return (Parameters) result.getPayload();
737  }
738  public ValueSet expandValueset(ValueSet source, Parameters expParams,Map<String, String> params) {
739    List<Header> headers = null;
740    Parameters p = expParams == null ? new Parameters() : expParams.copy();
741    p.addParameter().setName("valueSet").setResource(source);
742    for (String n : params.keySet())
743      p.addParameter().setName(n).setValue(new StringType(params.get(n)));
744    ResourceRequest<Resource> result = utils.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand", params), 
745        utils.getResourceAsByteArray(p, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, 4);
746    result.addErrorStatus(410); //gone
747    result.addErrorStatus(404); //unknown
748    result.addErrorStatus(405);
749    result.addErrorStatus(422); //Unprocessable Entity
750    result.addSuccessStatus(200);
751    result.addSuccessStatus(201);
752    if(result.isUnsuccessfulRequest()) {
753      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
754    }
755    return (ValueSet) result.getPayload();
756  }
757  
758//  public ValueSet expandValueset(ValueSet source, ExpansionProfile profile, Map<String, String> params) {
759//    List<Header> headers = null;
760//    ResourceRequest<Resource> result = utils.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand", params), 
761//        utils.getResourceAsByteArray(source, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, proxy);
762//    result.addErrorStatus(410);//gone
763//    result.addErrorStatus(404);//unknown
764//    result.addErrorStatus(405);
765//    result.addErrorStatus(422);//Unprocessable Entity
766//    result.addSuccessStatus(200);
767//    result.addSuccessStatus(201);
768//    if(result.isUnsuccessfulRequest()) {
769//      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
770//    }
771//    return (ValueSet) result.getPayload();
772//  }
773  
774  
775  public String getAddress() {
776    return base;
777  }
778
779  public ConceptMap initializeClosure(String name) {
780    Parameters params = new Parameters();
781    params.addParameter().setName("name").setValue(new StringType(name));
782    List<Header> headers = null;
783    ResourceRequest<Resource> result = utils.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
784        utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, TIMEOUT_NORMAL);
785    result.addErrorStatus(410);//gone
786    result.addErrorStatus(404);//unknown
787    result.addErrorStatus(405);
788    result.addErrorStatus(422);//Unprocessable Entity
789    result.addSuccessStatus(200);
790    result.addSuccessStatus(201);
791    if(result.isUnsuccessfulRequest()) {
792      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
793    }
794    return (ConceptMap) result.getPayload();
795  }
796
797  public ConceptMap updateClosure(String name, Coding coding) {
798    Parameters params = new Parameters();
799    params.addParameter().setName("name").setValue(new StringType(name));
800    params.addParameter().setName("concept").setValue(coding);
801    List<Header> headers = null;
802    ResourceRequest<Resource> result = utils.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
803        utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), headers, TIMEOUT_OPERATION);
804    result.addErrorStatus(410);//gone
805    result.addErrorStatus(404);//unknown
806    result.addErrorStatus(405);
807    result.addErrorStatus(422);//Unprocessable Entity
808    result.addSuccessStatus(200);
809    result.addSuccessStatus(201);
810    if(result.isUnsuccessfulRequest()) {
811      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
812    }
813    return (ConceptMap) result.getPayload();
814  }
815
816  public int getTimeout() {
817    return utils.getTimeout();
818  }
819
820  public void setTimeout(int timeout) {
821    utils.setTimeout(timeout);
822  }
823
824  public String getUsername() {
825    return utils.getUsername();
826  }
827
828  public void setUsername(String username) {
829    utils.setUsername(username);
830  }
831
832  public String getPassword() {
833    return utils.getPassword();
834  }
835
836  public void setPassword(String password) {
837    utils.setPassword(password);
838  }
839
840  
841  public Parameters getTerminologyCapabilities() {
842    return (Parameters) utils.issueGetResourceRequest(resourceAddress.resolveMetadataTxCaps(), getPreferredResourceFormat(), TIMEOUT_NORMAL).getReference();
843  }
844
845
846  public org.hl7.fhir.utilities.ToolingClientLogger getLogger() {
847    return utils.getLogger();
848  }
849
850  public void setLogger(ToolingClientLogger logger) {
851    utils.setLogger(logger);
852  }
853
854  public int getRetryCount() {
855    return utils.getRetryCount();
856  }
857
858  public void setRetryCount(int retryCount) {
859    utils.setRetryCount(retryCount);
860  }
861
862  public String getUserAgent() {
863    return utils.getUserAgent();
864  }
865
866  public void setUserAgent(String userAgent) {
867    utils.setUserAgent(userAgent);
868  }
869
870  public String getServerVersion() {
871    return conf == null ? null : conf.getSoftware().getVersion();
872  }
873}