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