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