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.*;
023import static org.apache.juneau.commons.utils.StringUtils.*;
024
025import java.time.*;
026import java.util.*;
027import java.util.function.*;
028
029/**
030 * Represents an HTTP <c>Retry-After</c> header.
031 *
032 * <p>
033 * Delay before retrying, as a delta-seconds integer or an HTTP-date.
034 *
035 * <h5 class='section'>See Also:</h5><ul>
036 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a>
037 * </ul>
038 *
039 * @since 9.2.1
040 */
041@SuppressWarnings({
042   "java:S2160" // equals() on HttpHeaderBean uses name + getValue()
043})
044public class RetryAfter extends HttpHeaderBean {
045
046   /** The header name */
047   public static final String NAME = "Retry-After";
048
049   private final Integer delaySeconds;
050   private final ZonedDateTime retryAt;
051   private final Supplier<?> supplier;
052
053   /**
054    * Constructor with delay seconds.
055    *
056    * @param value Seconds to wait. May be <jk>null</jk>.
057    */
058   public RetryAfter(Integer value) {
059      super(NAME, (String)null);
060      this.delaySeconds = value;
061      this.retryAt = null;
062      this.supplier = null;
063   }
064
065   /**
066    * Constructor with a wire string (integer or HTTP-date).
067    *
068    * @param value The header value. May be <jk>null</jk>.
069    */
070   public RetryAfter(String value) {
071      super(NAME, (String)null);
072      if (isNumeric(value)) {
073         this.delaySeconds = Integer.parseInt(value);
074         this.retryAt = null;
075      } else {
076         this.delaySeconds = null;
077         this.retryAt = ie(value) ? null : parseHttpDate(value);
078      }
079      this.supplier = null;
080   }
081
082   /**
083    * Constructor with a lazy supplier of {@link Integer} or {@link ZonedDateTime}.
084    *
085    * @param valueSupplier Supplier for the value. Must not be <jk>null</jk>.
086    */
087   public RetryAfter(Supplier<?> valueSupplier) {
088      super(NAME, (String)null);
089      this.delaySeconds = null;
090      this.retryAt = null;
091      this.supplier = valueSupplier;
092   }
093
094   /**
095    * Constructor with an HTTP-date.
096    *
097    * @param value The retry time. May be <jk>null</jk>.
098    */
099   public RetryAfter(ZonedDateTime value) {
100      super(NAME, (String)null);
101      this.delaySeconds = null;
102      this.retryAt = value;
103      this.supplier = null;
104   }
105
106   /**
107    * @return The value as seconds when encoded as a delta-seconds integer.
108    */
109   public Optional<Integer> asInteger() {
110      if (nn(supplier)) {
111         var o = supplier.get();
112         return o(o instanceof Integer o2 ? o2 : null);
113      }
114      return o(delaySeconds);
115   }
116
117   /**
118    * @return The value as {@link ZonedDateTime} when encoded as an HTTP-date.
119    */
120   public Optional<ZonedDateTime> asZonedDateTime() {
121      if (nn(supplier)) {
122         var o = supplier.get();
123         return o(o instanceof ZonedDateTime o2 ? o2 : null);
124      }
125      return o(retryAt);
126   }
127
128   @Override /* HttpHeader */
129   public String getValue() {
130      if (nn(supplier)) {
131         var o = supplier.get();
132         if (o == null)
133            return null;
134         if (o instanceof Integer o2)
135            return o2.toString();
136         if (o instanceof ZonedDateTime o2)
137            return RFC_1123_DATE_TIME.format(o2);
138         throw rex("Invalid object type returned by supplier: %s", cn(o));
139      }
140      if (nn(delaySeconds))
141         return delaySeconds.toString();
142      if (nn(retryAt))
143         return RFC_1123_DATE_TIME.format(retryAt);
144      return null;
145   }
146
147   /**
148    * @param value Delay seconds. May be <jk>null</jk>, in which case <jk>null</jk> is returned.
149    * @return A new instance, or <jk>null</jk> if <c>value</c> is <jk>null</jk>.
150    */
151   public static RetryAfter of(Integer value) {
152      return value == null ? null : new RetryAfter(value);
153   }
154
155   /**
156    * @param value Wire string. May be <jk>null</jk>, in which case <jk>null</jk> is returned.
157    * @return A new instance, or <jk>null</jk> if <c>value</c> is <jk>null</jk>.
158    */
159   public static RetryAfter of(String value) {
160      return value == null ? null : new RetryAfter(value);
161   }
162
163   /**
164    * @param valueSupplier Supplier of {@link Integer} or {@link ZonedDateTime}. Must not be <jk>null</jk>.
165    * @return A new instance. Never <jk>null</jk>.
166    */
167   public static RetryAfter of(Supplier<?> valueSupplier) {
168      return new RetryAfter(valueSupplier);
169   }
170
171   /**
172    * @param value HTTP-date. May be <jk>null</jk>, in which case <jk>null</jk> is returned.
173    * @return A new instance, or <jk>null</jk> if <c>value</c> is <jk>null</jk>.
174    */
175   public static RetryAfter of(ZonedDateTime value) {
176      return value == null ? null : new RetryAfter(value);
177   }
178
179   private static ZonedDateTime parseHttpDate(String value) {
180      return ZonedDateTime.from(RFC_1123_DATE_TIME.parse(value)).truncatedTo(SECONDS);
181   }
182}