Browse Source
Renamed ResourceUrlMapper to ResourceUrlGenerator and refactored it to be configured with Resource-serving HandlerMappings as opposed to having them detected in the ApplicationContext through the BeanPostProcessor contact. Renamed and polished ResourceUrlEncodingFilter to ResourceUrlFilter and added tests.pull/366/head
6 changed files with 357 additions and 143 deletions
@ -0,0 +1,129 @@
@@ -0,0 +1,129 @@
|
||||
/* |
||||
* 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.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Map.Entry; |
||||
|
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.util.PathMatcher; |
||||
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; |
||||
|
||||
|
||||
/** |
||||
* A helper class for generating the URL for a resource. Given knowledge of all configured |
||||
* resource handler mappings (see {@link #setResourceHandlerMappings(List)}), it can |
||||
* determine whether a given path is a path to a resource, as well as what URL should be |
||||
* sent to the client to access that resource. This is essentially the reverse of |
||||
* resolving an incoming request URL to a resource. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.0 |
||||
*/ |
||||
public class ResourceUrlGenerator { |
||||
|
||||
private final List<ResourceMapping> resourceMappings = new ArrayList<ResourceMapping>(); |
||||
|
||||
|
||||
/** |
||||
* Configure this instance with the handler mappings used to serve resources. It is |
||||
* expected that the handler mapping URL map contains handlers of type |
||||
* {@link ResourceHttpRequestHandler}. |
||||
* |
||||
* @param handlerMappings resource handler mappings |
||||
*/ |
||||
public void setResourceHandlerMappings(List<SimpleUrlHandlerMapping> handlerMappings) { |
||||
this.resourceMappings.clear(); |
||||
if (handlerMappings == null) { |
||||
return; |
||||
} |
||||
for (SimpleUrlHandlerMapping handlerMapping : handlerMappings) { |
||||
PathMatcher pathMatcher = handlerMapping.getPathMatcher(); |
||||
|
||||
for(Entry<String, ?> entry : handlerMapping.getUrlMap().entrySet()) { |
||||
Object value = entry.getValue(); |
||||
if (value instanceof ResourceHttpRequestHandler) { |
||||
ResourceHttpRequestHandler handler = (ResourceHttpRequestHandler) value; |
||||
|
||||
String pattern = entry.getKey(); |
||||
List<ResourceResolver> resolvers = handler.getResourceResolvers(); |
||||
List<Resource> locations = handler.getLocations(); |
||||
this.resourceMappings.add(new ResourceMapping(pattern, pathMatcher, resolvers, locations)); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Resolve the given resource path to a URL path. This is useful when rendering URL |
||||
* links to clients to determine the actual URL to use. |
||||
* |
||||
* @param candidatePath the resource path to resolve |
||||
* |
||||
* @return the resolved URL path or {@code null} if the given path does not match to |
||||
* any resource or otherwise could not be resolved to a resource URL path |
||||
*/ |
||||
public String getResourceUrl(String candidatePath) { |
||||
for (ResourceMapping mapping : this.resourceMappings) { |
||||
String url = mapping.getUrlForResource(candidatePath); |
||||
if (url != null) { |
||||
return url; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
|
||||
private static class ResourceMapping { |
||||
|
||||
private final String pattern; |
||||
|
||||
private final PathMatcher pathMatcher; |
||||
|
||||
private final List<ResourceResolver> resolvers; |
||||
|
||||
private final List<Resource> locations; |
||||
|
||||
|
||||
public ResourceMapping(String pattern, PathMatcher pathMatcher, |
||||
List<ResourceResolver> resolvers, List<Resource> locations) { |
||||
|
||||
this.pattern = pattern; |
||||
this.pathMatcher = pathMatcher; |
||||
this.resolvers = resolvers; |
||||
this.locations = locations; |
||||
} |
||||
|
||||
public String getUrlForResource(String candidatePath) { |
||||
|
||||
if (this.pathMatcher.match(this.pattern, candidatePath)) { |
||||
|
||||
String pathWithinMapping = this.pathMatcher.extractPathWithinPattern(this.pattern, candidatePath); |
||||
String pathMapping = candidatePath.replace(pathWithinMapping, ""); |
||||
|
||||
DefaultResourceResolverChain chain = new DefaultResourceResolverChain(this.resolvers); |
||||
String url = chain.resolveUrlPath(pathWithinMapping, this.locations); |
||||
if (url != null) { |
||||
return pathMapping + url; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,106 +0,0 @@
@@ -1,106 +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.util.ArrayList; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Map.Entry; |
||||
|
||||
import org.springframework.beans.BeansException; |
||||
import org.springframework.beans.factory.config.BeanPostProcessor; |
||||
import org.springframework.context.ApplicationListener; |
||||
import org.springframework.context.event.ContextRefreshedEvent; |
||||
import org.springframework.core.OrderComparator; |
||||
import org.springframework.util.AntPathMatcher; |
||||
import org.springframework.util.ClassUtils; |
||||
import org.springframework.util.PathMatcher; |
||||
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; |
||||
|
||||
|
||||
/** |
||||
* |
||||
* @author Jeremy Grelle |
||||
* @since 4.0 |
||||
*/ |
||||
public class ResourceUrlMapper implements BeanPostProcessor, ApplicationListener<ContextRefreshedEvent>{ |
||||
|
||||
private final Map<String, ResourceHttpRequestHandler> handlers = new LinkedHashMap<String, ResourceHttpRequestHandler>(); |
||||
|
||||
private final List<SimpleUrlHandlerMapping> mappings = new ArrayList<SimpleUrlHandlerMapping>(); |
||||
|
||||
private final PathMatcher matcher = new AntPathMatcher(); |
||||
|
||||
@Override |
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) |
||||
throws BeansException { |
||||
return bean; |
||||
} |
||||
|
||||
@Override |
||||
public Object postProcessAfterInitialization(Object bean, String beanName) |
||||
throws BeansException { |
||||
if (ClassUtils.isAssignableValue(SimpleUrlHandlerMapping.class, bean)) { |
||||
SimpleUrlHandlerMapping mapping = (SimpleUrlHandlerMapping) bean; |
||||
for(Entry<String, ?> mappingEntry : mapping.getUrlMap().entrySet()) { |
||||
Object val = mappingEntry.getValue(); |
||||
if (val instanceof ResourceHttpRequestHandler) { |
||||
this.mappings.add(mapping); |
||||
} |
||||
} |
||||
} |
||||
return bean; |
||||
} |
||||
|
||||
@Override |
||||
public void onApplicationEvent(ContextRefreshedEvent event) { |
||||
OrderComparator.sort(this.mappings); |
||||
for (SimpleUrlHandlerMapping mapping : mappings) { |
||||
for(Entry<String, ?> mappingEntry : mapping.getUrlMap().entrySet()) { |
||||
Object val = mappingEntry.getValue(); |
||||
this.handlers.put(mappingEntry.getKey(), (ResourceHttpRequestHandler) val); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public String getUrlForResource(String resourcePath) { |
||||
for (Entry<String, ResourceHttpRequestHandler> mapping : this.handlers.entrySet()) { |
||||
if (matcher.match(mapping.getKey(), resourcePath)) { |
||||
ResourceHttpRequestHandler handler = mapping.getValue(); |
||||
String nestedPath = matcher.extractPathWithinPattern(mapping.getKey(), resourcePath); |
||||
String prefix = resourcePath.replace(nestedPath, ""); |
||||
List<ResourceResolver> resolvers = handler.getResourceResolvers(); |
||||
DefaultResourceResolverChain chain = new DefaultResourceResolverChain(resolvers); |
||||
String url = chain.resolveUrlPath(nestedPath, handler.getLocations()); |
||||
if (url != null) { |
||||
return prefix + url; |
||||
} |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public boolean isResourceUrl(String relativeUrl) { |
||||
for (String mapping : this.handlers.keySet()) { |
||||
if (matcher.match(mapping, relativeUrl)) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
@ -0,0 +1,154 @@
@@ -0,0 +1,154 @@
|
||||
/* |
||||
* 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.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import javax.servlet.ServletException; |
||||
import javax.servlet.http.HttpServlet; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
|
||||
import org.junit.Test; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.mock.web.test.MockFilterChain; |
||||
import org.springframework.mock.web.test.MockHttpServletRequest; |
||||
import org.springframework.mock.web.test.MockHttpServletResponse; |
||||
import org.springframework.mock.web.test.MockServletContext; |
||||
import org.springframework.web.context.WebApplicationContext; |
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; |
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; |
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; |
||||
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; |
||||
|
||||
import static org.junit.Assert.*; |
||||
|
||||
|
||||
/** |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class ResourceUrlFilterTests { |
||||
|
||||
private MockFilterChain filterChain; |
||||
|
||||
private TestServlet servlet; |
||||
|
||||
|
||||
@Test |
||||
public void rootServletMapping() throws Exception { |
||||
|
||||
initFilterChain(WebConfig.class); |
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); |
||||
request.setRequestURI("/myapp/index.html"); |
||||
request.setContextPath("/myapp"); |
||||
request.setServletPath("/index.html"); |
||||
this.filterChain.doFilter(request, new MockHttpServletResponse()); |
||||
|
||||
String actual = this.servlet.response.encodeURL("/myapp/resources/foo.css"); |
||||
assertEquals("/myapp/resources/foo-e36d2e05253c6c7085a91522ce43a0b4.css", actual); |
||||
} |
||||
|
||||
@Test |
||||
public void prefixServletMapping() throws Exception { |
||||
|
||||
initFilterChain(WebConfig.class); |
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); |
||||
request.setRequestURI("/myapp/myservlet/index.html"); |
||||
request.setContextPath("/myapp"); |
||||
request.setServletPath("/myservlet"); |
||||
this.filterChain.doFilter(request, new MockHttpServletResponse()); |
||||
|
||||
String actual = this.servlet.response.encodeURL("/myapp/myservlet/resources/foo.css"); |
||||
assertEquals("/myapp/myservlet/resources/foo-e36d2e05253c6c7085a91522ce43a0b4.css", actual); |
||||
} |
||||
|
||||
@Test |
||||
public void extensionServletMapping() throws Exception { |
||||
|
||||
initFilterChain(WebConfig.class); |
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); |
||||
request.setRequestURI("/myapp/index.html"); |
||||
request.setContextPath("/myapp"); |
||||
request.setServletPath("/index.html"); |
||||
this.filterChain.doFilter(request, new MockHttpServletResponse()); |
||||
|
||||
String actual = this.servlet.response.encodeURL("/myapp/resources/foo.css"); |
||||
assertEquals("/myapp/resources/foo-e36d2e05253c6c7085a91522ce43a0b4.css", actual); |
||||
} |
||||
|
||||
private void initFilterChain(Class<?> configClass) throws ServletException { |
||||
|
||||
MockServletContext servletContext = new MockServletContext(); |
||||
|
||||
AnnotationConfigWebApplicationContext cxt = new AnnotationConfigWebApplicationContext(); |
||||
cxt.setServletContext(servletContext); |
||||
cxt.register(configClass); |
||||
cxt.refresh(); |
||||
|
||||
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, cxt); |
||||
|
||||
ResourceUrlFilter filter = new ResourceUrlFilter(); |
||||
filter.setServletContext(servletContext); |
||||
filter.initFilterBean(); |
||||
|
||||
this.servlet = new TestServlet(); |
||||
this.filterChain = new MockFilterChain(servlet, filter); |
||||
} |
||||
|
||||
|
||||
@Configuration |
||||
static class WebConfig extends WebMvcConfigurationSupport { |
||||
|
||||
@Override |
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) { |
||||
|
||||
List<ResourceResolver> resourceResolvers = new ArrayList<>(); |
||||
resourceResolvers.add(new FingerprintResourceResolver()); |
||||
resourceResolvers.add(new PathResourceResolver()); |
||||
|
||||
registry.addResourceHandler("/resources/**") |
||||
.addResourceLocations("classpath:org/springframework/web/servlet/resource/test/") |
||||
.setResourceResolvers(resourceResolvers); |
||||
} |
||||
|
||||
@Bean |
||||
public ResourceUrlGenerator resourceUrlGenerator() { |
||||
ResourceUrlGenerator generator = new ResourceUrlGenerator(); |
||||
SimpleUrlHandlerMapping handlerMapping = (SimpleUrlHandlerMapping) resourceHandlerMapping(); |
||||
generator.setResourceHandlerMappings(Collections.singletonList(handlerMapping)); |
||||
return generator; |
||||
} |
||||
} |
||||
|
||||
private static class TestServlet extends HttpServlet { |
||||
|
||||
private HttpServletResponse response; |
||||
|
||||
@Override |
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) { |
||||
this.response = response; |
||||
} |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue