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