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