001package org.hl7.fhir.r5.utils.client;
002
003import lombok.Getter;
004import lombok.Setter;
005import org.hl7.fhir.exceptions.FHIRException;
006import org.hl7.fhir.r5.model.Bundle;
007import org.hl7.fhir.r5.model.OperationOutcome;
008import org.hl7.fhir.r5.model.Resource;
009
010/*
011  Copyright (c) 2011+, HL7, Inc.
012  All rights reserved.
013  
014  Redistribution and use in source and binary forms, with or without modification, 
015  are permitted provided that the following conditions are met:
016  
017   * Redistributions of source code must retain the above copyright notice, this 
018     list of conditions and the following disclaimer.
019   * Redistributions in binary form must reproduce the above copyright notice, 
020     this list of conditions and the following disclaimer in the documentation 
021     and/or other materials provided with the distribution.
022   * Neither the name of HL7 nor the names of its contributors may be used to 
023     endorse or promote products derived from this software without specific 
024     prior written permission.
025  
026  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
027  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
028  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
029  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
030  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
031  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
032  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
033  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
034  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
035  POSSIBILITY OF SUCH DAMAGE.
036  
037*/
038
039import org.hl7.fhir.r5.model.*;
040import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
041import org.hl7.fhir.r5.utils.client.network.ByteUtils;
042import org.hl7.fhir.r5.utils.client.network.Client;
043import org.hl7.fhir.r5.utils.client.network.ResourceRequest;
044import org.hl7.fhir.utilities.FHIRBaseToolingClient;
045import org.hl7.fhir.utilities.ToolingClientLogger;
046import org.hl7.fhir.utilities.Utilities;
047import org.hl7.fhir.utilities.http.HTTPHeader;
048import org.slf4j.Logger;
049import org.slf4j.LoggerFactory;
050
051import java.io.IOException;
052import java.net.URI;
053import java.net.URISyntaxException;
054import java.util.*;
055import java.util.stream.Collectors;
056import java.util.stream.Stream;
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 * <p>
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 * <p>
070 * Default Accept and Content-Type headers are application/fhir+xml and application/fhir+json.
071 * <p>
072 * These can be changed by invoking the following setter functions:
073 *
074 * <pre><code>
075 * setPreferredResourceFormat()
076 * setPreferredFeedFormat()
077 * </code></pre>
078 * <p>
079 * TODO Review all sad paths.
080 *
081 * @author Claude Nanjo
082 */
083public class FHIRToolingClient extends FHIRBaseToolingClient {
084
085  private static final Logger logger = LoggerFactory.getLogger(FHIRToolingClient.class);
086
087
088  public static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssK";
089  public static final String DATE_FORMAT = "yyyy-MM-dd";
090  public static final String hostKey = "http.proxyHost";
091  public static final String portKey = "http.proxyPort";
092
093  private String base;
094  private ResourceAddress resourceAddress;
095  @Setter
096  private ResourceFormat preferredResourceFormat;
097  private int maxResultSetSize = -1;//_count
098  private CapabilityStatement capabilities;
099  @Getter
100  @Setter
101  private Client client = new Client();
102  private List<HTTPHeader> headers = new ArrayList<>();
103
104  @Setter
105  @Getter
106  private String userAgent;
107
108  @Setter
109  private String acceptLanguage;
110
111  @Setter
112  private String contentLanguage;
113
114
115  private int useCount;
116
117
118  //Pass endpoint for client - URI
119  public FHIRToolingClient(String baseServiceUrl, String userAgent) throws URISyntaxException {
120    preferredResourceFormat = ResourceFormat.RESOURCE_JSON;
121    this.userAgent = userAgent;
122    initialize(baseServiceUrl);
123  }
124
125  public void initialize(String baseServiceUrl) throws URISyntaxException {
126    base = baseServiceUrl;
127    client.setBase(base);
128    resourceAddress = new ResourceAddress(baseServiceUrl);
129    this.maxResultSetSize = -1;
130  }
131
132  public String getPreferredResourceFormat() {
133    return preferredResourceFormat.getHeader();
134  }
135
136  public int getMaximumRecordCount() {
137    return maxResultSetSize;
138  }
139
140  public void setMaximumRecordCount(int maxResultSetSize) {
141    this.maxResultSetSize = maxResultSetSize;
142  }
143
144  private List<ResourceFormat> getResourceFormatsWithPreferredFirst() {
145    return Stream.concat(
146      Arrays.stream(new ResourceFormat[]{preferredResourceFormat}),
147      Arrays.stream(ResourceFormat.values()).filter(a -> a != preferredResourceFormat)
148    ).collect(Collectors.toList());
149  }
150
151  private <T extends Resource> T getCapabilities(URI resourceUri, String message, String exceptionMessage) throws FHIRException {
152    final List<ResourceFormat> resourceFormats = getResourceFormatsWithPreferredFirst();
153
154    for (ResourceFormat attemptedResourceFormat : resourceFormats) {
155      try {
156        T output =  (T) client.issueGetResourceRequest(resourceUri,
157          withVer(preferredResourceFormat.getHeader(), "5.0"),
158          generateHeaders(false),
159          message,
160          timeoutNormal).getReference();
161        if (attemptedResourceFormat != preferredResourceFormat) {
162          setPreferredResourceFormat(attemptedResourceFormat);
163        }
164        return output;
165      } catch (Exception e) {
166        logger.warn("Failed attempt to fetch " + resourceUri, e);
167      }
168    }
169    throw new FHIRException(exceptionMessage);
170  }
171
172  public TerminologyCapabilities getTerminologyCapabilities() {
173    TerminologyCapabilities capabilities = null;
174
175    try {
176      capabilities = getCapabilities(resourceAddress.resolveMetadataTxCaps(),
177        "TerminologyCapabilities",
178        "Error fetching the server's terminology capabilities");
179    } catch (ClassCastException e) {
180      throw new FHIRException("Unexpected response format for Terminology Capability metadata", e);
181    }
182    return capabilities;
183  }
184
185  public CapabilityStatement getCapabilitiesStatement() {
186    CapabilityStatement capabilityStatement = null;
187
188      capabilityStatement = getCapabilities(resourceAddress.resolveMetadataUri(false),
189
190        "CapabilitiesStatement", "Error fetching the server's conformance statement");
191    return capabilityStatement;
192  }
193
194  public CapabilityStatement getCapabilitiesStatementQuick() throws EFhirClientException {
195    if (capabilities != null) return capabilities;
196
197       capabilities = getCapabilities(resourceAddress.resolveMetadataUri(true),
198
199        "CapabilitiesStatement-Quick",
200        "Error fetching the server's capability statement");
201
202    return capabilities;
203  }
204
205  public Resource read(String resourceClass, String id) {// TODO Change this to AddressableResource
206    recordUse();
207    ResourceRequest<Resource> result = null;
208    try {
209      result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
210          withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), "Read " + resourceClass + "/" + id,
211          timeoutNormal);
212      if (result.isUnsuccessfulRequest()) {
213        throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
214            (OperationOutcome) result.getPayload());
215      }
216    } catch (Exception e) {
217      throw new FHIRException(e);
218    }
219    return result.getPayload();
220  }
221
222  
223  public <T extends Resource> T read(Class<T> resourceClass, String id) {//TODO Change this to AddressableResource
224    recordUse();
225    ResourceRequest<T> result = null;
226    try {
227      result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
228        withVer(getPreferredResourceFormat(), "4.0"),
229        generateHeaders(false),
230        "Read " + resourceClass.getName() + "/" + id,
231        timeoutNormal);
232      if (result.isUnsuccessfulRequest()) {
233        throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
234      }
235    } catch (Exception e) {
236      throw new FHIRException(e);
237    }
238    return result.getPayload();
239  }
240
241  public <T extends Resource> T vread(Class<T> resourceClass, String id, String version) {
242    recordUse();
243    ResourceRequest<T> result = null;
244    try {
245      result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version),
246        withVer(getPreferredResourceFormat(), "4.0"),
247        generateHeaders(false),
248        "VRead " + resourceClass.getName() + "/" + id + "/?_history/" + version,
249        timeoutNormal);
250      if (result.isUnsuccessfulRequest()) {
251        throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
252      }
253    } catch (Exception e) {
254      throw new FHIRException("Error trying to read this version of the resource", e);
255    }
256    return result.getPayload();
257  }
258
259  public <T extends Resource> T getCanonical(Class<T> resourceClass, String canonicalURL) {
260    recordUse();
261    ResourceRequest<T> result = null;
262    try {
263      result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndCanonical(resourceClass, canonicalURL),
264        withVer(getPreferredResourceFormat(), "4.0"),
265        generateHeaders(false),
266        "Read " + resourceClass.getName() + "?url=" + canonicalURL,
267        timeoutNormal);
268      if (result.isUnsuccessfulRequest()) {
269        throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
270      }
271    } catch (Exception e) {
272      handleException(0, "An error has occurred while trying to read this version of the resource", e);
273    }
274    Bundle bnd = (Bundle) result.getPayload();
275    if (bnd.getEntry().size() == 0)
276      throw new EFhirClientException(0, "No matching resource found for canonical URL '" + canonicalURL + "'");
277    if (bnd.getEntry().size() > 1)
278      throw new EFhirClientException(0, "Multiple matching resources found for canonical URL '" + canonicalURL + "'");
279    return (T) bnd.getEntry().get(0).getResource();
280  }
281
282  public Resource update(Resource resource) {
283    recordUse();
284    org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null;
285    try {
286      result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()),
287        ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false),
288        withVer(getPreferredResourceFormat(), "4.0"),
289        generateHeaders(true),
290        "Update " + resource.fhirType() + "/" + resource.getId(),
291        timeoutOperation);
292      if (result.isUnsuccessfulRequest()) {
293        throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
294      }
295    } catch (Exception e) {
296      throw new EFhirClientException(0, "An error has occurred while trying to update this resource", e);
297    }
298    // 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
299    try {
300      OperationOutcome operationOutcome = (OperationOutcome) result.getPayload();
301      ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress.parseCreateLocation(result.getLocation());
302      return this.vread(resource.getClass(), resVersionedIdentifier.getId(), resVersionedIdentifier.getVersionId());
303    } catch (ClassCastException e) {
304      // if we fall throught we have the correct type already in the create
305    }
306
307    return result.getPayload();
308  }
309
310  public <T extends Resource> T update(Class<T> resourceClass, T resource, String id) {
311    recordUse();
312    ResourceRequest<T> result = null;
313    try {
314      result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
315        ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false),
316        withVer(getPreferredResourceFormat(), "4.0"),
317        generateHeaders(true),
318        "Update " + resource.fhirType() + "/" + id,
319        timeoutOperation);
320      if (result.isUnsuccessfulRequest()) {
321        throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
322      }
323    } catch (Exception e) {
324      throw new EFhirClientException(0, "An error has occurred while trying to update this resource", e);
325    }
326    // 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
327    try {
328      OperationOutcome operationOutcome = (OperationOutcome) result.getPayload();
329      ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress.parseCreateLocation(result.getLocation());
330      return this.vread(resourceClass, resVersionedIdentifier.getId(), resVersionedIdentifier.getVersionId());
331    } catch (ClassCastException e) {
332      // if we fall through we have the correct type already in the create
333    }
334
335    return result.getPayload();
336  }
337
338  public <T extends Resource> Parameters operateType(Class<T> resourceClass, String name, Parameters params) {
339    recordUse();
340    boolean complex = false;
341    for (ParametersParameterComponent p : params.getParameter())
342      complex = complex || !(p.getValue() instanceof PrimitiveType);
343    String ps = "";
344    try {
345      if (!complex)
346        for (ParametersParameterComponent p : params.getParameter())
347          if (p.getValue() instanceof PrimitiveType)
348            ps += p.getName() + "=" + Utilities.encodeUriParam(((PrimitiveType) p.getValue()).asStringValue()) + "&";
349      ResourceRequest<T> result;
350      URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps);
351      if (complex) {
352        byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true);
353        result = client.issuePostRequest(url, body, withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(true),
354            "POST " + resourceClass.getName() + "/$" + name, timeoutLong);
355      } else {
356        result = client.issueGetResourceRequest(url, withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), "GET " + resourceClass.getName() + "/$" + name, timeoutLong);
357      }
358      if (result.isUnsuccessfulRequest()) {
359        throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
360      }
361      if (result.getPayload() instanceof Parameters) {
362        return (Parameters) result.getPayload();
363      } else {
364        Parameters p_out = new Parameters();
365        p_out.addParameter().setName("return").setResource(result.getPayload());
366        return p_out;
367      }
368    } catch (Exception e) {
369      handleException(0, "Error performing tx5 operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);               
370    }
371    return null;
372  }
373
374  public Bundle transaction(Bundle batch) {
375    recordUse();
376    Bundle transactionResult = null;
377    try {
378      transactionResult = client.postBatchRequest(resourceAddress.getBaseServiceUri(), ByteUtils.resourceToByteArray(batch, false, isJson(getPreferredResourceFormat()), false), withVer(getPreferredResourceFormat(), "4.0"),
379          generateHeaders(true),
380          "transaction", timeoutOperation + (timeoutEntry * batch.getEntry().size()));
381    } catch (Exception e) {
382      handleException(0, "An error occurred trying to process this transaction request", e);
383    }
384    return transactionResult;
385  }
386
387  @SuppressWarnings("unchecked")
388  public <T extends Resource> OperationOutcome validate(Class<T> resourceClass, T resource, String id) {
389    recordUse();
390    ResourceRequest<T> result = null;
391    try {
392      result = client.issuePostRequest(resourceAddress.resolveValidateUri(resourceClass, id),
393        ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false),
394        withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(true),
395        "POST " + resourceClass.getName() + (id != null ? "/" + id : "") + "/$validate", timeoutLong);
396      if (result.isUnsuccessfulRequest()) {
397        throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
398      }
399    } catch (Exception e) {
400      handleException(0, "An error has occurred while trying to validate this resource", e);
401    }
402    return (OperationOutcome) result.getPayload();
403  }
404
405  /**
406   * Helper method to prevent nesting of previously thrown EFhirClientExceptions. If the e param is an instance of
407   * EFhirClientException, it will be rethrown. Otherwise, a new EFhirClientException will be thrown with e as the
408   * cause.
409   *
410   * @param code The EFhirClientException code.
411   * @param message The EFhirClientException message.
412   * @param e The exception.
413   * @throws EFhirClientException representing the exception.
414   */
415  protected void handleException(int code, String message, Exception e) throws EFhirClientException {
416    if (e instanceof EFhirClientException) {
417      throw (EFhirClientException) e;
418    } else {
419      throw new EFhirClientException(code, message, e);
420    }
421  }
422
423  /**
424   * Helper method to determine whether desired resource representation
425   * is Json or XML.
426   *
427   * @param format The format
428   * @return true if the format is JSON, false otherwise
429   */
430  protected boolean isJson(String format) {
431    boolean isJson = false;
432    if (format.toLowerCase().contains("json")) {
433      isJson = true;
434    }
435    return isJson;
436  }
437
438  public Bundle fetchFeed(String url) {
439    recordUse();
440    Bundle feed = null;
441    try {
442      feed = client.issueGetFeedRequest(new URI(url), getPreferredResourceFormat());
443    } catch (Exception e) {
444      handleException(0, "An error has occurred while trying to read a bundle", e);
445    }
446    return feed;
447  }
448
449  public ValueSet expandValueset(ValueSet source, Parameters expParams) {
450    recordUse();
451    Parameters p = expParams == null ? new Parameters() : expParams.copy();
452    if (source != null) {
453      p.addParameter().setName("valueSet").setResource(source);
454    }
455    org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null;
456    try {
457      result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand"),
458          ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat()), true),
459          withVer(getPreferredResourceFormat(), "4.0"),
460          generateHeaders(true),
461          source == null ? "ValueSet/$expand" : "ValueSet/$expand?url=" + source.getUrl(),
462          timeoutExpand);
463    } catch (IOException e) {
464      throw new FHIRException(e);
465    }
466    if (result.isUnsuccessfulRequest()) {
467      throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
468    }
469    return result == null ? null : (ValueSet) result.getPayload();
470  }
471
472  public Parameters lookupCode(Map<String, String> params) {
473    recordUse();
474    org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null;
475    try {
476      result = client.issueGetResourceRequest(resourceAddress.resolveOperationUriNoEscape(CodeSystem.class, "lookup", params),
477        withVer(getPreferredResourceFormat(), "4.0"),
478        generateHeaders(false),
479        "CodeSystem/$lookup",
480        timeoutNormal);
481    } catch (IOException e) {
482      e.printStackTrace();
483    }
484    if (result.isUnsuccessfulRequest()) {
485      throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
486    }
487    return (Parameters) result.getPayload();
488  }
489
490  public Parameters lookupCode(Parameters p) {
491    recordUse();
492    org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null;
493    try {
494      result = client.issuePostRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup"),
495          ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat()), true),
496        withVer(getPreferredResourceFormat(), "4.0"),
497        generateHeaders(true),
498        "CodeSystem/$lookup",
499        timeoutNormal);
500    } catch (IOException e) {
501      e.printStackTrace();
502    }
503    if (result.isUnsuccessfulRequest()) {
504      throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
505    }
506    return (Parameters) result.getPayload();
507  }
508  
509  public Parameters translate(Parameters p) {
510    recordUse();
511    org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null;
512    try {
513      result = client.issuePostRequest(resourceAddress.resolveOperationUri(ConceptMap.class, "translate"),
514          ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat()), true),
515        withVer(getPreferredResourceFormat(), "4.0"),
516        generateHeaders(true),
517        "ConceptMap/$translate",
518        timeoutNormal);
519    } catch (IOException e) {
520      e.printStackTrace();
521    }
522    if (result.isUnsuccessfulRequest()) {
523      throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
524    }
525    return (Parameters) result.getPayload();
526  }
527
528  public String getAddress() {
529    return base;
530  }
531
532  public ConceptMap initializeClosure(String name) {
533    recordUse();
534    Parameters params = new Parameters();
535    params.addParameter().setName("name").setValue(new StringType(name));
536    ResourceRequest<Resource> result = null;
537    try {
538      result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
539        ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true),
540        withVer(getPreferredResourceFormat(), "4.0"),
541        generateHeaders(true),
542        "Closure?name=" + name,
543        timeoutNormal);
544      if (result.isUnsuccessfulRequest()) {
545        throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
546      }
547    } catch (IOException e) {
548      e.printStackTrace();
549    }
550    return result == null ? null : (ConceptMap) result.getPayload();
551  }
552
553  public ConceptMap updateClosure(String name, Coding coding) {
554    recordUse();
555    Parameters params = new Parameters();
556    params.addParameter().setName("name").setValue(new StringType(name));
557    params.addParameter().setName("concept").setValue(coding);
558    org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null;
559    try {
560      result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
561        ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true),
562        withVer(getPreferredResourceFormat(), "4.0"),
563        generateHeaders(true),
564        "UpdateClosure?name=" + name,
565        timeoutOperation);
566      if (result.isUnsuccessfulRequest()) {
567        throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
568      }
569    } catch (IOException e) {
570      e.printStackTrace();
571    }
572    return result == null ? null : (ConceptMap) result.getPayload();
573  }
574
575  public long getTimeout() {
576    return client.getTimeout();
577  }
578
579  public void setTimeout(long timeout) {
580    client.setTimeout(timeout);
581  }
582
583  public ToolingClientLogger getLogger() {
584    return client.getLogger();
585  }
586
587  public void setLogger(ToolingClientLogger logger) {
588    client.setLogger(logger);
589  }
590
591  public int getRetryCount() {
592    return client.getRetryCount();
593  }
594
595  public void setRetryCount(int retryCount) {
596    client.setRetryCount(retryCount);
597  }
598
599  public void setClientHeaders(Iterable<HTTPHeader> headers) {
600    this.headers = new ArrayList<>();
601    headers.forEach(this.headers::add);
602  }
603
604  private Iterable<HTTPHeader> generateHeaders(boolean hasBody) {
605    // Add any other headers
606    List<HTTPHeader> headers = new ArrayList<>(this.headers);
607    if (!Utilities.noString(userAgent)) {
608      headers.add(new HTTPHeader("User-Agent",userAgent));
609    }
610
611    if (!Utilities.noString(acceptLanguage)) {
612      headers.add(new HTTPHeader("Accept-Language", acceptLanguage));
613    }
614    
615    if (hasBody && !Utilities.noString(contentLanguage)) {
616      headers.add(new HTTPHeader("Content-Language", contentLanguage));
617    }
618    
619    return headers;
620  }
621
622  public String getServerVersion() {
623    if (capabilities == null) {
624      try {
625        getCapabilitiesStatementQuick();
626      } catch (Throwable e) {
627        //#TODO  This is creepy. Shouldn't we report this at some level?
628      }
629    }
630    return capabilities == null ? null : capabilities.getSoftware().getVersion();
631  }
632
633  public Bundle search(String type, String criteria) {
634    recordUse();
635    return fetchFeed(Utilities.pathURL(base, type+criteria));
636  }
637  
638  public <T extends Resource> T fetchResource(Class<T> resourceClass, String id) {
639    recordUse();
640    org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null;
641    try {
642      result = client.issueGetResourceRequest(resourceAddress.resolveGetResource(resourceClass, id),
643          withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), resourceClass.getName()+"/"+id, timeoutNormal);
644    } catch (IOException e) {
645      throw new FHIRException(e);
646    }
647    if (result.isUnsuccessfulRequest()) {
648      throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
649          (OperationOutcome) result.getPayload());
650    }
651    return (T) result.getPayload();
652  }
653
654  private void recordUse() {
655    useCount++;    
656  }
657
658  public int getUseCount() {
659    return useCount;
660  }
661  
662}
663