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