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