diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AbstractHttpRequestHandlerBeanDefinitionParser.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AbstractHttpRequestHandlerBeanDefinitionParser.java new file mode 100644 index 00000000000..7bb54395068 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AbstractHttpRequestHandlerBeanDefinitionParser.java @@ -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)); + } + } +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/DefaultServletHandlerBeanDefinitionParser.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/DefaultServletHandlerBeanDefinitionParser.java new file mode 100644 index 00000000000..7ef0d3b6a4a --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/DefaultServletHandlerBeanDefinitionParser.java @@ -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 urlMap = new ManagedMap(); + 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)); + } + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java index b85da7bf2ad..ecf70c794b9 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java @@ -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()); diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java index 8ebed5f2395..bca860f02e2 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java @@ -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); - - registerHandlerAdapterIfNecessary(parserContext, source); - BeanDefinition handlerMappingDef = registerHandlerMappingIfNecessary(parserContext, source); - - List resourcePaths = new ManagedList(); - resourcePaths.add("/"); + registerResourceMappings(parserContext, element, source); + } + + private void registerResourceMappings(ParserContext parserContext, Element element, Object source) { + String resourceHandlerName = registerResourceHandler(parserContext, element, source); + if (!StringUtils.hasText(resourceHandlerName)) { + return; + } + + Map urlMap = new ManagedMap(); + 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 locations = new ManagedList(); + 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 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 getUrlMap(BeanDefinition handlerMappingDef) { - Map urlMap; - if (handlerMappingDef.getPropertyValues().contains("urlMap")) { - urlMap = (Map) handlerMappingDef.getPropertyValues().getPropertyValue("urlMap").getValue(); - } - else { - urlMap = new ManagedMap(); - handlerMappingDef.getPropertyValues().add("urlMap", urlMap); - } - return urlMap; - } - } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resources/DefaultServletHttpRequestHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resources/DefaultServletHttpRequestHandler.java index 6e753f36f65..e6ef69101b0 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resources/DefaultServletHttpRequestHandler.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resources/DefaultServletHttpRequestHandler.java @@ -23,10 +23,10 @@ import org.springframework.web.context.ServletContextAware; * can be matched. * *

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; } /** diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resources/ResourceHttpRequestHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resources/ResourceHttpRequestHandler.java index c086b11a90c..fc65e15d5df 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resources/ResourceHttpRequestHandler.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resources/ResourceHttpRequestHandler.java @@ -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. * *

TODO - expand the docs further * @@ -54,39 +49,14 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler, ServletCo private final List resourcePaths; - private int maxAge = 31556926; - - private static final String defaultMediaTypes = "image/*,text/css,text/javascript,text/html"; - - private List allowedMediaTypes = new ArrayList(); + private final int maxAge = 31556926; private FileMediaTypeMap fileMediaTypeMap; - - private boolean gzipEnabled = true; - - private int minGzipSize = 150; - - private int maxGzipSize = 500000; public ResourceHttpRequestHandler(List resourcePaths) { - this(resourcePaths, defaultMediaTypes); - } - - public ResourceHttpRequestHandler(List resourcePaths, String allowedMediaTypes) { - this(resourcePaths, allowedMediaTypes, false); - } - - public ResourceHttpRequestHandler(List 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); } @@ -167,18 +125,9 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler, ServletCo response.setHeader("Cache-Control", "max-age=" + this.maxAge); } } - - 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; diff --git a/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.0.xsd b/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.0.xsd index 6f3df5e0d90..1c7b0c02b56 100644 --- a/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.0.xsd +++ b/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.0.xsd @@ -53,10 +53,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java index 281bbb8fbbc..45b28b6a151 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java @@ -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"); @@ -227,6 +237,76 @@ public class MvcNamespaceTests { ModelAndView mv = adapter.handle(request, response, chain.getHandler()); 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 { @@ -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; + } + } + + } } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/resources/ResourceHttpRequestHandlerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/resources/ResourceHttpRequestHandlerTests.java index 61acf1b7d98..170de3bbb2c 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/resources/ResourceHttpRequestHandlerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/resources/ResourceHttpRequestHandlerTests.java @@ -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 resourcePaths = new ArrayList(); - 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 resourcePaths = new ArrayList(); - 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) { diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-default-servlet-optional-attrs.xml b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-default-servlet-optional-attrs.xml new file mode 100644 index 00000000000..36a1cb2dc71 --- /dev/null +++ b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-default-servlet-optional-attrs.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-default-servlet.xml b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-default-servlet.xml new file mode 100644 index 00000000000..89f79e8d8d6 --- /dev/null +++ b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-default-servlet.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-resources-optional-attrs.xml b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-resources-optional-attrs.xml new file mode 100644 index 00000000000..9582c74d493 --- /dev/null +++ b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-resources-optional-attrs.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-resources.xml b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-resources.xml index aa8fdc88274..1d3dff96048 100644 --- a/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-resources.xml +++ b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-resources.xml @@ -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"> - + diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/test/test.unknown b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/test/test.unknown deleted file mode 100644 index 8df4c4a81d8..00000000000 --- a/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/test/test.unknown +++ /dev/null @@ -1 +0,0 @@ -This shouldn't be served. \ No newline at end of file