Refactor VersionResourceResolver in strategies
Prior to this commit, one of the available strategies for resolving resources was the PrefixResourceResolver. Reconsidering the core goal of this resolver and the FingerprintResourceResolver, we found that the true core feature is versioning static resources application-wide. This commit refactors both Resolvers by: * having only on VersionResourceResolver * that resolver takes a mapping of paths -> VersionStrategy * provided VersionStrategy implementations are ContentBasedVS (previously FingerprintRR), FixedVS (previously PrefixRR) One can add a VersionResourceResolver like this: Map<String, VersionStrategy> versionStrategies = new HashMap<>(); versionStrategies.put("/**/*.js", new PrefixVersionStrategy("prefix")); versionStrategies.put("/**", new ContentBasedVersionStrategy()); VersionResourceResolver versionResolver = new VersionResourceResolver(); versionResolver.setVersionStrategyMap(versionStrategies); List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>(); resolvers.add(versionResolver); resolvers.add(new PathResourceResolver()); Issue: SPR-11871
This commit is contained in:
parent
18131bf611
commit
13c4a0396d
|
@ -0,0 +1,92 @@
|
|||
package org.springframework.web.servlet.resource;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link VersionStrategy} implementations.
|
||||
* Supports versions as:
|
||||
* <ul>
|
||||
* <li>prefix in the request path, like "version/static/myresource.js"
|
||||
* <li>file name suffix in the request path, like "static/myresource-version.js"
|
||||
* </ul>
|
||||
*
|
||||
* <p>Note: This base class does <i>not</i> provide support for generating the
|
||||
* version string.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 4.1
|
||||
*/
|
||||
public abstract class AbstractVersionStrategy implements VersionStrategy {
|
||||
|
||||
private static final Pattern pattern = Pattern.compile("-(\\S*)\\.");
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
/**
|
||||
* Extracts a version string from the request path, as a suffix in the resource
|
||||
* file name.
|
||||
* @param requestPath the request path to extract the version string from
|
||||
* @return a version string or an empty string if none was found
|
||||
*/
|
||||
protected String extractVersionFromFilename(String requestPath) {
|
||||
Matcher matcher = pattern.matcher(requestPath);
|
||||
if (matcher.find()) {
|
||||
String match = matcher.group(1);
|
||||
return match.contains("-") ? match.substring(match.lastIndexOf("-") + 1) : match;
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given candidate version string from the request path.
|
||||
* The version string should be a suffix in the resource file name.
|
||||
*/
|
||||
protected String deleteVersionFromFilename(String requestPath, String candidateVersion) {
|
||||
return StringUtils.delete(requestPath, "-" + candidateVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given version string to the baseUrl, as a file name suffix.
|
||||
*/
|
||||
protected String addVersionToFilename(String baseUrl, String version) {
|
||||
String baseFilename = StringUtils.stripFilenameExtension(baseUrl);
|
||||
String extension = StringUtils.getFilenameExtension(baseUrl);
|
||||
return baseFilename + "-" + version + "." + extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a version string from the request path, as a prefix in the request path.
|
||||
* @param requestPath the request path to extract the version string from
|
||||
* @return a version string or an empty string if none was found
|
||||
*/
|
||||
protected String extractVersionAsPrefix(String requestPath, String prefix) {
|
||||
if (requestPath.startsWith(prefix)) {
|
||||
return prefix;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given candidate version string from the request path.
|
||||
* The version string should be a prefix in the request path.
|
||||
*/
|
||||
protected String deleteVersionAsPrefix(String requestPath, String version) {
|
||||
return requestPath.substring(version.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given version string to the baseUrl, as a prefix in the request path.
|
||||
*/
|
||||
protected String addVersionAsPrefix(String baseUrl, String version) {
|
||||
return version + baseUrl;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.web.servlet.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.DigestUtils;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* A {@code VersionStrategy} that handles version strings as a Hex MD5 hash in resource file names.
|
||||
*
|
||||
* <p>For example the path "styles/foo-e36d2e05253c6c7085a91522ce43a0b4.css" will
|
||||
* match to "styles/foo.css" assuming the hash computed from the content of
|
||||
* "foo.css" matches the hash in the path.
|
||||
*
|
||||
* @author Jeremy Grelle
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sam Brannen
|
||||
* @author Brian Clozel
|
||||
* @since 4.1
|
||||
* @see VersionResourceResolver
|
||||
*/
|
||||
public class ContentBasedVersionStrategy extends AbstractVersionStrategy {
|
||||
|
||||
@Override
|
||||
public String extractVersionFromPath(String requestPath) {
|
||||
return extractVersionFromFilename(requestPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String deleteVersionFromPath(String requestPath, String candidateVersion) {
|
||||
return deleteVersionFromFilename(requestPath, candidateVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resourceVersionMatches(Resource baseResource, String candidateVersion) {
|
||||
String resourceHash = calculateHash(baseResource);
|
||||
return candidateVersion.equals(resourceHash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String addVersionToUrl(String baseUrl, List<? extends Resource> locations, ResourceResolverChain chain) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Getting the original resource to calculate hash");
|
||||
}
|
||||
Resource original = chain.resolveResource(null, baseUrl, locations);
|
||||
String hash = calculateHash(original);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Calculated hash=" + hash);
|
||||
}
|
||||
return addVersionToFilename(baseUrl, hash);
|
||||
}
|
||||
|
||||
private String calculateHash(Resource resource) {
|
||||
try {
|
||||
byte[] content = FileCopyUtils.copyToByteArray(resource.getInputStream());
|
||||
return DigestUtils.md5DigestAsHex(content);
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.error("Failed to calculate hash for resource [" + resource + "]");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.web.servlet.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.DigestUtils;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@code ResourceResolver} that resolves request paths containing an additional
|
||||
* MD5 hash in the file name.
|
||||
*
|
||||
* <p>For example the path "styles/foo-e36d2e05253c6c7085a91522ce43a0b4.css" will
|
||||
* match to "styles/foo.css" assuming the hash computed from the content of
|
||||
* "foo.css" matches the hash in the path.
|
||||
*
|
||||
* <p>The resolver first delegates to the chain so that if
|
||||
* "foo-e36d2e05253c6c7085a91522ce43a0b4.css" has been written to disk (e.g. at
|
||||
* build time) it is simply found. Or if the chain does not find an existing
|
||||
* resource, this resolver removes the hash, attempts to find a matching resource
|
||||
* with the resulting file name ("foo.css") and then compares the hash from the
|
||||
* request path to the hash computed from the file content.
|
||||
*
|
||||
* @author Jeremy Grelle
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sam Brannen
|
||||
* @since 4.1
|
||||
*/
|
||||
public class FingerprintResourceResolver extends AbstractResourceResolver {
|
||||
|
||||
private static final Pattern pattern = Pattern.compile("-(\\S*)\\.");
|
||||
|
||||
|
||||
@Override
|
||||
protected Resource resolveResourceInternal(HttpServletRequest request, String requestPath,
|
||||
List<? extends Resource> locations, ResourceResolverChain chain) {
|
||||
|
||||
Resource resolved = chain.resolveResource(request, requestPath, locations);
|
||||
if (resolved != null) {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
String hash = extractHash(requestPath);
|
||||
if (StringUtils.isEmpty(hash)) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("No hash found");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String simplePath = StringUtils.delete(requestPath, "-" + hash);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Extracted hash from path, re-resolving without hash, path=\"" + simplePath + "\"");
|
||||
}
|
||||
|
||||
Resource baseResource = chain.resolveResource(request, simplePath, locations);
|
||||
if (baseResource == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String candidateHash = calculateHash(baseResource);
|
||||
if (candidateHash.equals(hash)) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Calculated hash matches extracted hash");
|
||||
}
|
||||
return baseResource;
|
||||
}
|
||||
else {
|
||||
logger.trace("Potential resource found for [" + requestPath + "], but fingerprint doesn't match.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String resolveUrlPathInternal(String resourceUrlPath, List<? extends Resource> locations,
|
||||
ResourceResolverChain chain) {
|
||||
|
||||
String baseUrl = chain.resolveUrlPath(resourceUrlPath, locations);
|
||||
if (StringUtils.hasText(baseUrl)) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Getting the original resource to calculate hash");
|
||||
}
|
||||
Resource original = chain.resolveResource(null, resourceUrlPath, locations);
|
||||
String hash = calculateHash(original);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Calculated hash=" + hash);
|
||||
}
|
||||
String baseFilename = StringUtils.stripFilenameExtension(baseUrl);
|
||||
String extension = StringUtils.getFilenameExtension(baseUrl);
|
||||
return baseFilename + "-" + hash + "." + extension;
|
||||
}
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
|
||||
private String extractHash(String path) {
|
||||
Matcher matcher = pattern.matcher(path);
|
||||
if (matcher.find()) {
|
||||
String match = matcher.group(1);
|
||||
return match.contains("-") ? match.substring(match.lastIndexOf("-") + 1) : match;
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String calculateHash(Resource resource) {
|
||||
try {
|
||||
byte[] content = FileCopyUtils.copyToByteArray(resource.getInputStream());
|
||||
return DigestUtils.md5DigestAsHex(content);
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.error("Failed to calculate hash for resource [" + resource + "]");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.web.servlet.resource;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@code VersionStrategy} that handles unique, static, application-wide version strings
|
||||
* as prefixes in the request path.
|
||||
*
|
||||
* <p>Enables inserting a unique and static version String (e.g. reduced SHA, version name,
|
||||
* release date) at the beginning of resource paths so that when a new version of the application
|
||||
* is released, clients are forced to reload application resources.
|
||||
*
|
||||
* <p>This is useful when changing resource names is not an option (e.g. when
|
||||
* using JavaScript module loaders). If that's not the case, the use of
|
||||
* {@link ContentBasedVersionStrategy} provides more optimal performance since
|
||||
* version is generated on a per-resource basis: only actually modified resources are reloaded
|
||||
* by the client.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Sam Brannen
|
||||
* @since 4.1
|
||||
* @see VersionResourceResolver
|
||||
*/
|
||||
public class FixedVersionStrategy extends AbstractVersionStrategy {
|
||||
|
||||
private final String version;
|
||||
|
||||
/**
|
||||
* Create a new FixedVersionStrategy with the given version string.
|
||||
* @param fixedVersion the fixed version string to use
|
||||
*/
|
||||
public FixedVersionStrategy(String fixedVersion) {
|
||||
Assert.hasText(fixedVersion, "version must not be null or empty");
|
||||
this.version = fixedVersion.endsWith("/") ? fixedVersion : fixedVersion + "/";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new FixedVersionStrategy and get the version string to use by
|
||||
* calling the given {@code Callable} instance.
|
||||
*/
|
||||
public FixedVersionStrategy(Callable<String> versionInitializer) throws Exception {
|
||||
String fixedVersion = versionInitializer.call();
|
||||
Assert.hasText(fixedVersion, "version must not be null or empty");
|
||||
this.version = fixedVersion.endsWith("/") ? fixedVersion : fixedVersion + "/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String extractVersionFromPath(String requestPath) {
|
||||
|
||||
return extractVersionAsPrefix(requestPath, this.version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String deleteVersionFromPath(String requestPath, String candidateVersion) {
|
||||
return deleteVersionAsPrefix(requestPath, candidateVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resourceVersionMatches(Resource baseResource, String candidateVersion) {
|
||||
return this.version.equals(candidateVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String addVersionToUrl(String baseUrl, List<? extends Resource> locations,
|
||||
ResourceResolverChain chain) {
|
||||
|
||||
return addVersionAsPrefix(baseUrl, this.version);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.web.servlet.resource;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@code ResourceResolver} configured with a prefix to be removed from the
|
||||
* request path before delegating to the chain to find a matching resource.
|
||||
*
|
||||
* <p>Enables inserting a unique virtual prefix (e.g. reduced SHA, version number,
|
||||
* release date) into resource paths so that when a new version of the application
|
||||
* is released, clients are forced to reload application resources.
|
||||
*
|
||||
* <p>This is useful when changing resource names is not an option (e.g. when
|
||||
* using JavaScript module loaders). If that's not the case, the use of
|
||||
* {@link FingerprintResourceResolver} provides more optimal performance since
|
||||
* it causes only actually modified resources to be reloaded.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Sam Brannen
|
||||
* @since 4.1
|
||||
*/
|
||||
public class PrefixResourceResolver extends AbstractResourceResolver {
|
||||
|
||||
private final String prefix;
|
||||
|
||||
public PrefixResourceResolver(String prefix) {
|
||||
Assert.hasText(prefix, "prefix must not be null or empty");
|
||||
this.prefix = prefix.startsWith("/") ? prefix.substring(1) : prefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Resource resolveResourceInternal(HttpServletRequest request, String requestPath,
|
||||
List<? extends Resource> locations, ResourceResolverChain chain) {
|
||||
|
||||
if (requestPath.startsWith(this.prefix)) {
|
||||
requestPath = requestPath.substring(this.prefix.length());
|
||||
}
|
||||
|
||||
return chain.resolveResource(request, requestPath, locations);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String resolveUrlPathInternal(String resourceUrlPath, List<? extends Resource> locations,
|
||||
ResourceResolverChain chain) {
|
||||
|
||||
String baseUrl = chain.resolveUrlPath(resourceUrlPath, locations);
|
||||
if (StringUtils.hasText(baseUrl)) {
|
||||
return this.prefix + (baseUrl.startsWith("/") ? baseUrl : "/" + baseUrl);
|
||||
}
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.web.servlet.resource;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@code ResourceResolver} that resolves request paths containing a version
|
||||
* string, i.e. version information about the resource being requested.
|
||||
* This resolver can be useful to set up HTTP caching strategies by changing
|
||||
* resources' URLs as they are updated.
|
||||
*
|
||||
* <p>Because resource versioning depends on the resource types, this {@code ResourceResolver}
|
||||
* needs to be configured with at least one {@link VersionStrategy}. The process of matching
|
||||
* and generating version strings is delegated to the {@code VersionStrategy}.
|
||||
*
|
||||
* <p>When resolving resources, this resolver will first delegate to the chain to locate
|
||||
* an existing resource and then attempt to extract a version string from the request path
|
||||
* and then find a resource that matches that version.
|
||||
*
|
||||
* <p>When resolving URLs, this resolver will, if necessary, add a version string in the
|
||||
* request path.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 4.1
|
||||
* @see VersionStrategy
|
||||
*/
|
||||
public class VersionResourceResolver extends AbstractResourceResolver {
|
||||
|
||||
private AntPathMatcher pathMatcher = new AntPathMatcher();
|
||||
|
||||
private Map<String, VersionStrategy> versionStrategyMap = Collections.emptyMap();
|
||||
|
||||
/**
|
||||
* Set a Map with URL paths as keys and {@code VersionStrategy}
|
||||
* as values.
|
||||
*
|
||||
* <p>Supports direct URL matches and Ant-style pattern matches. For syntax
|
||||
* details, see the {@link org.springframework.util.AntPathMatcher} javadoc.
|
||||
*
|
||||
* @param versionStrategyMap map with URLs as keys and version strategies as values
|
||||
*/
|
||||
public void setVersionStrategyMap(Map<String, VersionStrategy> versionStrategyMap) {
|
||||
this.versionStrategyMap = versionStrategyMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Resource resolveResourceInternal(HttpServletRequest request, String requestPath,
|
||||
List<? extends Resource> locations, ResourceResolverChain chain) {
|
||||
|
||||
Resource resolved = chain.resolveResource(request, requestPath, locations);
|
||||
if (resolved != null) {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
VersionStrategy versionStrategy = findStrategy(requestPath);
|
||||
if(versionStrategy == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String candidateVersion = versionStrategy.extractVersionFromPath(requestPath);
|
||||
if (StringUtils.isEmpty(candidateVersion)) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("No version found in path=\"" + requestPath + "\"");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String simplePath = versionStrategy.deleteVersionFromPath(requestPath, candidateVersion);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Extracted version from path, re-resolving without version, path=\"" + simplePath + "\"");
|
||||
}
|
||||
|
||||
Resource baseResource = chain.resolveResource(request, simplePath, locations);
|
||||
if (baseResource == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (versionStrategy.resourceVersionMatches(baseResource, candidateVersion)) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("resource matches extracted version");
|
||||
}
|
||||
return baseResource;
|
||||
}
|
||||
else {
|
||||
logger.trace("Potential resource found for [" + requestPath + "], but version ["
|
||||
+ candidateVersion + "] doesn't match.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String resolveUrlPathInternal(String resourceUrlPath, List<? extends Resource> locations, ResourceResolverChain chain) {
|
||||
String baseUrl = chain.resolveUrlPath(resourceUrlPath, locations);
|
||||
if (StringUtils.hasText(baseUrl)) {
|
||||
VersionStrategy versionStrategy = findStrategy(resourceUrlPath);
|
||||
if(versionStrategy == null) {
|
||||
return null;
|
||||
}
|
||||
return versionStrategy.addVersionToUrl(baseUrl, locations, chain);
|
||||
}
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a {@code VersionStrategy} for the request path of the requested resource.
|
||||
* @return an instance of a {@code VersionStrategy} or null if none matches that request path
|
||||
*/
|
||||
protected VersionStrategy findStrategy(String requestPath) {
|
||||
String path = "/".concat(requestPath);
|
||||
List<String> matchingPatterns = new ArrayList<String>();
|
||||
for(String pattern : this.versionStrategyMap.keySet()) {
|
||||
if(this.pathMatcher.match(pattern, path)) {
|
||||
matchingPatterns.add(pattern);
|
||||
}
|
||||
}
|
||||
if(!matchingPatterns.isEmpty()) {
|
||||
Comparator<String> comparator = this.pathMatcher.getPatternComparator(path);
|
||||
Collections.sort(matchingPatterns, comparator);
|
||||
return this.versionStrategyMap.get(matchingPatterns.get(0));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.web.servlet.resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* A strategy for handling version strings in request paths when resolving
|
||||
* static resources with a {@link VersionResourceResolver}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 4.1
|
||||
* @see VersionResourceResolver
|
||||
*/
|
||||
public interface VersionStrategy {
|
||||
|
||||
/**
|
||||
* Extracts a version string from the request path.
|
||||
* @param requestPath the request path of the resource being resolved
|
||||
* @return the version string or an empty string if none was found
|
||||
*/
|
||||
String extractVersionFromPath(String requestPath);
|
||||
|
||||
/**
|
||||
* Deletes the given candidate version string from the given request path.
|
||||
* @param requestPath the request path of the resource being resolved
|
||||
* @param candidateVersion the candidate version string
|
||||
* @return the modified request path, without the version string
|
||||
*/
|
||||
String deleteVersionFromPath(String requestPath, String candidateVersion);
|
||||
|
||||
/**
|
||||
* Checks whether the given {@code Resource} matches the candidate version string.
|
||||
* Useful when the version string is managed on a per-resource basis.
|
||||
* @param baseResource the resource to check against the given version
|
||||
* @param candidateVersion the candidate version for the given resource
|
||||
* @return true if the resource matches the version string, false otherwise
|
||||
*/
|
||||
boolean resourceVersionMatches(Resource baseResource, String candidateVersion);
|
||||
|
||||
/**
|
||||
* Adds a version string to the given baseUrl.
|
||||
* @param baseUrl the baseUrl of the requested resource
|
||||
* @param locations the resource locations to resolve resources from
|
||||
* @param chain the chain of resource resolvers
|
||||
* @return the baseUrl updated with a version string
|
||||
*/
|
||||
String addVersionToUrl(String baseUrl, List<? extends Resource> locations, ResourceResolverChain chain);
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.web.servlet.resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.DigestUtils;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link org.springframework.web.servlet.resource.ContentBasedVersionStrategy}
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class ContentBasedVersionStrategyTests {
|
||||
|
||||
private List<Resource> locations;
|
||||
|
||||
private ContentBasedVersionStrategy versionStrategy = new ContentBasedVersionStrategy();
|
||||
|
||||
private ResourceResolverChain chain;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.locations = new ArrayList<Resource>();
|
||||
this.locations.add(new ClassPathResource("test/", getClass()));
|
||||
this.locations.add(new ClassPathResource("testalternatepath/", getClass()));
|
||||
|
||||
VersionResourceResolver versionResourceResolver = new VersionResourceResolver();
|
||||
versionResourceResolver.setVersionStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
|
||||
this.chain = new DefaultResourceResolverChain(Arrays.asList(versionResourceResolver, new PathResourceResolver()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractVersionFromPath() throws Exception {
|
||||
String hash = "7fbe76cdac6093784895bb4989203e5a";
|
||||
String path = "font-awesome/css/font-awesome.min-" + hash + ".css";
|
||||
|
||||
assertEquals(hash, this.versionStrategy.extractVersionFromPath(path));
|
||||
assertEquals("", this.versionStrategy.extractVersionFromPath("foo/bar.css"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteVersionFromPath() throws Exception {
|
||||
String file = "font-awesome/css/font-awesome.min%s%s.css";
|
||||
String hash = "7fbe76cdac6093784895bb4989203e5a";
|
||||
|
||||
assertEquals(String.format(file, "", ""), this.versionStrategy.deleteVersionFromPath(String.format(file, "-", hash), hash));
|
||||
assertEquals("", this.versionStrategy.extractVersionFromPath("foo/bar.css"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resourceVersionMatches() throws Exception {
|
||||
Resource expected = new ClassPathResource("test/bar.css", getClass());
|
||||
String hash = DigestUtils.md5DigestAsHex(FileCopyUtils.copyToByteArray(expected.getInputStream()));
|
||||
String wrongHash = "wronghash";
|
||||
|
||||
assertTrue(this.versionStrategy.resourceVersionMatches(expected, hash));
|
||||
assertFalse(this.versionStrategy.resourceVersionMatches(expected, wrongHash));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addVersionToUrl() throws Exception {
|
||||
String file = "bar.css";
|
||||
Resource expected = new ClassPathResource("test/" + file, getClass());
|
||||
String hash = DigestUtils.md5DigestAsHex(FileCopyUtils.copyToByteArray(expected.getInputStream()));
|
||||
String path = "bar-" + hash + ".css";
|
||||
String resultUrl = this.versionStrategy.addVersionToUrl(file, this.locations, this.chain);
|
||||
|
||||
assertEquals(path, resultUrl);
|
||||
}
|
||||
|
||||
}
|
|
@ -18,7 +18,9 @@ package org.springframework.web.servlet.resource;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -47,8 +49,13 @@ public class CssLinkResourceTransformerTests {
|
|||
|
||||
@Before
|
||||
public void setUp() {
|
||||
Map<String, VersionStrategy> versionStrategyMap = new HashMap<>();
|
||||
versionStrategyMap.put("/**", new ContentBasedVersionStrategy());
|
||||
VersionResourceResolver versionResolver = new VersionResourceResolver();
|
||||
versionResolver.setVersionStrategyMap(versionStrategyMap);
|
||||
|
||||
List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>();
|
||||
resolvers.add(new FingerprintResourceResolver());
|
||||
resolvers.add(versionResolver);
|
||||
resolvers.add(new PathResourceResolver());
|
||||
ResourceResolverChain resolverChain = new DefaultResourceResolverChain(resolvers);
|
||||
|
||||
|
|
|
@ -1,124 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2013 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.web.servlet.resource;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.DigestUtils;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jeremy Grelle
|
||||
*/
|
||||
public class FingerprintResourceResolverTests {
|
||||
|
||||
private ResourceResolverChain chain;
|
||||
|
||||
private FingerprintResourceResolver resolver = new FingerprintResourceResolver();
|
||||
|
||||
private List<Resource> locations;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>();
|
||||
resolvers.add(resolver);
|
||||
resolvers.add(new PathResourceResolver());
|
||||
this.chain = new DefaultResourceResolverChain(resolvers);
|
||||
|
||||
this.locations = new ArrayList<Resource>();
|
||||
this.locations.add(new ClassPathResource("test/", getClass()));
|
||||
this.locations.add(new ClassPathResource("testalternatepath/", getClass()));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void resolveWithoutHash() throws Exception {
|
||||
String file = "bar.css";
|
||||
Resource expected = new ClassPathResource("test/" + file, getClass());
|
||||
Resource actual = chain.resolveResource(null, file, locations);
|
||||
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWithHashNoMatch() throws Exception {
|
||||
String file = "bogus-e36d2e05253c6c7085a91522ce43a0b4.css";
|
||||
assertNull(chain.resolveResource(null, file, locations));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveStaticFingerprintedResource() throws Exception {
|
||||
String file = "foo-e36d2e05253c6c7085a91522ce43a0b4.css";
|
||||
Resource expected = new ClassPathResource("test/" + file, getClass());
|
||||
Resource actual = chain.resolveResource(null, file, locations);
|
||||
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveDynamicFingerprintedResource() throws Exception {
|
||||
Resource expected = new ClassPathResource("test/bar.css", getClass());
|
||||
String hash = DigestUtils.md5DigestAsHex(FileCopyUtils.copyToByteArray(expected.getInputStream()));
|
||||
String path = "/bar-" + hash + ".css";
|
||||
Resource actual = chain.resolveResource(null, path, locations);
|
||||
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWithMultipleExtensions() throws Exception {
|
||||
Resource expected = new ClassPathResource("test/bar.min.css", getClass());
|
||||
String hash = DigestUtils.md5DigestAsHex(FileCopyUtils.copyToByteArray(expected.getInputStream()));
|
||||
String path = "/bar.min-" + hash + ".css";
|
||||
Resource actual = chain.resolveResource(null, path, locations);
|
||||
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWithMultipleHyphens() throws Exception {
|
||||
Resource expected = new ClassPathResource("test/foo-bar/foo-bar.css", getClass());
|
||||
String hash = DigestUtils.md5DigestAsHex(FileCopyUtils.copyToByteArray(expected.getInputStream()));
|
||||
String path = "/foo-bar/foo-bar-" + hash + ".css";
|
||||
Resource actual = chain.resolveResource(null, path, locations);
|
||||
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractHash() throws Exception {
|
||||
String hash = "7fbe76cdac6093784895bb4989203e5a";
|
||||
String path = "font-awesome/css/font-awesome.min-" + hash + ".css";
|
||||
|
||||
Method method = ReflectionUtils.findMethod(resolver.getClass(), "extractHash", String.class);
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
String result = (String) ReflectionUtils.invokeMethod(method, resolver, path);
|
||||
|
||||
assertEquals(hash, result);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.web.servlet.resource;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link org.springframework.web.servlet.resource.FixedVersionStrategy}
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class FixedVersionStrategyTests {
|
||||
|
||||
private final String version = "1df341f";
|
||||
|
||||
private final String resourceId = "js/foo.js";
|
||||
|
||||
private FixedVersionStrategy versionStrategy;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.versionStrategy = new FixedVersionStrategy(this.version);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructWithEmptyPrefixVersion() throws Exception {
|
||||
|
||||
FixedVersionStrategy versionStrategy = new FixedVersionStrategy(" ");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructWithEmptyCallableVersion() throws Exception {
|
||||
|
||||
FixedVersionStrategy versionStrategy = new FixedVersionStrategy(
|
||||
new Callable<String>() {
|
||||
@Override
|
||||
public String call() throws Exception {
|
||||
return " ";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractVersionFromPath() throws Exception {
|
||||
assertEquals(this.version + "/", this.versionStrategy.extractVersionFromPath(this.version + "/" + this.resourceId));
|
||||
assertEquals("", this.versionStrategy.extractVersionFromPath(this.resourceId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteVersionFromPath() throws Exception {
|
||||
assertEquals(this.resourceId,
|
||||
this.versionStrategy.deleteVersionFromPath(this.version + "/" + this.resourceId, this.version + "/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addVersionToUrl() throws Exception {
|
||||
assertEquals(this.version + "/" + this.resourceId,
|
||||
this.versionStrategy.addVersionToUrl(this.resourceId, Collections.<Resource>emptyList(), null));
|
||||
|
||||
}
|
||||
}
|
|
@ -19,7 +19,9 @@ package org.springframework.web.servlet.resource;
|
|||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import org.junit.Before;
|
||||
|
@ -68,9 +70,14 @@ public class GzipResourceResolverTests {
|
|||
|
||||
@Before
|
||||
public void setUp() {
|
||||
Map<String, VersionStrategy> versionStrategyMap = new HashMap<>();
|
||||
versionStrategyMap.put("/**", new ContentBasedVersionStrategy());
|
||||
VersionResourceResolver versionResolver = new VersionResourceResolver();
|
||||
versionResolver.setVersionStrategyMap(versionStrategyMap);
|
||||
|
||||
List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>();
|
||||
resolvers.add(new GzipResourceResolver());
|
||||
resolvers.add(new FingerprintResourceResolver());
|
||||
resolvers.add(versionResolver);
|
||||
resolvers.add(new PathResourceResolver());
|
||||
resolver = new DefaultResourceResolverChain(resolvers);
|
||||
locations = new ArrayList<Resource>();
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.web.servlet.resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Test fixture for {@link PrefixResourceResolver}
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class PrefixResourceResolverTests {
|
||||
|
||||
private final List<? extends Resource> locations = Arrays.asList(new ClassPathResource("test/", getClass()));
|
||||
|
||||
private final String shaPrefix = "1df341f";
|
||||
|
||||
private ResourceResolverChain chain;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>();
|
||||
resolvers.add(new PrefixResourceResolver(this.shaPrefix));
|
||||
resolvers.add(new PathResourceResolver());
|
||||
this.chain = new DefaultResourceResolverChain(resolvers);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResource() {
|
||||
String resourceId = "foo.css";
|
||||
Resource expected = new ClassPathResource("test/foo.css", getClass());
|
||||
Resource actual = this.chain.resolveResource(null, this.shaPrefix + "/" + resourceId, this.locations);
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveUrlPath() {
|
||||
String resourceId = "/foo.css";
|
||||
String url = this.shaPrefix + resourceId;
|
||||
assertEquals(url, chain.resolveUrlPath(resourceId, locations));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructWithEmptyPrefix() {
|
||||
new PrefixResourceResolver(" ");
|
||||
}
|
||||
|
||||
}
|
|
@ -32,6 +32,9 @@ import org.springframework.web.servlet.config.annotation.*;
|
|||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* Integration tests using {@link ResourceUrlEncodingFilter} and
|
||||
|
@ -111,9 +114,14 @@ public class ResourceUrlProviderJavaConfigTests {
|
|||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
Map<String, VersionStrategy> versionStrategyMap = new HashMap<>();
|
||||
versionStrategyMap.put("/**", new ContentBasedVersionStrategy());
|
||||
VersionResourceResolver versionResolver = new VersionResourceResolver();
|
||||
versionResolver.setVersionStrategyMap(versionStrategyMap);
|
||||
|
||||
registry.addResourceHandler("/resources/**")
|
||||
.addResourceLocations("classpath:org/springframework/web/servlet/resource/test/")
|
||||
.setResourceResolvers(new FingerprintResourceResolver(), new PathResourceResolver());
|
||||
.setResourceResolvers(versionResolver, new PathResourceResolver());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -67,8 +67,13 @@ public class ResourceUrlProviderTests {
|
|||
|
||||
@Test
|
||||
public void getFingerprintedResourceUrl() {
|
||||
Map<String, VersionStrategy> versionStrategyMap = new HashMap<>();
|
||||
versionStrategyMap.put("/**", new ContentBasedVersionStrategy());
|
||||
VersionResourceResolver versionResolver = new VersionResourceResolver();
|
||||
versionResolver.setVersionStrategyMap(versionStrategyMap);
|
||||
|
||||
List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>();
|
||||
resolvers.add(new FingerprintResourceResolver());
|
||||
resolvers.add(versionResolver);
|
||||
resolvers.add(new PathResourceResolver());
|
||||
this.handler.setResourceResolvers(resolvers);
|
||||
initTranslator();
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* Copyright 2002-2014 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.web.servlet.resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link VersionResourceResolver}
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class VersionResourceResolverTests {
|
||||
|
||||
private List<Resource> locations;
|
||||
|
||||
private VersionResourceResolver versionResourceResolver;
|
||||
|
||||
private ResourceResolverChain chain;
|
||||
|
||||
private VersionStrategy versionStrategy;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.locations = new ArrayList<Resource>();
|
||||
this.locations.add(new ClassPathResource("test/", getClass()));
|
||||
this.locations.add(new ClassPathResource("testalternatepath/", getClass()));
|
||||
|
||||
this.versionResourceResolver = new VersionResourceResolver();
|
||||
this.chain = mock(ResourceResolverChain.class);
|
||||
this.versionStrategy = mock(VersionStrategy.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceInternalExisting() throws Exception {
|
||||
String file = "bar.css";
|
||||
Resource expected = new ClassPathResource("test/" + file, getClass());
|
||||
when(this.chain.resolveResource(null, file, this.locations)).thenReturn(expected);
|
||||
|
||||
this.versionResourceResolver
|
||||
.setVersionStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
|
||||
Resource actual = this.versionResourceResolver.resolveResourceInternal(null, file, this.locations, this.chain);
|
||||
assertEquals(expected, actual);
|
||||
verify(this.chain, times(1)).resolveResource(null, file, this.locations);
|
||||
verify(this.versionStrategy, never()).extractVersionFromPath(file);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceInternalNoStrategy() throws Exception {
|
||||
String file = "missing.css";
|
||||
when(this.chain.resolveResource(null, file, this.locations)).thenReturn(null);
|
||||
|
||||
this.versionResourceResolver.setVersionStrategyMap(Collections.emptyMap());
|
||||
Resource actual = this.versionResourceResolver.resolveResourceInternal(null, file, this.locations, this.chain);
|
||||
assertNull(actual);
|
||||
verify(this.chain, times(1)).resolveResource(null, file, this.locations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceInternalNoCandidateVersion() throws Exception {
|
||||
|
||||
String file = "bar.css";
|
||||
when(this.chain.resolveResource(null, file, this.locations)).thenReturn(null);
|
||||
when(this.versionStrategy.extractVersionFromPath(file)).thenReturn("");
|
||||
|
||||
this.versionResourceResolver
|
||||
.setVersionStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
|
||||
Resource actual = this.versionResourceResolver.resolveResourceInternal(null, file, this.locations, this.chain);
|
||||
assertNull(actual);
|
||||
verify(this.chain, times(1)).resolveResource(null, file, this.locations);
|
||||
verify(this.versionStrategy, times(1)).extractVersionFromPath(file);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceInternalNoBaseResource() throws Exception {
|
||||
|
||||
String versionFile = "bar-version.css";
|
||||
String version = "version";
|
||||
String file = "bar.css";
|
||||
when(this.chain.resolveResource(null, versionFile, this.locations)).thenReturn(null);
|
||||
when(this.chain.resolveResource(null, file, this.locations)).thenReturn(null);
|
||||
when(this.versionStrategy.extractVersionFromPath(versionFile)).thenReturn(version);
|
||||
when(this.versionStrategy.deleteVersionFromPath(versionFile, version)).thenReturn(file);
|
||||
|
||||
this.versionResourceResolver
|
||||
.setVersionStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
|
||||
Resource actual = this.versionResourceResolver.resolveResourceInternal(null, versionFile, this.locations, this.chain);
|
||||
assertNull(actual);
|
||||
verify(this.versionStrategy, times(1)).deleteVersionFromPath(versionFile, version);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceInternalNoMatch() throws Exception {
|
||||
|
||||
String versionFile = "bar-version.css";
|
||||
String version = "version";
|
||||
String file = "bar.css";
|
||||
Resource expected = new ClassPathResource("test/" + file, getClass());
|
||||
when(this.chain.resolveResource(null, versionFile, this.locations)).thenReturn(null);
|
||||
when(this.chain.resolveResource(null, file, this.locations)).thenReturn(expected);
|
||||
when(this.versionStrategy.extractVersionFromPath(versionFile)).thenReturn(version);
|
||||
when(this.versionStrategy.deleteVersionFromPath(versionFile, version)).thenReturn(file);
|
||||
when(this.versionStrategy.resourceVersionMatches(expected, version)).thenReturn(false);
|
||||
|
||||
this.versionResourceResolver
|
||||
.setVersionStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
|
||||
Resource actual = this.versionResourceResolver.resolveResourceInternal(null, versionFile, this.locations, this.chain);
|
||||
assertNull(actual);
|
||||
verify(this.versionStrategy, times(1)).resourceVersionMatches(expected, version);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceInternalMatch() throws Exception {
|
||||
|
||||
String versionFile = "bar-version.css";
|
||||
String version = "version";
|
||||
String file = "bar.css";
|
||||
Resource expected = new ClassPathResource("test/" + file, getClass());
|
||||
when(this.chain.resolveResource(null, versionFile, this.locations)).thenReturn(null);
|
||||
when(this.chain.resolveResource(null, file, this.locations)).thenReturn(expected);
|
||||
when(this.versionStrategy.extractVersionFromPath(versionFile)).thenReturn(version);
|
||||
when(this.versionStrategy.deleteVersionFromPath(versionFile, version)).thenReturn(file);
|
||||
when(this.versionStrategy.resourceVersionMatches(expected, version)).thenReturn(true);
|
||||
|
||||
this.versionResourceResolver
|
||||
.setVersionStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
|
||||
Resource actual = this.versionResourceResolver.resolveResourceInternal(null, versionFile, this.locations, this.chain);
|
||||
assertEquals(expected, actual);
|
||||
verify(this.versionStrategy, times(1)).resourceVersionMatches(expected, version);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findStrategy() throws Exception {
|
||||
Map<String, VersionStrategy> strategies = new HashMap<>();
|
||||
VersionStrategy jsStrategy = mock(VersionStrategy.class);
|
||||
VersionStrategy catchAllStrategy = mock(VersionStrategy.class);
|
||||
strategies.put("/**", catchAllStrategy);
|
||||
strategies.put("/**/*.js", jsStrategy);
|
||||
this.versionResourceResolver.setVersionStrategyMap(strategies);
|
||||
|
||||
assertEquals(catchAllStrategy, this.versionResourceResolver.findStrategy("foo.css"));
|
||||
assertEquals(catchAllStrategy, this.versionResourceResolver.findStrategy("foo-js.css"));
|
||||
assertEquals(jsStrategy, this.versionResourceResolver.findStrategy("foo.js"));
|
||||
assertEquals(jsStrategy, this.versionResourceResolver.findStrategy("bar/foo.js"));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue