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