diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/AbstractVersionStrategy.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/AbstractVersionStrategy.java
new file mode 100644
index 0000000000..8cb3c3b5f4
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/AbstractVersionStrategy.java
@@ -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:
+ *
+ * - prefix in the request path, like "version/static/myresource.js"
+ *
- file name suffix in the request path, like "static/myresource-version.js"
+ *
+ *
+ * Note: This base class does not 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;
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ContentBasedVersionStrategy.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ContentBasedVersionStrategy.java
new file mode 100644
index 0000000000..778e2e03e2
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ContentBasedVersionStrategy.java
@@ -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.
+ *
+ *
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 "";
+ }
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/FingerprintResourceResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/FingerprintResourceResolver.java
deleted file mode 100644
index df80870a86..0000000000
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/FingerprintResourceResolver.java
+++ /dev/null
@@ -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.
- *
- *
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.
- *
- *
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 "";
- }
- }
-
-}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/FixedVersionStrategy.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/FixedVersionStrategy.java
new file mode 100644
index 0000000000..9c8d9da229
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/FixedVersionStrategy.java
@@ -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.
+ *
+ *
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.
+ *
+ *
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 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);
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PrefixResourceResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PrefixResourceResolver.java
deleted file mode 100644
index 1637eb6f14..0000000000
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PrefixResourceResolver.java
+++ /dev/null
@@ -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.
- *
- * 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.
- *
- *
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;
- }
-
-}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/VersionResourceResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/VersionResourceResolver.java
new file mode 100644
index 0000000000..212ef71278
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/VersionResourceResolver.java
@@ -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.
+ *
+ *
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}.
+ *
+ *
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.
+ *
+ *
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 versionStrategyMap = Collections.emptyMap();
+
+ /**
+ * Set a Map with URL paths as keys and {@code VersionStrategy}
+ * as values.
+ *
+ * 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 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 matchingPatterns = new ArrayList();
+ for(String pattern : this.versionStrategyMap.keySet()) {
+ if(this.pathMatcher.match(pattern, path)) {
+ matchingPatterns.add(pattern);
+ }
+ }
+ if(!matchingPatterns.isEmpty()) {
+ Comparator comparator = this.pathMatcher.getPatternComparator(path);
+ Collections.sort(matchingPatterns, comparator);
+ return this.versionStrategyMap.get(matchingPatterns.get(0));
+ }
+
+ return null;
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/VersionStrategy.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/VersionStrategy.java
new file mode 100644
index 0000000000..5385e036e0
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/VersionStrategy.java
@@ -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);
+}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ContentBasedVersionStrategyTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ContentBasedVersionStrategyTests.java
new file mode 100644
index 0000000000..9d2b0fc14d
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ContentBasedVersionStrategyTests.java
@@ -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 locations;
+
+ private ContentBasedVersionStrategy versionStrategy = new ContentBasedVersionStrategy();
+
+ private ResourceResolverChain chain;
+
+ @Before
+ public void setup() {
+ this.locations = new ArrayList();
+ 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);
+ }
+
+}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/CssLinkResourceTransformerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/CssLinkResourceTransformerTests.java
index a37c7e52da..c4d391098b 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/CssLinkResourceTransformerTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/CssLinkResourceTransformerTests.java
@@ -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 versionStrategyMap = new HashMap<>();
+ versionStrategyMap.put("/**", new ContentBasedVersionStrategy());
+ VersionResourceResolver versionResolver = new VersionResourceResolver();
+ versionResolver.setVersionStrategyMap(versionStrategyMap);
+
List resolvers = new ArrayList();
- resolvers.add(new FingerprintResourceResolver());
+ resolvers.add(versionResolver);
resolvers.add(new PathResourceResolver());
ResourceResolverChain resolverChain = new DefaultResourceResolverChain(resolvers);
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/FingerprintResourceResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/FingerprintResourceResolverTests.java
deleted file mode 100644
index 668fbfe468..0000000000
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/FingerprintResourceResolverTests.java
+++ /dev/null
@@ -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 locations;
-
- @Before
- public void setUp() {
- List resolvers = new ArrayList();
- resolvers.add(resolver);
- resolvers.add(new PathResourceResolver());
- this.chain = new DefaultResourceResolverChain(resolvers);
-
- this.locations = new ArrayList();
- 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);
- }
-}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/FixedVersionStrategyTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/FixedVersionStrategyTests.java
new file mode 100644
index 0000000000..b3cab06411
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/FixedVersionStrategyTests.java
@@ -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() {
+ @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.emptyList(), null));
+
+ }
+}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/GzipResourceResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/GzipResourceResolverTests.java
index 61f161ba47..40a9ad18bf 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/GzipResourceResolverTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/GzipResourceResolverTests.java
@@ -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 versionStrategyMap = new HashMap<>();
+ versionStrategyMap.put("/**", new ContentBasedVersionStrategy());
+ VersionResourceResolver versionResolver = new VersionResourceResolver();
+ versionResolver.setVersionStrategyMap(versionStrategyMap);
+
List resolvers = new ArrayList();
resolvers.add(new GzipResourceResolver());
- resolvers.add(new FingerprintResourceResolver());
+ resolvers.add(versionResolver);
resolvers.add(new PathResourceResolver());
resolver = new DefaultResourceResolverChain(resolvers);
locations = new ArrayList();
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/PrefixResourceResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/PrefixResourceResolverTests.java
deleted file mode 100644
index 78924531a4..0000000000
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/PrefixResourceResolverTests.java
+++ /dev/null
@@ -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 resolvers = new ArrayList();
- 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(" ");
- }
-
-}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderJavaConfigTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderJavaConfigTests.java
index 32959ce18c..48749ff3eb 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderJavaConfigTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderJavaConfigTests.java
@@ -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 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());
}
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderTests.java
index fa35ed33f1..12e4ced623 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderTests.java
@@ -67,8 +67,13 @@ public class ResourceUrlProviderTests {
@Test
public void getFingerprintedResourceUrl() {
+ Map versionStrategyMap = new HashMap<>();
+ versionStrategyMap.put("/**", new ContentBasedVersionStrategy());
+ VersionResourceResolver versionResolver = new VersionResourceResolver();
+ versionResolver.setVersionStrategyMap(versionStrategyMap);
+
List resolvers = new ArrayList();
- resolvers.add(new FingerprintResourceResolver());
+ resolvers.add(versionResolver);
resolvers.add(new PathResourceResolver());
this.handler.setResourceResolvers(resolvers);
initTranslator();
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/VersionResourceResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/VersionResourceResolverTests.java
new file mode 100644
index 0000000000..cbeddb0e0f
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/VersionResourceResolverTests.java
@@ -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 locations;
+
+ private VersionResourceResolver versionResourceResolver;
+
+ private ResourceResolverChain chain;
+
+ private VersionStrategy versionStrategy;
+
+
+ @Before
+ public void setup() {
+ this.locations = new ArrayList();
+ 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 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"));
+ }
+
+}