Skip to content

Commit

Permalink
Support named dispatchers in MockServletContext
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sbrannen committed Jul 26, 2012
1 parent 914557b commit 37dc211
Show file tree
Hide file tree
Showing 16 changed files with 1,678 additions and 304 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -34,22 +34,24 @@
*
* @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;
}


Expand All @@ -59,24 +61,24 @@ public void forward(ServletRequest request, ServletResponse response) {
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 javax.servlet.http.HttpServletResponseWrapper} decorators if necessary.
* Obtain the underlying {@link MockHttpServletResponse}, unwrapping
* {@link HttpServletResponseWrapper} decorators if necessary.
*/
protected MockHttpServletResponse getMockHttpServletResponse(ServletResponse response) {
if (response instanceof MockHttpServletResponse) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -58,40 +58,57 @@
*
* <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).
* {@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 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
* @see org.springframework.context.support.FileSystemXmlApplicationContext
*/
public class MockServletContext implements ServletContext {

private static final String TEMP_DIR_SYSTEM_PROPERTY = "java.io.tmpdir";
/** 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 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;
Expand All @@ -106,15 +123,9 @@ public class MockServletContext implements ServletContext {

private int effectiveMinorVersion = 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 String servletContextName = "MockServletContext";

private final Set<String> declaredRoles = new HashSet<String>();
private String defaultServletName = COMMON_DEFAULT_SERVLET_NAME;


/**
Expand All @@ -128,7 +139,7 @@ public MockServletContext() {

/**
* 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) {
Expand All @@ -145,9 +156,13 @@ public MockServletContext(ResourceLoader resourceLoader) {
}

/**
* 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());
Expand All @@ -158,8 +173,9 @@ public MockServletContext(String resourceBasePath, ResourceLoader resourceLoader
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,
Expand All @@ -174,7 +190,6 @@ protected String getResourceLocation(String path) {
return this.resourceBasePath + path;
}


public void setContextPath(String contextPath) {
this.contextPath = (contextPath != null ? contextPath : "");
}
Expand Down Expand Up @@ -295,7 +310,60 @@ public RequestDispatcher getRequestDispatcher(String path) {
}

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) {
Expand Down Expand Up @@ -410,7 +478,7 @@ public Set<String> getDeclaredRoles() {


/**
* 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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -34,22 +34,24 @@
*
* @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;
}


Expand All @@ -59,24 +61,24 @@ public void forward(ServletRequest request, ServletResponse response) {
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) {
Expand Down
Loading

0 comments on commit 37dc211

Please sign in to comment.