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.http.classic.header;
018
019import static java.time.format.DateTimeFormatter.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021import static org.apache.juneau.commons.utils.StringUtils.*;
022
023import java.time.*;
024import java.util.*;
025import java.util.function.*;
026
027import org.apache.juneau.http.*;
028
029/**
030 * Represents a parsed <l>Retry-After</l> HTTP response header.
031 *
032 * <p>
033 * If an entity is temporarily unavailable, this instructs the client to try again later.
034 * Value could be a specified period of time (in seconds) or a HTTP-date.
035 *
036 * <h5 class='figure'>Example</h5>
037 * <p class='bcode'>
038 *    Retry-After: 120
039 *    Retry-After: Fri, 07 Nov 2014 23:59:59 GMT
040 * </p>
041 *
042 * <h5 class='topic'>RFC2616 Specification</h5>
043 *
044 * The Retry-After response-header field can be used with a 503 (Service Unavailable) response to indicate how long the
045 * service is expected to be unavailable to the requesting client.
046 * This field MAY also be used with any 3xx (Redirection) response to indicate the minimum time the user-agent is asked
047 * wait before issuing the redirected request.
048 * The value of this field can be either an HTTP-date or an integer number of seconds (in decimal) after the time of the
049 * response.
050 *
051 * <p class='bcode'>
052 *    Retry-After  = "Retry-After" ":" ( HTTP-date | delta-seconds )
053 * </p>
054 *
055 * <p>
056 * Two examples of its use are
057 * <p class='bcode'>
058 *    Retry-After: Fri, 31 Dec 1999 23:59:59 GMT
059 *    Retry-After: 120
060 * </p>
061 *
062 * <p>
063 * In the latter example, the delay is 2 minutes.
064 *
065 * <h5 class='section'>See Also:</h5><ul>
066 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommon">juneau-rest-common</a>
067 *    <li class='extlink'><a class="doclink" href="https://www.w3.org/Protocols/rfc2616/rfc2616.html">Hypertext Transfer Protocol -- HTTP/1.1</a>
068 * </ul>
069 *
070 * @serial exclude
071 */
072@Header("Retry-After")
073@SuppressWarnings({
074   "java:S2160" // equals() inherited from BasicHeader compares name+value; no additional equality-relevant fields
075})
076public class RetryAfter extends BasicDateHeader {
077   private static final long serialVersionUID = 1L;
078   private static final String NAME = "Retry-After";
079
080   /**
081    * Static creator.
082    *
083    * @param value
084    *    The header value.
085    *    <br>Can be <jk>null</jk>.
086    * @return A new header bean, or <jk>null</jk> if the name is <jk>null</jk> or empty or the value is <jk>null</jk>.
087    */
088   public static RetryAfter of(Integer value) {
089      return value == null ? null : new RetryAfter(value);
090   }
091
092   /**
093    * Static creator.
094    *
095    * @param value
096    *    The header value.
097    *    <br>Must be an RFC-1123 formated string (e.g. <js>"Sat, 29 Oct 1994 19:43:31 GMT"</js>) or an integer.
098    *    <br>Can be <jk>null</jk>.
099    * @return A new header bean, or <jk>null</jk> if the name is <jk>null</jk> or empty or the value is <jk>null</jk>.
100    */
101   public static RetryAfter of(String value) {
102      return value == null ? null : new RetryAfter(value);
103   }
104
105   /**
106    * Static creator with delayed value.
107    *
108    * <p>
109    * Header value is re-evaluated on each call to {@link #getValue()}.
110    *
111    * @param value
112    *    The supplier of the header value.
113    *    <br>Supplier must supply either {@link Integer} or {@link ZonedDateTime} objects.
114    *    <br>Can be <jk>null</jk>.
115    * @return A new header bean, or <jk>null</jk> if the name is <jk>null</jk> or empty or the value is <jk>null</jk>.
116    */
117   public static RetryAfter of(Supplier<?> value) {
118      return value == null ? null : new RetryAfter(value);
119   }
120
121   /**
122    * Static creator.
123    *
124    * @param value
125    *    The header value.
126    *    <br>Can be <jk>null</jk>.
127    * @return A new header bean, or <jk>null</jk> if the name is <jk>null</jk> or empty or the value is <jk>null</jk>.
128    */
129   public static RetryAfter of(ZonedDateTime value) {
130      return value == null ? null : new RetryAfter(value);
131   }
132
133   private final Integer value;
134   private final transient Supplier<?> supplier;
135
136   /**
137    * Constructor.
138    *
139    * @param value
140    *    The header value.
141    *    <br>Can be <jk>null</jk> to create a header with no value.
142    */
143   public RetryAfter(Integer value) {
144      super(NAME, (String)null);
145      this.value = value;
146      this.supplier = null;
147   }
148
149   /**
150    * Constructor.
151    *
152    * @param value
153    *    The header value.
154    *    <br>Must be an RFC-1123 formated string (e.g. <js>"Sat, 29 Oct 1994 19:43:31 GMT"</js>) or an integer.
155    *    <br>Can be <jk>null</jk> to create a header with no value.
156    */
157   public RetryAfter(String value) {
158      super(NAME, isNumeric(value) ? null : value);
159      this.value = isNumeric(value) ? Integer.parseInt(value) : null;
160      this.supplier = null;
161   }
162
163   /**
164    * Constructor with delayed value.
165    *
166    * <p>
167    * Header value is re-evaluated on each call to {@link #getValue()}.
168    *
169    * @param value
170    *    The supplier of the header value.
171    *    <br>Supplier must supply either {@link Integer} or {@link ZonedDateTime} objects.
172    *    <br>Can be <jk>null</jk>.
173    */
174   public RetryAfter(Supplier<?> value) {
175      super(NAME, (String)null);
176      this.value = null;
177      supplier = value;
178   }
179
180   /**
181    * Constructor.
182    *
183    * @param value
184    *    The header value.
185    *    <br>Can be <jk>null</jk> to create a header with no value.
186    */
187   public RetryAfter(ZonedDateTime value) {
188      super(NAME, value);
189      this.value = null;
190      this.supplier = null;
191   }
192
193   /**
194    * Returns this header value as an integer.
195    *
196    * @return This header value as a integer, or an empty optional if value was <jk>null</jk> or not an integer.
197    */
198   public Optional<Integer> asInteger() {
199      if (nn(supplier)) {
200         Object o = supplier.get();
201         return o(o instanceof Integer o2 ? o2 : null);
202      }
203      return o(value);
204   }
205
206   @Override /* Overridden from Header */
207   public String getValue() {
208      if (nn(supplier)) {
209         Object o = supplier.get();
210         if (o == null)
211            return null;
212         if (o instanceof Integer o2) {
213            return o2.toString();
214         } else if (o instanceof ZonedDateTime o2) {
215            return RFC_1123_DATE_TIME.format(o2);
216         }
217         throw rex("Invalid object type returned by supplier: %s", cn(o));
218      }
219      if (nn(value))
220         return s(value);
221      return super.getValue();
222   }
223}