View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.client5.http.impl.cache;
29  
30  import java.util.Arrays;
31  import java.util.Collections;
32  import java.util.HashSet;
33  import java.util.Set;
34  
35  import org.apache.hc.core5.annotation.Contract;
36  import org.apache.hc.core5.annotation.Internal;
37  import org.apache.hc.core5.annotation.ThreadingBehavior;
38  
39  /**
40   * Represents the values of the Cache-Control header in an HTTP response, which indicate whether and for how long
41   * the response can be cached by the client and intermediary proxies.
42   * <p>
43   * The class provides methods to retrieve the maximum age of the response and the maximum age that applies to shared
44   * caches. The values are expressed in seconds, with -1 indicating that the value was not specified in the header.
45   * <p>
46   * Instances of this class are immutable, meaning that their values cannot be changed once they are set. To create an
47   * instance, use one of the constructors that take the desired values as arguments. Alternatively, use the default
48   * constructor to create an instance with both values set to -1, indicating that the header was not present in the
49   * response.
50   *
51   * @since 5.3
52   */
53  @Internal
54  @Contract(threading = ThreadingBehavior.IMMUTABLE)
55  final class ResponseCacheControl implements CacheControl {
56  
57      /**
58       * The max-age directive value.
59       */
60      private final long maxAge;
61      /**
62       * The shared-max-age directive value.
63       */
64      private final long sharedMaxAge;
65      /**
66       * The isNoCache flag indicates whether the Cache-Control header includes the no-cache directive.
67       */
68      private final boolean noCache;
69      /**
70       * The isNoStore flag indicates whether the Cache-Control header includes the no-store directive.
71       */
72      private final boolean noStore;
73      /**
74       * The isPrivate flag indicates whether the Cache-Control header includes the private directive.
75       */
76      private final boolean cachePrivate;
77      /**
78       * Indicates whether the Cache-Control header includes the "must-revalidate" directive.
79       */
80      private final boolean mustRevalidate;
81      /**
82       * Indicates whether the Cache-Control header includes the "proxy-revalidate" directive.
83       */
84      private final boolean proxyRevalidate;
85      /**
86       * Indicates whether the Cache-Control header includes the "public" directive.
87       */
88      private final boolean cachePublic;
89      /**
90       * Indicates whether the Cache-Control header includes the "must-understand" directive.
91       */
92      private final boolean mustUnderstand;
93  
94      /**
95       * The number of seconds that a stale response is considered fresh for the purpose
96       * of serving a response while a revalidation request is made to the origin server.
97       */
98      private final long staleWhileRevalidate;
99      /**
100      * The number of seconds that a cached stale response MAY be used to satisfy the request,
101      * regardless of other freshness information..
102      */
103     private final long staleIfError;
104     /**
105      * A set of field names specified in the "no-cache" directive of the Cache-Control header.
106      */
107     private final Set<String> noCacheFields;
108 
109     private final boolean undefined;
110 
111     /**
112      * Flag for the 'immutable' Cache-Control directive.
113      * If this field is true, then the 'immutable' directive is present in the Cache-Control header.
114      * The 'immutable' directive is meant to inform a cache or user agent that the response body will not
115      * change over time, even though it may be requested multiple times.
116      */
117     private final boolean immutable;
118 
119     /**
120      * Creates a new instance of {@code CacheControl} with the specified values.
121      *
122      * @param maxAge          The max-age value from the Cache-Control header.
123      * @param sharedMaxAge    The shared-max-age value from the Cache-Control header.
124      * @param mustRevalidate  The must-revalidate value from the Cache-Control header.
125      * @param noCache         The no-cache value from the Cache-Control header.
126      * @param noStore         The no-store value from the Cache-Control header.
127      * @param cachePrivate    The private value from the Cache-Control header.
128      * @param proxyRevalidate The proxy-revalidate value from the Cache-Control header.
129      * @param cachePublic     The public value from the Cache-Control header.
130      * @param staleWhileRevalidate  The stale-while-revalidate value from the Cache-Control header.
131      * @param staleIfError    The stale-if-error value from the Cache-Control header.
132      * @param noCacheFields   The set of field names specified in the "no-cache" directive of the Cache-Control header.
133      * @param mustUnderstand  The must-understand value from the Cache-Control header.
134      * @param immutable       The immutable value from the Cache-Control header.
135      */
136     ResponseCacheControl(final long maxAge, final long sharedMaxAge, final boolean mustRevalidate, final boolean noCache,
137                          final boolean noStore, final boolean cachePrivate, final boolean proxyRevalidate,
138                          final boolean cachePublic, final long staleWhileRevalidate, final long staleIfError,
139                          final Set<String> noCacheFields, final boolean mustUnderstand, final boolean immutable) {
140         this.maxAge = maxAge;
141         this.sharedMaxAge = sharedMaxAge;
142         this.noCache = noCache;
143         this.noStore = noStore;
144         this.cachePrivate = cachePrivate;
145         this.mustRevalidate = mustRevalidate;
146         this.proxyRevalidate = proxyRevalidate;
147         this.cachePublic = cachePublic;
148         this.staleWhileRevalidate = staleWhileRevalidate;
149         this.staleIfError = staleIfError;
150         this.noCacheFields = noCacheFields != null ? Collections.unmodifiableSet(noCacheFields) : Collections.emptySet();
151         this.undefined = maxAge == -1 &&
152                 sharedMaxAge == -1 &&
153                 !noCache &&
154                 !noStore &&
155                 !cachePrivate &&
156                 !mustRevalidate &&
157                 !proxyRevalidate &&
158                 !cachePublic &&
159                 staleWhileRevalidate == -1
160                 && staleIfError == -1;
161         this.mustUnderstand = mustUnderstand;
162         this.immutable = immutable;
163     }
164 
165     /**
166      * Returns the max-age value from the Cache-Control header.
167      *
168      * @return The max-age value.
169      */
170     @Override
171     public long getMaxAge() {
172         return maxAge;
173     }
174 
175     /**
176      * Returns the shared-max-age value from the Cache-Control header.
177      *
178      * @return The shared-max-age value.
179      */
180     public long getSharedMaxAge() {
181         return sharedMaxAge;
182     }
183 
184     /**
185      * Returns the no-cache flag from the Cache-Control header.
186      *
187      * @return The no-cache flag.
188      */
189     @Override
190     public boolean isNoCache() {
191         return noCache;
192     }
193 
194     /**
195      * Returns the no-store flag from the Cache-Control header.
196      *
197      * @return The no-store flag.
198      */
199     @Override
200     public boolean isNoStore() {
201         return noStore;
202     }
203 
204     /**
205      * Returns the private flag from the Cache-Control header.
206      *
207      * @return The private flag.
208      */
209     public boolean isCachePrivate() {
210         return cachePrivate;
211     }
212 
213     /**
214      * Returns the must-understand directive from the Cache-Control header.
215      *
216      * @return The must-understand directive.
217      */
218     public boolean isMustUnderstand() {
219         return mustUnderstand;
220     }
221 
222     /**
223      * Returns whether the must-revalidate directive is present in the Cache-Control header.
224      *
225      * @return {@code true} if the must-revalidate directive is present, otherwise {@code false}
226      */
227     public boolean isMustRevalidate() {
228         return mustRevalidate;
229     }
230 
231     /**
232      * Returns whether the proxy-revalidate value is set in the Cache-Control header.
233      *
234      * @return {@code true} if proxy-revalidate is set, {@code false} otherwise.
235      */
236     public boolean isProxyRevalidate() {
237         return proxyRevalidate;
238     }
239 
240     /**
241      * Returns whether the public value is set in the Cache-Control header.
242      *
243      * @return {@code true} if public is set, {@code false} otherwise.
244      */
245     public boolean isPublic() {
246         return cachePublic;
247     }
248 
249     /**
250      * Returns the stale-while-revalidate value from the Cache-Control header.
251      *
252      * @return The stale-while-revalidate value.
253      */
254     public long getStaleWhileRevalidate() {
255         return staleWhileRevalidate;
256     }
257 
258     /**
259      * Returns the stale-if-error value from the Cache-Control header.
260      *
261      * @return The stale-if-error value.
262      */
263     @Override
264     public long getStaleIfError() {
265         return staleIfError;
266     }
267 
268     /**
269      * Returns an unmodifiable set of field names specified in the "no-cache" directive of the Cache-Control header.
270      *
271      * @return The set of field names specified in the "no-cache" directive.
272      */
273     public Set<String> getNoCacheFields() {
274         return noCacheFields;
275     }
276 
277     /**
278      * Returns the 'immutable' Cache-Control directive status.
279      *
280      * @return true if the 'immutable' directive is present in the Cache-Control header.
281      */
282     public boolean isUndefined() {
283         return undefined;
284     }
285 
286     /**
287      * Returns the 'immutable' Cache-Control directive status.
288      *
289      * @return true if the 'immutable' directive is present in the Cache-Control header.
290      */
291     public boolean isImmutable() {
292         return immutable;
293     }
294 
295     @Override
296     public String toString() {
297             final StringBuilder buf = new StringBuilder();
298             buf.append("[");
299             if (maxAge >= 0) {
300                 buf.append("max-age=").append(maxAge).append(",");
301             }
302             if (sharedMaxAge >= 0) {
303                 buf.append("shared-max-age=").append(sharedMaxAge).append(",");
304             }
305             if (noCache) {
306                 buf.append("no-cache").append(",");
307             }
308             if (noStore) {
309                 buf.append("no-store").append(",");
310             }
311             if (cachePrivate) {
312                 buf.append("private").append(",");
313             }
314             if (cachePublic) {
315                 buf.append("public").append(",");
316             }
317             if (mustRevalidate) {
318                 buf.append("must-revalidate").append(",");
319             }
320             if (proxyRevalidate) {
321                 buf.append("proxy-revalidate").append(",");
322             }
323             if (staleWhileRevalidate >= 0) {
324                 buf.append("state-while-revalidate=").append(staleWhileRevalidate).append(",");
325             }
326             if (staleIfError >= 0) {
327                 buf.append("stale-if-error").append(staleIfError).append(",");
328             }
329             if (mustUnderstand) {
330                 buf.append("must-understand").append(",");
331             }
332             if (immutable) {
333                 buf.append("immutable").append(",");
334             }
335             if (buf.charAt(buf.length() - 1) == ',') {
336                 buf.setLength(buf.length() - 1);
337             }
338             buf.append("]");
339             return buf.toString();
340     }
341 
342     static Builder builder() {
343         return new Builder();
344     }
345 
346     static class Builder {
347 
348         private long maxAge = -1;
349         private long sharedMaxAge = -1;
350         private boolean noCache;
351         private boolean noStore;
352         private boolean cachePrivate;
353         private boolean mustRevalidate;
354         private boolean proxyRevalidate;
355         private boolean cachePublic;
356         private long staleWhileRevalidate = -1;
357         private long staleIfError = -1;
358         private Set<String> noCacheFields;
359         private boolean mustUnderstand;
360         private boolean noTransform;
361         private boolean immutable;
362 
363         Builder() {
364         }
365 
366         public long getMaxAge() {
367             return maxAge;
368         }
369 
370         public Builder setMaxAge(final long maxAge) {
371             this.maxAge = maxAge;
372             return this;
373         }
374 
375         public long getSharedMaxAge() {
376             return sharedMaxAge;
377         }
378 
379         public Builder setSharedMaxAge(final long sharedMaxAge) {
380             this.sharedMaxAge = sharedMaxAge;
381             return this;
382         }
383 
384         public boolean isNoCache() {
385             return noCache;
386         }
387 
388         public Builder setNoCache(final boolean noCache) {
389             this.noCache = noCache;
390             return this;
391         }
392 
393         public boolean isNoStore() {
394             return noStore;
395         }
396 
397         public Builder setNoStore(final boolean noStore) {
398             this.noStore = noStore;
399             return this;
400         }
401 
402         public boolean isCachePrivate() {
403             return cachePrivate;
404         }
405 
406         public Builder setCachePrivate(final boolean cachePrivate) {
407             this.cachePrivate = cachePrivate;
408             return this;
409         }
410 
411         public boolean isMustRevalidate() {
412             return mustRevalidate;
413         }
414 
415         public Builder setMustRevalidate(final boolean mustRevalidate) {
416             this.mustRevalidate = mustRevalidate;
417             return this;
418         }
419 
420         public boolean isProxyRevalidate() {
421             return proxyRevalidate;
422         }
423 
424         public Builder setProxyRevalidate(final boolean proxyRevalidate) {
425             this.proxyRevalidate = proxyRevalidate;
426             return this;
427         }
428 
429         public boolean isCachePublic() {
430             return cachePublic;
431         }
432 
433         public Builder setCachePublic(final boolean cachePublic) {
434             this.cachePublic = cachePublic;
435             return this;
436         }
437 
438         public long getStaleWhileRevalidate() {
439             return staleWhileRevalidate;
440         }
441 
442         public Builder setStaleWhileRevalidate(final long staleWhileRevalidate) {
443             this.staleWhileRevalidate = staleWhileRevalidate;
444             return this;
445         }
446 
447         public long getStaleIfError() {
448             return staleIfError;
449         }
450 
451         public Builder setStaleIfError(final long staleIfError) {
452             this.staleIfError = staleIfError;
453             return this;
454         }
455 
456         public Set<String> getNoCacheFields() {
457             return noCacheFields;
458         }
459 
460         public Builder setNoCacheFields(final Set<String> noCacheFields) {
461             this.noCacheFields = noCacheFields;
462             return this;
463         }
464 
465         public Builder setNoCacheFields(final String... noCacheFields) {
466             this.noCacheFields = new HashSet<>();
467             this.noCacheFields.addAll(Arrays.asList(noCacheFields));
468             return this;
469         }
470 
471         public boolean isMustUnderstand() {
472             return mustUnderstand;
473         }
474 
475         public Builder setMustUnderstand(final boolean mustUnderstand) {
476             this.mustUnderstand = mustUnderstand;
477             return this;
478         }
479 
480         public boolean isNoTransform() {
481             return noStore;
482         }
483 
484         public Builder setNoTransform(final boolean noTransform) {
485             this.noTransform = noTransform;
486             return this;
487         }
488 
489         public boolean isImmutable() {
490             return immutable;
491         }
492 
493         public Builder setImmutable(final boolean immutable) {
494             this.immutable = immutable;
495             return this;
496         }
497 
498         public ResponseCacheControl build() {
499             return new ResponseCacheControl(maxAge, sharedMaxAge, mustRevalidate, noCache, noStore, cachePrivate, proxyRevalidate,
500                     cachePublic, staleWhileRevalidate, staleIfError, noCacheFields, mustUnderstand, immutable);
501         }
502 
503     }
504 
505 }