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.*;
021import static org.apache.juneau.commons.utils.ThrowableUtils.*;
022
023import java.lang.reflect.*;
024import java.time.*;
025import java.util.*;
026import java.util.regex.*;
027
028import org.apache.http.*;
029import org.apache.juneau.commons.lang.*;
030import org.apache.juneau.commons.reflect.*;
031import org.apache.juneau.http.classic.header.*;
032import org.apache.juneau.marshall.*;
033import org.apache.juneau.marshall.httppart.*;
034import org.apache.juneau.marshall.oapi.*;
035import org.apache.juneau.marshall.parser.ParseException;
036import org.apache.juneau.rest.client.classic.assertion.*;
037import org.apache.juneau.bct.assertions.*;
038
039/**
040 * Represents a single header on an HTTP response.
041 *
042 * <p>
043 * An extension of an HttpClient {@link Header} that provides various support for converting the header to POJOs and
044 * other convenience methods.
045 *
046 * <h5 class='section'>See Also:</h5><ul>
047 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestClient">juneau-rest-client-classic</a>
048 * </ul>
049 */
050@SuppressWarnings({
051   "java:S2160", // equals() inherited from BasicHeader compares name+value; request/response fields are contextual not identity-defining
052   "resource" // request and response are owned by the RestCall that created this header; lifecycle managed externally
053})
054public class ResponseHeader extends BasicHeader {
055
056   private static final long serialVersionUID = 1L;
057
058   static final Header NULL_HEADER = new Header() {
059
060      @Override /* Overridden from Header */
061      public HeaderElement[] getElements() throws org.apache.http.ParseException { return new HeaderElement[0]; }
062
063      @Override /* Overridden from Header */
064      public String getName() { return null; }
065
066      @Override /* Overridden from Header */
067      public String getValue() { return null; }
068   };
069
070   private final transient HeaderElement[] elements;
071   private final transient RestRequest request;
072   private final transient RestResponse response;
073   private transient HttpPartParserSession parser;
074   private transient HttpPartSchema schema;
075
076   /**
077    * Constructor.
078    * @param name The header name.
079    * @param request The request object.
080    * @param response The response object.
081    * @param header The wrapped header.  Can be <jk>null</jk> (treated as an absent header with no value and no elements).
082    * @throws IllegalArgumentException If name is <jk>null</jk> or empty.
083    */
084   public ResponseHeader(String name, RestRequest request, RestResponse response, Header header) {
085      super(name, header == null ? null : header.getValue());
086      this.request = request;
087      this.response = response;
088      this.elements = header == null ? new HeaderElement[0] : header.getElements();
089      parser(null);
090   }
091
092   /**
093    * Converts this header to the specified type.
094    *
095    * @param <T> The type to convert to.
096    * @param type The type to convert to.
097    * @return The converted type, or an empty {@link Optional} if the header is not present.
098    */
099   public <T> Optional<T> as(Class<T> type) {
100      return as(request.getClassMeta(type));
101   }
102
103   /**
104    * Converts this header to the specified type.
105    *
106    * @param <T> The type to convert to.
107    * @param type The type to convert to.
108    * @return The converted type, or an empty {@link Optional} if the header is not present.
109    */
110   public <T> Optional<T> as(ClassMeta<T> type) {
111      try {
112         return o(parser.read(HEADER, schema, getValue(), type));
113      } catch (ParseException e) {
114         throw rex(e, "Could not parse response header %s.", getName());
115      }
116   }
117
118   /**
119    * Converts this header to the specified type.
120    *
121    * <p>
122    * See <a class="doclink" href="https://juneau.apache.org/docs/topics/ComplexDataTypes">Complex Data Types</a> for information on defining complex generic types of {@link Map Maps} and {@link Collection Collections}.
123    *
124    * @param <T> The type to convert to.
125    * @param type The type to convert to.
126    * @param args The type parameters.
127    * @return The converted type, or an empty {@link Optional} if the header is not present.
128    */
129   public <T> Optional<T> as(Type type, Type...args) {
130      return as(request.getClassMeta(type, args));
131   }
132
133   /**
134    * Same as {@link #as(Class)} but sets the value in a mutable for fluent calls.
135    *
136    * @param value The mutable to set the parsed header value in.
137    * @param <T> The type to convert to.
138    * @param type The type to convert to.
139    * @return This object.
140    */
141   public <T> RestResponse as(Holder<T> value, Class<T> type) {
142      value.set(as(type).orElse(null));
143      return response;
144   }
145
146   /**
147    * Same as {@link #as(ClassMeta)} but sets the value in a mutable for fluent calls.
148    *
149    * @param value The mutable to set the parsed header value in.
150    * @param <T> The type to convert to.
151    * @param type The type to convert to.
152    * @return This object.
153    */
154   public <T> RestResponse as(Holder<T> value, ClassMeta<T> type) {
155      value.set(as(type).orElse(null));
156      return response;
157   }
158
159   /**
160    * Same as {@link #as(Type,Type...)} but sets the value in a mutable for fluent calls.
161    *
162    * <p>
163    * See <a class="doclink" href="https://juneau.apache.org/docs/topics/ComplexDataTypes">Complex Data Types</a> for information on defining complex generic types of {@link Map Maps} and {@link Collection Collections}.
164    *
165    * @param value The mutable to set the parsed header value in.
166    * @param <T> The type to convert to.
167    * @param type The type to convert to.
168    * @param args The type parameters.
169    * @return This object.
170    */
171   @SuppressWarnings({
172      "unchecked" // Type erasure requires unchecked casts
173   })
174   public <T> RestResponse as(Holder<T> value, Type type, Type...args) {
175      value.set((T)as(type, args).orElse(null));
176      return response;
177   }
178
179   /**
180    * Returns the value of this header as a boolean.
181    *
182    * @return The value of this header as a boolean, or {@link Optional#empty()} if the header was not present.
183    */
184   public Optional<Boolean> asBoolean() {
185      return asBooleanHeader().asBoolean();
186   }
187
188   /**
189    * Returns the value of this header as an boolean header.
190    *
191    * @return The value of this header as an boolean header, never <jk>null</jk>.
192    */
193   public BasicBooleanHeader asBooleanHeader() {
194      return new BasicBooleanHeader(getName(), getValue());
195   }
196
197   /**
198    * Returns the value of this header as a list from a comma-delimited string.
199    *
200    * @return The value of this header as a list from a comma-delimited string, or {@link Optional#empty()} if the header was not present.
201    */
202   public Optional<String[]> asCsvArray() {
203      return asCsvHeader().asArray();
204   }
205
206   /**
207    * Returns the value of this header as a CSV array header.
208    *
209    * @return The value of this header as a CSV array header, never <jk>null</jk>.
210    */
211   public BasicCsvHeader asCsvHeader() {
212      return new BasicCsvHeader(getName(), getValue());
213   }
214
215   /**
216    * Returns the value of this header as a date.
217    *
218    * @return The value of this header as a date, or {@link Optional#empty()} if the header was not present.
219    */
220   public Optional<ZonedDateTime> asDate() {
221      return asDateHeader().asZonedDateTime();
222   }
223
224   /**
225    * Returns the value of this header as a date header.
226    *
227    * @return The value of this header as a date header, never <jk>null</jk>.
228    */
229   public BasicDateHeader asDateHeader() {
230      return new BasicDateHeader(getName(), getValue());
231   }
232
233   /**
234    * Returns the value of this header as an entity validator header.
235    *
236    * @return The value of this header as an entity validator array, never <jk>null</jk>.
237    */
238   public BasicEntityTagHeader asEntityTagHeader() {
239      return new BasicEntityTagHeader(getName(), getValue());
240   }
241
242   /**
243    * Returns the value of this header as an entity validator array header.
244    *
245    * @return The value of this header as an entity validator array header, never <jk>null</jk>.
246    */
247   public BasicEntityTagsHeader asEntityTagsHeader() {
248      return new BasicEntityTagsHeader(getName(), getValue());
249   }
250
251   /**
252    * Returns the value of this header as a {@link BasicHeader}.
253    *
254    * @param c The subclass of {@link BasicHeader} to instantiate.
255    * @param <T> The subclass of {@link BasicHeader} to instantiate.
256    * @return The value of this header as a string, never <jk>null</jk>.
257    */
258   public <T extends BasicHeader> T asHeader(Class<T> c) {
259      try {
260         var ci = ClassInfo.of(c);
261         var cc = ci.getPublicConstructor(x -> x.hasParameterTypes(String.class)).orElse(null);
262         if (nn(cc))
263            return cc.newInstance(getValue());
264         cc = ci.getPublicConstructor(x -> x.hasParameterTypes(String.class, String.class)).orElse(null);
265         if (nn(cc))
266            return cc.newInstance(getName(), getValue());
267      } catch (ExecutableException e) {
268         var cause = e.getCause();
269         if (cause instanceof Exception cause2)
270            throw toRex(cause2);
271         throw toRex(cause);
272      } catch (Exception e) {
273         throw toRex(e);
274      }
275      throw rex("Could not determine a method to construct type %s", cn(c));
276   }
277
278   /**
279    * Returns the value of this header as an integer.
280    *
281    * @return The value of this header as an integer, or {@link Optional#empty()} if the header was not present.
282    */
283   public Optional<Integer> asInteger() {
284      return asIntegerHeader().asInteger();
285   }
286
287   /**
288    * Returns the value of this header as an integer header.
289    *
290    * @return The value of this header as an integer header, never <jk>null</jk>.
291    */
292   public BasicIntegerHeader asIntegerHeader() {
293      return new BasicIntegerHeader(getName(), getValue());
294   }
295
296   /**
297    * Returns the value of this header as a long.
298    *
299    * @return The value of this header as a long, or {@link Optional#empty()} if the header was not present.
300    */
301   public Optional<Long> asLong() {
302      return asLongHeader().asLong();
303   }
304
305   /**
306    * Returns the value of this header as a long header.
307    *
308    * @return The value of this header as a long header, never <jk>null</jk>.
309    */
310   public BasicLongHeader asLongHeader() {
311      return new BasicLongHeader(getName(), getValue());
312   }
313
314   /**
315    * Matches the specified pattern against this header value.
316    *
317    * <h5 class='section'>Example:</h5>
318    * <p class='bjava'>
319    *    <jc>// Parse header using a regular expression.</jc>
320    *    Matcher <jv>matcher</jv> = <jv>client</jv>
321    *       .get(<jsf>URI</jsf>)
322    *       .run()
323    *       .getResponseHeader(<js>"Content-Type"</js>).asMatcher(Pattern.<jsm>compile</jsm>(<js>"application/(.*)"</js>));
324    *
325    *    <jk>if</jk> (<jv>matcher</jv>.matches()) {
326    *       String <jv>mediaType</jv> = <jv>matcher</jv>.group(1);
327    *    }
328    * </p>
329    *
330    * @param pattern The regular expression pattern to match.
331    * @return The matcher.
332    */
333   public Matcher asMatcher(Pattern pattern) {
334      return pattern.matcher(orElse(""));
335   }
336
337   /**
338    * Matches the specified pattern against this header value.
339    *
340    * <h5 class='section'>Example:</h5>
341    * <p class='bjava'>
342    *    <jc>// Parse header using a regular expression.</jc>
343    *    Matcher <jv>matcher</jv> = <jv>client</jv>
344    *       .get(<jsf>URI</jsf>)
345    *       .run()
346    *       .getHeader(<js>"Content-Type"</js>).asMatcher(<js>"application/(.*)"</js>);
347    *
348    *    <jk>if</jk> (<jv>matcher</jv>.matches()) {
349    *       String <jv>mediaType</jv> = <jv>matcher</jv>.group(1);
350    *    }
351    * </p>
352    *
353    * @param regex The regular expression pattern to match.
354    * @return The matcher.
355    */
356   public Matcher asMatcher(String regex) {
357      return asMatcher(regex, 0);
358   }
359
360   /**
361    * Matches the specified pattern against this header value.
362    *
363    * <h5 class='section'>Example:</h5>
364    * <p class='bjava'>
365    *    <jc>// Parse header using a regular expression.</jc>
366    *    Matcher <jv>matcher</jv> = <jv>client</jv>
367    *       .get(<jsf>URI</jsf>)
368    *       .run()
369    *       .getHeader(<js>"Content-Type"</js>).asMatcher(<js>"application/(.*)"</js>, <jsf>CASE_INSENSITIVE</jsf>);
370    *
371    *    <jk>if</jk> (<jv>matcher</jv>.matches()) {
372    *       String <jv>mediaType</jv> = <jv>matcher</jv>.group(1);
373    *    }
374    * </p>
375    *
376    * @param regex The regular expression pattern to match.
377    * @param flags Pattern match flags.  See {@link Pattern#compile(String, int)}.
378    * @return The matcher.
379    */
380   public Matcher asMatcher(String regex, int flags) {
381      return asMatcher(Pattern.compile(regex, flags));
382   }
383
384   /**
385    * Shortcut for calling <c>assertValue().asInteger()</c>.
386    *
387    * @return A new fluent assertion.
388    */
389   public FluentIntegerAssertion<ResponseHeader> assertInteger() {
390      return new FluentResponseHeaderAssertion<>(this, this).asInteger();
391   }
392
393   /**
394    * Shortcut for calling <c>assertValue().asLong()</c>.
395    *
396    * @return A new fluent assertion.
397    */
398   public FluentLongAssertion<ResponseHeader> assertLong() {
399      return new FluentResponseHeaderAssertion<>(this, this).asLong();
400   }
401
402   /**
403    * Shortcut for calling <c>assertValue().asString()</c>.
404    *
405    * @return A new fluent assertion.
406    */
407   public FluentStringAssertion<ResponseHeader> assertString() {
408      return new FluentResponseHeaderAssertion<>(this, this).asString();
409   }
410
411   /**
412    * Provides the ability to perform fluent-style assertions on this response header.
413    *
414    * <h5 class='section'>Examples:</h5>
415    * <p class='bjava'>
416    *    <jc>// Validates the content type header is provided.</jc>
417    *    <jv>client</jv>
418    *       .get(<jsf>URI</jsf>)
419    *       .run()
420    *       .getHeader(<js>"Content-Type"</js>).assertValue().exists();
421    *
422    *    <jc>// Validates the content type is JSON.</jc>
423    *    <jv>client</jv>
424    *       .get(<jsf>URI</jsf>)
425    *       .run()
426    *       .getHeader(<js>"Content-Type"</js>).assertValue().equals(<js>"application/json"</js>);
427    *
428    *    <jc>// Validates the content type is JSON using test predicate.</jc>
429    *    <jv>client</jv>
430    *       .get(<jsf>URI</jsf>)
431    *       .run()
432    *       .getHeader(<js>"Content-Type"</js>).assertValue().is(<jv>x</jv> -&gt; <jv>x</jv>.equals(<js>"application/json"</js>));
433    *
434    *    <jc>// Validates the content type is JSON by just checking for substring.</jc>
435    *    <jv>client</jv>
436    *       .get(<jsf>URI</jsf>)
437    *       .run()
438    *       .getHeader(<js>"Content-Type"</js>).assertValue().contains(<js>"json"</js>);
439    *
440    *    <jc>// Validates the content type is JSON using regular expression.</jc>
441    *    <jv>client</jv>
442    *       .get(<jsf>URI</jsf>)
443    *       .run()
444    *       .getHeader(<js>"Content-Type"</js>).assertValue().isPattern(<js>".*json.*"</js>);
445    *
446    *    <jc>// Validates the content type is JSON using case-insensitive regular expression.</jc>
447    *    <jv>client</jv>
448    *       .get(<jsf>URI</jsf>)
449    *       .run()
450    *       .getHeader(<js>"Content-Type"</js>).assertValue().isPattern(<js>".*json.*"</js>, <jsf>CASE_INSENSITIVE</jsf>);
451    * </p>
452    *
453    * <p>
454    * The assertion test returns the original response object allowing you to chain multiple requests like so:
455    * <p class='bjava'>
456    *    <jc>// Validates the header and converts it to a bean.</jc>
457    *    MediaType <jv>mediaType</jv> = <jv>client</jv>
458    *       .get(<jsf>URI</jsf>)
459    *       .run()
460    *       .getHeader(<js>"Content-Type"</js>).assertValue().isNotEmpty()
461    *       .getHeader(<js>"Content-Type"</js>).assertValue().isPattern(<js>".*json.*"</js>)
462    *       .getHeader(<js>"Content-Type"</js>).as(MediaType.<jk>class</jk>);
463    * </p>
464    *
465    * @return A new fluent assertion object.
466    */
467   public FluentResponseHeaderAssertion<ResponseHeader> assertValue() {
468      return new FluentResponseHeaderAssertion<>(this, this);
469   }
470
471   /**
472    * Shortcut for calling <c>assertValue().asZonedDateTime()</c>.
473    *
474    * @return A new fluent assertion.
475    */
476   public FluentZonedDateTimeAssertion<ResponseHeader> assertZonedDateTime() {
477      return new FluentResponseHeaderAssertion<>(this, this).asZonedDateTime();
478   }
479
480   /**
481    * Same as {@link #asString()} but sets the value in a mutable for fluent calls.
482    *
483    * @param value The mutable to set the header value in.
484    * @return This object.
485    */
486   public RestResponse asString(Holder<String> value) {
487      value.set(orElse(null));
488      return response;
489   }
490
491   /**
492    * Returns the value of this header as a string header.
493    *
494    * @return The value of this header as a string header, never <jk>null</jk>.
495    */
496   public BasicStringHeader asStringHeader() {
497      return new BasicStringHeader(getName(), getValue());
498   }
499
500   /**
501    * Returns the value of this header as a range array header.
502    *
503    * @return The value of this header as a range array header, never <jk>null</jk>.
504    */
505   public BasicStringRangesHeader asStringRangesHeader() {
506      return new BasicStringRangesHeader(getName(), getValue());
507   }
508
509   /**
510    * Returns the value of this header as a URI header.
511    *
512    * @return The value of this header as a URI header, never <jk>null</jk>.
513    */
514   public BasicUriHeader asUriHeader() {
515      return new BasicUriHeader(getName(), getValue());
516   }
517
518   /**
519    * Parses the value.
520    *
521    * @return An array of {@link HeaderElement} entries, may be empty, but is never <jk>null</jk>.
522    * @throws org.apache.http.ParseException In case of a parsing error.
523    */
524   @Override /* Overridden from Header */
525   public HeaderElement[] getElements() throws org.apache.http.ParseException { return cp(elements); }
526
527   /**
528    * Specifies the part parser to use for this header.
529    *
530    * <p>
531    * If not specified, uses the part parser defined on the client by calling {@link RestClient.Builder#partParser(Class)}.
532    *
533    * @param value
534    *    The new part parser to use for this header.
535    *    <br>If <jk>null</jk>, {@link SimplePartParser#DEFAULT_SESSION} will be used.
536    * @return This object.
537    */
538   public ResponseHeader parser(HttpPartParserSession value) {
539      this.parser = or(value, SimplePartParser.DEFAULT_SESSION);
540      return this;
541   }
542
543   /**
544    * Returns the response that created this object.
545    *
546    * @return The response that created this object.
547    */
548   public RestResponse response() {
549      return response;
550   }
551
552   /**
553    * Specifies the part schema for this header.
554    *
555    * <p>
556    * Used by schema-based part parsers such as {@link OpenApiParser}.
557    *
558    * @param value
559    *    The part schema.
560    * @return This object.
561    */
562   public ResponseHeader schema(HttpPartSchema value) {
563      schema = value;
564      return this;
565   }
566}