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.internal.ClassUtils.*;
021
022import java.time.*;
023import java.util.*;
024import java.util.function.*;
025
026import org.apache.juneau.*;
027import org.apache.juneau.common.utils.*;
028import org.apache.juneau.http.annotation.*;
029
030/**
031 * Represents a parsed <l>If-Range</l> HTTP request header.
032 *
033 * <p>
034 * If the entity is unchanged, send me the part(s) that I am missing; otherwise, send me the entire new entity.
035 *
036 * <h5 class='figure'>Example</h5>
037 * <p class='bcode'>
038 *    If-Range: "737060cd8c284d8af7ad3082f209582d"
039 * </p>
040 *
041 * <h5 class='topic'>RFC2616 Specification</h5>
042 *
043 * 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
044 * in its cache, it could use the Range request-header with a conditional GET (using either or both of
045 * If-Unmodified-Since and If-Match.)
046 * However, if the condition fails because the entity has been modified, the client would then have to make a second
047 * request to obtain the entire current entity-body.
048 *
049 * <p>
050 * The If-Range header allows a client to "short-circuit" the second request.
051 * Informally, its meaning is `if the entity is unchanged, send me the part(s) that I am missing; otherwise, send me
052 * the entire new entity'.
053 * <p class='bcode'>
054 *    If-Range = "If-Range" ":" ( entity-tag | HTTP-date )
055 * </p>
056 *
057 * <p>
058 * If the client has no entity tag for an entity, but does have a Last- Modified date, it MAY use that date in an
059 * If-Range header.
060 * (The server can distinguish between a valid HTTP-date and any form of entity-tag by examining no more than two
061 * characters.)
062 * The If-Range header SHOULD only be used together with a Range header, and MUST be ignored if the request does not
063 * include a Range header, or if the server does not support the sub-range operation.
064 *
065 * <p>
066 * If the entity tag given in the If-Range header matches the current entity tag for the entity, then the server SHOULD
067 * provide the specified sub-range of the entity using a 206 (Partial content) response.
068 * If the entity tag does not match, then the server SHOULD return the entire entity using a 200 (OK) response.
069 *
070 * <h5 class='section'>See Also:</h5><ul>
071 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommonBasics">juneau-rest-common Basics</a>
072 *    <li class='extlink'><a class="doclink" href="https://www.w3.org/Protocols/rfc2616/rfc2616.html">Hypertext Transfer Protocol -- HTTP/1.1</a>
073 * </ul>
074 *
075 * @serial exclude
076 */
077@Header("If-Range")
078public class IfRange extends BasicDateHeader {
079
080   //-----------------------------------------------------------------------------------------------------------------
081   // Static
082   //-----------------------------------------------------------------------------------------------------------------
083
084   private static final long serialVersionUID = 1L;
085   private static final String NAME = "If-Range";
086
087   /**
088    * Static creator.
089    *
090    * @param value
091    *    The header value.
092    *    <br>Must be an RFC-1123 formated string (e.g. <js>"Sat, 29 Oct 1994 19:43:31 GMT"</js>).
093    *    <br>Can be <jk>null</jk>.
094    * @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>.
095    */
096   public static IfRange of(String value) {
097      return value == null ? null : new IfRange(value);
098   }
099
100   /**
101    * Static creator.
102    *
103    * @param value
104    *    The header value.
105    *    <br>Can be <jk>null</jk>.
106    * @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>.
107    */
108   public static IfRange of(ZonedDateTime value) {
109      return value == null ? null : new IfRange(value);
110   }
111
112   /**
113    * Static creator.
114    *
115    * @param value
116    *    The header value.
117    *    <br>Can be <jk>null</jk>.
118    * @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>.
119    */
120   public static IfRange of(EntityTag value) {
121      return value == null ? null : new IfRange(value);
122   }
123
124   /**
125    * Static creator with delayed value.
126    *
127    * <p>
128    * Header value is re-evaluated on each call to {@link #getValue()}.
129    *
130    * @param value
131    *    The supplier of the header value.
132    *    <br>Supplier must supply either {@link EntityTag} or {@link ZonedDateTime} objects.
133    *    <br>Can be <jk>null</jk>.
134    * @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>.
135    */
136   public static IfRange of(Supplier<?> value) {
137      return value == null ? null : new IfRange(value);
138   }
139
140   //-----------------------------------------------------------------------------------------------------------------
141   // Instance
142   //-----------------------------------------------------------------------------------------------------------------
143
144   private final EntityTag value;
145   private final Supplier<?> supplier;
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>).
153    *    <br>Can be <jk>null</jk>.
154    */
155   public IfRange(String value) {
156      super(NAME, isEtag(value) ? null : value);
157      this.value = isEtag(value) ? EntityTag.of(value) : null;
158      this.supplier = null;
159   }
160
161   /**
162    * Constructor.
163    *
164    * @param value
165    *    The header value.
166    *    <br>Can be <jk>null</jk>.
167    */
168   public IfRange(ZonedDateTime value) {
169      super(NAME, value);
170      this.value = null;
171      this.supplier = null;
172   }
173
174   /**
175    * Constructor.
176    *
177    * @param value
178    *    The header value.
179    *    <br>Can be <jk>null</jk>.
180    */
181   public IfRange(EntityTag value) {
182      super(NAME, (String)null);
183      this.value = value;
184      this.supplier = null;
185   }
186
187   /**
188    * Constructor with delayed value.
189    *
190    * <p>
191    * Header value is re-evaluated on each call to {@link #getValue()}.
192    *
193    * @param value
194    *    The supplier of the header value.
195    *    <br>Supplier must supply either {@link EntityTag} or {@link ZonedDateTime} objects.
196    *    <br>Can be <jk>null</jk>.
197    */
198   public IfRange(Supplier<?> value) {
199      super(NAME, (String)null);
200      this.value = null;
201      this.supplier = value;
202   }
203
204   @Override /* Header */
205   public String getValue() {
206      if (supplier != null) {
207         Object o = supplier.get();
208         if (o == null)
209            return null;
210         if (o instanceof EntityTag) {
211            return o.toString();
212         } else if (o instanceof ZonedDateTime) {
213            return RFC_1123_DATE_TIME.format((ZonedDateTime)o);
214         }
215         throw new BasicRuntimeException("Invalid object type returned by supplier: {0}", className(o));
216      }
217      if (value != null)
218         return Utils.s(value);
219      return super.getValue();
220   }
221
222   /**
223    * Returns this header as an {@link EntityTag}.
224    *
225    * @return This header as an {@link EntityTag}.
226    */
227   public Optional<EntityTag> asEntityTag() {
228      if (supplier != null) {
229         Object o = supplier.get();
230         return Utils.opt(o instanceof EntityTag ? (EntityTag)o : null);
231      }
232      return Utils.opt(value);
233   }
234
235   private static boolean isEtag(String s) {
236      return s.startsWith("\"") || s.startsWith("W/");
237   }
238}