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