Support named dispatchers in MockServletContext

Currently the getNamedDispatcher(String) method of MockServletContext
always returns null. This poses a problem in certain testing scenarios
since one would always expect at least a default Servlet to be present.
This is specifically important for web application tests that involve
the DefaultServletHttpRequestHandler which attempts to forward to the
default Servlet after retrieving it by name. Furthermore, there is no
way to register a named RequestDispatcher with the MockServletContext.

This commit addresses these issues by introducing the following in
MockServletContext.

 - a new defaultServletName property for configuring the name of the
   default Servlet, which defaults to "default"
 - named RequestDispatchers can be registered and unregistered
 - a MockRequestDispatcher is registered for the "default" Servlet
   automatically in the constructor
 - when the defaultServletName property is set to a new value the
   the current default RequestDispatcher is unregistered and replaced
   with a MockRequestDispatcher for the new defaultServletName

Issue: SPR-9587
This commit is contained in:
Sam Brannen
2012-07-26 03:06:07 +02:00
parent 914557b975
commit 37dc211f58
16 changed files with 1688 additions and 314 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2012 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.
@@ -34,22 +34,24 @@ import org.springframework.util.Assert;
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Sam Brannen
* @since 1.0.2
*/
public class MockRequestDispatcher implements RequestDispatcher {
private final Log logger = LogFactory.getLog(getClass());
private final String url;
private final String resource;
/**
* Create a new MockRequestDispatcher for the given URL.
* @param url the URL to dispatch to.
* Create a new MockRequestDispatcher for the given resource.
* @param resource the server resource to dispatch to, located at a
* particular path or given by a particular name
*/
public MockRequestDispatcher(String url) {
Assert.notNull(url, "URL must not be null");
this.url = url;
public MockRequestDispatcher(String resource) {
Assert.notNull(resource, "resource must not be null");
this.resource = resource;
}
@@ -59,24 +61,24 @@ public class MockRequestDispatcher implements RequestDispatcher {
if (response.isCommitted()) {
throw new IllegalStateException("Cannot perform forward - response is already committed");
}
getMockHttpServletResponse(response).setForwardedUrl(this.url);
getMockHttpServletResponse(response).setForwardedUrl(this.resource);
if (logger.isDebugEnabled()) {
logger.debug("MockRequestDispatcher: forwarding to URL [" + this.url + "]");
logger.debug("MockRequestDispatcher: forwarding to [" + this.resource + "]");
}
}
public void include(ServletRequest request, ServletResponse response) {
Assert.notNull(request, "Request must not be null");
Assert.notNull(response, "Response must not be null");
getMockHttpServletResponse(response).addIncludedUrl(this.url);
getMockHttpServletResponse(response).addIncludedUrl(this.resource);
if (logger.isDebugEnabled()) {
logger.debug("MockRequestDispatcher: including URL [" + this.url + "]");
logger.debug("MockRequestDispatcher: including [" + this.resource + "]");
}
}
/**
* Obtain the underlying MockHttpServletResponse,
* unwrapping {@link HttpServletResponseWrapper} decorators if necessary.
* Obtain the underlying {@link MockHttpServletResponse}, unwrapping
* {@link HttpServletResponseWrapper} decorators if necessary.
*/
protected MockHttpServletResponse getMockHttpServletResponse(ServletResponse response) {
if (response instanceof MockHttpServletResponse) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 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.
@@ -30,10 +30,10 @@ import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.activation.FileTypeMap;
import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
import javax.servlet.FilterRegistration.Dynamic;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
@@ -50,40 +50,51 @@ import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.util.WebUtils;
/**
* Mock implementation of the {@link javax.servlet.ServletContext} interface.
*
* <p>Compatible with Servlet 2.5 and partially with Servlet 3.0 but throws
* {@link UnsupportedOperationException} for most methods introduced in Servlet
* 3.0. Can be configured to expose a specific version through
* {@link #setMajorVersion}/{@link #setMinorVersion}; default is 2.5. Note that
* Servlet 3.0 support is limited: servlet, filter and listener registration
* methods are not supported; neither is cookie or JSP configuration. We generally
* do not recommend to unit-test your ServletContainerInitializers and
* WebApplicationInitializers which is where those registration methods would be used.
*
* <p>Used for testing the Spring web framework; only rarely necessary for testing
* application controllers. As long as application components don't explicitly
* access the ServletContext, ClassPathXmlApplicationContext or
* FileSystemXmlApplicationContext can be used to load the context files for testing,
* even for DispatcherServlet context definitions.
* access the {@code ServletContext}, {@code ClassPathXmlApplicationContext} or
* {@code FileSystemXmlApplicationContext} can be used to load the context files
* for testing, even for {@code DispatcherServlet} context definitions.
*
* <p>For setting up a full WebApplicationContext in a test environment, you can
* use XmlWebApplicationContext (or GenericWebApplicationContext), passing in an
* appropriate MockServletContext instance. You might want to configure your
* MockServletContext with a FileSystemResourceLoader in that case, to make your
* resource paths interpreted as relative file system locations.
* <p>For setting up a full {@code WebApplicationContext} in a test environment,
* you can use {@code AnnotationConfigWebApplicationContext},
* {@code XmlWebApplicationContext}, or {@code GenericWebApplicationContext},
* passing in an appropriate {@code MockServletContext} instance. You might want
* to configure your {@code MockServletContext} with a {@code FileSystemResourceLoader}
* in that case to ensure that resource paths are interpreted as relative filesystem
* locations.
*
* <p>A common setup is to point your JVM working directory to the root of your
* web application directory, in combination with filesystem-based resource loading.
* This allows to load the context files as used in the web application, with
* relative paths getting interpreted correctly. Such a setup will work with both
* FileSystemXmlApplicationContext (which will load straight from the file system)
* and XmlWebApplicationContext with an underlying MockServletContext (as long as
* the MockServletContext has been configured with a FileSystemResourceLoader).
*
* Supports the Servlet 3.0 API level, but throws {@link UnsupportedOperationException}
* for all methods introduced in Servlet 3.0.
* {@code FileSystemXmlApplicationContext} (which will load straight from the
* filesystem) and {@code XmlWebApplicationContext} with an underlying
* {@code MockServletContext} (as long as the {@code MockServletContext} has been
* configured with a {@code FileSystemResourceLoader}).
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Chris Beams
* @author Sam Brannen
* @since 1.0.2
* @see #MockServletContext(org.springframework.core.io.ResourceLoader)
* @see org.springframework.web.context.support.AnnotationConfigWebApplicationContext
* @see org.springframework.web.context.support.XmlWebApplicationContext
* @see org.springframework.web.context.support.GenericWebApplicationContext
* @see org.springframework.context.support.ClassPathXmlApplicationContext
@@ -91,27 +102,41 @@ import org.springframework.web.util.WebUtils;
*/
public class MockServletContext implements ServletContext {
/** Default Servlet name used by Tomcat, Jetty, JBoss, and GlassFish: {@value}. */
private static final String COMMON_DEFAULT_SERVLET_NAME = "default";
private static final String TEMP_DIR_SYSTEM_PROPERTY = "java.io.tmpdir";
private final Log logger = LogFactory.getLog(getClass());
private final ResourceLoader resourceLoader;
private final String resourceBasePath;
private String contextPath = "";
private int minorVersion = 5;
private final Map<String, ServletContext> contexts = new HashMap<String, ServletContext>();
private final Map<String, String> initParameters = new LinkedHashMap<String, String>();
private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
private final Set<String> declaredRoles = new HashSet<String>();
private final Map<String, RequestDispatcher> namedRequestDispatchers = new HashMap<String, RequestDispatcher>();
private final ResourceLoader resourceLoader;
private final String resourceBasePath;
private String contextPath = "";
private int majorVersion = 2;
private int minorVersion = 5;
private int effectiveMajorVersion = 2;
private int effectiveMinorVersion = 5;
private String servletContextName = "MockServletContext";
private String defaultServletName = COMMON_DEFAULT_SERVLET_NAME;
/**
* Create a new MockServletContext, using no base path and a
@@ -124,7 +149,7 @@ public class MockServletContext implements ServletContext {
/**
* Create a new MockServletContext, using a DefaultResourceLoader.
* @param resourceBasePath the WAR root directory (should not end with a slash)
* @param resourceBasePath the root directory of the WAR (should not end with a slash)
* @see org.springframework.core.io.DefaultResourceLoader
*/
public MockServletContext(String resourceBasePath) {
@@ -141,9 +166,13 @@ public class MockServletContext implements ServletContext {
}
/**
* Create a new MockServletContext.
* @param resourceBasePath the WAR root directory (should not end with a slash)
* Create a new MockServletContext using the supplied resource base path and
* resource loader.
* <p>Registers a {@link MockRequestDispatcher} for the Servlet named
* {@value #COMMON_DEFAULT_SERVLET_NAME}.
* @param resourceBasePath the root directory of the WAR (should not end with a slash)
* @param resourceLoader the ResourceLoader to use (or null for the default)
* @see #registerNamedDispatcher
*/
public MockServletContext(String resourceBasePath, ResourceLoader resourceLoader) {
this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader());
@@ -154,8 +183,9 @@ public class MockServletContext implements ServletContext {
if (tempDir != null) {
this.attributes.put(WebUtils.TEMP_DIR_CONTEXT_ATTRIBUTE, new File(tempDir));
}
}
registerNamedDispatcher(this.defaultServletName, new MockRequestDispatcher(this.defaultServletName));
}
/**
* Build a full resource location for the given path,
@@ -170,7 +200,6 @@ public class MockServletContext implements ServletContext {
return this.resourceBasePath + path;
}
public void setContextPath(String contextPath) {
this.contextPath = (contextPath != null ? contextPath : "");
}
@@ -191,14 +220,15 @@ public class MockServletContext implements ServletContext {
return this.contexts.get(contextPath);
}
public void setMajorVersion(int majorVersion) {
this.majorVersion = majorVersion;
}
public int getMajorVersion() {
return 2;
return this.majorVersion;
}
public void setMinorVersion(int minorVersion) {
if (minorVersion < 3 || minorVersion > 5) {
throw new IllegalArgumentException("Only Servlet minor versions between 3 and 5 are supported");
}
this.minorVersion = minorVersion;
}
@@ -206,6 +236,22 @@ public class MockServletContext implements ServletContext {
return this.minorVersion;
}
public void setEffectiveMajorVersion(int effectiveMajorVersion) {
this.effectiveMajorVersion = effectiveMajorVersion;
}
public int getEffectiveMajorVersion() {
return this.effectiveMajorVersion;
}
public void setEffectiveMinorVersion(int effectiveMinorVersion) {
this.effectiveMinorVersion = effectiveMinorVersion;
}
public int getEffectiveMinorVersion() {
return this.effectiveMinorVersion;
}
public String getMimeType(String filePath) {
return MimeTypeResolver.getMimeType(filePath);
}
@@ -274,7 +320,60 @@ public class MockServletContext implements ServletContext {
}
public RequestDispatcher getNamedDispatcher(String path) {
return null;
return this.namedRequestDispatchers.get(path);
}
/**
* Register a {@link RequestDispatcher} (typically a {@link MockRequestDispatcher})
* that acts as a wrapper for the named Servlet.
*
* @param name the name of the wrapped Servlet
* @param requestDispatcher the dispatcher that wraps the named Servlet
* @see #getNamedDispatcher
* @see #unregisterNamedDispatcher
*/
public void registerNamedDispatcher(String name, RequestDispatcher requestDispatcher) {
Assert.notNull(name, "RequestDispatcher name must not be null");
Assert.notNull(requestDispatcher, "RequestDispatcher must not be null");
this.namedRequestDispatchers.put(name, requestDispatcher);
}
/**
* Unregister the {@link RequestDispatcher} with the given name.
*
* @param name the name of the dispatcher to unregister
* @see #getNamedDispatcher
* @see #registerNamedDispatcher
*/
public void unregisterNamedDispatcher(String name) {
Assert.notNull(name, "RequestDispatcher name must not be null");
this.namedRequestDispatchers.remove(name);
}
/**
* Get the name of the <em>default</em> {@code Servlet}.
* <p>Defaults to {@value #COMMON_DEFAULT_SERVLET_NAME}.
* @see #setDefaultServletName
*/
public String getDefaultServletName() {
return this.defaultServletName;
}
/**
* Set the name of the <em>default</em> {@code Servlet}.
* <p>Also {@link #unregisterNamedDispatcher unregisters} the current default
* {@link RequestDispatcher} and {@link #registerNamedDispatcher replaces}
* it with a {@link MockRequestDispatcher} for the provided
* {@code defaultServletName}.
* @param defaultServletName the name of the <em>default</em> {@code Servlet};
* never {@code null} or empty
* @see #getDefaultServletName
*/
public void setDefaultServletName(String defaultServletName) {
Assert.hasText(defaultServletName, "defaultServletName must not be null or empty");
unregisterNamedDispatcher(this.defaultServletName);
this.defaultServletName = defaultServletName;
registerNamedDispatcher(this.defaultServletName, new MockRequestDispatcher(this.defaultServletName));
}
public Servlet getServlet(String name) {
@@ -321,15 +420,24 @@ public class MockServletContext implements ServletContext {
return this.initParameters.get(name);
}
public Enumeration<String> getInitParameterNames() {
return Collections.enumeration(this.initParameters.keySet());
}
public boolean setInitParameter(String name, String value) {
Assert.notNull(name, "Parameter name must not be null");
if (this.initParameters.containsKey(name)) {
return false;
}
this.initParameters.put(name, value);
return true;
}
public void addInitParameter(String name, String value) {
Assert.notNull(name, "Parameter name must not be null");
this.initParameters.put(name, value);
}
public Enumeration<String> getInitParameterNames() {
return Collections.enumeration(this.initParameters.keySet());
}
public Object getAttribute(String name) {
Assert.notNull(name, "Attribute name must not be null");
return this.attributes.get(name);
@@ -362,9 +470,25 @@ public class MockServletContext implements ServletContext {
return this.servletContextName;
}
public ClassLoader getClassLoader() {
return ClassUtils.getDefaultClassLoader();
}
public void declareRoles(String... roleNames) {
Assert.notNull(roleNames, "Role names array must not be null");
for (String roleName : roleNames) {
Assert.hasLength(roleName, "Role name must not be empty");
this.declaredRoles.add(roleName);
}
}
public Set<String> getDeclaredRoles() {
return Collections.unmodifiableSet(this.declaredRoles);
}
/**
* Inner factory class used to just introduce a Java Activation Framework
* Inner factory class used to introduce a Java Activation Framework
* dependency when actually asked to resolve a MIME type.
*/
private static class MimeTypeResolver {
@@ -375,68 +499,55 @@ public class MockServletContext implements ServletContext {
}
//---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Methods introduced in Servlet 3.0
//---------------------------------------------------------------------
// ---------------------------------------------------------------------
public Dynamic addFilter(String arg0, String arg1) {
public FilterRegistration.Dynamic addFilter(String filterName, String className) {
throw new UnsupportedOperationException();
}
public Dynamic addFilter(String arg0, Filter arg1) {
public FilterRegistration.Dynamic addFilter(String filterName, Filter filter) {
throw new UnsupportedOperationException();
}
public Dynamic addFilter(String arg0, Class<? extends Filter> arg1) {
public FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass) {
throw new UnsupportedOperationException();
}
public void addListener(Class<? extends EventListener> arg0) {
public void addListener(Class<? extends EventListener> listenerClass) {
throw new UnsupportedOperationException();
}
public void addListener(String arg0) {
public void addListener(String className) {
throw new UnsupportedOperationException();
}
public <T extends EventListener> void addListener(T arg0) {
public <T extends EventListener> void addListener(T t) {
throw new UnsupportedOperationException();
}
public javax.servlet.ServletRegistration.Dynamic addServlet(String arg0, String arg1) {
public ServletRegistration.Dynamic addServlet(String servletName, String className) {
throw new UnsupportedOperationException();
}
public javax.servlet.ServletRegistration.Dynamic addServlet(String arg0,
Servlet arg1) {
public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet) {
throw new UnsupportedOperationException();
}
public javax.servlet.ServletRegistration.Dynamic addServlet(String arg0,
Class<? extends Servlet> arg1) {
public ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass) {
throw new UnsupportedOperationException();
}
public <T extends Filter> T createFilter(Class<T> arg0)
throws ServletException {
public <T extends Filter> T createFilter(Class<T> c) throws ServletException {
throw new UnsupportedOperationException();
}
public <T extends EventListener> T createListener(Class<T> arg0)
throws ServletException {
public <T extends EventListener> T createListener(Class<T> c) throws ServletException {
throw new UnsupportedOperationException();
}
public <T extends Servlet> T createServlet(Class<T> arg0)
throws ServletException {
throw new UnsupportedOperationException();
}
public void declareRoles(String... arg0) {
throw new UnsupportedOperationException();
}
public ClassLoader getClassLoader() {
public <T extends Servlet> T createServlet(Class<T> c) throws ServletException {
throw new UnsupportedOperationException();
}
@@ -444,19 +555,11 @@ public class MockServletContext implements ServletContext {
throw new UnsupportedOperationException();
}
public int getEffectiveMajorVersion() {
throw new UnsupportedOperationException();
}
public int getEffectiveMinorVersion() {
throw new UnsupportedOperationException();
}
public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() {
throw new UnsupportedOperationException();
}
public FilterRegistration getFilterRegistration(String arg0) {
public FilterRegistration getFilterRegistration(String filterName) {
throw new UnsupportedOperationException();
}
@@ -468,7 +571,7 @@ public class MockServletContext implements ServletContext {
throw new UnsupportedOperationException();
}
public ServletRegistration getServletRegistration(String arg0) {
public ServletRegistration getServletRegistration(String servletName) {
throw new UnsupportedOperationException();
}
@@ -480,12 +583,8 @@ public class MockServletContext implements ServletContext {
throw new UnsupportedOperationException();
}
public boolean setInitParameter(String arg0, String arg1) {
throw new UnsupportedOperationException();
}
public void setSessionTrackingModes(Set<SessionTrackingMode> arg0)
throws IllegalStateException, IllegalArgumentException {
public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes) throws IllegalStateException,
IllegalArgumentException {
throw new UnsupportedOperationException();
}