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