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