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 (the
006 * "License"); you may not use this file except in compliance with the
007 * 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.http.header;
018
019
020import static java.time.format.DateTimeFormatter.*;
021import static java.time.temporal.ChronoUnit.*;
022import static org.apache.juneau.commons.utils.Shorts.*;
023
024import java.time.*;
025import java.util.*;
026import java.util.function.*;
027
028/**
029 * Base for HTTP-date headers.
030 *
031 * @since 9.2.1
032 */
033@SuppressWarnings({
034   "java:S2160", // equals() on HttpHeaderBean uses name + getValue(); typed state is reflected in getValue()
035   "java:S9149", // Public Juneau DSL factory; hiding parent of()/create() is intentional.
036   "unchecked" // Supplier<?> branches cast to typed suppliers after lazy-mode check
037})
038public class HttpDateHeader extends HttpHeaderBean {
039
040   public static final int LAZY_WIRE_STRING = 0;
041   public static final int LAZY_ZONED_DATE_TIME = 1;
042
043   private final ZonedDateTime cachedZdt;
044   private final Supplier<?> lazySupplier;
045   private final int lazyMode;
046
047   /**
048    * Creates an {@link HttpDateHeader} by parsing the given RFC-1123 wire-format value.
049    *
050    * @param name Header name. Must not be {@code null}.
051    * @param wireValue Wire value (e.g. {@code "Sun, 06 Nov 1994 08:49:37 GMT"}). May be {@code null} or empty.
052    * @return A new instance. Never {@code null}.
053    */
054   public static HttpDateHeader of(String name, String wireValue) {
055      return new HttpDateHeader(name, wireValue);
056   }
057
058   /**
059    * Creates an {@link HttpDateHeader} with the given typed value.
060    *
061    * @param name Header name. Must not be {@code null}.
062    * @param typedValue The date value. May be {@code null}.
063    * @return A new instance. Never {@code null}.
064    */
065   public static HttpDateHeader of(String name, ZonedDateTime typedValue) {
066      return new HttpDateHeader(name, typedValue);
067   }
068
069   /**
070    * Constructor.
071    *
072    * @param name Header name. Must not be <jk>null</jk>.
073    * @param value Wire value. Can be <jk>null</jk> or empty, in which case the parsed value is <jk>null</jk>.
074    */
075   protected HttpDateHeader(String name, String value) {
076      super(name, (String)null);
077      this.cachedZdt = ie(value) ? null : parseHttpDate(value);
078      this.lazySupplier = null;
079      this.lazyMode = -1;
080   }
081
082   /**
083    * Constructor.
084    *
085    * @param name Header name. Must not be <jk>null</jk>.
086    * @param value The date value. Can be <jk>null</jk>, in which case the parsed value is <jk>null</jk>.
087    */
088   protected HttpDateHeader(String name, ZonedDateTime value) {
089      super(name, (String)null);
090      this.cachedZdt = value;
091      this.lazySupplier = null;
092      this.lazyMode = -1;
093   }
094
095   /**
096    * Constructor with lazy value supplier.
097    *
098    * @param name Header name. Must not be <jk>null</jk>.
099    * @param supplier The lazy value supplier. Must not be <jk>null</jk>.
100    * @param lazyMode Either {@link #LAZY_WIRE_STRING} or {@link #LAZY_ZONED_DATE_TIME}.
101    */
102   protected HttpDateHeader(String name, Supplier<?> supplier, int lazyMode) {
103      super(name, lazyMode == LAZY_WIRE_STRING
104         ? ((Supplier<String>) supplier)::get
105         : () -> {
106            var z = ((Supplier<ZonedDateTime>) supplier).get();
107            if (z == null)
108               return null;
109            return RFC_1123_DATE_TIME.format(z);
110         });
111      this.cachedZdt = null;
112      this.lazySupplier = supplier;
113      this.lazyMode = lazyMode;
114   }
115
116   public Optional<ZonedDateTime> asZonedDateTime() {
117      return o(toZonedDateTime());
118   }
119
120   /**
121    * Returns the RFC-1123 wire-format value of this header.
122    *
123    * @return The wire value, or <jk>null</jk> if the value is unset.
124    */
125   @Override
126   public String getValue() {
127      if (lazyMode == LAZY_ZONED_DATE_TIME) {
128         var x = ((Supplier<ZonedDateTime>) lazySupplier).get();
129         return x == null ? null : RFC_1123_DATE_TIME.format(x);
130      }
131      if (cachedZdt != null)
132         return RFC_1123_DATE_TIME.format(cachedZdt);
133      return super.getValue();
134   }
135
136   /**
137    * Returns the parsed value of this header, or the specified default if unset.
138    *
139    * @param other The default value. Can be <jk>null</jk> to allow a <jk>null</jk> result when the header is unset.
140    * @return The parsed value, or <c>other</c> if the value is unset. Can be <jk>null</jk> if <c>other</c> is <jk>null</jk>.
141    */
142   public ZonedDateTime orElse(ZonedDateTime other) {
143      var x = toZonedDateTime();
144      return nn(x) ? x : other;
145   }
146
147   /**
148    * Returns the parsed value of this header.
149    *
150    * @return The parsed value, or <jk>null</jk> if the value is unset.
151    */
152   public ZonedDateTime toZonedDateTime() {
153      if (lazyMode == LAZY_ZONED_DATE_TIME)
154         return ((Supplier<ZonedDateTime>) lazySupplier).get();
155      if (cachedZdt != null)
156         return cachedZdt;
157      var v = super.getValue();
158      return ie(v) ? null : parseHttpDate(v);
159   }
160
161   private static ZonedDateTime parseHttpDate(String value) {
162      return ZonedDateTime.from(RFC_1123_DATE_TIME.parse(value)).truncatedTo(SECONDS);
163   }
164}