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 org.apache.juneau.commons.utils.Shorts.*;
020
021import java.time.*;
022import java.util.*;
023import java.util.function.*;
024
025/**
026 * Represents an HTTP <c>Cache-Control</c> header.
027 *
028 * <p>
029 * Used to specify directives that must be obeyed by all caching mechanisms along the request-response chain.
030 *
031 * <h5 class='section'>See Also:</h5><ul>
032 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a>
033 * </ul>
034 *
035 * @since 9.2.1
036 */
037public class CacheControl extends HttpStringHeader {
038
039   /** The header name */
040   public static final String NAME = "Cache-Control";
041
042   /**
043    * Constructor with an eager value.
044    *
045    * @param value The header value. May be <jk>null</jk>.
046    */
047   public CacheControl(String value) {
048      super(NAME, value);
049   }
050
051   /**
052    * Constructor with a lazy value supplier.
053    *
054    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
055    */
056   public CacheControl(Supplier<String> valueSupplier) {
057      super(NAME, valueSupplier);
058   }
059
060   /**
061    * Static factory method with an eager value.
062    *
063    * @param value The header value. May be <jk>null</jk>.
064    * @return A new instance. Never <jk>null</jk>.
065    */
066   public static CacheControl of(String value) {
067      return new CacheControl(value);
068   }
069
070   /**
071    * Static factory method with a lazy value supplier.
072    *
073    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
074    * @return A new instance. Never <jk>null</jk>.
075    */
076   public static CacheControl of(Supplier<String> valueSupplier) {
077      return new CacheControl(valueSupplier);
078   }
079
080   /**
081    * Creates a new empty {@link Builder}.
082    *
083    * @return A new builder. Never <jk>null</jk>.
084    */
085   public static Builder create() {
086      return new Builder();
087   }
088
089   /**
090    * Fluent builder for assembling an HTTP <c>Cache-Control</c> header value.
091    *
092    * <p>
093    * Composes the directives defined by
094    * <a class='doclink' href='https://www.rfc-editor.org/rfc/rfc9111#name-cache-control'>RFC 9111 §5.2</a> into the
095    * comma-separated wire format expected by the {@code Cache-Control} response header. Mutually exclusive
096    * cacheability directives (e.g. {@link #publicCache()} vs {@link #privateCache()}) overwrite one another;
097    * boolean directives (e.g. {@link #noStore()}) are idempotent.
098    *
099    * <h5 class='section'>Example:</h5>
100    * <p class='bjava'>
101    *    String <jv>value</jv> = CacheControl.<jsm>create</jsm>()
102    *       .publicCache()
103    *       .maxAge(3600)
104    *       .mustRevalidate()
105    *       .build();
106    *    <jc>// =&gt; "public, max-age=3600, must-revalidate"</jc>
107    * </p>
108    *
109    * <h5 class='section'>See Also:</h5><ul>
110    *    <li class='jc'>{@link CacheControl}
111    *    <li class='extlink'><a class='doclink' href='https://www.rfc-editor.org/rfc/rfc9111'>RFC 9111 - HTTP Caching</a>
112    * </ul>
113    *
114    * @since 10.0.0
115    */
116   @SuppressWarnings({
117      "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants
118   })
119   public static class Builder {
120
121      private String cacheability;        // "public" or "private" or null
122      private boolean noCache;
123      private boolean noStore;
124      private boolean noTransform;
125      private boolean mustRevalidate;
126      private boolean proxyRevalidate;
127      private boolean immutable;
128      private Long maxAge;
129      private Long sMaxAge;
130      private Long staleWhileRevalidate;
131      private Long staleIfError;
132      private final List<String> extensions = l();
133
134      /**
135       * Adds the <c>public</c> directive (responses may be cached by any cache).
136       *
137       * <p>
138       * Clears any previously set <c>private</c> directive.
139       *
140       * @return This object.
141       */
142      public Builder publicCache() {
143         cacheability = "public";
144         return this;
145      }
146
147      /**
148       * Adds the <c>private</c> directive (responses may only be cached by the originating client).
149       *
150       * <p>
151       * Clears any previously set <c>public</c> directive.
152       *
153       * @return This object.
154       */
155      public Builder privateCache() {
156         cacheability = "private";
157         return this;
158      }
159
160      /**
161       * Adds the <c>no-cache</c> directive.
162       *
163       * <p>
164       * Forces caches to revalidate with the origin server before reusing a stored response.
165       *
166       * @return This object.
167       */
168      public Builder noCache() {
169         noCache = true;
170         return this;
171      }
172
173      /**
174       * Adds the <c>no-store</c> directive.
175       *
176       * <p>
177       * Forbids caches from storing any part of the request or response.
178       *
179       * @return This object.
180       */
181      public Builder noStore() {
182         noStore = true;
183         return this;
184      }
185
186      /**
187       * Adds the <c>no-transform</c> directive.
188       *
189       * <p>
190       * Forbids intermediaries from transforming the response payload.
191       *
192       * @return This object.
193       */
194      public Builder noTransform() {
195         noTransform = true;
196         return this;
197      }
198
199      /**
200       * Adds the <c>must-revalidate</c> directive.
201       *
202       * <p>
203       * Forces caches to revalidate stale responses with the origin before serving them.
204       *
205       * @return This object.
206       */
207      public Builder mustRevalidate() {
208         mustRevalidate = true;
209         return this;
210      }
211
212      /**
213       * Adds the <c>proxy-revalidate</c> directive (must-revalidate for shared caches only).
214       *
215       * @return This object.
216       */
217      public Builder proxyRevalidate() {
218         proxyRevalidate = true;
219         return this;
220      }
221
222      /**
223       * Adds the <c>immutable</c> directive.
224       *
225       * <p>
226       * Signals that the response body will not change for the duration of its freshness lifetime.
227       *
228       * @return This object.
229       */
230      public Builder immutable() {
231         immutable = true;
232         return this;
233      }
234
235      /**
236       * Adds the <c>max-age=N</c> directive (seconds).
237       *
238       * @param seconds Maximum freshness lifetime in seconds. Must be non-negative.
239       * @return This object.
240       * @throws IllegalArgumentException If {@code seconds} is negative.
241       */
242      public Builder maxAge(long seconds) {
243         if (seconds < 0)
244            throw iaex("max-age must be non-negative: %s", seconds);
245         maxAge = seconds;
246         return this;
247      }
248
249      /**
250       * Adds the <c>max-age=N</c> directive from a {@link Duration}.
251       *
252       * @param value Maximum freshness lifetime. Must not be <jk>null</jk> or negative.
253       * @return This object.
254       * @throws IllegalArgumentException If {@code value} is <jk>null</jk> or negative.
255       */
256      public Builder maxAge(Duration value) {
257         reqnn("value", value);
258         return maxAge(value.getSeconds());
259      }
260
261      /**
262       * Adds the <c>s-maxage=N</c> directive (shared-cache max-age in seconds).
263       *
264       * @param seconds Maximum freshness lifetime in seconds for shared caches. Must be non-negative.
265       * @return This object.
266       * @throws IllegalArgumentException If {@code seconds} is negative.
267       */
268      public Builder sMaxAge(long seconds) {
269         if (seconds < 0)
270            throw iaex("s-maxage must be non-negative: %s", seconds);
271         sMaxAge = seconds;
272         return this;
273      }
274
275      /**
276       * Adds the <c>s-maxage=N</c> directive from a {@link Duration}.
277       *
278       * @param value Maximum freshness lifetime for shared caches. Must not be <jk>null</jk> or negative.
279       * @return This object.
280       * @throws IllegalArgumentException If {@code value} is <jk>null</jk> or negative.
281       */
282      public Builder sMaxAge(Duration value) {
283         reqnn("value", value);
284         return sMaxAge(value.getSeconds());
285      }
286
287      /**
288       * Adds the <c>stale-while-revalidate=N</c> directive (seconds).
289       *
290       * @param seconds Number of seconds a cache may serve a stale response while asynchronously revalidating.
291       * @return This object.
292       * @throws IllegalArgumentException If {@code seconds} is negative.
293       */
294      public Builder staleWhileRevalidate(long seconds) {
295         if (seconds < 0)
296            throw iaex("stale-while-revalidate must be non-negative: %s", seconds);
297         staleWhileRevalidate = seconds;
298         return this;
299      }
300
301      /**
302       * Adds the <c>stale-if-error=N</c> directive (seconds).
303       *
304       * @param seconds Number of seconds a cache may serve a stale response when the origin is unreachable.
305       * @return This object.
306       * @throws IllegalArgumentException If {@code seconds} is negative.
307       */
308      public Builder staleIfError(long seconds) {
309         if (seconds < 0)
310            throw iaex("stale-if-error must be non-negative: %s", seconds);
311         staleIfError = seconds;
312         return this;
313      }
314
315      /**
316       * Adds an arbitrary cache-control extension directive (e.g. <c>"community=\"UCI\""</c>).
317       *
318       * <p>
319       * Extensions are appended in registration order at the end of the directive list. The caller is responsible for
320       * supplying a token that conforms to the <c>cache-directive</c> grammar of RFC 9111 §5.2.3.
321       *
322       * @param value The extension token. Must not be <jk>null</jk> or blank.
323       * @return This object.
324       * @throws IllegalArgumentException If {@code value} is <jk>null</jk> or blank.
325       */
326      public Builder extension(String value) {
327         reqnn("value", value);
328         var v = value.trim();
329         if (v.isEmpty())
330            throw iaex("cache-control extension must not be blank");
331         extensions.add(v);
332         return this;
333      }
334
335      /**
336       * Builds the comma-separated <c>Cache-Control</c> header value.
337       *
338       * <p>
339       * Returns an empty string when no directives have been registered.
340       *
341       * @return The header value. Never <jk>null</jk>.
342       */
343      public String build() {
344         var parts = new ArrayList<String>(12);
345         if (cacheability != null)
346            parts.add(cacheability);
347         if (noCache)
348            parts.add("no-cache");
349         if (noStore)
350            parts.add("no-store");
351         if (noTransform)
352            parts.add("no-transform");
353         if (mustRevalidate)
354            parts.add("must-revalidate");
355         if (proxyRevalidate)
356            parts.add("proxy-revalidate");
357         if (immutable)
358            parts.add("immutable");
359         if (maxAge != null)
360            parts.add("max-age=" + maxAge);
361         if (sMaxAge != null)
362            parts.add("s-maxage=" + sMaxAge);
363         if (staleWhileRevalidate != null)
364            parts.add("stale-while-revalidate=" + staleWhileRevalidate);
365         if (staleIfError != null)
366            parts.add("stale-if-error=" + staleIfError);
367         parts.addAll(extensions);
368         return String.join(", ", parts);
369      }
370
371      /**
372       * Builds a {@link CacheControl} header bean directly from this builder.
373       *
374       * @return A {@link CacheControl} carrying {@link #build()}. Never <jk>null</jk>.
375       */
376      public CacheControl toHeader() {
377         return CacheControl.of(build());
378      }
379
380      @Override
381      public String toString() {
382         return build();
383      }
384   }
385}