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>// => "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}