diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java index 206d3fc271..98c8341930 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java @@ -29,15 +29,19 @@ import org.springframework.core.io.Resource; * A {@code ResourceResolver} that delegates to the chain to locate a resource * and then attempts to find a matching versioned resource contained in a WebJar JAR file. * - *
This allows WabJar users to use version-less paths in their templates, like {@code "/jquery/jquery.min.js"} - * while this path is resolved to the unique version {@code "/jquery/1.2.0/jquery.min.js"}, which is a better fit - * for HTTP caching and version management in applications. + *
This allows WebJars.org users to write version agnostic paths in their templates, + * like {@code }. + * This path will be resolved to the unique version {@code }, + * which is a better fit for HTTP caching and version management in applications. + * + *
This also resolves Resources for version agnostic HTTP requests {@code "GET /jquery/jquery.min.js"}. * *
This resolver requires the "org.webjars:webjars-locator" library on classpath, and is automatically * registered if that library is present. * * @author Brian Clozel * @since 4.2 + * @see org.springframework.web.servlet.config.annotation.ResourceChainRegistration * @see webjars.org */ public class WebJarsResourceResolver extends AbstractResourceResolver { @@ -56,7 +60,12 @@ public class WebJarsResourceResolver extends AbstractResourceResolver { protected Resource resolveResourceInternal(HttpServletRequest request, String requestPath, List extends Resource> locations, ResourceResolverChain chain) { - return chain.resolveResource(request, requestPath, locations); + Resource resolved = chain.resolveResource(request, requestPath, locations); + if (resolved == null) { + String webJarResourcePath = findWebJarResourcePath(requestPath); + return chain.resolveResource(request, webJarResourcePath, locations); + } + return resolved; } @Override @@ -65,26 +74,31 @@ public class WebJarsResourceResolver extends AbstractResourceResolver { String path = chain.resolveUrlPath(resourceUrlPath, locations); if (path == null) { - try { - int startOffset = resourceUrlPath.startsWith("/") ? 1 : 0; - int endOffset = resourceUrlPath.indexOf("/", 1); - if (endOffset != -1) { - String webjar = resourceUrlPath.substring(startOffset, endOffset); - String partialPath = resourceUrlPath.substring(endOffset); - String webJarPath = webJarAssetLocator.getFullPath(webjar, partialPath); - return chain.resolveUrlPath(webJarPath.substring(WEBJARS_LOCATION_LENGTH), locations); - } - } - catch (MultipleMatchesException ex) { - logger.warn("WebJar version conflict for \"" + resourceUrlPath + "\"", ex); - } - catch (IllegalArgumentException ex) { - if (logger.isTraceEnabled()) { - logger.trace("No WebJar resource found for \"" + resourceUrlPath + "\""); - } - } + String webJarResourcePath = findWebJarResourcePath(resourceUrlPath); + return chain.resolveUrlPath(webJarResourcePath, locations); } return path; } + protected String findWebJarResourcePath(String path) { + try { + int startOffset = path.startsWith("/") ? 1 : 0; + int endOffset = path.indexOf("/", 1); + if (endOffset != -1) { + String webjar = path.substring(startOffset, endOffset); + String partialPath = path.substring(endOffset); + String webJarPath = webJarAssetLocator.getFullPath(webjar, partialPath); + return webJarPath.substring(WEBJARS_LOCATION_LENGTH); + } + } catch (MultipleMatchesException ex) { + logger.warn("WebJar version conflict for \"" + path + "\"", ex); + } + catch (IllegalArgumentException ex) { + if (logger.isTraceEnabled()) { + logger.trace("No WebJar resource found for \"" + path + "\""); + } + } + return null; + } + } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/WebJarsResourceResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/WebJarsResourceResolverTests.java index 3af4aea72c..f80183304e 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/WebJarsResourceResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/WebJarsResourceResolverTests.java @@ -26,11 +26,14 @@ import static org.mockito.Mockito.verify; import java.util.Collections; import java.util.List; +import javax.servlet.http.HttpServletRequest; + import org.junit.Before; import org.junit.Test; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; +import org.springframework.mock.web.test.MockHttpServletRequest; /** * Unit tests for @@ -46,6 +49,8 @@ public class WebJarsResourceResolverTests { private ResourceResolverChain chain; + private HttpServletRequest request = new MockHttpServletRequest(); + @Before public void setup() { // for this to work, an actual WebJar must be on the test classpath @@ -104,4 +109,31 @@ public class WebJarsResourceResolverTests { verify(this.chain, times(1)).resolveUrlPath(file, this.locations); } + @Test + public void resolveResourceExisting() { + Resource expected = mock(Resource.class); + this.locations = Collections.singletonList(new ClassPathResource("/META-INF/resources/webjars/", getClass())); + String file = "/foo/2.3/foo.txt"; + given(this.chain.resolveResource(this.request, file, this.locations)).willReturn(expected); + + Resource actual = this.resolver.resolveResource(this.request, file, this.locations, this.chain); + + assertEquals(expected, actual); + verify(this.chain, times(1)).resolveResource(this.request, file, this.locations); + } + + @Test + public void resolveResourceWebJar() { + Resource expected = mock(Resource.class); + String file = "/underscorejs/underscore.js"; + String expectedPath = "/underscorejs/1.8.2/underscore.js"; + this.locations = Collections.singletonList(new ClassPathResource("/META-INF/resources/webjars/", getClass())); + given(this.chain.resolveResource(this.request, expectedPath, this.locations)).willReturn(expected); + + Resource actual = this.resolver.resolveResource(this.request, file, this.locations, this.chain); + + assertEquals(expected, actual); + verify(this.chain, times(1)).resolveResource(this.request, file, this.locations); + } + }