Resource handling configuration

Issue: SPR-14521
This commit is contained in:
Rossen Stoyanchev
2016-09-05 21:57:32 -04:00
parent 1ae64bfbd3
commit 4346d15cb5
6 changed files with 730 additions and 2 deletions

View File

@@ -0,0 +1,132 @@
/*
* Copyright 2002-2016 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.reactive.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.cache.Cache;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.reactive.resource.CachingResourceResolver;
import org.springframework.web.reactive.resource.CachingResourceTransformer;
import org.springframework.web.reactive.resource.CssLinkResourceTransformer;
import org.springframework.web.reactive.resource.PathResourceResolver;
import org.springframework.web.reactive.resource.ResourceResolver;
import org.springframework.web.reactive.resource.ResourceTransformer;
import org.springframework.web.reactive.resource.VersionResourceResolver;
import org.springframework.web.reactive.resource.WebJarsResourceResolver;
/**
* Assists with the registration of resource resolvers and transformers.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public class ResourceChainRegistration {
private static final String DEFAULT_CACHE_NAME = "spring-resource-chain-cache";
private static final boolean isWebJarsAssetLocatorPresent = ClassUtils.isPresent(
"org.webjars.WebJarAssetLocator", ResourceChainRegistration.class.getClassLoader());
private final List<ResourceResolver> resolvers = new ArrayList<>(4);
private final List<ResourceTransformer> transformers = new ArrayList<>(4);
private boolean hasVersionResolver;
private boolean hasPathResolver;
private boolean hasCssLinkTransformer;
private boolean hasWebjarsResolver;
public ResourceChainRegistration(boolean cacheResources) {
this(cacheResources, cacheResources ? new ConcurrentMapCache(DEFAULT_CACHE_NAME) : null);
}
public ResourceChainRegistration(boolean cacheResources, Cache cache) {
Assert.isTrue(!cacheResources || cache != null, "'cache' is required when cacheResources=true");
if (cacheResources) {
this.resolvers.add(new CachingResourceResolver(cache));
this.transformers.add(new CachingResourceTransformer(cache));
}
}
/**
* Add a resource resolver to the chain.
* @param resolver the resolver to add
* @return the current instance for chained method invocation
*/
public ResourceChainRegistration addResolver(ResourceResolver resolver) {
Assert.notNull(resolver, "The provided ResourceResolver should not be null");
this.resolvers.add(resolver);
if (resolver instanceof VersionResourceResolver) {
this.hasVersionResolver = true;
}
else if (resolver instanceof PathResourceResolver) {
this.hasPathResolver = true;
}
else if (resolver instanceof WebJarsResourceResolver) {
this.hasWebjarsResolver = true;
}
return this;
}
/**
* Add a resource transformer to the chain.
* @param transformer the transformer to add
* @return the current instance for chained method invocation
*/
public ResourceChainRegistration addTransformer(ResourceTransformer transformer) {
Assert.notNull(transformer, "The provided ResourceTransformer should not be null");
this.transformers.add(transformer);
if (transformer instanceof CssLinkResourceTransformer) {
this.hasCssLinkTransformer = true;
}
return this;
}
protected List<ResourceResolver> getResourceResolvers() {
if (!this.hasPathResolver) {
List<ResourceResolver> result = new ArrayList<>(this.resolvers);
if (isWebJarsAssetLocatorPresent && !this.hasWebjarsResolver) {
result.add(new WebJarsResourceResolver());
}
result.add(new PathResourceResolver());
return result;
}
return this.resolvers;
}
protected List<ResourceTransformer> getResourceTransformers() {
if (this.hasVersionResolver && !this.hasCssLinkTransformer) {
List<ResourceTransformer> result = new ArrayList<>(this.transformers);
boolean hasTransformers = !this.transformers.isEmpty();
boolean hasCaching = hasTransformers && this.transformers.get(0) instanceof CachingResourceTransformer;
result.add(hasCaching ? 1 : 0, new CssLinkResourceTransformer());
return result;
}
return this.transformers;
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2002-2016 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.reactive.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.cache.Cache;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.CacheControl;
import org.springframework.util.Assert;
import org.springframework.web.reactive.resource.ResourceWebHandler;
/**
* Assist with creating and configuring a static resources handler.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public class ResourceHandlerRegistration {
private final ResourceLoader resourceLoader;
private final String[] pathPatterns;
private final List<Resource> locations = new ArrayList<>();
private CacheControl cacheControl;
private ResourceChainRegistration resourceChainRegistration;
/**
* Create a {@link ResourceHandlerRegistration} instance.
* @param resourceLoader a resource loader for turning a String location
* into a {@link Resource}
* @param pathPatterns one or more resource URL path patterns
*/
public ResourceHandlerRegistration(ResourceLoader resourceLoader, String... pathPatterns) {
Assert.notEmpty(pathPatterns, "At least one path pattern is required for resource handling.");
this.resourceLoader = resourceLoader;
this.pathPatterns = pathPatterns;
}
/**
* Add one or more resource locations from which to serve static content.
* Each location must point to a valid directory. Multiple locations may
* be specified as a comma-separated list, and the locations will be checked
* for a given resource in the order specified.
*
* <p>For example, {{@code "/"},
* {@code "classpath:/META-INF/public-web-resources/"}} allows resources to
* be served both from the web application root and from any JAR on the
* classpath that contains a {@code /META-INF/public-web-resources/} directory,
* with resources in the web application root taking precedence.
* @return the same {@link ResourceHandlerRegistration} instance, for
* chained method invocation
*/
public ResourceHandlerRegistration addResourceLocations(String... resourceLocations) {
for (String location : resourceLocations) {
this.locations.add(resourceLoader.getResource(location));
}
return this;
}
/**
* Specify the {@link CacheControl} which should be used
* by the resource handler.
*
* @param cacheControl the CacheControl configuration to use
* @return the same {@link ResourceHandlerRegistration} instance, for
* chained method invocation
*/
public ResourceHandlerRegistration setCacheControl(CacheControl cacheControl) {
this.cacheControl = cacheControl;
return this;
}
/**
* Configure a chain of resource resolvers and transformers to use. This
* can be useful, for example, to apply a version strategy to resource URLs.
*
* <p>If this method is not invoked, by default only a simple
* {@code PathResourceResolver} is used in order to match URL paths to
* resources under the configured locations.
*
* @param cacheResources whether to cache the result of resource resolution;
* setting this to "true" is recommended for production (and "false" for
* development, especially when applying a version strategy)
* @return the same {@link ResourceHandlerRegistration} instance, for
* chained method invocation
*/
public ResourceChainRegistration resourceChain(boolean cacheResources) {
this.resourceChainRegistration = new ResourceChainRegistration(cacheResources);
return this.resourceChainRegistration;
}
/**
* Configure a chain of resource resolvers and transformers to use. This
* can be useful, for example, to apply a version strategy to resource URLs.
*
* <p>If this method is not invoked, by default only a simple
* {@code PathResourceResolver} is used in order to match URL paths to
* resources under the configured locations.
*
* @param cacheResources whether to cache the result of resource resolution;
* setting this to "true" is recommended for production (and "false" for
* development, especially when applying a version strategy
* @param cache the cache to use for storing resolved and transformed resources;
* by default a {@link org.springframework.cache.concurrent.ConcurrentMapCache}
* is used. Since Resources aren't serializable and can be dependent on the
* application host, one should not use a distributed cache but rather an
* in-memory cache.
* @return the same {@link ResourceHandlerRegistration} instance, for chained method invocation
*/
public ResourceChainRegistration resourceChain(boolean cacheResources, Cache cache) {
this.resourceChainRegistration = new ResourceChainRegistration(cacheResources, cache);
return this.resourceChainRegistration;
}
/**
* Returns the URL path patterns for the resource handler.
*/
protected String[] getPathPatterns() {
return this.pathPatterns;
}
/**
* Returns a {@link ResourceWebHandler} instance.
*/
protected ResourceWebHandler getRequestHandler() {
ResourceWebHandler handler = new ResourceWebHandler();
if (this.resourceChainRegistration != null) {
handler.setResourceResolvers(this.resourceChainRegistration.getResourceResolvers());
handler.setResourceTransformers(this.resourceChainRegistration.getResourceTransformers());
}
handler.setLocations(this.locations);
if (this.cacheControl != null) {
handler.setCacheControl(this.cacheControl);
}
return handler;
}
}

