org.springframework.boot.autoconfigure.web.ResourceProperties Maven / Gradle / Ivy
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.http.CacheControl;
/**
* Properties used to configure resource handling.
*
* @author Phillip Webb
* @author Brian Clozel
* @author Dave Syer
* @author Venil Noronha
* @author Kristine Jetzke
* @since 1.1.0
*/
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/", "classpath:/resources/",
"classpath:/static/", "classpath:/public/" };
/**
* Locations of static resources. Defaults to classpath:[/META-INF/resources/,
* /resources/, /static/, /public/].
*/
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
/**
* Whether to enable default resource handling.
*/
private boolean addMappings = true;
private final Chain chain = new Chain();
private final Cache cache = new Cache();
public String[] getStaticLocations() {
return this.staticLocations;
}
public void setStaticLocations(String[] staticLocations) {
this.staticLocations = appendSlashIfNecessary(staticLocations);
}
private String[] appendSlashIfNecessary(String[] staticLocations) {
String[] normalized = new String[staticLocations.length];
for (int i = 0; i < staticLocations.length; i++) {
String location = staticLocations[i];
normalized[i] = location.endsWith("/") ? location : location + "/";
}
return normalized;
}
public boolean isAddMappings() {
return this.addMappings;
}
public void setAddMappings(boolean addMappings) {
this.addMappings = addMappings;
}
public Chain getChain() {
return this.chain;
}
public Cache getCache() {
return this.cache;
}
/**
* Configuration for the Spring Resource Handling chain.
*/
public static class Chain {
/**
* Whether to enable the Spring Resource Handling chain. By default, disabled
* unless at least one strategy has been enabled.
*/
private Boolean enabled;
/**
* Whether to enable caching in the Resource chain.
*/
private boolean cache = true;
/**
* Whether to enable HTML5 application cache manifest rewriting.
*/
private boolean htmlApplicationCache = false;
/**
* Whether to enable resolution of already compressed resources (gzip, brotli).
* Checks for a resource name with the '.gz' or '.br' file extensions.
*/
private boolean compressed = false;
private final Strategy strategy = new Strategy();
/**
* Return whether the resource chain is enabled. Return {@code null} if no
* specific settings are present.
* @return whether the resource chain is enabled or {@code null} if no specified
* settings are present.
*/
public Boolean getEnabled() {
return getEnabled(getStrategy().getFixed().isEnabled(),
getStrategy().getContent().isEnabled(), this.enabled);
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isCache() {
return this.cache;
}
public void setCache(boolean cache) {
this.cache = cache;
}
public Strategy getStrategy() {
return this.strategy;
}
public boolean isHtmlApplicationCache() {
return this.htmlApplicationCache;
}
public void setHtmlApplicationCache(boolean htmlApplicationCache) {
this.htmlApplicationCache = htmlApplicationCache;
}
public boolean isCompressed() {
return this.compressed;
}
public void setCompressed(boolean compressed) {
this.compressed = compressed;
}
static Boolean getEnabled(boolean fixedEnabled, boolean contentEnabled,
Boolean chainEnabled) {
return (fixedEnabled || contentEnabled) ? Boolean.TRUE : chainEnabled;
}
}
/**
* Strategies for extracting and embedding a resource version in its URL path.
*/
public static class Strategy {
private final Fixed fixed = new Fixed();
private final Content content = new Content();
public Fixed getFixed() {
return this.fixed;
}
public Content getContent() {
return this.content;
}
}
/**
* Version Strategy based on content hashing.
*/
public static class Content {
/**
* Whether to enable the content Version Strategy.
*/
private boolean enabled;
/**
* Comma-separated list of patterns to apply to the content Version Strategy.
*/
private String[] paths = new String[] { "/**" };
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String[] getPaths() {
return this.paths;
}
public void setPaths(String[] paths) {
this.paths = paths;
}
}
/**
* Version Strategy based on a fixed version string.
*/
public static class Fixed {
/**
* Whether to enable the fixed Version Strategy.
*/
private boolean enabled;
/**
* Comma-separated list of patterns to apply to the fixed Version Strategy.
*/
private String[] paths = new String[] { "/**" };
/**
* Version string to use for the fixed Version Strategy.
*/
private String version;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String[] getPaths() {
return this.paths;
}
public void setPaths(String[] paths) {
this.paths = paths;
}
public String getVersion() {
return this.version;
}
public void setVersion(String version) {
this.version = version;
}
}
/**
* Cache configuration.
*/
public static class Cache {
/**
* Cache period for the resources served by the resource handler. If a duration
* suffix is not specified, seconds will be used. Can be overridden by the
* 'spring.resources.cache.cachecontrol' properties.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration period;
/**
* Cache control HTTP headers, only allows valid directive combinations. Overrides
* the 'spring.resources.cache.period' property.
*/
private final Cachecontrol cachecontrol = new Cachecontrol();
public Duration getPeriod() {
return this.period;
}
public void setPeriod(Duration period) {
this.period = period;
}
public Cachecontrol getCachecontrol() {
return this.cachecontrol;
}
/**
* Cache Control HTTP header configuration.
*/
public static class Cachecontrol {
/**
* Maximum time the response should be cached, in seconds if no duration
* suffix is not specified.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration maxAge;
/**
* Indicate that the cached response can be reused only if re-validated with
* the server.
*/
private Boolean noCache;
/**
* Indicate to not cache the response in any case.
*/
private Boolean noStore;
/**
* Indicate that once it has become stale, a cache must not use the response
* without re-validating it with the server.
*/
private Boolean mustRevalidate;
/**
* Indicate intermediaries (caches and others) that they should not transform
* the response content.
*/
private Boolean noTransform;
/**
* Indicate that any cache may store the response.
*/
private Boolean cachePublic;
/**
* Indicate that the response message is intended for a single user and must
* not be stored by a shared cache.
*/
private Boolean cachePrivate;
/**
* Same meaning as the "must-revalidate" directive, except that it does not
* apply to private caches.
*/
private Boolean proxyRevalidate;
/**
* Maximum time the response can be served after it becomes stale, in seconds
* if no duration suffix is not specified.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration staleWhileRevalidate;
/**
* Maximum time the response may be used when errors are encountered, in
* seconds if no duration suffix is not specified.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration staleIfError;
/**
* Maximum time the response should be cached by shared caches, in seconds if
* no duration suffix is not specified.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration sMaxAge;
public Duration getMaxAge() {
return this.maxAge;
}
public void setMaxAge(Duration maxAge) {
this.maxAge = maxAge;
}
public Boolean getNoCache() {
return this.noCache;
}
public void setNoCache(Boolean noCache) {
this.noCache = noCache;
}
public Boolean getNoStore() {
return this.noStore;
}
public void setNoStore(Boolean noStore) {
this.noStore = noStore;
}
public Boolean getMustRevalidate() {
return this.mustRevalidate;
}
public void setMustRevalidate(Boolean mustRevalidate) {
this.mustRevalidate = mustRevalidate;
}
public Boolean getNoTransform() {
return this.noTransform;
}
public void setNoTransform(Boolean noTransform) {
this.noTransform = noTransform;
}
public Boolean getCachePublic() {
return this.cachePublic;
}
public void setCachePublic(Boolean cachePublic) {
this.cachePublic = cachePublic;
}
public Boolean getCachePrivate() {
return this.cachePrivate;
}
public void setCachePrivate(Boolean cachePrivate) {
this.cachePrivate = cachePrivate;
}
public Boolean getProxyRevalidate() {
return this.proxyRevalidate;
}
public void setProxyRevalidate(Boolean proxyRevalidate) {
this.proxyRevalidate = proxyRevalidate;
}
public Duration getStaleWhileRevalidate() {
return this.staleWhileRevalidate;
}
public void setStaleWhileRevalidate(Duration staleWhileRevalidate) {
this.staleWhileRevalidate = staleWhileRevalidate;
}
public Duration getStaleIfError() {
return this.staleIfError;
}
public void setStaleIfError(Duration staleIfError) {
this.staleIfError = staleIfError;
}
public Duration getSMaxAge() {
return this.sMaxAge;
}
public void setSMaxAge(Duration sMaxAge) {
this.sMaxAge = sMaxAge;
}
public CacheControl toHttpCacheControl() {
PropertyMapper map = PropertyMapper.get();
CacheControl control = createCacheControl();
map.from(this::getMustRevalidate).whenTrue()
.toCall(control::mustRevalidate);
map.from(this::getNoTransform).whenTrue().toCall(control::noTransform);
map.from(this::getCachePublic).whenTrue().toCall(control::cachePublic);
map.from(this::getCachePrivate).whenTrue().toCall(control::cachePrivate);
map.from(this::getProxyRevalidate).whenTrue()
.toCall(control::proxyRevalidate);
map.from(this::getStaleWhileRevalidate).whenNonNull().to(
(duration) -> control.staleWhileRevalidate(duration.getSeconds(),
TimeUnit.SECONDS));
map.from(this::getStaleIfError).whenNonNull().to((duration) -> control
.staleIfError(duration.getSeconds(), TimeUnit.SECONDS));
map.from(this::getSMaxAge).whenNonNull().to((duration) -> control
.sMaxAge(duration.getSeconds(), TimeUnit.SECONDS));
return control;
}
private CacheControl createCacheControl() {
if (Boolean.TRUE.equals(this.noStore)) {
return CacheControl.noStore();
}
if (Boolean.TRUE.equals(this.noCache)) {
return CacheControl.noCache();
}
if (this.maxAge != null) {
return CacheControl.maxAge(this.maxAge.getSeconds(),
TimeUnit.SECONDS);
}
return CacheControl.empty();
}
}
}
}