SPR-7116 - Simplified (removed gzip and white-listing) and completed XML namespace config.

This commit is contained in:
Jeremy Grelle 2010-07-31 01:09:40 +00:00
parent 061af2f25f
commit 367048c5d1
14 changed files with 355 additions and 268 deletions

View File

@ -0,0 +1,39 @@
package org.springframework.web.servlet.config;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.w3c.dom.Element;
/**
* Abstract base class for {@link BeanDefinitonParser}s that register an {@link HttpRequestHandler}.
*
* @author Jeremy Grelle
* @since 3.0.4
*/
public abstract class AbstractHttpRequestHandlerBeanDefinitionParser implements BeanDefinitionParser{
private static final String HANDLER_ADAPTER_BEAN_NAME = "org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter";
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
registerHandlerAdapterIfNecessary(parserContext, source);
doParse(element, parserContext);
return null;
}
public abstract void doParse(Element element, ParserContext parserContext);
private void registerHandlerAdapterIfNecessary(ParserContext parserContext, Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_ADAPTER_BEAN_NAME)) {
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(HttpRequestHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
}
}
}

View File

@ -0,0 +1,56 @@
package org.springframework.web.servlet.config;
import java.util.Map;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.resources.DefaultServletHttpRequestHandler;
import org.w3c.dom.Element;
/**
* {@link BeanDefinitionParser} that parses a {@code default-servlet-handler} element to
* register a {@link DefaultServletHttpRequestHandler}. Will also register a
* {@link SimpleUrlHandlerMapping} for mapping resource requests, and a
* {@link HttpRequestHandlerAdapter} if necessary.
*
* @author Jeremy Grelle
* @since 3.0.4
*/
public class DefaultServletHandlerBeanDefinitionParser extends AbstractHttpRequestHandlerBeanDefinitionParser {
@Override
public void doParse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
String defaultServletName = element.getAttribute("default-servlet-name");
RootBeanDefinition defaultServletHandlerDef = new RootBeanDefinition(DefaultServletHttpRequestHandler.class);
defaultServletHandlerDef.setSource(source);
defaultServletHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
if (StringUtils.hasText(defaultServletName)) {
defaultServletHandlerDef.getPropertyValues().add("defaultServletName", defaultServletName);
}
String defaultServletHandlerName = parserContext.getReaderContext().generateBeanName(defaultServletHandlerDef);
parserContext.getRegistry().registerBeanDefinition(defaultServletHandlerName, defaultServletHandlerDef);
parserContext.registerComponent(new BeanComponentDefinition(defaultServletHandlerDef, defaultServletHandlerName));
Map<String, String> urlMap = new ManagedMap<String, String>();
urlMap.put("/**", defaultServletHandlerName);
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
String handlerMappingBeanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
parserContext.getRegistry().registerBeanDefinition(handlerMappingBeanName, handlerMappingDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, handlerMappingBeanName));
}
}

View File

@ -23,12 +23,14 @@ import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
* {@link NamespaceHandler} for Spring MVC configuration namespace.
*
* @author Keith Donald
* @author Jeremy Grelle
* @since 3.0
*/
public class MvcNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());

View File