View File

@@ -0,0 +1,158 @@
/*
* Copyright 2002-2016 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.reactive.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.web.reactive.accept.CompositeContentTypeResolver;
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.resource.ResourceWebHandler;
import org.springframework.web.server.WebHandler;
/**
* Stores registrations of resource handlers for serving static resources such
* as images, css files and others through Spring MVC including setting cache
* headers optimized for efficient loading in a web browser. Resources can be
* served out of locations under web application root, from the classpath, and
* others.
*
* <p>To create a resource handler, use {@link #addResourceHandler(String...)}
* providing the URL path patterns for which the handler should be invoked to
* serve static resources (e.g. {@code "/resources/**"}).
*
* <p>Then use additional methods on the returned
* {@link ResourceHandlerRegistration} to add one or more locations from which
* to serve static content from (e.g. {{@code "/"},
* {@code "classpath:/META-INF/public-web-resources/"}}) or to specify a cache
* period for served resources.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public class ResourceHandlerRegistry {
private final ApplicationContext applicationContext;
private final CompositeContentTypeResolver contentTypeResolver;
private final List<ResourceHandlerRegistration> registrations = new ArrayList<>();
private int order = Integer.MAX_VALUE -1;
/**
* Create a new resource handler registry for the given application context.
* @param applicationContext the Spring application context
*/
public ResourceHandlerRegistry(ApplicationContext applicationContext) {
this(applicationContext, null);
}
/**
* Create a new resource handler registry for the given application context.
* @param applicationContext the Spring application context
* @param contentTypeResolver the content type resolver to use
*/
public ResourceHandlerRegistry(ApplicationContext applicationContext,
CompositeContentTypeResolver contentTypeResolver) {
Assert.notNull(applicationContext, "ApplicationContext is required");
this.applicationContext = applicationContext;
this.contentTypeResolver = contentTypeResolver;
}
/**
* Add a resource handler for serving static resources based on the specified
* URL path patterns. The handler will be invoked for every incoming request
* that matches to one of the specified path patterns.
*
* <p>Patterns like {@code "/static/**"} or {@code "/css/{filename:\\w+\\.css}"}
* are allowed. See {@link org.springframework.util.AntPathMatcher} for more
* details on the syntax.
* @return A {@link ResourceHandlerRegistration} to use to further
* configure the registered resource handler
*/
public ResourceHandlerRegistration addResourceHandler(String... patterns) {
ResourceHandlerRegistration registration =
new ResourceHandlerRegistration(this.applicationContext, patterns);
this.registrations.add(registration);
return registration;
}
/**
* Whether a resource handler has already been registered for the given path pattern.
*/
public boolean hasMappingForPattern(String pathPattern) {
for (ResourceHandlerRegistration registration : this.registrations) {
if (Arrays.asList(registration.getPathPatterns()).contains(pathPattern)) {
return true;
}
}
return false;
}
/**
* Specify the order to use for resource handling relative to other
* {@code HandlerMapping}s configured in the Spring configuration.
* <p>The default value used is {@code Integer.MAX_VALUE-1}.
*/
public ResourceHandlerRegistry setOrder(int order) {
this.order = order;
return this;
}
/**
* Return a handler mapping with the mapped resource handlers; or {@code null} in case
* of no registrations.
*/
protected AbstractHandlerMapping getHandlerMapping() {
if (this.registrations.isEmpty()) {
return null;
}
Map<String, WebHandler> urlMap = new LinkedHashMap<>();
for (ResourceHandlerRegistration registration : this.registrations) {
for (String pathPattern : registration.getPathPatterns()) {
ResourceWebHandler handler = registration.getRequestHandler();
handler.setContentTypeResolver(this.contentTypeResolver);
try {
handler.afterPropertiesSet();
handler.afterSingletonsInstantiated();
}
catch (Exception ex) {
throw new BeanInitializationException("Failed to init ResourceHttpRequestHandler", ex);
}
urlMap.put(pathPattern, handler);
}
}
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
handlerMapping.setOrder(order);
handlerMapping.setUrlMap(urlMap);
return handlerMapping;
}
}

