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