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.http.header;
014
015import static java.time.format.DateTimeFormatter.*;
016import static java.time.temporal.ChronoUnit.*;
017import static org.apache.juneau.internal.StringUtils.*;
018
019import java.time.*;
020import java.util.*;
021import java.util.function.*;
022
023import org.apache.juneau.annotation.*;
024import org.apache.juneau.assertions.*;
025import org.apache.juneau.http.*;
026
027/**
028 * Category of headers that consist of a single HTTP-date.
029 *
030 * <p>
031 * <h5 class='figure'>Example</h5>
032 * <p class='bcode w800'>
033 *    If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT
034 * </p>
035 *
036 * <ul class='seealso'>
037 *    <li class='extlink'>{@doc ExtRFC2616}
038 * </ul>
039 */
040@BeanIgnore
041public class BasicDateHeader extends BasicHeader {
042
043   private static final long serialVersionUID = 1L;
044
045   /**
046    * Convenience creator.
047    *
048    * @param name The header name.
049    * @param value
050    *    The header value.
051    *    <br>Can be any of the following:
052    *    <ul>
053    *       <li><c>String</c> - An RFC-1123 formated string (e.g. <js>"Sat, 29 Oct 1994 19:43:31 GMT"</js>).
054    *       <li>{@link ZonedDateTime}
055    *       <li>{@link Calendar}
056    *       <li>Anything else - Converted to <c>String</c>.
057    *    </ul>
058    * @return A new {@link BasicDateHeader} object, or <jk>null</jk> if the name or value is <jk>null</jk>.
059    */
060   public static BasicDateHeader of(String name, Object value) {
061      if (isEmpty(name) || value == null)
062         return null;
063      return new BasicDateHeader(name, value);
064   }
065
066   /**
067    * Convenience creator using supplier.
068    *
069    * <p>
070    * Header value is re-evaluated on each call to {@link #getValue()}.
071    *
072    * @param name The header name.
073    * @param value
074    *    The header value supplier.
075    *    <br>Can be any of the following:
076    *    <ul>
077    *       <li><c>String</c> - An RFC-1123 formated string (e.g. <js>"Sat, 29 Oct 1994 19:43:31 GMT"</js>).
078    *       <li>{@link ZonedDateTime}
079    *       <li>{@link Calendar}
080    *       <li>Anything else - Converted to <c>String</c>.
081    *    </ul>
082    * @return A new {@link BasicDateHeader} object, or <jk>null</jk> if the name or value is <jk>null</jk>.
083    */
084   public static BasicDateHeader of(String name, Supplier<?> value) {
085      if (isEmpty(name) || value == null)
086         return null;
087      return new BasicDateHeader(name, value);
088   }
089
090   private ZonedDateTime parsed;
091
092   /**
093    * Constructor.
094    *
095    * @param name The header name.
096    * @param value The header value.
097    *    <br>Can be any of the following:
098    *    <ul>
099    *       <li><c>String</c> - An RFC-1123 formated string (e.g. <js>"Sat, 29 Oct 1994 19:43:31 GMT"</js>).
100    *       <li>{@link ZonedDateTime} - Will be truncated to seconds.
101    *       <li>{@link Calendar} - Will be truncated to seconds.
102    *       <li>Anything else - Converted to <c>String</c>.
103    *       <li>A {@link Supplier} of anything on this list.
104    *    </ul>
105    */
106   public BasicDateHeader(String name, Object value) {
107      super(name, value);
108      if (! isSupplier(value))
109         parsed = getParsedValue();
110   }
111
112   @Override /* Header */
113   public String getValue() {
114      Object o = getRawValue();
115      if (o == null)
116         return null;
117      if (o instanceof String)
118         return (String)o;
119      return RFC_1123_DATE_TIME.format(getParsedValue());
120   }
121
122   /**
123    * Returns this header value as a {@link java.util.Calendar}.
124    *
125    * @return This header value as a {@link java.util.Calendar}, or <jk>null</jk> if the header could not be parsed.
126    */
127   public Calendar asCalendar() {
128      ZonedDateTime zdt = getParsedValue();
129      return zdt == null ? null : GregorianCalendar.from(zdt);
130   }
131
132   /**
133    * Returns this header value as a {@link java.util.Date}.
134    *
135    * @return This header value as a {@link java.util.Date}, or <jk>null</jk> if the header could not be parsed.
136    */
137   public java.util.Date asDate() {
138      Calendar c = asCalendar();
139      return c == null ? null : c.getTime();
140   }
141
142   /**
143    * Returns this header value as a {@link ZonedDateTime}.
144    *
145    * @return This header value as a {@link ZonedDateTime}, or <jk>null</jk> if the header could not be parsed.
146    */
147   public ZonedDateTime asZonedDateTime() {
148      return getParsedValue();
149   }
150
151   /**
152    * Provides the ability to perform fluent-style assertions on this header.
153    *
154    * <h5 class='section'>Examples:</h5>
155    * <p class='bcode w800'>
156    *    <jc>// Validates the response body content is not expired.</jc>
157    *    client
158    *       .get(<jsf>URL</jsf>)
159    *       .run()
160    *       .getDateHeader(<js>"Expires"</js>).assertThat().isLessThan(<jk>new</jk> Date());
161    * </p>
162    *
163    * @return A new fluent assertion object.
164    * @throws AssertionError If assertion failed.
165    */
166   public FluentZonedDateTimeAssertion<BasicDateHeader> assertZonedDateTime() {
167      return new FluentZonedDateTimeAssertion<>(asZonedDateTime(), this);
168   }
169
170   private ZonedDateTime getParsedValue() {
171      if (parsed != null)
172         return parsed;
173      Object o = getRawValue();
174      if (o == null)
175         return null;
176      if (o instanceof ZonedDateTime)
177         return ((ZonedDateTime)o).truncatedTo(SECONDS);
178      if (o instanceof GregorianCalendar)
179         return ((GregorianCalendar)o).toZonedDateTime().truncatedTo(SECONDS);
180      return ZonedDateTime.from(RFC_1123_DATE_TIME.parse(o.toString())).truncatedTo(SECONDS);
181   }
182}