001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.rest.client.classic; 018 019import static org.apache.juneau.commons.httppart.HttpPartType.*; 020import static org.apache.juneau.commons.utils.Shorts.*; 021 022import java.lang.reflect.*; 023import java.util.*; 024import java.util.logging.*; 025 026import org.apache.http.*; 027import org.apache.http.message.*; 028import org.apache.http.params.*; 029import org.apache.http.util.*; 030import org.apache.juneau.http.classic.header.*; 031import org.apache.juneau.httppart.bean.*; 032import org.apache.juneau.marshall.*; 033import org.apache.juneau.marshall.httppart.*; 034import org.apache.juneau.marshall.parser.*; 035import org.apache.juneau.rest.client.classic.assertion.*; 036import org.apache.juneau.bct.assertions.*; 037 038/** 039 * Represents a response from a remote REST resource. 040 * 041 * <p> 042 * Instances of this class are created by calling the {@link RestRequest#run()} method. 043 * 044 * <h5 class='section'>Example:</h5> 045 * <p class='bjava'> 046 * <jc>// Create a request and response, automatically closing both.</jc> 047 * <jk>try</jk> ( 048 * <jv>RestRequest</jv> <jv>req</jv> = <jv>client</jv>.get(<js>"/myResource"</js>); 049 * <jv>RestResponse</jv> <jv>res</jv> = <jv>req</jv>.run() 050 * ) { 051 * String <jv>body</jv> = <jv>res</jv>.getContent().asString(); 052 * } 053 * </p> 054 * 055 * <p> 056 * Alternatively, you can rely on {@link RestRequest#close()} to automatically close the response: 057 * 058 * <p class='bjava'> 059 * <jc>// Only specify RestRequest - it will close the response automatically.</jc> 060 * <jk>try</jk> (<jv>RestRequest</jv> <jv>req</jv> = <jv>client</jv>.get(<js>"/myResource"</js>)) { 061 * String <jv>body</jv> = <jv>req</jv>.run().getContent().asString(); 062 * } 063 * </p> 064 * 065 * <h5 class='section'>Notes:</h5><ul> 066 * <li class='note'>This class implements {@link AutoCloseable} and can be used in try-with-resources blocks. 067 * The {@link #close()} method allows unchecked exceptions to propagate for debuggability, 068 * while catching and logging checked exceptions to follow AutoCloseable best practices. 069 * </ul> 070 * 071 * <h5 class='section'>See Also:</h5><ul> 072 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestClient">juneau-rest-client-classic</a> 073 * </ul> 074 */ 075@SuppressWarnings({ 076 "java:S1133", // Intentional deprecation retained for backward compatibility until the documented removal; the reminder is not actionable now. 077 "java:S4144", // Identical methods intentional for different test scenarios 078 "resource" // Resource management handled externally 079}) 080public class RestResponse implements HttpResponse, AutoCloseable { 081 082 private final RestClient client; 083 private final RestRequest request; 084 private final HttpResponse response; 085 private final Parser parser; 086 private ResponseContent responseContent; 087 private boolean isClosed; 088 private HeaderList headers; 089 090 private final Map<HttpPartParser,HttpPartParserSession> partParserSessions = new IdentityHashMap<>(); 091 private HttpPartParserSession partParserSession; 092 093 /** 094 * Constructor. 095 * 096 * @param client The RestClient that created this response. 097 * @param request The REST request. 098 * @param response The HTTP response. Can be <jk>null</jk> (a default empty response with a status code of <c>0</c> is substituted). 099 * @param parser The overridden parser passed into {@link RestRequest#parser(Parser)}. 100 */ 101 protected RestResponse(RestClient client, RestRequest request, HttpResponse response, Parser parser) { 102 this.client = client; 103 this.request = request; 104 this.parser = parser; 105 this.response = response == null ? new BasicHttpResponse(null, 0, null) : response; 106 this.responseContent = new ResponseContent(client, request, this, parser); 107 this.headers = HeaderList.of(this.response.getAllHeaders()); 108 } 109 110 /** 111 * Adds a header to this message. 112 * 113 * The header will be appended to the end of the list. 114 * 115 * @param header The header to append. 116 */ 117 @Override /* Overridden from HttpMessage */ 118 public void addHeader(Header header) { 119 headers.append(header); 120 } 121 122 /** 123 * Adds a header to this message. 124 * 125 * The header will be appended to the end of the list. 126 * 127 * @param name The name of the header. 128 * @param value The value of the header. 129 */ 130 @Override /* Overridden from HttpMessage */ 131 public void addHeader(String name, String value) { 132 headers.append(name, value); 133 } 134 135 /** 136 * Provides the ability to perform fluent-style assertions on the response character encoding. 137 * 138 * <h5 class='section'>Examples:</h5> 139 * <p class='bjava'> 140 * <jc>// Validates that the response content charset is UTF-8.</jc> 141 * <jv>client</jv> 142 * .get(<jsf>URI</jsf>) 143 * .run() 144 * .assertCharset().is(<js>"utf-8"</js>); 145 * </p> 146 * 147 * @return A new fluent assertion object. 148 * @throws RestCallException If REST call failed. 149 */ 150 public FluentStringAssertion<RestResponse> assertCharset() throws RestCallException { 151 return new FluentStringAssertion<>(getCharacterEncoding(), this); 152 } 153 154 /** 155 * Provides the ability to perform fluent-style assertions on this response body. 156 * 157 * <h5 class='section'>Examples:</h5> 158 * <p class='bjava'> 159 * <jc>// Validates the response body equals the text "OK".</jc> 160 * <jv>client</jv> 161 * .get(<jsf>URI</jsf>) 162 * .run() 163 * .assertContent().is(<js>"OK"</js>); 164 * 165 * <jc>// Validates the response body contains the text "OK".</jc> 166 * <jv>client</jv> 167 * .get(<jsf>URI</jsf>) 168 * .run() 169 * .assertContent().isContains(<js>"OK"</js>); 170 * 171 * <jc>// Validates the response body passes a predicate test.</jc> 172 * <jv>client</jv> 173 * .get(<jsf>URI</jsf>) 174 * .run() 175 * .assertContent().is(<jv>x</jv> -> <jv>x</jv>.contains(<js>"OK"</js>)); 176 * 177 * <jc>// Validates the response body matches a regular expression.</jc> 178 * <jv>client</jv> 179 * .get(<jsf>URI</jsf>) 180 * .run() 181 * .assertContent().isPattern(<js>".*OK.*"</js>); 182 * 183 * <jc>// Validates the response body matches a regular expression using regex flags.</jc> 184 * <jv>client</jv> 185 * .get(<jsf>URI</jsf>) 186 * .run() 187 * .assertContent().isPattern(<js>".*OK.*"</js>, <jsf>MULTILINE</jsf> & <jsf>CASE_INSENSITIVE</jsf>); 188 * 189 * <jc>// Validates the response body matches a regular expression in the form of an existing Pattern.</jc> 190 * Pattern <jv>pattern</jv> = Pattern.<jsm>compile</jsm>(<js>".*OK.*"</js>); 191 * <jv>client</jv> 192 * .get(<jsf>URI</jsf>) 193 * .run() 194 * .assertContent().isPattern(<jv>pattern</jv>); 195 * </p> 196 * 197 * <p> 198 * The assertion test returns the original response object allowing you to chain multiple requests like so: 199 * <p class='bjava'> 200 * <jc>// Validates the response body matches a regular expression.</jc> 201 * MyBean <jv>bean</jv> = <jv>client</jv> 202 * .get(<jsf>URI</jsf>) 203 * .run() 204 * .assertContent().isPattern(<js>".*OK.*"</js>); 205 * .assertContent().isNotPattern(<js>".*ERROR.*"</js>) 206 * .getContent().as(MyBean.<jk>class</jk>); 207 * </p> 208 * 209 * <h5 class='section'>Notes:</h5><ul> 210 * <li class='note'> 211 * If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed. 212 * <li class='note'> 213 * When using this method, the body is automatically cached by calling the {@link ResponseContent#cache()}. 214 * <li class='note'> 215 * The input stream is automatically closed after this call. 216 * </ul> 217 * 218 * @return A new fluent assertion object. 219 */ 220 public FluentResponseBodyAssertion<RestResponse> assertContent() { 221 return new FluentResponseBodyAssertion<>(responseContent, this); 222 } 223 224 /** 225 * Provides the ability to perform fluent-style assertions on this response body. 226 * 227 * <p> 228 * A shortcut for calling <c>assertContent().is(<jv>value</jv>)</c>. 229 * 230 * <h5 class='section'>Examples:</h5> 231 * <p class='bjava'> 232 * <jc>// Validates the response body equals the text "OK".</jc> 233 * <jv>client</jv> 234 * .get(<jsf>URI</jsf>) 235 * .run() 236 * .assertContent(<js>"OK"</js>); 237 * </p> 238 * 239 * @param value The value to assert. 240 * @return This object. 241 */ 242 public RestResponse assertContent(String value) { 243 assertContent().is(value); 244 return this; 245 } 246 247 /** 248 * Provides the ability to perform fluent-style assertions on this response body. 249 * 250 * <p> 251 * A shortcut for calling <c>assertContent().asString().isMatches(<jv>value</jv>)</c>. 252 * 253 * @see FluentStringAssertion#isMatches(String) 254 * @param value The value to assert. 255 * @return This object. 256 */ 257 public RestResponse assertContentMatches(String value) { 258 assertContent().asString().isMatches(value); 259 return this; 260 } 261 262 /** 263 * Provides the ability to perform fluent-style assertions on a response header. 264 * 265 * <h5 class='section'>Examples:</h5> 266 * <p class='bjava'> 267 * <jc>// Validates the content type header is provided.</jc> 268 * <jv>client</jv> 269 * .get(<jsf>URI</jsf>) 270 * .run() 271 * .assertHeader(<js>"Content-Type"</js>).exists(); 272 * 273 * <jc>// Validates the content type is JSON.</jc> 274 * <jv>client</jv> 275 * .get(<jsf>URI</jsf>) 276 * .run() 277 * .assertHeader(<js>"Content-Type"</js>).is(<js>"application/json"</js>); 278 * 279 * <jc>// Validates the content type is JSON using test predicate.</jc> 280 * <jv>client</jv> 281 * .get(<jsf>URI</jsf>) 282 * .run() 283 * .assertHeader(<js>"Content-Type"</js>).is(<jv>x</jv> -> <jv>x</jv>.equals(<js>"application/json"</js>)); 284 * 285 * <jc>// Validates the content type is JSON by just checking for substring.</jc> 286 * <jv>client</jv> 287 * .get(<jsf>URI</jsf>) 288 * .run() 289 * .assertHeader(<js>"Content-Type"</js>).contains(<js>"json"</js>); 290 * 291 * <jc>// Validates the content type is JSON using regular expression.</jc> 292 * <jv>client</jv> 293 * .get(<jsf>URI</jsf>) 294 * .run() 295 * .assertHeader(<js>"Content-Type"</js>).isPattern(<js>".*json.*"</js>); 296 * 297 * <jc>// Validates the content type is JSON using case-insensitive regular expression.</jc> 298 * <jv>client</jv> 299 * .get(<jsf>URI</jsf>) 300 * .run() 301 * .assertHeader(<js>"Content-Type"</js>).isPattern(<js>".*json.*"</js>, <jsf>CASE_INSENSITIVE</jsf>); 302 * </p> 303 * 304 * <p> 305 * The assertion test returns the original response object allowing you to chain multiple requests like so: 306 * <p class='bjava'> 307 * <jc>// Validates the header and converts it to a bean.</jc> 308 * MediaType <jv>mediaType</jv> = <jv>client</jv> 309 * .get(<jsf>URI</jsf>) 310 * .run() 311 * .assertHeader(<js>"Content-Type"</js>).isNotEmpty() 312 * .assertHeader(<js>"Content-Type"</js>).isPattern(<js>".*json.*"</js>) 313 * .getHeader(<js>"Content-Type"</js>).as(MediaType.<jk>class</jk>); 314 * </p> 315 * 316 * @param name The header name. 317 * @return A new fluent assertion object. 318 */ 319 public FluentResponseHeaderAssertion<RestResponse> assertHeader(String name) { 320 return new FluentResponseHeaderAssertion<>(getHeader(name), this); 321 } 322 323 /** 324 * Provides the ability to perform fluent-style assertions on the response {@link StatusLine} object. 325 * 326 * <h5 class='section'>Examples:</h5> 327 * <p class='bjava'> 328 * MyBean <jv>bean</jv> = <jv>client</jv> 329 * .get(<jsf>URI</jsf>) 330 * .run() 331 * .assertStatus().asCode().is(200) 332 * .getContent().as(MyBean.<jk>class</jk>); 333 * </p> 334 * 335 * @return A new fluent assertion object. 336 */ 337 public FluentResponseStatusLineAssertion<RestResponse> assertStatus() { 338 return new FluentResponseStatusLineAssertion<>(getStatusLine(), this); 339 } 340 341 /** 342 * Provides the ability to perform fluent-style assertions on the response status code. 343 * 344 * <h5 class='section'>Examples:</h5> 345 * <p class='bjava'> 346 * MyBean <jv>bean</jv> = <jv>client</jv> 347 * .get(<jsf>URI</jsf>) 348 * .run() 349 * .assertStatus(200) 350 * .getContent().as(MyBean.<jk>class</jk>); 351 * </p> 352 * 353 * @param value The value to assert. 354 * @return A new fluent assertion object. 355 */ 356 public RestResponse assertStatus(int value) { 357 assertStatus().asCode().is(value); 358 return this; 359 } 360 361 /** 362 * Caches the response body so that it can be read as a stream multiple times. 363 * 364 * This is equivalent to calling the following: 365 * <p class='bjava'> 366 * getContent().cache(); 367 * </p> 368 * 369 * @return The body of the response. 370 */ 371 public RestResponse cacheContent() { 372 responseContent.cache(); 373 return this; 374 } 375 376 /** 377 * Closes this response. 378 * 379 * <p> 380 * This method is idempotent and can be called multiple times without side effects. 381 * 382 * <h5 class='section'>Implementation Notes:</h5> 383 * <p> 384 * This implementation represents a compromise between strict AutoCloseable compliance and debuggability: 385 * <ul> 386 * <li>Unchecked exceptions ({@link RuntimeException} and {@link Error}) from interceptors are allowed to propagate. 387 * This ensures programming errors and serious issues are visible during development and testing. 388 * <li>Checked exceptions (including {@link RestCallException}) are caught and logged but not thrown. 389 * This follows AutoCloseable best practices and prevents close exceptions from interfering with 390 * try-with-resources cleanup or masking the original exception. 391 * </ul> 392 */ 393 @Override /* Overridden from AutoCloseable */ 394 @SuppressWarnings({ 395 "java:S3776" // Cognitive complexity acceptable for resource cleanup 396 }) 397 public void close() { 398 if (isClosed) 399 return; 400 isClosed = true; 401 402 try { 403 // Render FULL/SIMPLE request logging BEFORE consuming the underlying entity. 404 // 405 // FULL logging reads the response body via getContent().asString(), which reads 406 // from the same HttpEntity returned by response.getEntity(). If consumeQuietly() 407 // runs first it closes the underlying network input stream, leaving the entity in 408 // a state where any subsequent read throws "Stream closed". Running logging first 409 // lets it cache the body bytes into ResponseContent.body so both this log render 410 // and any later caller (e.g. user-level getContent().asString()) succeed. 411 // 412 // EntityUtils.consumeQuietly() is invoked in the finally block so the entity is 413 // still drained on the non-logged and exceptional paths. 414 try { 415 if (! request.isLoggingSuppressed() && (request.isDebug() || client.logRequestsPredicate.test(request, this))) { 416 if (client.logRequests == DetailLevel.SIMPLE) { 417 client.log(client.logRequestsLevel, "HTTP %s %s, %s", request.getMethod(), request.getURI(), this.getStatusLine()); 418 } else if (request.isDebug() || client.logRequests == DetailLevel.FULL) { 419 var output = getContent().asString(); 420 var sb = new StringBuilder(); 421 sb.append("\n=== HTTP Call (outgoing) ======================================================"); 422 sb.append("\n=== REQUEST ===\n"); 423 sb.append(request.getMethod()).append(" ").append(request.getURI()); 424 sb.append("\n---request headers---"); 425 request.getHeaders().forEach(x -> sb.append("\n\t").append(x)); 426 if (request.hasHttpEntity()) { 427 sb.append("\n---request entity---"); 428 var e = request.getHttpEntity(); 429 if (nn(e.getContentType())) 430 sb.append("\n\t").append(e.getContentType()); 431 if (e.isRepeatable()) { 432 appendRequestContent(sb, e); 433 } 434 } 435 sb.append("\n=== RESPONSE ===\n").append(getStatusLine()); 436 sb.append("\n---response headers---"); 437 for (var h : getAllHeaders()) 438 sb.append("\n\t").append(h); 439 sb.append("\n---response content---\n").append(output); 440 sb.append("\n=== END ======================================================================="); 441 client.log(client.logRequestsLevel, sb.toString()); 442 } 443 } 444 } finally { 445 EntityUtils.consumeQuietly(response.getEntity()); 446 } 447 448 for (var r : request.interceptors) { 449 invokeInterceptorOnClose(r, request); 450 } 451 client.onCallClose(request, this); 452 } catch (RuntimeException e) { 453 // Let unchecked exceptions propagate for debuggability 454 throw e; 455 } catch (Exception e) { 456 // Log checked exceptions but don't throw - follows AutoCloseable best practices 457 client.log(Level.WARNING, e, "Error during RestResponse close"); 458 } 459 } 460 461 /** 462 * Consumes the response body. 463 * 464 * <p> 465 * This is equivalent to closing the input stream. 466 * 467 * <p> 468 * Any exceptions thrown during close are logged but not propagated. 469 * 470 * @return This object. 471 */ 472 public RestResponse consume() { 473 close(); 474 return this; 475 } 476 477 private static void appendRequestContent(StringBuilder sb, HttpEntity e) { 478 try { 479 sb.append("\n---request content---\n").append(EntityUtils.toString(e)); 480 } catch (Exception ex) { 481 sb.append("\n---request content exception---\n").append(ex.getMessage()); 482 } 483 } 484 485 private void invokeInterceptorOnClose(RestCallInterceptor r, RestRequest request) throws RestCallException { 486 try { 487 r.onClose(request, this); 488 } catch (RuntimeException e) { 489 // Let unchecked exceptions propagate - these indicate programming errors that should be visible 490 throw e; 491 } catch (Exception e) { 492 // Wrap checked exceptions from interceptors (including RestCallException) 493 throw new RestCallException(this, e, "Interceptor throw exception on close"); 494 } 495 } 496 497 /** 498 * Checks if a certain header is present in this message. 499 * 500 * <p> 501 * Header values are ignored. 502 * 503 * @param name The header name to check for. 504 * @return <jk>true</jk> if at least one header with this name is present. 505 */ 506 @Override /* Overridden from HttpMessage */ 507 public boolean containsHeader(String name) { 508 return response.containsHeader(name); 509 } 510 511 /** 512 * Returns all the headers of this message. 513 * 514 * Headers are ordered in the sequence they were sent over a connection. 515 * 516 * @return All the headers of this message. 517 */ 518 @Override /* Overridden from HttpMessage */ 519 public ResponseHeader[] getAllHeaders() { 520 return headers.stream().map(x -> new ResponseHeader(x.getName(), request, this, x).parser(getPartParserSession())).toArray(ResponseHeader[]::new); 521 } 522 523 /** 524 * Shortcut for retrieving the response charset from the <l>Content-Type</l> header. 525 * 526 * @return The response charset. 527 * @throws RestCallException If REST call failed. 528 */ 529 public String getCharacterEncoding() throws RestCallException { 530 var ct = getContentType(); 531 String s = null; 532 if (ct.isPresent()) 533 s = ct.get().getParameter("charset"); 534 return ie(s) ? "utf-8" : s; 535 } 536 537 /** 538 * Returns the body of the response. 539 * 540 * This method can be called multiple times returning the same response body each time. 541 * 542 * @return The body of the response. 543 */ 544 public ResponseContent getContent() { return responseContent; } 545 546 /** 547 * Shortcut for retrieving the response content type from the <l>Content-Type</l> header. 548 * 549 * <p> 550 * This is equivalent to calling <c>getHeader(<js>"Content-Type"</js>).as(ContentType.<jk>class</jk>)</c>. 551 * 552 * @return The response content type, or an empty {@link Optional} if the <l>Content-Type</l> header is not present. 553 * @throws RestCallException If REST call failed. 554 */ 555 public Optional<ContentType> getContentType() throws RestCallException { return getHeader("Content-Type").as(ContentType.class); } 556 557 /** 558 * Obtains the message entity of this response. 559 * 560 * <p> 561 * The entity is provided by calling setEntity. 562 * 563 * <h5 class='section'>Notes:</h5><ul> 564 * <li class='note'>Unlike the {@link HttpResponse#getEntity()} method, this method never returns a <jk>null</jk> response. 565 * Instead, <c>getContent().isPresent()</c> can be used to determine whether the response has a body. 566 * </ul> 567 * 568 * @return The response entity. Never <jk>null</jk>. 569 */ 570 @Override /* Overridden from HttpResponse */ 571 public ResponseContent getEntity() { return responseContent; } 572 573 /** 574 * Returns the first header with a specified name of this message. 575 * 576 * <p> 577 * If there is more than one matching header in the message the first element of {@link #getHeaders(String)} is returned. 578 * <p> 579 * This method always returns a value so that you can perform assertions on the result. 580 * 581 * @param name The name of the header to return. 582 * @return The header, never <jk>null</jk>. 583 */ 584 @Override /* Overridden from HttpMessage */ 585 public ResponseHeader getFirstHeader(String name) { 586 return new ResponseHeader(name, request, this, headers.getFirst(name).orElse(null)).parser(getPartParserSession()); 587 } 588 589 // ----------------------------------------------------------------------------------------------------------------- 590 // HttpResponse pass-through methods. 591 // ----------------------------------------------------------------------------------------------------------------- 592 593 /** 594 * Returns the response header with the specified name. 595 * 596 * <p> 597 * If more that one header with the given name exists the values will be combined with <js>", "</js> as per <a href='https://tools.ietf.org/html/rfc2616#section-4.2'>RFC 2616 Section 4.2</a>. 598 * 599 * @param name The name of the header to return. 600 * @return The header, never <jk>null</jk>. 601 */ 602 public ResponseHeader getHeader(String name) { 603 return new ResponseHeader(name, request, this, headers.get(name).orElse(null)).parser(getPartParserSession()); 604 } 605 606 /** 607 * Returns all the headers with a specified name of this message. 608 * 609 * Header values are ignored. 610 * <br>Headers are ordered in the sequence they were sent over a connection. 611 * 612 * @param name The name of the headers to return. 613 * @return All the headers with a specified name of this message. 614 */ 615 @Override /* Overridden from HttpMessage */ 616 public ResponseHeader[] getHeaders(String name) { 617 return headers.stream(name).map(x -> new ResponseHeader(name, request, this, x).parser(getPartParserSession())).toArray(ResponseHeader[]::new); 618 } 619 620 /** 621 * Returns the last header with a specified name of this message. 622 * 623 * <p> 624 * If there is more than one matching header in the message the last element of {@link #getHeaders(String)} is returned. 625 * <p> 626 * This method always returns a value so that you can perform assertions on the result. 627 * 628 * @param name The name of the header to return. 629 * @return The header, never <jk>null</jk>. 630 */ 631 @Override /* Overridden from HttpMessage */ 632 public ResponseHeader getLastHeader(String name) { 633 return new ResponseHeader(name, request, this, headers.getLast(name).orElse(null)).parser(getPartParserSession()); 634 } 635 636 /** 637 * Obtains the locale of this response. 638 * 639 * The locale is used to determine the reason phrase for the status code. 640 * It can be changed using {@link #setLocale(Locale)}. 641 * 642 * @return The locale of this response, never <jk>null</jk>. 643 */ 644 @Override /* Overridden from HttpResponse */ 645 public Locale getLocale() { return response.getLocale(); } 646 647 /** 648 * Returns the parameters effective for this message as set by {@link #setParams(HttpParams)}. 649 * 650 * @return The parameters effective for this message as set by {@link #setParams(HttpParams)}. 651 * @deprecated Use configuration classes provided <jk>org.apache.http.config</jk> and <jk>org.apache.http.client.config</jk>. 652 */ 653 @Override /* Overridden from HttpMessage */ 654 @Deprecated(since = "10.0", forRemoval = true) 655 public HttpParams getParams() { return response.getParams(); } 656 657 /** 658 * Returns the protocol version this message is compatible with. 659 * 660 * @return The protocol version this message is compatible with. 661 */ 662 @Override /* Overridden from HttpMessage */ 663 public ProtocolVersion getProtocolVersion() { return response.getProtocolVersion(); } 664 665 /** 666 * Returns the status line reason phrase of the response. 667 * 668 * Shortcut for calling <code>getStatusLine().getReasonPhrase()</code>. 669 * 670 * @return The status line reason phrase of the response. 671 */ 672 public String getReasonPhrase() { return getStatusLine().getReasonPhrase(); } 673 674 /** 675 * Returns the request object that created this response object. 676 * 677 * @return The request object that created this response object. 678 */ 679 public RestRequest getRequest() { return request; } 680 681 /** 682 * Returns the status code of the response. 683 * 684 * Shortcut for calling <code>getStatusLine().getStatusCode()</code>. 685 * 686 * @return The status code of the response. 687 */ 688 public int getStatusCode() { return getStatusLine().getStatusCode(); } 689 690 /** 691 * Obtains the status line of this response. 692 * 693 * The status line can be set using one of the setStatusLine methods, or it can be initialized in a constructor. 694 * 695 * @return The status line. Never <jk>null</jk>. 696 */ 697 @Override /* Overridden from HttpResponse */ 698 public ResponseStatusLine getStatusLine() { return new ResponseStatusLine(this, response.getStatusLine()); } 699 700 /** 701 * Shortcut for calling <code>getHeader(name).asString()</code>. 702 * 703 * @param name The header name. 704 * @return The header value, never <jk>null</jk> 705 */ 706 public Optional<String> getStringHeader(String name) { 707 return getHeader(name).asString(); 708 } 709 710 /** 711 * Returns an iterator of all the headers. 712 * 713 * @return {@link Iterator} that returns {@link Header} objects in the sequence they are sent over a connection. 714 */ 715 @Override /* Overridden from HttpMessage */ 716 public HeaderIterator headerIterator() { 717 return headers.headerIterator(); 718 } 719 720 /** 721 * Returns an iterator of the headers with a given name. 722 * 723 * @param name The name of the headers over which to iterate, or <jk>null</jk> for all headers. 724 * @return {@link Iterator} that returns {@link Header} objects with the argument name in the sequence they are sent over a connection. 725 */ 726 @Override /* Overridden from HttpMessage */ 727 public HeaderIterator headerIterator(String name) { 728 return headers.headerIterator(name); 729 } 730 731 /** 732 * Logs a message. 733 * 734 * @param level The log level. 735 * @param msg The message with {@link String#format(String, Object...) String.format}-style (<c>%s</c>) arguments. 736 * @param args The arguments. 737 * @return This object. 738 */ 739 public RestResponse log(Level level, String msg, Object...args) { 740 client.log(level, msg, args); 741 return this; 742 } 743 744 /** 745 * Logs a message. 746 * 747 * @param level The log level. 748 * @param t The throwable cause. 749 * @param msg The message with {@link String#format(String, Object...) String.format}-style (<c>%s</c>) arguments. 750 * @param args The arguments. 751 * @return This object. 752 */ 753 public RestResponse log(Level level, Throwable t, String msg, Object...args) { 754 client.log(level, t, msg, args); 755 return this; 756 } 757 758 /** 759 * Removes a header from this message. 760 * 761 * @param header The header to remove. 762 */ 763 @Override /* Overridden from HttpMessage */ 764 public void removeHeader(Header header) { 765 headers.remove(header); 766 } 767 768 /** 769 * Removes all headers with a certain name from this message. 770 * 771 * @param name The name of the headers to remove. 772 */ 773 @Override /* Overridden from HttpMessage */ 774 public void removeHeaders(String name) { 775 headers.remove(name); 776 } 777 778 /** 779 * Associates a response entity with this response. 780 * 781 * <h5 class='section'>Notes:</h5><ul> 782 * <li class='note'>If an entity has already been set for this response and it depends on an input stream 783 * ({@link HttpEntity#isStreaming()} returns <jk>true</jk>), it must be fully consumed in order to ensure 784 * release of resources. 785 * </ul> 786 * 787 * @param entity The entity to associate with this response, or <jk>null</jk> to unset. 788 */ 789 @Override /* Overridden from HttpResponse */ 790 public void setEntity(HttpEntity entity) { 791 response.setEntity(entity); 792 this.responseContent = new ResponseContent(client, request, this, parser); 793 } 794 795 /** 796 * Overwrites the first header with the same name. 797 * 798 * The new header will be appended to the end of the list, if no header with the given name can be found. 799 * 800 * @param header The header to set. 801 */ 802 @Override /* Overridden from HttpMessage */ 803 public void setHeader(Header header) { 804 headers.set(header); 805 } 806 807 /** 808 * Overwrites the first header with the same name. 809 * 810 * The new header will be appended to the end of the list, if no header with the given name can be found. 811 * 812 * @param name The name of the header. 813 * @param value The value of the header. 814 */ 815 @Override /* Overridden from HttpMessage */ 816 public void setHeader(String name, String value) { 817 headers.set(name, value); 818 } 819 820 /** 821 * Overwrites all the headers in the message. 822 * 823 * @param headers The array of headers to set. 824 */ 825 @Override /* Overridden from HttpMessage */ 826 public void setHeaders(Header[] headers) { this.headers = HeaderList.of(headers); } 827 828 /** 829 * Changes the locale of this response. 830 * 831 * @param loc The new locale. 832 */ 833 @Override /* Overridden from HttpResponse */ 834 public void setLocale(Locale loc) { 835 response.setLocale(loc); 836 } 837 838 /** 839 * Provides parameters to be used for the processing of this message. 840 * 841 * @param params The parameters. 842 * @deprecated Use configuration classes provided <jk>org.apache.http.config</jk> and <jk>org.apache.http.client.config</jk>. 843 */ 844 @Override /* Overridden from HttpMessage */ 845 @Deprecated(since = "10.0", forRemoval = true) 846 public void setParams(HttpParams params) { 847 response.setParams(params); 848 } 849 850 /** 851 * Updates the status line of this response with a new reason phrase. 852 * 853 * @param reason The new reason phrase as a single-line string, or <jk>null</jk> to unset the reason phrase. 854 * @throws IllegalStateException If the status line has not be set. 855 */ 856 @Override /* Overridden from HttpResponse */ 857 public void setReasonPhrase(String reason) { 858 response.setReasonPhrase(reason); 859 } 860 861 /** 862 * Updates the status line of this response with a new status code. 863 * 864 * @param code The HTTP status code. 865 * @throws IllegalStateException If the status line has not be set. 866 */ 867 @Override /* Overridden from HttpResponse */ 868 public void setStatusCode(int code) { 869 response.setStatusCode(code); 870 } 871 872 /** 873 * Sets the status line of this response. 874 * 875 * <p> 876 * The reason phrase will be determined based on the current locale. 877 * 878 * @param ver The HTTP version. 879 * @param code The status code. 880 */ 881 @Override /* Overridden from HttpResponse */ 882 public void setStatusLine(ProtocolVersion ver, int code) { 883 response.setStatusLine(ver, code); 884 } 885 886 /** 887 * Sets the status line of this response with a reason phrase. 888 * 889 * @param ver The HTTP version. 890 * @param code The status code. 891 * @param reason The reason phrase, or <jk>null</jk> to omit. 892 */ 893 @Override /* Overridden from HttpResponse */ 894 public void setStatusLine(ProtocolVersion ver, int code, String reason) { 895 response.setStatusLine(ver, code, reason); 896 } 897 898 /** 899 * Sets the status line of this response. 900 * 901 * @param statusline The status line of this response 902 */ 903 @Override /* Overridden from HttpResponse */ 904 public void setStatusLine(StatusLine statusline) { 905 response.setStatusLine(statusline); 906 } 907 908 /** 909 * Creates a session of the client-default parat parser. 910 * 911 * @return A session of the specified parser. 912 */ 913 protected HttpPartParserSession getPartParserSession() { 914 if (partParserSession == null) 915 partParserSession = client.getPartParser().getPartSession(); 916 return partParserSession; 917 } 918 919 /** 920 * Creates a session of the specified part parser. 921 * 922 * @param parser The parser to create a session for. 923 * @return A session of the specified parser. 924 */ 925 protected HttpPartParserSession getPartParserSession(HttpPartParser parser) { 926 return partParserSessions.computeIfAbsent(parser, HttpPartParser::getPartSession); 927 } 928 929 @SuppressWarnings({ 930 "unchecked" // Type erasure requires unchecked casts 931 }) 932 <T> T as(ResponseBeanMeta rbm) { 933 var c = (Class<T>)rbm.getBeanInfo().inner(); 934 final RestClient rc = this.client; 935 return (T)Proxy.newProxyInstance(c.getClassLoader(), a(c), (InvocationHandler)(proxy, method, args) -> { 936 var pm = rbm.getProperty(method.getName()); 937 var pp = getPartParserSession(pm.getParser().orElse(rc.getPartParser())); 938 var schema = pm.getSchema(); 939 var pt = pm.getPartType(); 940 var name = pm.getPartName().orElse(null); 941 var type = rc.getMarshallingContext().getClassMeta(method.getGenericReturnType()); 942 if (pt == RESPONSE_HEADER) 943 return getHeader(name).parser(pp).schema(schema).as(type).orElse(null); 944 if (pt == RESPONSE_STATUS) 945 return getStatusCode(); 946 return getContent().schema(schema).as(type); 947 }); 948 } 949 950 HttpResponse asHttpResponse() { 951 return response; 952 } 953}