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