@ -10,6 +10,8 @@ import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.Ordered;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.resources.ResourceHttpRequestHandler;
@ -18,74 +20,66 @@ import org.w3c.dom.Element;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses a
* {@code resources} element to register a {@link ResourceHttpRequestHandler}.
* Will also register a {@link SimpleUrlHandlerMapping} for mapping resource requests, if necessary.
* Will also register a {@link HttpRequestHandlerAdapter} if necessary.
* Will also register a {@link SimpleUrlHandlerMapping} for mapping resource requests,
* and a {@link HttpRequestHandlerAdapter} if necessary.
*
* @author Keith Donald
* @author Jeremy Grelle
* @since 3.0.4
*/
public class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
public class ResourcesBeanDefinitionParser extends AbstractHttpRequestHandlerBeanDefinitionParser implements BeanDefinitionParser {
private static final String HANDLER_ADAPTER_BEAN_NAME = "org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter";
private static final String HANDLER_MAPPING_BEAN_NAME = "org.springframework.web.servlet.config.resourcesHandlerMapping";
public BeanDefinition parse(Element element, ParserContext parserContext) {
@Override
public void doParse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
registerResourceMappings(parserContext, element, source);
}
registerHandlerAdapterIfNecessary(parserContext, source);
BeanDefinition handlerMappingDef = registerHandlerMappingIfNecessary(parserContext, source);
private void registerResourceMappings(ParserContext parserContext, Element element, Object source) {
String resourceHandlerName = registerResourceHandler(parserContext, element, source);
if (!StringUtils.hasText(resourceHandlerName)) {
return;
}
List<String> resourcePaths = new ManagedList<String>();
resourcePaths.add("/");
Map<String, String> urlMap = new ManagedMap<String, String>();
String resourceRequestPath = element.getAttribute("mapping");
if (!StringUtils.hasText(resourceRequestPath)) {
parserContext.getReaderContext().error("The 'mapping' attribute is required.", parserContext.extractSource(element));
return;
}
urlMap.put(resourceRequestPath, resourceHandlerName);
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
String mappingOrder = element.getAttribute("mapping-order");
handlerMappingDef.getPropertyValues().add("order", StringUtils.hasText(mappingOrder) ? mappingOrder : Ordered.LOWEST_PRECEDENCE - 1);
String beanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
parserContext.getRegistry().registerBeanDefinition(beanName, handlerMappingDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, beanName));
}
private String registerResourceHandler(ParserContext parserContext, Element element, Object source) {
String locationAttr = element.getAttribute("location");
if (!StringUtils.hasText(locationAttr)) {
parserContext.getReaderContext().error("The 'location' attribute is required.", parserContext.extractSource(element));
return "";
}
String[] locationPatterns = locationAttr.split(",\\s*");
List<String> locations = new ManagedList<String>();
for (String location : locationPatterns) {
locations.add(location);
}
RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class);
resourceHandlerDef.setSource(source);
resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
resourceHandlerDef.getConstructorArgumentValues().addIndexedArgumentValue(0, resourcePaths);
Map<String, BeanDefinition> urlMap = getUrlMap(handlerMappingDef);
String resourceRequestPath = "/resources/**";
urlMap.put(resourceRequestPath, resourceHandlerDef);
return null;
resourceHandlerDef.getConstructorArgumentValues().addIndexedArgumentValue(0, locations);
String beanName = parserContext.getReaderContext().generateBeanName(resourceHandlerDef);
parserContext.getRegistry().registerBeanDefinition(beanName, resourceHandlerDef);
parserContext.registerComponent(new BeanComponentDefinition(resourceHandlerDef, beanName));
return beanName;
}
private void registerHandlerAdapterIfNecessary(ParserContext parserContext, Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_ADAPTER_BEAN_NAME)) {
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(HttpRequestHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
}
}
private BeanDefinition registerHandlerMappingIfNecessary(ParserContext parserContext, Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_MAPPING_BEAN_NAME)) {
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.getPropertyValues().add("order", "2");
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
return handlerMappingDef;
}
else {
return parserContext.getRegistry().getBeanDefinition(HANDLER_MAPPING_BEAN_NAME);
}
}
@SuppressWarnings("unchecked")
private Map<String, BeanDefinition> getUrlMap(BeanDefinition handlerMappingDef) {
Map<String, BeanDefinition> urlMap;
if (handlerMappingDef.getPropertyValues().contains("urlMap")) {
urlMap = (Map<String, BeanDefinition>) handlerMappingDef.getPropertyValues().getPropertyValue("urlMap").getValue();
}
else {
urlMap = new ManagedMap<String, BeanDefinition>();
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
}
return urlMap;
}
}

View File

