001package ca.uhn.fhir.rest.client.impl; 002 003/* 004 * #%L 005 * HAPI FHIR - Client Framework 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.context.*; 024import ca.uhn.fhir.interceptor.api.HookParams; 025import ca.uhn.fhir.interceptor.api.IInterceptorService; 026import ca.uhn.fhir.interceptor.api.Pointcut; 027import ca.uhn.fhir.interceptor.executor.InterceptorService; 028import ca.uhn.fhir.parser.DataFormatException; 029import ca.uhn.fhir.parser.IParser; 030import ca.uhn.fhir.rest.api.*; 031import ca.uhn.fhir.rest.client.api.*; 032import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException; 033import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException; 034import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; 035import ca.uhn.fhir.rest.client.interceptor.AdditionalRequestHeadersInterceptor; 036import ca.uhn.fhir.rest.client.method.HttpGetClientInvocation; 037import ca.uhn.fhir.rest.client.method.IClientResponseHandler; 038import ca.uhn.fhir.rest.client.method.IClientResponseHandlerHandlesBinary; 039import ca.uhn.fhir.rest.client.method.MethodUtil; 040import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; 041import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 042import ca.uhn.fhir.util.BinaryUtil; 043import ca.uhn.fhir.util.OperationOutcomeUtil; 044import ca.uhn.fhir.util.XmlDetectionUtil; 045import com.google.common.base.Charsets; 046import org.apache.commons.io.IOUtils; 047import org.apache.commons.lang3.StringUtils; 048import org.apache.commons.lang3.Validate; 049import org.hl7.fhir.instance.model.api.*; 050 051import javax.annotation.Nonnull; 052import java.io.ByteArrayInputStream; 053import java.io.IOException; 054import java.io.InputStream; 055import java.io.Reader; 056import java.util.*; 057 058import static org.apache.commons.lang3.StringUtils.isBlank; 059import static org.apache.commons.lang3.StringUtils.isNotBlank; 060 061public abstract class BaseClient implements IRestfulClient { 062 063 /** 064 * This property is used by unit tests - do not rely on it in production code 065 * as it may change at any time. If you want to capture responses in a reliable 066 * way in your own code, just use client interceptors 067 */ 068 public static final String HAPI_CLIENT_KEEPRESPONSES = "hapi.client.keepresponses"; 069 070 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseClient.class); 071 072 private final IHttpClient myClient; 073 private final RestfulClientFactory myFactory; 074 private final String myUrlBase; 075 private boolean myDontValidateConformance; 076 private EncodingEnum myEncoding = null; // default unspecified (will be JSON) 077 private boolean myKeepResponses = false; 078 private IHttpResponse myLastResponse; 079 private String myLastResponseBody; 080 private Boolean myPrettyPrint = false; 081 private SummaryEnum mySummary; 082 private RequestFormatParamStyleEnum myRequestFormatParamStyle = RequestFormatParamStyleEnum.SHORT; 083 private IInterceptorService myInterceptorService; 084 085 BaseClient(IHttpClient theClient, String theUrlBase, RestfulClientFactory theFactory) { 086 super(); 087 myClient = theClient; 088 myUrlBase = theUrlBase; 089 myFactory = theFactory; 090 091 /* 092 * This property is used by unit tests - do not rely on it in production code 093 * as it may change at any time. If you want to capture responses in a reliable 094 * way in your own code, just use client interceptors 095 */ 096 if ("true".equals(System.getProperty(HAPI_CLIENT_KEEPRESPONSES))) { 097 setKeepResponses(true); 098 } 099 100 if (XmlDetectionUtil.isStaxPresent() == false) { 101 myEncoding = EncodingEnum.JSON; 102 } 103 104 setInterceptorService(new InterceptorService()); 105 } 106 107 @Override 108 public IInterceptorService getInterceptorService() { 109 return myInterceptorService; 110 } 111 112 @Override 113 public void setInterceptorService(@Nonnull IInterceptorService theInterceptorService) { 114 Validate.notNull(theInterceptorService, "theInterceptorService must not be null"); 115 myInterceptorService = theInterceptorService; 116 } 117 118 protected Map<String, List<String>> createExtraParams(String theCustomAcceptHeader) { 119 HashMap<String, List<String>> retVal = new LinkedHashMap<>(); 120 121 if (isBlank(theCustomAcceptHeader)) { 122 if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT) { 123 if (getEncoding() == EncodingEnum.XML) { 124 retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("xml")); 125 } else if (getEncoding() == EncodingEnum.JSON) { 126 retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("json")); 127 } 128 } 129 } 130 131 if (isPrettyPrint()) { 132 retVal.put(Constants.PARAM_PRETTY, Collections.singletonList(Constants.PARAM_PRETTY_VALUE_TRUE)); 133 } 134 135 return retVal; 136 } 137 138 @Override 139 public <T extends IBaseResource> T fetchResourceFromUrl(Class<T> theResourceType, String theUrl) { 140 BaseHttpClientInvocation clientInvocation = new HttpGetClientInvocation(getFhirContext(), theUrl); 141 ResourceResponseHandler<T> binding = new ResourceResponseHandler<>(theResourceType); 142 return invokeClient(getFhirContext(), binding, clientInvocation, null, false, false, null, null, null, null, null); 143 } 144 145 void forceConformanceCheck() { 146 myFactory.validateServerBase(myUrlBase, myClient, this); 147 } 148 149 @Override 150 public EncodingEnum getEncoding() { 151 return myEncoding; 152 } 153 154 /** 155 * Sets the encoding that will be used on requests. Default is <code>null</code>, which means the client will not 156 * explicitly request an encoding. (This is perfectly acceptable behaviour according to the FHIR specification. In 157 * this case, the server will choose which encoding to return, and the client can handle either XML or JSON) 158 */ 159 @Override 160 public void setEncoding(EncodingEnum theEncoding) { 161 myEncoding = theEncoding; 162 // return this; 163 } 164 165 /** 166 * {@inheritDoc} 167 */ 168 @Override 169 public IHttpClient getHttpClient() { 170 return myClient; 171 } 172 173 /** 174 * For now, this is a part of the internal API of HAPI - Use with caution as this method may change! 175 */ 176 public IHttpResponse getLastResponse() { 177 return myLastResponse; 178 } 179 180 /** 181 * For now, this is a part of the internal API of HAPI - Use with caution as this method may change! 182 */ 183 public String getLastResponseBody() { 184 return myLastResponseBody; 185 } 186 187 /** 188 * {@inheritDoc} 189 */ 190 @Override 191 public String getServerBase() { 192 return myUrlBase; 193 } 194 195 public SummaryEnum getSummary() { 196 return mySummary; 197 } 198 199 @Override 200 public void setSummary(SummaryEnum theSummary) { 201 mySummary = theSummary; 202 } 203 204 public String getUrlBase() { 205 return myUrlBase; 206 } 207 208 @Override 209 public void setFormatParamStyle(RequestFormatParamStyleEnum theRequestFormatParamStyle) { 210 Validate.notNull(theRequestFormatParamStyle, "theRequestFormatParamStyle must not be null"); 211 myRequestFormatParamStyle = theRequestFormatParamStyle; 212 } 213 214 protected <T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation) { 215 return invokeClient(theContext, binding, clientInvocation, false); 216 } 217 218 protected <T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, boolean theLogRequestAndResponse) { 219 return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse, null, null, null, null, null); 220 } 221 222 protected <T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint, 223 boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set<String> theSubsetElements, CacheControlDirective theCacheControlDirective, String theCustomAcceptHeader, 224 Map<String, List<String>> theCustomHeaders) { 225 226 if (!myDontValidateConformance) { 227 myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient, this); 228 } 229 230 // TODO: handle non 2xx status codes by throwing the correct exception, 231 // and ensure it's passed upwards 232 IHttpRequest httpRequest = null; 233 IHttpResponse response = null; 234 try { 235 Map<String, List<String>> params = createExtraParams(theCustomAcceptHeader); 236 237 if (clientInvocation instanceof HttpGetClientInvocation) { 238 if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT && isBlank(theCustomAcceptHeader)) { 239 if (theEncoding == EncodingEnum.XML) { 240 params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml")); 241 } else if (theEncoding == EncodingEnum.JSON) { 242 params.put(Constants.PARAM_FORMAT, Collections.singletonList("json")); 243 } 244 } 245 } 246 247 if (theSummaryMode != null) { 248 params.put(Constants.PARAM_SUMMARY, Collections.singletonList(theSummaryMode.getCode())); 249 } else if (mySummary != null) { 250 params.put(Constants.PARAM_SUMMARY, Collections.singletonList(mySummary.getCode())); 251 } 252 253 if (thePrettyPrint == Boolean.TRUE) { 254 params.put(Constants.PARAM_PRETTY, Collections.singletonList(Constants.PARAM_PRETTY_VALUE_TRUE)); 255 } 256 257 if (theSubsetElements != null && theSubsetElements.isEmpty() == false) { 258 params.put(Constants.PARAM_ELEMENTS, Collections.singletonList(StringUtils.join(theSubsetElements, ','))); 259 } 260 261 EncodingEnum encoding = getEncoding(); 262 if (theEncoding != null) { 263 encoding = theEncoding; 264 } 265 266 httpRequest = clientInvocation.asHttpRequest(myUrlBase, params, encoding, thePrettyPrint); 267 268 if (isNotBlank(theCustomAcceptHeader)) { 269 httpRequest.removeHeaders(Constants.HEADER_ACCEPT); 270 httpRequest.addHeader(Constants.HEADER_ACCEPT, theCustomAcceptHeader); 271 } 272 273 if (theCacheControlDirective != null) { 274 StringBuilder b = new StringBuilder(); 275 addToCacheControlHeader(b, Constants.CACHE_CONTROL_NO_CACHE, theCacheControlDirective.isNoCache()); 276 addToCacheControlHeader(b, Constants.CACHE_CONTROL_NO_STORE, theCacheControlDirective.isNoStore()); 277 if (theCacheControlDirective.getMaxResults() != null) { 278 addToCacheControlHeader(b, Constants.CACHE_CONTROL_MAX_RESULTS + "=" + theCacheControlDirective.getMaxResults().intValue(), true); 279 } 280 if (b.length() > 0) { 281 httpRequest.addHeader(Constants.HEADER_CACHE_CONTROL, b.toString()); 282 } 283 } 284 285 if (theLogRequestAndResponse) { 286 ourLog.info("Client invoking: {}", httpRequest); 287 String body = httpRequest.getRequestBodyFromStream(); 288 if (body != null) { 289 ourLog.info("Client request body: {}", body); 290 } 291 } 292 293 if (theCustomHeaders != null) { 294 AdditionalRequestHeadersInterceptor interceptor = new AdditionalRequestHeadersInterceptor(theCustomHeaders); 295 interceptor.interceptRequest(httpRequest); 296 } 297 298 HookParams requestParams = new HookParams(); 299 requestParams.add(IHttpRequest.class, httpRequest); 300 requestParams.add(IRestfulClient.class, this); 301 getInterceptorService().callHooks(Pointcut.CLIENT_REQUEST, requestParams); 302 303 response = httpRequest.execute(); 304 305 HookParams responseParams = new HookParams(); 306 responseParams.add(IHttpRequest.class, httpRequest); 307 responseParams.add(IHttpResponse.class, response); 308 responseParams.add(IRestfulClient.class, this); 309 getInterceptorService().callHooks(Pointcut.CLIENT_RESPONSE, responseParams); 310 311 String mimeType; 312 if (Constants.STATUS_HTTP_204_NO_CONTENT == response.getStatus()) { 313 mimeType = null; 314 } else { 315 mimeType = response.getMimeType(); 316 } 317 318 Map<String, List<String>> headers = response.getAllHeaders(); 319 320 if (response.getStatus() < 200 || response.getStatus() > 299) { 321 String body = null; 322 try (Reader reader = response.createReader()) { 323 body = IOUtils.toString(reader); 324 } catch (Exception e) { 325 ourLog.debug("Failed to read input stream", e); 326 } 327 328 String message = "HTTP " + response.getStatus() + " " + response.getStatusInfo(); 329 IBaseOperationOutcome oo = null; 330 if (Constants.CT_TEXT.equals(mimeType)) { 331 message = message + ": " + body; 332 } else { 333 EncodingEnum enc = EncodingEnum.forContentType(mimeType); 334 if (enc != null) { 335 IParser p = enc.newParser(theContext); 336 try { 337 // TODO: handle if something other than OO comes back 338 oo = (IBaseOperationOutcome) p.parseResource(body); 339 String details = OperationOutcomeUtil.getFirstIssueDetails(getFhirContext(), oo); 340 if (isNotBlank(details)) { 341 message = message + ": " + details; 342 } 343 } catch (Exception e) { 344 ourLog.debug("Failed to process OperationOutcome response"); 345 } 346 } 347 } 348 349 keepResponseAndLogIt(theLogRequestAndResponse, response, body); 350 351 BaseServerResponseException exception = BaseServerResponseException.newInstance(response.getStatus(), message); 352 exception.setOperationOutcome(oo); 353 354 if (body != null) { 355 exception.setResponseBody(body); 356 } 357 358 throw exception; 359 } 360 if (binding instanceof IClientResponseHandlerHandlesBinary) { 361 IClientResponseHandlerHandlesBinary<T> handlesBinary = (IClientResponseHandlerHandlesBinary<T>) binding; 362 if (handlesBinary.isBinary()) { 363 try (InputStream reader = response.readEntity()) { 364 return handlesBinary.invokeClientForBinary(mimeType, reader, response.getStatus(), headers); 365 } 366 } 367 } 368 369 try (InputStream inputStream = response.readEntity()) { 370 InputStream inputStreamToReturn = inputStream; 371 372 if (ourLog.isTraceEnabled() || myKeepResponses || theLogRequestAndResponse) { 373 if (inputStream != null) { 374 String responseString = IOUtils.toString(inputStream, Charsets.UTF_8); 375 keepResponseAndLogIt(theLogRequestAndResponse, response, responseString); 376 inputStreamToReturn = new ByteArrayInputStream(responseString.getBytes(Charsets.UTF_8)); 377 } 378 } 379 380 if (inputStreamToReturn == null) { 381 inputStreamToReturn = new ByteArrayInputStream(new byte[]{}); 382 } 383 384 return binding.invokeClient(mimeType, inputStreamToReturn, response.getStatus(), headers); 385 } 386 387 } catch (DataFormatException e) { 388 String msg; 389 if (httpRequest != null) { 390 msg = getFhirContext().getLocalizer().getMessage(BaseClient.class, "failedToParseResponse", httpRequest.getHttpVerbName(), httpRequest.getUri(), e.toString()); 391 } else { 392 msg = getFhirContext().getLocalizer().getMessage(BaseClient.class, "failedToParseResponse", "UNKNOWN", "UNKNOWN", e.toString()); 393 } 394 throw new FhirClientConnectionException(msg, e); 395 } catch (IllegalStateException e) { 396 throw new FhirClientConnectionException(e); 397 } catch (IOException e) { 398 String msg; 399 msg = getFhirContext().getLocalizer().getMessage(BaseClient.class, "failedToParseResponse", httpRequest.getHttpVerbName(), httpRequest.getUri(), e.toString()); 400 throw new FhirClientConnectionException(msg, e); 401 } catch (RuntimeException e) { 402 throw e; 403 } catch (Exception e) { 404 throw new FhirClientConnectionException(e); 405 } finally { 406 if (response != null) { 407 response.close(); 408 } 409 } 410 } 411 412 private void addToCacheControlHeader(StringBuilder theBuilder, String theDirective, boolean theActive) { 413 if (theActive) { 414 if (theBuilder.length() > 0) { 415 theBuilder.append(", "); 416 } 417 theBuilder.append(theDirective); 418 } 419 } 420 421 /** 422 * For now, this is a part of the internal API of HAPI - Use with caution as this method may change! 423 */ 424 public boolean isKeepResponses() { 425 return myKeepResponses; 426 } 427 428 /** 429 * For now, this is a part of the internal API of HAPI - Use with caution as this method may change! 430 */ 431 public void setKeepResponses(boolean theKeepResponses) { 432 myKeepResponses = theKeepResponses; 433 } 434 435 /** 436 * Returns the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note 437 * that this is currently a non-standard flag (_pretty) which is supported only by HAPI based servers (and any other 438 * servers which might implement it). 439 */ 440 public boolean isPrettyPrint() { 441 return Boolean.TRUE.equals(myPrettyPrint); 442 } 443 444 /** 445 * Sets the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note 446 * that this is currently a non-standard flag (_pretty) which is supported only by HAPI based servers (and any other 447 * servers which might implement it). 448 */ 449 @Override 450 public void setPrettyPrint(Boolean thePrettyPrint) { 451 myPrettyPrint = thePrettyPrint; 452 // return this; 453 } 454 455 private void keepResponseAndLogIt(boolean theLogRequestAndResponse, IHttpResponse response, String responseString) { 456 if (myKeepResponses) { 457 myLastResponse = response; 458 myLastResponseBody = responseString; 459 } 460 if (theLogRequestAndResponse) { 461 String message = "HTTP " + response.getStatus() + " " + response.getStatusInfo(); 462 if (StringUtils.isNotBlank(responseString)) { 463 ourLog.info("Client response: {}\n{}", message, responseString); 464 } else { 465 ourLog.info("Client response: {}", message); 466 } 467 } else { 468 ourLog.trace("FHIR response:\n{}\n{}", response, responseString); 469 } 470 } 471 472 @Override 473 public void registerInterceptor(Object theInterceptor) { 474 Validate.notNull(theInterceptor, "Interceptor can not be null"); 475 getInterceptorService().registerInterceptor(theInterceptor); 476 } 477 478 /** 479 * This method is an internal part of the HAPI API and may change, use with caution. If you want to disable the 480 * loading of conformance statements, use 481 * {@link IRestfulClientFactory#setServerValidationMode(ServerValidationModeEnum)} 482 */ 483 public void setDontValidateConformance(boolean theDontValidateConformance) { 484 myDontValidateConformance = theDontValidateConformance; 485 } 486 487 @Override 488 public void unregisterInterceptor(Object theInterceptor) { 489 Validate.notNull(theInterceptor, "Interceptor can not be null"); 490 getInterceptorService().unregisterInterceptor(theInterceptor); 491 } 492 493 protected final class ResourceOrBinaryResponseHandler extends ResourceResponseHandler<IBaseResource> { 494 495 496 @Override 497 public IBaseResource invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException { 498 499 /* 500 * For operation responses, if the response content type is a FHIR content-type 501 * (which is will probably almost always be) we just handle it normally. However, 502 * if we get back a successful (2xx) response from an operation, and the content 503 * type is something other than FHIR, we'll return it as a Binary wrapped in 504 * a Parameters resource. 505 */ 506 EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType); 507 if (respType != null || theResponseStatusCode < 200 || theResponseStatusCode >= 300) { 508 return super.invokeClient(theResponseMimeType, theResponseInputStream, theResponseStatusCode, theHeaders); 509 } 510 511 // Create a Binary resource to return 512 IBaseBinary responseBinary = BinaryUtil.newBinary(getFhirContext()); 513 514 // Fetch the content type 515 String contentType = null; 516 List<String> contentTypeHeaders = theHeaders.get(Constants.HEADER_CONTENT_TYPE_LC); 517 if (contentTypeHeaders != null && contentTypeHeaders.size() > 0) { 518 contentType = contentTypeHeaders.get(0); 519 } 520 responseBinary.setContentType(contentType); 521 522 // Fetch the content itself 523 try { 524 responseBinary.setContent(IOUtils.toByteArray(theResponseInputStream)); 525 } catch (IOException e) { 526 throw new InternalErrorException("IO failure parsing response", e); 527 } 528 529 return responseBinary; 530 } 531 532 } 533 534 protected class ResourceResponseHandler<T extends IBaseResource> implements IClientResponseHandler<T> { 535 536 private boolean myAllowHtmlResponse; 537 private IIdType myId; 538 private List<Class<? extends IBaseResource>> myPreferResponseTypes; 539 private Class<T> myReturnType; 540 541 public ResourceResponseHandler() { 542 this(null); 543 } 544 545 public ResourceResponseHandler(Class<T> theReturnType) { 546 this(theReturnType, null, null); 547 } 548 549 public ResourceResponseHandler(Class<T> theReturnType, Class<? extends IBaseResource> thePreferResponseType, IIdType theId) { 550 this(theReturnType, thePreferResponseType, theId, false); 551 } 552 553 public ResourceResponseHandler(Class<T> theReturnType, Class<? extends IBaseResource> thePreferResponseType, IIdType theId, boolean theAllowHtmlResponse) { 554 this(theReturnType, toTypeList(thePreferResponseType), theId, theAllowHtmlResponse); 555 } 556 557 public ResourceResponseHandler(Class<T> theClass, List<Class<? extends IBaseResource>> thePreferResponseTypes) { 558 this(theClass, thePreferResponseTypes, null, false); 559 } 560 561 public ResourceResponseHandler(Class<T> theReturnType, List<Class<? extends IBaseResource>> thePreferResponseTypes, IIdType theId, boolean theAllowHtmlResponse) { 562 myReturnType = theReturnType; 563 myId = theId; 564 myPreferResponseTypes = thePreferResponseTypes; 565 myAllowHtmlResponse = theAllowHtmlResponse; 566 } 567 568 @Override 569 public T invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException { 570 if (theResponseStatusCode == Constants.STATUS_HTTP_204_NO_CONTENT) { 571 return null; 572 } 573 574 EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType); 575 if (respType == null) { 576 if (myAllowHtmlResponse && theResponseMimeType.toLowerCase().contains(Constants.CT_HTML) && myReturnType != null) { 577 return readHtmlResponse(theResponseInputStream); 578 } 579 throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseInputStream); 580 } 581 IParser parser = respType.newParser(getFhirContext()); 582 parser.setServerBaseUrl(getUrlBase()); 583 if (myPreferResponseTypes != null) { 584 parser.setPreferTypes(myPreferResponseTypes); 585 } 586 T retVal = parser.parseResource(myReturnType, theResponseInputStream); 587 588 MethodUtil.parseClientRequestResourceHeaders(myId, theHeaders, retVal); 589 590 return retVal; 591 } 592 593 @SuppressWarnings("unchecked") 594 private T readHtmlResponse(InputStream theResponseInputStream) { 595 RuntimeResourceDefinition resDef = getFhirContext().getResourceDefinition(myReturnType); 596 IBaseResource instance = resDef.newInstance(); 597 BaseRuntimeChildDefinition textChild = resDef.getChildByName("text"); 598 BaseRuntimeElementCompositeDefinition<?> textElement = (BaseRuntimeElementCompositeDefinition<?>) textChild.getChildByName("text"); 599 IBase textInstance = textElement.newInstance(); 600 textChild.getMutator().addValue(instance, textInstance); 601 602 BaseRuntimeChildDefinition divChild = textElement.getChildByName("div"); 603 BaseRuntimeElementDefinition<?> divElement = divChild.getChildByName("div"); 604 IPrimitiveType<?> divInstance = (IPrimitiveType<?>) divElement.newInstance(); 605 try { 606 divInstance.setValueAsString(IOUtils.toString(theResponseInputStream, Charsets.UTF_8)); 607 } catch (Exception e) { 608 throw new InvalidResponseException(400, "Failed to process HTML response from server: " + e.getMessage(), e); 609 } 610 divChild.getMutator().addValue(textInstance, divInstance); 611 return (T) instance; 612 } 613 614 public ResourceResponseHandler<T> setPreferResponseTypes(List<Class<? extends IBaseResource>> thePreferResponseTypes) { 615 myPreferResponseTypes = thePreferResponseTypes; 616 return this; 617 } 618 } 619 620 static ArrayList<Class<? extends IBaseResource>> toTypeList(Class<? extends IBaseResource> thePreferResponseType) { 621 ArrayList<Class<? extends IBaseResource>> preferResponseTypes = null; 622 if (thePreferResponseType != null) { 623 preferResponseTypes = new ArrayList<>(1); 624 preferResponseTypes.add(thePreferResponseType); 625 } 626 return preferResponseTypes; 627 } 628 629}