001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.http.header;
014
015import static java.time.format.DateTimeFormatter.*;
016import static org.apache.juneau.common.internal.StringUtils.*;
017import static org.apache.juneau.internal.ClassUtils.*;
018import static org.apache.juneau.internal.CollectionUtils.*;
019import java.time.*;
020import java.util.*;
021import java.util.function.*;
022
023import org.apache.juneau.*;
024import org.apache.juneau.http.annotation.*;
025
026/**
027 * Represents a parsed <l>If-Range</l> HTTP request header.
028 *
029 * <p>
030 * If the entity is unchanged, send me the part(s) that I am missing; otherwise, send me the entire new entity.
031 *
032 * <h5 class='figure'>Example</h5>
033 * <p class='bcode'>
034 *    If-Range: "737060cd8c284d8af7ad3082f209582d"
035 * </p>
036 *
037 * <h5 class='topic'>RFC2616 Specification</h5>
038 *
039 * If a client has a partial copy of an entity in its cache, and wishes to have an up-to-date copy of the entire entity
040 * in its cache, it could use the Range request-header with a conditional GET (using either or both of
041 * If-Unmodified-Since and If-Match.)
042 * However, if the condition fails because the entity has been modified, the client would then have to make a second
043 * request to obtain the entire current entity-body.
044 *
045 * <p>
046 * The If-Range header allows a client to "short-circuit" the second request.
047 * Informally, its meaning is `if the entity is unchanged, send me the part(s) that I am missing; otherwise, send me
048 * the entire new entity'.
049 * <p class='bcode'>
050 *    If-Range = "If-Range" ":" ( entity-tag | HTTP-date )
051 * </p>
052 *
053 * <p>
054 * If the client has no entity tag for an entity, but does have a Last- Modified date, it MAY use that date in an
055 * If-Range header.
056 * (The server can distinguish between a valid HTTP-date and any form of entity-tag by examining no more than two
057 * characters.)
058 * The If-Range header SHOULD only be used together with a Range header, and MUST be ignored if the request does not
059 * include a Range header, or if the server does not support the sub-range operation.
060 *
061 * <p>
062 * If the entity tag given in the If-Range header matches the current entity tag for the entity, then the server SHOULD
063 * provide the specified sub-range of the entity using a 206 (Partial content) response.
064 * If the entity tag does not match, then the server SHOULD return the entire entity using a 200 (OK) response.
065 *
066 * <h5 class='section'>See Also:</h5><ul>
067 *    <li class='link'><a class="doclink" href="../../../../../index.html#juneau-rest-common">juneau-rest-common</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("If-Range")
074public class IfRange extends BasicDateHeader {
075
076   //-----------------------------------------------------------------------------------------------------------------
077   // Static
078   //-----------------------------------------------------------------------------------------------------------------
079
080   private static final long serialVersionUID = 1L;
081   private static final String NAME = "If-Range";
082
083   /**
084    * Static creator.
085    *
086    * @param value
087    *    The header value.
088    *    <br>Must be an RFC-1123 formated string (e.g. <js>"Sat, 29 Oct 1994 19:43:31 GMT"</js>).
089    *    <br>Can be <jk>null</jk>.
090    * @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>.
091    */
092   public static IfRange of(String value) {
093      return value == null ? null : new IfRange(value);
094   }
095
096   /**
097    * Static creator.
098    *
099    * @param value
100    *    The header value.
101    *    <br>Can be <jk>null</jk>.
102    * @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>.
103    */
104   public static IfRange of(ZonedDateTime value) {
105      return value == null ? null : new IfRange(value);
106   }
107
108   /**
109    * Static creator.
110    *
111    * @param value
112    *    The header value.
113    *    <br>Can be <jk>null</jk>.
114    * @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>.
115    */
116   public static IfRange of(EntityTag value) {
117      return value == null ? null : new IfRange(value);
118   }
119
120   /**
121    * Static creator with delayed value.
122    *
123    * <p>
124    * Header value is re-evaluated on each call to {@link #getValue()}.
125    *
126    * @param value
127    *    The supplier of the header value.
128    *    <br>Supplier must supply either {@link EntityTag} or {@link ZonedDateTime} objects.
129    *    <br>Can be <jk>null</jk>.
130    * @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>.
131    */
132   public static IfRange of(Supplier<?> value) {
133      return value == null ? null : new IfRange(value);
134   }
135
136   //-----------------------------------------------------------------------------------------------------------------
137   // Instance
138   //-----------------------------------------------------------------------------------------------------------------
139
140   private final EntityTag value;
141   private final Supplier<?> supplier;
142
143   /**
144    * Constructor.
145    *
146    * @param value
147    *    The header value.
148    *    <br>Must be an RFC-1123 formated string (e.g. <js>"Sat, 29 Oct 1994 19:43:31 GMT"</js>).
149    *    <br>Can be <jk>null</jk>.
150    */
151   public IfRange(String value) {
152      super(NAME, isEtag(value) ? null : value);
153      this.value = isEtag(value) ? EntityTag.of(value) : null;
154      this.supplier = null;
155   }
156
157   /**
158    * Constructor.
159    *
160    * @param value
161    *    The header value.
162    *    <br>Can be <jk>null</jk>.
163    */
164   public IfRange(ZonedDateTime value) {
165      super(NAME, value);
166      this.value = null;
167      this.supplier = null;
168   }
169
170   /**
171    * Constructor.
172    *
173    * @param value
174    *    The header value.
175    *    <br>Can be <jk>null</jk>.
176    */
177   public IfRange(EntityTag value) {
178      super(NAME, (String)null);
179      this.value = value;
180      this.supplier = null;
181   }
182
183   /**
184    * Constructor with delayed value.
185    *
186    * <p>
187    * Header value is re-evaluated on each call to {@link #getValue()}.
188    *
189    * @param value
190    *    The supplier of the header value.
191    *    <br>Supplier must supply either {@link EntityTag} or {@link ZonedDateTime} objects.
192    *    <br>Can be <jk>null</jk>.
193    */
194   public IfRange(Supplier<?> value) {
195      super(NAME, (String)null);
196      this.value = null;
197      this.supplier = value;
198   }
199
200   @Override /* Header */
201   public String getValue() {
202      if (supplier != null) {
203         Object o = supplier.get();
204         if (o == null)
205            return null;
206         if (o instanceof EntityTag) {
207            return o.toString();
208         } else if (o instanceof ZonedDateTime) {
209            return RFC_1123_DATE_TIME.format((ZonedDateTime)o);
210         }
211         throw new BasicRuntimeException("Invalid object type returned by supplier: {0}", className(o));
212      }
213      if (value != null)
214         return stringify(value);
215      return super.getValue();
216   }
217
218   /**
219    * Returns this header as an {@link EntityTag}.
220    *
221    * @return This header as an {@link EntityTag}.
222    */
223   public Optional<EntityTag> asEntityTag() {
224      if (supplier != null) {
225         Object o = supplier.get();
226         return optional(o instanceof EntityTag ? (EntityTag)o : null);
227      }
228      return optional(value);
229   }
230
231   private static boolean isEtag(String s) {
232      return s.startsWith("\"") || s.startsWith("W/");
233   }
234}