@ -23,10 +23,10 @@ import org.springframework.web.context.ServletContextAware;
* can be matched.
*
* <p>Requests are handled by forwarding through the {@link RequestDispatcher} obtained via the name specified through the
* {@code fileServletName} property. In most cases, the {@code fileServletName} does not need to be set explicitly, as the
* {@code defaultServletName} property. In most cases, the {@code defaultServletName} does not need to be set explicitly, as the
* handler checks at initialization time for the presence of the default Servlet of one of the known containers. However, if
* running in a container where the default Servlet's name is not known, or where it has been customized via configuration, the
* {@code fileServletName} will need to be set explicitly.
* {@code defaultServletName} will need to be set explicitly.
*
* @author Jeremy Grelle
* @since 3.0.4
@ -55,40 +55,40 @@ public class DefaultServletHttpRequestHandler implements InitializingBean, HttpR
private ServletContext servletContext;
private String fileServletName;
private String defaultServletName;
/**
* If the {@code filedServletName} property has not been explicitly set, attempts to locate the default Servlet using the
* known common container-specific names.
*/
public void afterPropertiesSet() throws Exception {
if (!StringUtils.hasText(this.fileServletName)) {
if (!StringUtils.hasText(this.defaultServletName)) {
if (this.servletContext.getNamedDispatcher(COMMON_DEFAULT_SERVLET_NAME) != null) {
this.fileServletName = COMMON_DEFAULT_SERVLET_NAME;
this.defaultServletName = COMMON_DEFAULT_SERVLET_NAME;
} else if (this.servletContext.getNamedDispatcher(RESIN_DEFAULT_SERVLET_NAME) != null) {
this.fileServletName = RESIN_DEFAULT_SERVLET_NAME;
this.defaultServletName = RESIN_DEFAULT_SERVLET_NAME;
} else if (this.servletContext.getNamedDispatcher(WEBLOGIC_DEFAULT_SERVLET_NAME) != null) {
this.fileServletName = WEBLOGIC_DEFAULT_SERVLET_NAME;
this.defaultServletName = WEBLOGIC_DEFAULT_SERVLET_NAME;
} else if (this.servletContext.getNamedDispatcher(WEBSPHERE_DEFAULT_SERVLET_NAME) != null) {
this.fileServletName = WEBSPHERE_DEFAULT_SERVLET_NAME;
this.defaultServletName = WEBSPHERE_DEFAULT_SERVLET_NAME;
}
Assert.hasText(this.fileServletName, "Unable to locate the default servlet for serving static content. Please set the 'fileServletName' property explicitly.");
Assert.hasText(this.defaultServletName, "Unable to locate the default servlet for serving static content. Please set the 'defaultServletName' property explicitly.");
}
}
public void handleRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.fileServletName);
Assert.notNull(rd, "A RequestDispatcher could not be located for the servlet name '"+this.fileServletName+"'");
RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
Assert.notNull(rd, "A RequestDispatcher could not be located for the servlet name '"+this.defaultServletName+"'");
rd.forward(request, response);
}
/**
* Set the name of the default Servlet to be forwarded to for static resource requests.
* @param fileServletName The name of the Servlet to use for static resources.
* @param defaultServletName The name of the Servlet to use for static resources.
*/
public void setDefaultServletName(String fileServletName) {
this.fileServletName = fileServletName;
public void setDefaultServletName(String defaultServletName) {
this.defaultServletName = defaultServletName;
}
/**

View File

@ -1,6 +1,5 @@
package org.springframework.web.servlet.resources;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@ -8,18 +7,15 @@ import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.zip.GZIPOutputStream;
import javax.activation.FileTypeMap;
import javax.activation.MimetypesFileTypeMap;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -39,8 +35,7 @@ import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
/**
* {@link HttpRequestHandler} that serves static resources optimized for superior browser performance
* (according to the guidelines of Page Speed, YSlow, etc.) by adding far future cache expiration headers
* and gzip compressing the resources if supported by the client.
* (according to the guidelines of Page Speed, YSlow, etc.) by adding far future cache expiration headers.
*
* <p>TODO - expand the docs further
*
@ -54,39 +49,14 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler, ServletCo
private final List<Resource> resourcePaths;
private int maxAge = 31556926;
private static final String defaultMediaTypes = "image/*,text/css,text/javascript,text/html";
private List<MediaType> allowedMediaTypes = new ArrayList<MediaType>();
private final int maxAge = 31556926;
private FileMediaTypeMap fileMediaTypeMap;
private boolean gzipEnabled = true;
private int minGzipSize = 150;
private int maxGzipSize = 500000;
public ResourceHttpRequestHandler(List<Resource> resourcePaths) {
this(resourcePaths, defaultMediaTypes);
}
public ResourceHttpRequestHandler(List<Resource> resourcePaths, String allowedMediaTypes) {
this(resourcePaths, allowedMediaTypes, false);
}
public ResourceHttpRequestHandler(List<Resource> resourcePaths, String allowedMediaTypes, boolean overrideDefaultMediaTypes) {
Assert.notNull(resourcePaths, "Resource paths must not be null");
validateResourcePaths(resourcePaths);
this.resourcePaths = resourcePaths;
if (StringUtils.hasText(allowedMediaTypes)) {
this.allowedMediaTypes.addAll(MediaType.parseMediaTypes(allowedMediaTypes));
}
if (!overrideDefaultMediaTypes) {
this.allowedMediaTypes.addAll(MediaType.parseMediaTypes(defaultMediaTypes));
}
MediaType.sortBySpecificity(this.allowedMediaTypes);
}
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
@ -95,7 +65,7 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler, ServletCo
new String[] {"GET"}, "ResourceHttpRequestHandler only supports GET requests");
}
URLResource resource = getResource(request);
if (resource == null || !isResourceAllowed(resource)) {
if (resource == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
@ -106,18 +76,6 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler, ServletCo
writeResponse(resource, request, response);
}
public void setGzipEnabled(boolean gzipEnabled) {
this.gzipEnabled = gzipEnabled;
}
public void setMinGzipSize(int minGzipSize) {
this.minGzipSize = minGzipSize;
}
public void setMaxGzipSize(int maxGzipSize) {
this.maxGzipSize = maxGzipSize;
}
public void setServletContext(ServletContext servletContext) {
this.fileMediaTypeMap = new DefaultFileMediaTypeMap(servletContext);
}
@ -168,17 +126,8 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler, ServletCo
}
}
private boolean isResourceAllowed(URLResource resource) {
for(MediaType allowedType : allowedMediaTypes) {
if (allowedType.includes(resource.getMediaType())) {
return true;
}
}
return false;
}
private void writeResponse(URLResource resource, HttpServletRequest request, HttpServletResponse response) throws IOException {
OutputStream out = selectOutputStream(resource, request, response);
OutputStream out = response.getOutputStream();
try {
InputStream in = resource.getInputStream();
try {
@ -199,19 +148,6 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler, ServletCo
}
}
private OutputStream selectOutputStream(URLResource resource, HttpServletRequest request,
HttpServletResponse response) throws IOException {
String acceptEncoding = request.getHeader("Accept-Encoding");
boolean isGzipEligible = resource.getContentLength() >= this.minGzipSize && resource.getContentLength() <= this.maxGzipSize;
if (this.gzipEnabled && isGzipEligible && StringUtils.hasText(acceptEncoding)
&& acceptEncoding.indexOf("gzip") > -1
&& response.getContentType().startsWith("text/")){
return new GZIPResponseStream(response);
} else {
return response.getOutputStream();
}
}
private boolean isValidFile(Resource resource) throws IOException {
return resource.exists() && StringUtils.hasText(resource.getFilename());
}
@ -223,13 +159,11 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler, ServletCo
}
}
// TODO promote to top-level and make reusable
public interface FileMediaTypeMap {
private interface FileMediaTypeMap {
MediaType getMediaType(String fileName);
}
public static class DefaultFileMediaTypeMap implements FileMediaTypeMap {
private static class DefaultFileMediaTypeMap implements FileMediaTypeMap {
private static final boolean jafPresent =
ClassUtils.isPresent("javax.activation.FileTypeMap", ContentNegotiatingViewResolver.class.getClassLoader());
@ -314,67 +248,6 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler, ServletCo
}
}
private class GZIPResponseStream extends ServletOutputStream {
private ByteArrayOutputStream byteStream = null;
private GZIPOutputStream gzipStream = null;
private boolean closed = false;
private HttpServletResponse response = null;
private ServletOutputStream servletStream = null;
public GZIPResponseStream(HttpServletResponse response) throws IOException {
super();
closed = false;
this.response = response;
this.servletStream = response.getOutputStream();
byteStream = new ByteArrayOutputStream();
gzipStream = new GZIPOutputStream(byteStream);
}
public void close() throws IOException {
if (closed) {
throw new IOException("This output stream has already been closed");
}
gzipStream.finish();
byte[] bytes = byteStream.toByteArray();
response.setContentLength(bytes.length);
response.addHeader("Content-Encoding", "gzip");
servletStream.write(bytes);
servletStream.flush();
servletStream.close();
closed = true;
}
public void flush() throws IOException {
if (closed) {
throw new IOException("Cannot flush a closed output stream");
}
gzipStream.flush();
}
public void write(int b) throws IOException {
if (closed) {
throw new IOException("Cannot write to a closed output stream");
}
gzipStream.write((byte) b);
}
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
public void write(byte b[], int off, int len) throws IOException {
if (closed) {
throw new IOException("Cannot write to a closed output stream");
}
gzipStream.write(b, off, len);
}
}
private static class URLResource implements Resource {
private final Resource wrapped;

View File

@ -53,10 +53,64 @@
<xsd:annotation>
<xsd:documentation
source="java:org.springframework.web.servlet.resources.ResourceHttpRequestHandler"><![CDATA[
Configures support for efficiently serving static resources such as images, js, and, css files.
By default, registers a handler mapped to /resources/** capable of serving all resources located in the ${webappRoot}/resources directory.
Configures a handler for serving static resources such as images, js, and, css files with cache headers optimized for efficient
loading in a web browser. Allows resources to be served out of any path that is reachable via Spring's Resource handling.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="mapping" use="required" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The URL mapping pattern, within the current Sevlet context, to use for serving resources from this handler, such as "/resources/**"
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="location" use="required" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The resource location from which to serve static content, specified at a Spring Resource pattern. Each location must point to a valid directory.
Multiple locations may be specified as a comma-seperated list, and the locations will be checked for a given resource in the order specified. For example,
a value of "/, classpath:/META-INF/public-web-resources/" will allow resources to be served both from the web app root and from any JAR on the classpath
that contains a /META-INF/public-web-resources/ directory, with resources in the web app root taking precedence.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mapping-order" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Specifies the order of the HandlerMapping for the resource handler. The default order is Ordered.LOWEST_PRECEDENCE - 1.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="default-servlet-handler">
<xsd:annotation>
<xsd:documentation
source="java:org.springframework.web.servlet.resources.DefaultServletHttpRequestHandler"><![CDATA[
Configures a handler for serving static resources by forwarding to the Servlet container's default Servlet. Use of this
handler allows using a "/" mapping with the DispatcherServlet while still utilizing the Servlet container to serve static
resources.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="default-servlet-name" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the default Servlet to forward to for static resource requests. The handler will try to auto-detect the container's
default Servlet at startup time using a list of known names. If the default Servlet cannot be detected because of using an unknown
container or because it has been manually configured, the servlet name must be set explicitly.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="interceptors">

View File

@ -18,6 +18,8 @@ package org.springframework.web.servlet.config;
import java.util.Date;
import java.util.Locale;
import javax.servlet.RequestDispatcher;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
@ -28,6 +30,7 @@ import org.junit.Test;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.Ordered;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.io.ClassPathResource;
import org.springframework.format.annotation.DateTimeFormat;
@ -36,6 +39,7 @@ import org.springframework.format.support.FormattingConversionServiceFactoryBean
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockRequestDispatcher;
import org.springframework.mock.web.MockServletContext;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
@ -56,12 +60,14 @@ import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping;
import org.springframework.web.servlet.resources.DefaultServletHttpRequestHandler;
import org.springframework.web.servlet.resources.ResourceHttpRequestHandler;
import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
/**
* @author Keith Donald
* @author Arjen Poutsma
* @author Jeremy Grelle
*/
public class MvcNamespaceTests {
@ -70,7 +76,7 @@ public class MvcNamespaceTests {
@Before
public void setUp() {
appContext = new GenericWebApplicationContext();
appContext.setServletContext(new MockServletContext());
appContext.setServletContext(new TestMockServletContext());
LocaleContextHolder.setLocale(Locale.US);
}
@ -204,14 +210,18 @@ public class MvcNamespaceTests {
public void testResources() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
reader.loadBeanDefinitions(new ClassPathResource("mvc-config-resources.xml", getClass()));
assertEquals(2, appContext.getBeanDefinitionCount());
assertEquals(3, appContext.getBeanDefinitionCount());
appContext.refresh();
HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class);
assertNotNull(adapter);
ResourceHttpRequestHandler handler = appContext.getBean(ResourceHttpRequestHandler.class);
assertNotNull(handler);
SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(mapping);
assertEquals(Ordered.LOWEST_PRECEDENCE - 1, mapping.getOrder());
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/resources/foo.css");
@ -228,6 +238,76 @@ public class MvcNamespaceTests {
assertNull(mv);
}
@Test
public void testResourcesWithOptionalAttributes() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
reader.loadBeanDefinitions(new ClassPathResource("mvc-config-resources-optional-attrs.xml", getClass()));
assertEquals(3, appContext.getBeanDefinitionCount());
appContext.refresh();
SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(mapping);
assertEquals(5, mapping.getOrder());
}
@Test
public void testDefaultServletHandler() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
reader.loadBeanDefinitions(new ClassPathResource("mvc-config-default-servlet.xml", getClass()));
assertEquals(3, appContext.getBeanDefinitionCount());
appContext.refresh();
HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class);
assertNotNull(adapter);
DefaultServletHttpRequestHandler handler = appContext.getBean(DefaultServletHttpRequestHandler.class);
assertNotNull(handler);
SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(mapping);
assertEquals(Ordered.LOWEST_PRECEDENCE, mapping.getOrder());
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/foo.css");
request.setMethod("GET");
HandlerExecutionChain chain = mapping.getHandler(request);
assertTrue(chain.getHandler() instanceof DefaultServletHttpRequestHandler);
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndView mv = adapter.handle(request, response, chain.getHandler());
assertNull(mv);
}
@Test
public void testDefaultServletHandlerWithOptionalAtrributes() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
reader.loadBeanDefinitions(new ClassPathResource("mvc-config-default-servlet-optional-attrs.xml", getClass()));
assertEquals(3, appContext.getBeanDefinitionCount());
appContext.refresh();
HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class);
assertNotNull(adapter);
DefaultServletHttpRequestHandler handler = appContext.getBean(DefaultServletHttpRequestHandler.class);
assertNotNull(handler);
SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(mapping);
assertEquals(Ordered.LOWEST_PRECEDENCE, mapping.getOrder());
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/foo.css");
request.setMethod("GET");
HandlerExecutionChain chain = mapping.getHandler(request);
assertTrue(chain.getHandler() instanceof DefaultServletHttpRequestHandler);
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndView mv = adapter.handle(request, response, chain.getHandler());
assertNull(mv);
}
@Test
public void testBeanDecoration() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
@ -393,14 +473,29 @@ public class MvcNamespaceTests {
@NotNull
private String field;
@SuppressWarnings("unused")
public String getField() {
return field;
}
@SuppressWarnings("unused")
public void setField(String field) {
this.field = field;
}
}
private static class TestMockServletContext extends MockServletContext {
@Override
public RequestDispatcher getNamedDispatcher(String path) {
if (path.equals("default") || path.equals("custom")) {
return new MockRequestDispatcher("/");
} else {
return null;
}
}
}
}

