Add ResourceTransformer and CSS link implementation
This change adds a ResourceTransformer that can be invoked in a chain after resource resolution. The CssLinkResourceTransformer modifies a CSS file being served in order to update its @import and url() links (e.g. to images or other CSS files) to match the resource resolution strategy (e.g. adding MD5 content-based hashes). Issue: SPR-11800
This commit is contained in:
@@ -29,11 +29,12 @@ import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
|
||||
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
|
||||
import org.springframework.web.servlet.resource.ResourceResolver;
|
||||
import org.springframework.web.servlet.resource.ResourceTransformer;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Test fixture with a {@link ResourceHandlerRegistry}.
|
||||
* Unit tests for {@link ResourceHandlerRegistry}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
@@ -45,18 +46,19 @@ public class ResourceHandlerRegistryTests {
|
||||
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
registry = new ResourceHandlerRegistry(new GenericWebApplicationContext(), new MockServletContext());
|
||||
registration = registry.addResourceHandler("/resources/**");
|
||||
registration.addResourceLocations("classpath:org/springframework/web/servlet/config/annotation/");
|
||||
response = new MockHttpServletResponse();
|
||||
this.registry = new ResourceHandlerRegistry(new GenericWebApplicationContext(), new MockServletContext());
|
||||
this.registration = registry.addResourceHandler("/resources/**");
|
||||
this.registration.addResourceLocations("classpath:org/springframework/web/servlet/config/annotation/");
|
||||
this.response = new MockHttpServletResponse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noResourceHandlers() throws Exception {
|
||||
registry = new ResourceHandlerRegistry(new GenericWebApplicationContext(), new MockServletContext());
|
||||
assertNull(registry.getHandlerMapping());
|
||||
this.registry = new ResourceHandlerRegistry(new GenericWebApplicationContext(), new MockServletContext());
|
||||
assertNull(this.registry.getHandlerMapping());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -66,16 +68,16 @@ public class ResourceHandlerRegistryTests {
|
||||
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/testStylesheet.css");
|
||||
|
||||
ResourceHttpRequestHandler handler = getHandler("/resources/**");
|
||||
handler.handleRequest(request, response);
|
||||
handler.handleRequest(request, this.response);
|
||||
|
||||
assertEquals("test stylesheet content", response.getContentAsString());
|
||||
assertEquals("test stylesheet content", this.response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cachePeriod() {
|
||||
assertEquals(-1, getHandler("/resources/**").getCacheSeconds());
|
||||
|
||||
registration.setCachePeriod(0);
|
||||
this.registration.setCachePeriod(0);
|
||||
assertEquals(0, getHandler("/resources/**").getCacheSeconds());
|
||||
}
|
||||
|
||||
@@ -89,23 +91,27 @@ public class ResourceHandlerRegistryTests {
|
||||
|
||||
@Test
|
||||
public void hasMappingForPattern() {
|
||||
assertTrue(registry.hasMappingForPattern("/resources/**"));
|
||||
assertFalse(registry.hasMappingForPattern("/whatever"));
|
||||
assertTrue(this.registry.hasMappingForPattern("/resources/**"));
|
||||
assertFalse(this.registry.hasMappingForPattern("/whatever"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resourceResolversAndTransformers() {
|
||||
ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
|
||||
registry.setResourceResolvers(resolver);
|
||||
this.registry.setResourceResolvers(resolver);
|
||||
|
||||
SimpleUrlHandlerMapping hm = (SimpleUrlHandlerMapping) registry.getHandlerMapping();
|
||||
ResourceTransformer transformer = Mockito.mock(ResourceTransformer.class);
|
||||
this.registry.setResourceTransformers(transformer);
|
||||
|
||||
SimpleUrlHandlerMapping hm = (SimpleUrlHandlerMapping) this.registry.getHandlerMapping();
|
||||
ResourceHttpRequestHandler handler = (ResourceHttpRequestHandler) hm.getUrlMap().values().iterator().next();
|
||||
|
||||
assertEquals(Arrays.asList(resolver), handler.getResourceResolvers());
|
||||
assertEquals(Arrays.asList(transformer), handler.getResourceTransformers());
|
||||
}
|
||||
|
||||
private ResourceHttpRequestHandler getHandler(String pathPattern) {
|
||||
SimpleUrlHandlerMapping handlerMapping = (SimpleUrlHandlerMapping) registry.getHandlerMapping();
|
||||
SimpleUrlHandlerMapping handlerMapping = (SimpleUrlHandlerMapping) this.registry.getHandlerMapping();
|
||||
return (ResourceHttpRequestHandler) handlerMapping.getUrlMap().get(pathPattern);
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ public class CachingResourceResolverTests {
|
||||
public void resolveResourceInternalFromCache() {
|
||||
|
||||
Resource expected = Mockito.mock(Resource.class);
|
||||
this.cache.put("requestPath:bar.css", expected);
|
||||
this.cache.put(CachingResourceResolver.RESOLVED_RESOURCE_CACHE_KEY_PREFIX + "bar.css", expected);
|
||||
|
||||
String file = "bar.css";
|
||||
Resource actual = this.chain.resolveResource(null, file, this.locations);
|
||||
@@ -98,7 +98,7 @@ public class CachingResourceResolverTests {
|
||||
@Test
|
||||
public void resolverUrlPathFromCache() {
|
||||
String expected = "cached-imaginary.css";
|
||||
this.cache.put("resourceUrlPath:imaginary.css", expected);
|
||||
this.cache.put(CachingResourceResolver.RESOLVED_URL_PATH_CACHE_KEY_PREFIX + "imaginary.css", expected);
|
||||
String actual = this.chain.resolveUrlPath("imaginary.css", this.locations);
|
||||
|
||||
assertEquals(expected, actual);
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
/**
|
||||
* Unit tests for
|
||||
* {@link org.springframework.web.servlet.resource.CssLinkResourceTransformer}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.1
|
||||
*/
|
||||
public class CssLinkResourceTransformerTests {
|
||||
|
||||
private ResourceTransformerChain transformerChain;
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>();
|
||||
resolvers.add(new FingerprintResourceResolver());
|
||||
resolvers.add(new PathResourceResolver());
|
||||
ResourceResolverChain resolverChain = new DefaultResourceResolverChain(resolvers);
|
||||
|
||||
List<ResourceTransformer> transformers = new ArrayList<>();
|
||||
transformers.add(new CssLinkResourceTransformer());
|
||||
this.transformerChain = new DefaultResourceTransformerChain(resolverChain, transformers);
|
||||
|
||||
this.request = new MockHttpServletRequest();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void transformNotCss() throws Exception {
|
||||
Resource expected = new ClassPathResource("test/images/image.png", getClass());
|
||||
Resource actual = this.transformerChain.transform(this.request, expected);
|
||||
assertSame(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transform() throws Exception {
|
||||
Resource mainCss = new ClassPathResource("test/main.css", getClass());
|
||||
Resource resource = this.transformerChain.transform(this.request, mainCss);
|
||||
TransformedResource transformedResource = (TransformedResource) resource;
|
||||
|
||||
String expected = "\n" +
|
||||
"@import url(\"bar-11e16cf79faee7ac698c805cf28248d2.css\");\n" +
|
||||
"@import url('bar-11e16cf79faee7ac698c805cf28248d2.css');\n" +
|
||||
"@import url(bar-11e16cf79faee7ac698c805cf28248d2.css);\n\n" +
|
||||
"@import \"foo-e36d2e05253c6c7085a91522ce43a0b4.css\";\n" +
|
||||
"@import 'foo-e36d2e05253c6c7085a91522ce43a0b4.css';\n\n" +
|
||||
"body { background: url(\"images/image-f448cd1d5dba82b774f3202c878230b3.png\") }\n\n" +
|
||||
"li { list-style: url(http://www.example.com/redball.png) disc }\n";
|
||||
|
||||
assertEquals(expected, new String(transformedResource.getByteArray(), "UTF-8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformNoLinks() throws Exception {
|
||||
Resource expected = new ClassPathResource("test/foo.css", getClass());
|
||||
Resource actual = this.transformerChain.transform(this.request, expected);
|
||||
assertSame(expected, actual);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -48,10 +48,11 @@ public class FingerprintResourceResolverTests {
|
||||
List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>();
|
||||
resolvers.add(resolver);
|
||||
resolvers.add(new PathResourceResolver());
|
||||
chain = new DefaultResourceResolverChain(resolvers);
|
||||
locations = new ArrayList<Resource>();
|
||||
locations.add(new ClassPathResource("test/", getClass()));
|
||||
locations.add(new ClassPathResource("testalternatepath/", getClass()));
|
||||
this.chain = new DefaultResourceResolverChain(resolvers);
|
||||
|
||||
this.locations = new ArrayList<Resource>();
|
||||
this.locations.add(new ClassPathResource("test/", getClass()));
|
||||
this.locations.add(new ClassPathResource("testalternatepath/", getClass()));
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +74,7 @@ public class FingerprintResourceResolverTests {
|
||||
@Test
|
||||
public void resolveStaticFingerprintedResource() throws Exception {
|
||||
String file = "foo-e36d2e05253c6c7085a91522ce43a0b4.css";
|
||||
Resource expected = new ClassPathResource("test/"+file, getClass());
|
||||
Resource expected = new ClassPathResource("test/" + file, getClass());
|
||||
Resource actual = chain.resolveResource(null, file, locations);
|
||||
|
||||
assertEquals(expected, actual);
|
||||
|
||||
Reference in New Issue
Block a user