001package org.hl7.fhir.dstu2.utils.client;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009  
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030*/
031
032import java.net.URI;
033import java.net.URISyntaxException;
034import java.util.HashMap;
035import java.util.List;
036import java.util.Map;
037
038import org.apache.http.Header;
039import org.apache.http.HttpHost;
040import org.hl7.fhir.dstu2.model.Bundle;
041import org.hl7.fhir.dstu2.model.Coding;
042import org.hl7.fhir.dstu2.model.ConceptMap;
043import org.hl7.fhir.dstu2.model.Conformance;
044import org.hl7.fhir.dstu2.model.OperationOutcome;
045import org.hl7.fhir.dstu2.model.Parameters;
046import org.hl7.fhir.dstu2.model.Parameters.ParametersParameterComponent;
047import org.hl7.fhir.dstu2.model.PrimitiveType;
048import org.hl7.fhir.dstu2.model.Resource;
049import org.hl7.fhir.dstu2.model.StringType;
050import org.hl7.fhir.dstu2.model.ValueSet;
051import org.hl7.fhir.utilities.FHIRBaseToolingClient;
052import org.hl7.fhir.utilities.ToolingClientLogger;
053import org.hl7.fhir.utilities.Utilities;
054
055/**
056 * Very Simple RESTful client. This is purely for use in the standalone tools
057 * jar packages. It doesn't support many features, only what the tools need.
058 * 
059 * To use, initialize class and set base service URI as follows:
060 * 
061 * <pre>
062 * <code>
063 * FHIRSimpleClient fhirClient = new FHIRSimpleClient();
064 * fhirClient.initialize("http://my.fhir.domain/myServiceRoot");
065 * </code>
066 * </pre>
067 * 
068 * Default Accept and Content-Type headers are application/xml+fhir and
069 * application/j+fhir.
070 * 
071 * These can be changed by invoking the following setter functions:
072 * 
073 * <pre>
074 * <code>
075 * setPreferredResourceFormat()
076 * setPreferredFeedFormat()
077 * </code>
078 * </pre>
079 * 
080 * TODO Review all sad paths.
081 * 
082 * @author Claude Nanjo
083 *
084 */
085@Deprecated
086public class FHIRToolingClient extends FHIRBaseToolingClient {
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  private ResourceFormat preferredResourceFormat;
096  private HttpHost proxy;
097  private int maxResultSetSize = -1;// _count
098  private Conformance conf;
099  private ClientUtils utils = null;
100  private int useCount;
101
102  protected ClientUtils getClientUtils() {
103    return new ClientUtils();
104  }
105
106  // Pass enpoint for client - URI
107  public FHIRToolingClient(String baseServiceUrl, String userAgent) throws URISyntaxException {
108    preferredResourceFormat = ResourceFormat.RESOURCE_XML;
109    utils = getClientUtils();
110    utils.setUserAgent(userAgent);
111    initialize(baseServiceUrl);
112  }
113
114  public void initialize(String baseServiceUrl) throws URISyntaxException {
115    base = baseServiceUrl;
116    resourceAddress = new ResourceAddress(baseServiceUrl);
117    this.maxResultSetSize = -1;
118    checkConformance();
119  }
120
121  private void checkConformance() {
122    try {
123      conf = getConformanceStatementQuick();
124    } catch (Throwable e) {
125    }
126  }
127
128  public String getPreferredResourceFormat() {
129    return preferredResourceFormat.getHeader();
130  }
131
132  public void setPreferredResourceFormat(ResourceFormat resourceFormat) {
133    preferredResourceFormat = resourceFormat;
134  }
135
136  public int getMaximumRecordCount() {
137    return maxResultSetSize;
138  }
139
140  public void setMaximumRecordCount(int maxResultSetSize) {
141    this.maxResultSetSize = maxResultSetSize;
142  }
143
144  public Conformance getConformanceStatement() throws EFhirClientException {
145    if (conf != null)
146      return conf;
147    return getConformanceStatement(false);
148  }
149
150  public Conformance getConformanceStatement(boolean useOptionsVerb) {
151    Conformance conformance = null;
152    try {
153      if (useOptionsVerb) {
154        conformance = (Conformance) utils
155            .issueOptionsRequest(resourceAddress.getBaseServiceUri(), withVer(getPreferredResourceFormat(), "1.0"), timeoutNormal)
156            .getReference();// TODO fix this
157      } else {
158        conformance = (Conformance) utils.issueGetResourceRequest(resourceAddress.resolveMetadataUri(false),
159            withVer(getPreferredResourceFormat(), "1.0"), timeoutNormal).getReference();
160      }
161    } catch (Exception e) {
162      handleException("An error has occurred while trying to fetch the server's conformance statement", e);
163    }
164    return conformance;
165  }
166
167  public Conformance getConformanceStatementQuick() throws EFhirClientException {
168    if (conf != null)
169      return conf;
170    return getConformanceStatementQuick(false);
171  }
172
173  public Conformance getConformanceStatementQuick(boolean useOptionsVerb) {
174    Conformance conformance = null;
175    try {
176      if (useOptionsVerb) {
177        conformance = (Conformance) utils
178            .issueOptionsRequest(resourceAddress.getBaseServiceUri(), withVer(getPreferredResourceFormat(), "1.0"), timeoutNormal)
179            .getReference();// TODO fix this
180      } else {
181        conformance = (Conformance) utils.issueGetResourceRequest(resourceAddress.resolveMetadataUri(true),
182            withVer(getPreferredResourceFormat(), "1.0"), timeoutNormal).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 <T extends Resource> T read(Class<T> resourceClass, String id) {// TODO Change this to AddressableResource
191    recordUse();
192    ResourceRequest<T> result = null;
193    try {
194      result = utils.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
195          withVer(getPreferredResourceFormat(), "1.0"), timeoutNormal);
196      result.addErrorStatus(410);// gone
197      result.addErrorStatus(404);// unknown
198      result.addSuccessStatus(200);// Only one for now
199      if (result.isUnsuccessfulRequest()) {
200        throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
201            (OperationOutcome) result.getPayload());
202      }
203    } catch (Exception e) {
204      handleException("An error has occurred while trying to read this resource", e);
205    }
206    return result.getPayload();
207  }
208
209  public <T extends Resource> T vread(Class<T> resourceClass, String id, String version) {
210    recordUse();
211    ResourceRequest<T> result = null;
212    try {
213      result = utils.issueGetResourceRequest(
214          resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version),
215          withVer(getPreferredResourceFormat(), "1.0"), timeoutNormal);
216      result.addErrorStatus(410);// gone
217      result.addErrorStatus(404);// unknown
218      result.addErrorStatus(405);// unknown
219      result.addSuccessStatus(200);// Only one for now
220      if (result.isUnsuccessfulRequest()) {
221        throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
222            (OperationOutcome) result.getPayload());
223      }
224    } catch (Exception e) {
225      handleException("An error has occurred while trying to read this version of the resource", e);
226    }
227    return result.getPayload();
228  }
229
230  // GET
231  // fhir/ValueSet?url=http://hl7.org/fhir/ValueSet/clinical-findings&version=0.8
232
233  public <T extends Resource> T getCanonical(Class<T> resourceClass, String canonicalURL) {
234    recordUse();
235    ResourceRequest<T> result = null;
236    try {
237      result = utils.issueGetResourceRequest(
238          resourceAddress.resolveGetUriFromResourceClassAndCanonical(resourceClass, canonicalURL),
239          withVer(getPreferredResourceFormat(), "1.0"), timeoutNormal);
240      result.addErrorStatus(410);// gone
241      result.addErrorStatus(404);// unknown
242      result.addErrorStatus(405);// unknown
243      result.addSuccessStatus(200);// Only one for now
244      if (result.isUnsuccessfulRequest()) {
245        throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
246            (OperationOutcome) result.getPayload());
247      }
248    } catch (Exception e) {
249      handleException("An error has occurred while trying to read this version of the resource", e);
250    }
251    Bundle bnd = (Bundle) result.getPayload();
252    if (bnd.getEntry().size() == 0)
253      throw new EFhirClientException("No matching resource found for canonical URL '" + canonicalURL + "'");
254    if (bnd.getEntry().size() > 1)
255      throw new EFhirClientException("Multiple matching resources found for canonical URL '" + canonicalURL + "'");
256    return (T) bnd.getEntry().get(0).getResource();
257  }
258
259  public Resource update(Resource resource) {
260    recordUse();
261    ResourceRequest<Resource> result = null;
262    try {
263
264      result = utils.issuePutRequest(
265          resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()),
266          utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())),
267          withVer(getPreferredResourceFormat(), "1.0"), null, timeoutOperation);
268      result.addErrorStatus(410);// gone
269      result.addErrorStatus(404);// unknown
270      result.addErrorStatus(405);
271      result.addErrorStatus(422);// Unprocessable Entity
272      result.addSuccessStatus(200);
273      result.addSuccessStatus(201);
274      if (result.isUnsuccessfulRequest()) {
275        throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
276            (OperationOutcome) result.getPayload());
277      }
278    } catch (Exception e) {
279      throw new EFhirClientException("An error has occurred while trying to update this resource", e);
280    }
281    // TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader
282    // is returned with an operationOutcome would be returned (and not the resource
283    // also) we make another read
284    try {
285      OperationOutcome operationOutcome = (OperationOutcome) result.getPayload();
286      ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress
287          .parseCreateLocation(result.getLocation());
288      return this.vread(resource.getClass(), resVersionedIdentifier.getId(), resVersionedIdentifier.getVersionId());
289    } catch (ClassCastException e) {
290      // if we fall throught we have the correct type already in the create
291    }
292
293    return result.getPayload();
294  }
295
296  public <T extends Resource> T update(Class<T> resourceClass, T resource, String id) {
297    recordUse();
298    ResourceRequest<T> result = null;
299    try {
300      result = utils.issuePutRequest(
301        resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
302          utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())),
303          withVer(getPreferredResourceFormat(), "1.0"),
304        null,
305        timeoutOperation);
306      result.addErrorStatus(410);// gone
307      result.addErrorStatus(404);// unknown
308      result.addErrorStatus(405);
309      result.addErrorStatus(422);// Unprocessable Entity
310      result.addSuccessStatus(200);
311      result.addSuccessStatus(201);
312      if (result.isUnsuccessfulRequest()) {
313        throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
314            (OperationOutcome) result.getPayload());
315      }
316    } catch (Exception e) {
317      throw new EFhirClientException("An error has occurred while trying to update this resource", e);
318    }
319    // TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader
320    // is returned with an operationOutcome would be returned (and not the resource
321    // also) we make another read
322    try {
323      OperationOutcome operationOutcome = (OperationOutcome) result.getPayload();
324      ResourceAddress.ResourceVersionedIdentifier resVersionedIdentifier = ResourceAddress
325          .parseCreateLocation(result.getLocation());
326      return this.vread(resourceClass, resVersionedIdentifier.getId(), resVersionedIdentifier.getVersionId());
327    } catch (ClassCastException e) {
328      // if we fall throught we have the correct type already in the create
329    }
330
331    return result.getPayload();
332  }
333
334
335
336  public <T extends Resource> Parameters operateType(Class<T> resourceClass, String name, Parameters params) {
337    recordUse();
338    boolean complex = false;
339    for (ParametersParameterComponent p : params.getParameter())
340      complex = complex || !(p.getValue() instanceof PrimitiveType);
341    Parameters searchResults = null;
342    String ps = "";
343    if (!complex)
344      for (ParametersParameterComponent p : params.getParameter())
345        if (p.getValue() instanceof PrimitiveType)
346          ps += p.getName() + "=" + Utilities.encodeUriParam(((PrimitiveType) p.getValue()).asStringValue()) + "&";
347    ResourceRequest<T> result;
348    if (complex)
349      result = utils.issuePostRequest(resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps),
350          utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())),
351          withVer(getPreferredResourceFormat(), "1.0"), timeoutLong);
352    else
353      result = utils.issueGetResourceRequest(resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps),
354          withVer(getPreferredResourceFormat(), "1.0"), timeoutLong);
355    result.addErrorStatus(410);// gone
356    result.addErrorStatus(404);// unknown
357    result.addSuccessStatus(200);// Only one for now
358    if (result.isUnsuccessfulRequest())
359      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
360          (OperationOutcome) result.getPayload());
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  }
369
370  public Bundle transaction(Bundle batch) {
371    recordUse();
372    Bundle transactionResult = null;
373    try {
374      transactionResult = utils.postBatchRequest(resourceAddress.getBaseServiceUri(),
375          utils.getFeedAsByteArray(batch, false, isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"),
376          timeoutNormal + batch.getEntry().size());
377    } catch (Exception e) {
378      handleException("An error occurred trying to process this transaction request", e);
379    }
380    return transactionResult;
381  }
382
383  @SuppressWarnings("unchecked")
384  public <T extends Resource> OperationOutcome validate(Class<T> resourceClass, T resource, String id) {
385    recordUse();
386    ResourceRequest<T> result = null;
387    try {
388      result = utils.issuePostRequest(resourceAddress.resolveValidateUri(resourceClass, id),
389          utils.getResourceAsByteArray(resource, false, isJson(getPreferredResourceFormat())),
390          withVer(getPreferredResourceFormat(), "1.0"), 3);
391      result.addErrorStatus(400);// gone
392      result.addErrorStatus(422);// Unprocessable Entity
393      result.addSuccessStatus(200);// OK
394      if (result.isUnsuccessfulRequest()) {
395        throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
396            (OperationOutcome) result.getPayload());
397      }
398    } catch (Exception e) {
399      handleException("An error has occurred while trying to validate this resource", e);
400    }
401    return (OperationOutcome) result.getPayload();
402  }
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 message The EFhirClientException message.
411   * @param e The exception.
412   * @throws EFhirClientException representing the exception.
413   */
414  protected void handleException(String message, Exception e) throws EFhirClientException {
415    if (e instanceof EFhirClientException) {
416      throw (EFhirClientException) e;
417    } else {
418      throw new EFhirClientException(message, e);
419    }
420  }
421
422  /**
423   * Helper method to determine whether desired resource representation is Json or
424   * XML.
425   * 
426   * @param format the format to check
427   * @return true if JSON, false if XML
428   */
429  protected boolean isJson(String format) {
430    boolean isJson = false;
431    if (format.toLowerCase().contains("json")) {
432      isJson = true;
433    }
434    return isJson;
435  }
436
437  public Bundle fetchFeed(String url) {
438    recordUse();
439    Bundle feed = null;
440    try {
441      feed = utils.issueGetFeedRequest(new URI(url), getPreferredResourceFormat());
442    } catch (Exception e) {
443      handleException("An error has occurred while trying to read a bundle", e);
444    }
445    return feed;
446  }
447
448  public Parameters lookupCode(Map<String, String> params) {
449    recordUse();
450    ResourceRequest<Resource> result = utils.issueGetResourceRequest(
451        resourceAddress.resolveOperationUri(ValueSet.class, "lookup", params), withVer(getPreferredResourceFormat(), "1.0"),
452        timeoutNormal);
453    result.addErrorStatus(410);// gone
454    result.addErrorStatus(404);// unknown
455    result.addErrorStatus(405);
456    result.addErrorStatus(422);// Unprocessable Entity
457    result.addSuccessStatus(200);
458    result.addSuccessStatus(201);
459    if (result.isUnsuccessfulRequest()) {
460      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
461          (OperationOutcome) result.getPayload());
462    }
463    return (Parameters) result.getPayload();
464  }
465
466  public Parameters lookupCode(Parameters p) {
467    recordUse();
468    ResourceRequest<Resource> result = utils.issuePostRequest(
469        resourceAddress.resolveOperationUri(ValueSet.class, "lookup"), 
470        utils.getResourceAsByteArray(p, false, isJson(getPreferredResourceFormat())),
471        withVer(getPreferredResourceFormat(), "1.0"), 
472        timeoutNormal);
473    result.addErrorStatus(410);// gone
474    result.addErrorStatus(404);// unknown
475    result.addErrorStatus(405);
476    result.addErrorStatus(422);// Unprocessable Entity
477    result.addSuccessStatus(200);
478    result.addSuccessStatus(201);
479    if (result.isUnsuccessfulRequest()) {
480      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
481          (OperationOutcome) result.getPayload());
482    }
483    return (Parameters) result.getPayload();
484  }
485
486  public Parameters translate(Parameters p) {
487    recordUse();
488    ResourceRequest<Resource> result = utils.issuePostRequest(
489        resourceAddress.resolveOperationUri(ConceptMap.class, "translate"), 
490        utils.getResourceAsByteArray(p, false, isJson(getPreferredResourceFormat())),
491        withVer(getPreferredResourceFormat(), "1.0"), 
492        timeoutNormal);
493    result.addErrorStatus(410);// gone
494    result.addErrorStatus(404);// unknown
495    result.addErrorStatus(405);
496    result.addErrorStatus(422);// Unprocessable Entity
497    result.addSuccessStatus(200);
498    result.addSuccessStatus(201);
499    if (result.isUnsuccessfulRequest()) {
500      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
501          (OperationOutcome) result.getPayload());
502    }
503    return (Parameters) result.getPayload();
504  }
505
506  public ValueSet expandValueset(ValueSet source, Parameters expParams) {
507    recordUse();
508
509    Parameters p = expParams == null ? new Parameters() : expParams.copy();
510    p.addParameter().setName("valueSet").setResource(source);
511    ResourceRequest<Resource> result = utils.issuePostRequest(
512        resourceAddress.resolveOperationUri(ValueSet.class, "expand"),
513        utils.getResourceAsByteArray(p, false, isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"),
514        null, 4);
515    result.addErrorStatus(410); // gone
516    result.addErrorStatus(404); // unknown
517    result.addErrorStatus(405);
518    result.addErrorStatus(422); // Unprocessable Entity
519    result.addSuccessStatus(200);
520    result.addSuccessStatus(201);
521    if (result.isUnsuccessfulRequest()) {
522      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
523          (OperationOutcome) result.getPayload());
524    }
525    return (ValueSet) 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
537    ResourceRequest<Resource> result = utils.issuePostRequest(
538        resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
539        utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"),
540        null, timeoutNormal);
541    result.addErrorStatus(410);// gone
542    result.addErrorStatus(404);// unknown
543    result.addErrorStatus(405);
544    result.addErrorStatus(422);// Unprocessable Entity
545    result.addSuccessStatus(200);
546    result.addSuccessStatus(201);
547    if (result.isUnsuccessfulRequest()) {
548      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
549          (OperationOutcome) result.getPayload());
550    }
551    return (ConceptMap) result.getPayload();
552  }
553
554  public ConceptMap updateClosure(String name, Coding coding) {
555    recordUse();
556    Parameters params = new Parameters();
557    params.addParameter().setName("name").setValue(new StringType(name));
558    params.addParameter().setName("concept").setValue(coding);
559
560    ResourceRequest<Resource> result = utils.issuePostRequest(
561        resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
562        utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), withVer(getPreferredResourceFormat(), "1.0"),
563        null, timeoutOperation);
564    result.addErrorStatus(410);// gone
565    result.addErrorStatus(404);// unknown
566    result.addErrorStatus(405);
567    result.addErrorStatus(422);// Unprocessable Entity
568    result.addSuccessStatus(200);
569    result.addSuccessStatus(201);
570    if (result.isUnsuccessfulRequest()) {
571      throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
572          (OperationOutcome) result.getPayload());
573    }
574    return (ConceptMap) result.getPayload();
575  }
576
577  public int getTimeout() {
578    return utils.getTimeout();
579  }
580
581  public void setTimeout(int timeout) {
582    utils.setTimeout(timeout);
583  }
584
585  public Parameters getTerminologyCapabilities() {
586    return (Parameters) utils
587        .issueGetResourceRequest(resourceAddress.resolveMetadataTxCaps(), withVer(getPreferredResourceFormat(), "1.0"), timeoutNormal)
588        .getReference();
589  }
590
591  public org.hl7.fhir.utilities.ToolingClientLogger getLogger() {
592    return utils.getLogger();
593  }
594
595  public void setLogger(ToolingClientLogger logger) {
596    utils.setLogger(logger);
597  }
598
599  public int getRetryCount() {
600    return utils.getRetryCount();
601  }
602
603  public void setRetryCount(int retryCount) {
604    utils.setRetryCount(retryCount);
605  }
606
607  public String getUserAgent() {
608    return utils.getUserAgent();
609  }
610
611  public void setUserAgent(String userAgent) {
612    utils.setUserAgent(userAgent);
613  }
614
615  public String getServerVersion() {
616    return conf == null ? null : conf.getSoftware().getVersion();
617  }
618
619  public void setContentLanguage(String lang) {
620    utils.setContentLanguage(lang);
621  }
622
623  public void setAcceptLanguage(String lang) {
624    utils.setAcceptLanguage(lang);
625  }
626
627  public int getUseCount() {
628    return useCount;
629  }
630
631  private void recordUse() {
632    useCount++;    
633  }
634
635  public Bundle search(String type, String criteria) {
636    recordUse();
637    return fetchFeed(Utilities.pathURL(base, type+criteria));
638  }
639 
640}