View File

@ -61,16 +61,6 @@ public class ResourceHttpRequestHandlerTests {
assertEquals(response.getHeader("Last-Modified"), new ClassPathResource("test/foo.html", getClass()).getFile().lastModified());
}
@Test
public void getResourceWithUnknownMediaType() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/test.unknown");
request.setMethod("GET");
MockHttpServletResponse response = new MockHttpServletResponse();
handler.handleRequest(request, response);
assertEquals(404, response.getStatus());
}
@Test
public void getResourceFromAlternatePath() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
@ -193,51 +183,6 @@ public class ResourceHttpRequestHandlerTests {
handler = new ResourceHttpRequestHandler(resourcePaths);
}
@Test
public void getResourceOfAddedAllowedMimeType() throws Exception{
List<Resource> resourcePaths = new ArrayList<Resource>();
resourcePaths.add(new ClassPathResource("test/", getClass()));
handler = new ResourceHttpRequestHandler(resourcePaths, "text/plain");
handler.setServletContext(new TestServletContext());
MockHttpServletRequest request = new MockHttpServletRequest();
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/foo.txt");
request.setMethod("GET");
MockHttpServletResponse response = new MockHttpServletResponse();
handler.handleRequest(request, response);
assertEquals("text/plain", response.getContentType());
assertTrue(((Long)response.getHeader("Expires")) > System.currentTimeMillis() + (31556926 * 1000) - 10000);
assertEquals("max-age=31556926", response.getHeader("Cache-Control"));
assertTrue(response.containsHeader("Last-Modified"));
assertEquals(response.getHeader("Last-Modified"), new ClassPathResource("test/foo.txt", getClass()).getFile().lastModified());
}
@Test
public void getResourceWithDefaultMimeTypesOverriden() throws Exception{
List<Resource> resourcePaths = new ArrayList<Resource>();
resourcePaths.add(new ClassPathResource("test/", getClass()));
handler = new ResourceHttpRequestHandler(resourcePaths, "text/plain", true);
handler.setServletContext(new TestServletContext());
MockHttpServletRequest request = new MockHttpServletRequest();
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/foo.txt");
request.setMethod("GET");
MockHttpServletResponse response = new MockHttpServletResponse();
handler.handleRequest(request, response);
assertEquals("text/plain", response.getContentType());
assertTrue(((Long)response.getHeader("Expires")) > System.currentTimeMillis() + (31556926 * 1000) - 10000);
assertEquals("max-age=31556926", response.getHeader("Cache-Control"));
assertTrue(response.containsHeader("Last-Modified"));
assertEquals(response.getHeader("Last-Modified"), new ClassPathResource("test/foo.txt", getClass()).getFile().lastModified());
request = new MockHttpServletRequest();
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/foo.css");
request.setMethod("GET");
response = new MockHttpServletResponse();
handler.handleRequest(request, response);
assertEquals(404, response.getStatus());
}
private static class TestServletContext extends MockServletContext {
@Override
public String getMimeType(String filePath) {

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<mvc:default-servlet-handler default-servlet-name="custom" />
</beans>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<mvc:default-servlet-handler />
</beans>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/" mapping-order="5"/>
</beans>

View File

@ -5,6 +5,6 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<mvc:resources />
<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/" />
</beans>