View File

@@ -21,6 +21,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import reactor.core.publisher.Mono;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.context.ApplicationContext;
@@ -52,8 +54,10 @@ import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
import org.springframework.util.ClassUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.accept.CompositeContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
import org.springframework.web.reactive.result.SimpleHandlerAdapter;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
@@ -62,6 +66,7 @@ import org.springframework.web.reactive.result.method.annotation.ResponseBodyRes
import org.springframework.web.reactive.result.method.annotation.ResponseEntityResultHandler;
import org.springframework.web.reactive.result.view.ViewResolutionResultHandler;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
/**
* The main class for Spring Web Reactive configuration.
@@ -135,7 +140,7 @@ public class WebReactiveConfiguration implements ApplicationContextAware {
}
@Bean
public RequestedContentTypeResolver mvcContentTypeResolver() {
public CompositeContentTypeResolver mvcContentTypeResolver() {
RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder();
builder.mediaTypes(getDefaultMediaTypeMappings());
configureRequestedContentTypeResolver(builder);
@@ -178,6 +183,39 @@ public class WebReactiveConfiguration implements ApplicationContextAware {
public void configurePathMatching(PathMatchConfigurer configurer) {
}
/**
* Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped
* resource handlers. To configure resource handling, override
* {@link #addResourceHandlers}.
*/
@Bean
public HandlerMapping resourceHandlerMapping() {
ResourceHandlerRegistry registry =
new ResourceHandlerRegistry(this.applicationContext, mvcContentTypeResolver());
addResourceHandlers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
if (handlerMapping != null) {
if (getPathMatchConfigurer() != null) {
handlerMapping.setPathMatcher(getPathMatchConfigurer().getPathMatcher());
}
if (getPathMatchConfigurer() != null) {
handlerMapping.setPathHelper(getPathMatchConfigurer().getPathHelper());
}
}
else {
handlerMapping = new EmptyHandlerMapping();
}
return handlerMapping;
}
/**
* Override this method to add resource handlers for serving static resources.
* @see ResourceHandlerRegistry
*/
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
}
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
@@ -397,6 +435,14 @@ public class WebReactiveConfiguration implements ApplicationContextAware {
}
private static final class EmptyHandlerMapping extends AbstractHandlerMapping {
@Override
public Mono<Object> getHandler(ServerWebExchange exchange) {
return Mono.empty();
}
}
private static final class NoOpValidator implements Validator {
@Override

View File

@@ -0,0 +1,231 @@
/*
* Copyright 2002-2016 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.reactive.config;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpMethod;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.tests.TestSubscriber;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.resource.AppCacheManifestTransformer;
import org.springframework.web.reactive.resource.CachingResourceResolver;
import org.springframework.web.reactive.resource.CachingResourceTransformer;
import org.springframework.web.reactive.resource.CssLinkResourceTransformer;
import org.springframework.web.reactive.resource.PathResourceResolver;
import org.springframework.web.reactive.resource.ResourceResolver;
import org.springframework.web.reactive.resource.ResourceTransformer;
import org.springframework.web.reactive.resource.ResourceWebHandler;
import org.springframework.web.reactive.resource.VersionResourceResolver;
import org.springframework.web.reactive.resource.WebJarsResourceResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.DefaultWebSessionManager;
import org.springframework.web.server.session.WebSessionManager;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link ResourceHandlerRegistry}.
*
* @author Rossen Stoyanchev
*/
public class ResourceHandlerRegistryTests {
private ResourceHandlerRegistry registry;
private ResourceHandlerRegistration registration;
private ServerWebExchange exchange;
private MockServerHttpResponse response;
@Before
public void setUp() {
this.registry = new ResourceHandlerRegistry(new GenericApplicationContext());
this.registration = this.registry.addResourceHandler("/resources/**");
this.registration.addResourceLocations("classpath:org/springframework/web/reactive/config/");
MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, "");
this.response = new MockServerHttpResponse();
WebSessionManager manager = new DefaultWebSessionManager();
this.exchange = new DefaultServerWebExchange(request, this.response, manager);
}
@Test
public void noResourceHandlers() throws Exception {
this.registry = new ResourceHandlerRegistry(new GenericApplicationContext());
assertNull(this.registry.getHandlerMapping());
}
@Test
public void mapPathToLocation() throws Exception {
this.exchange.getAttributes().put(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/testStylesheet.css");
ResourceWebHandler handler = getHandler("/resources/**");
handler.handle(this.exchange).blockMillis(5000);
TestSubscriber.subscribe(this.response.getBody())
.assertValuesWith(buf -> assertEquals("test stylesheet content",
DataBufferTestUtils.dumpString(buf, StandardCharsets.UTF_8)));
}
@Test
public void cacheControl() {
assertThat(getHandler("/resources/**").getCacheControl(),
Matchers.nullValue());
this.registration.setCacheControl(CacheControl.noCache().cachePrivate());
assertThat(getHandler("/resources/**").getCacheControl().getHeaderValue(),
Matchers.equalTo(CacheControl.noCache().cachePrivate().getHeaderValue()));
}
@Test
public void order() {
assertEquals(Integer.MAX_VALUE -1, this.registry.getHandlerMapping().getOrder());
this.registry.setOrder(0);
assertEquals(0, this.registry.getHandlerMapping().getOrder());
}
@Test
public void hasMappingForPattern() {
assertTrue(this.registry.hasMappingForPattern("/resources/**"));
assertFalse(this.registry.hasMappingForPattern("/whatever"));
}
@Test
public void resourceChain() throws Exception {
ResourceResolver mockResolver = Mockito.mock(ResourceResolver.class);
ResourceTransformer mockTransformer = Mockito.mock(ResourceTransformer.class);
this.registration.resourceChain(true).addResolver(mockResolver).addTransformer(mockTransformer);
ResourceWebHandler handler = getHandler("/resources/**");
List<ResourceResolver> resolvers = handler.getResourceResolvers();
assertThat(resolvers.toString(), resolvers, Matchers.hasSize(4));
assertThat(resolvers.get(0), Matchers.instanceOf(CachingResourceResolver.class));
CachingResourceResolver cachingResolver = (CachingResourceResolver) resolvers.get(0);
assertThat(cachingResolver.getCache(), Matchers.instanceOf(ConcurrentMapCache.class));
assertThat(resolvers.get(1), Matchers.equalTo(mockResolver));
assertThat(resolvers.get(2), Matchers.instanceOf(WebJarsResourceResolver.class));
assertThat(resolvers.get(3), Matchers.instanceOf(PathResourceResolver.class));
List<ResourceTransformer> transformers = handler.getResourceTransformers();
assertThat(transformers, Matchers.hasSize(2));
assertThat(transformers.get(0), Matchers.instanceOf(CachingResourceTransformer.class));
assertThat(transformers.get(1), Matchers.equalTo(mockTransformer));
}
@Test
public void resourceChainWithoutCaching() throws Exception {
this.registration.resourceChain(false);
ResourceWebHandler handler = getHandler("/resources/**");
List<ResourceResolver> resolvers = handler.getResourceResolvers();
assertThat(resolvers, Matchers.hasSize(2));
assertThat(resolvers.get(0), Matchers.instanceOf(WebJarsResourceResolver.class));
assertThat(resolvers.get(1), Matchers.instanceOf(PathResourceResolver.class));
List<ResourceTransformer> transformers = handler.getResourceTransformers();
assertThat(transformers, Matchers.hasSize(0));
}
@Test
public void resourceChainWithVersionResolver() throws Exception {
VersionResourceResolver versionResolver = new VersionResourceResolver()
.addFixedVersionStrategy("fixed", "/**/*.js")
.addContentVersionStrategy("/**");
this.registration.resourceChain(true).addResolver(versionResolver)
.addTransformer(new AppCacheManifestTransformer());
ResourceWebHandler handler = getHandler("/resources/**");
List<ResourceResolver> resolvers = handler.getResourceResolvers();
assertThat(resolvers.toString(), resolvers, Matchers.hasSize(4));
assertThat(resolvers.get(0), Matchers.instanceOf(CachingResourceResolver.class));
assertThat(resolvers.get(1), Matchers.sameInstance(versionResolver));
assertThat(resolvers.get(2), Matchers.instanceOf(WebJarsResourceResolver.class));
assertThat(resolvers.get(3), Matchers.instanceOf(PathResourceResolver.class));
List<ResourceTransformer> transformers = handler.getResourceTransformers();
assertThat(transformers, Matchers.hasSize(3));
assertThat(transformers.get(0), Matchers.instanceOf(CachingResourceTransformer.class));
assertThat(transformers.get(1), Matchers.instanceOf(CssLinkResourceTransformer.class));
assertThat(transformers.get(2), Matchers.instanceOf(AppCacheManifestTransformer.class));
}
@Test
public void resourceChainWithOverrides() throws Exception {
CachingResourceResolver cachingResolver = Mockito.mock(CachingResourceResolver.class);
VersionResourceResolver versionResolver = Mockito.mock(VersionResourceResolver.class);
WebJarsResourceResolver webjarsResolver = Mockito.mock(WebJarsResourceResolver.class);
PathResourceResolver pathResourceResolver = new PathResourceResolver();
CachingResourceTransformer cachingTransformer = Mockito.mock(CachingResourceTransformer.class);
AppCacheManifestTransformer appCacheTransformer = Mockito.mock(AppCacheManifestTransformer.class);
CssLinkResourceTransformer cssLinkTransformer = new CssLinkResourceTransformer();
this.registration.setCacheControl(CacheControl.maxAge(3600, TimeUnit.MILLISECONDS))
.resourceChain(false)
.addResolver(cachingResolver)
.addResolver(versionResolver)
.addResolver(webjarsResolver)
.addResolver(pathResourceResolver)
.addTransformer(cachingTransformer)
.addTransformer(appCacheTransformer)
.addTransformer(cssLinkTransformer);
ResourceWebHandler handler = getHandler("/resources/**");
List<ResourceResolver> resolvers = handler.getResourceResolvers();
assertThat(resolvers.toString(), resolvers, Matchers.hasSize(4));
assertThat(resolvers.get(0), Matchers.sameInstance(cachingResolver));
assertThat(resolvers.get(1), Matchers.sameInstance(versionResolver));
assertThat(resolvers.get(2), Matchers.sameInstance(webjarsResolver));
assertThat(resolvers.get(3), Matchers.sameInstance(pathResourceResolver));
List<ResourceTransformer> transformers = handler.getResourceTransformers();
assertThat(transformers, Matchers.hasSize(3));
assertThat(transformers.get(0), Matchers.sameInstance(cachingTransformer));
assertThat(transformers.get(1), Matchers.sameInstance(appCacheTransformer));
assertThat(transformers.get(2), Matchers.sameInstance(cssLinkTransformer));
}
private ResourceWebHandler getHandler(String pathPattern) {
SimpleUrlHandlerMapping mapping = (SimpleUrlHandlerMapping) this.registry.getHandlerMapping();
return (ResourceWebHandler) mapping.getUrlMap().get(pathPattern);
}
}

View File

@@ -0,0 +1 @@
test stylesheet content