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