Rename modules {org.springframework.*=>spring-*}

This renaming more intuitively expresses the relationship between
subprojects and the JAR artifacts they produce.

Tracking history across these renames is possible, but it requires
use of the --follow flag to `git log`, for example

    $ git log spring-aop/src/main/java/org/springframework/aop/Advisor.java

will show history up until the renaming event, where

    $ git log --follow spring-aop/src/main/java/org/springframework/aop/Advisor.java

will show history for all changes to the file, before and after the
renaming.

See http://chrisbeams.com/git-diff-across-renamed-directories
This commit is contained in:
Chris Beams
2012-01-20 22:51:02 +01:00
parent b6cb514d38
commit 02a4473c62
5671 changed files with 20 additions and 32 deletions

View File

@@ -0,0 +1,24 @@
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.DefaultFlashMapManager

View File

@@ -0,0 +1,186 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import java.util.HashMap;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
* A FlashMap provides a way for one request to store attributes intended for
* use in another. This is most commonly needed when redirecting from one URL
* to another -- e.g. the Post/Redirect/Get pattern. A FlashMap is saved before
* the redirect (typically in the session) and is made available after the
* redirect and removed immediately.
*
* <p>A FlashMap can be set up with a request path and request parameters to
* help identify the target request. Without this information, a FlashMap is
* made available to the next request, which may or may not be the intended
* recipient. On a redirect, the target URL is known and for example
* {@code org.springframework.web.servlet.view.RedirectView} has the
* opportunity to automatically update the current FlashMap with target
* URL information.
*
* <p>Annotated controllers will usually not use this type directly.
* See {@code org.springframework.web.servlet.mvc.support.RedirectAttributes}
* for an overview of using flash attributes in annotated controllers.
*
* @author Rossen Stoyanchev
* @since 3.1
*
* @see FlashMapManager
*/
public final class FlashMap extends HashMap<String, Object> implements Comparable<FlashMap> {
private static final long serialVersionUID = 1L;
private String targetRequestPath;
private final MultiValueMap<String, String> targetRequestParams = new LinkedMultiValueMap<String, String>();
private long expirationStartTime;
private int timeToLive;
private final int createdBy;
/**
* Create a new instance with an id uniquely identifying the creator of
* this FlashMap.
* @param createdBy identifies the FlashMapManager instance that created
* and will manage this FlashMap instance (e.g. via a hashCode)
*/
public FlashMap(int createdBy) {
this.createdBy = createdBy;
}
/**
* Create a new instance.
*/
public FlashMap() {
this.createdBy = 0;
}
/**
* Provide a URL path to help identify the target request for this FlashMap.
* The path may be absolute (e.g. /application/resource) or relative to the
* current request (e.g. ../resource).
* @param path the URI path
*/
public void setTargetRequestPath(String path) {
this.targetRequestPath = path;
}
/**
* Return the target URL path or {@code null}.
*/
public String getTargetRequestPath() {
return targetRequestPath;
}
/**
* Provide request parameters identifying the request for this FlashMap.
* Null or empty keys and values are skipped.
* @param params a Map with the names and values of expected parameters.
*/
public FlashMap addTargetRequestParams(MultiValueMap<String, String> params) {
if (params != null) {
for (String key : params.keySet()) {
for (String value : params.get(key)) {
addTargetRequestParam(key, value);
}
}
}
return this;
}
/**
* Provide a request parameter identifying the request for this FlashMap.
* @param name the expected parameter name, skipped if {@code null}
* @param value the expected parameter value, skipped if {@code null}
*/
public FlashMap addTargetRequestParam(String name, String value) {
if (StringUtils.hasText(name) && StringUtils.hasText(value)) {
this.targetRequestParams.add(name, value);
}
return this;
}
/**
* Return the parameters identifying the target request, or an empty map.
*/
public MultiValueMap<String, String> getTargetRequestParams() {
return targetRequestParams;
}
/**
* Start the expiration period for this instance.
* @param timeToLive the number of seconds before expiration
*/
public void startExpirationPeriod(int timeToLive) {
this.expirationStartTime = System.currentTimeMillis();
this.timeToLive = timeToLive;
}
/**
* Whether this instance has expired depending on the amount of elapsed
* time since the call to {@link #startExpirationPeriod}.
*/
public boolean isExpired() {
if (this.expirationStartTime != 0) {
return (System.currentTimeMillis() - this.expirationStartTime) > this.timeToLive * 1000;
}
else {
return false;
}
}
/**
* Whether the given id matches the id of the creator of this FlashMap.
*/
public boolean isCreatedBy(int createdBy) {
return this.createdBy == createdBy;
}
/**
* Compare two FlashMaps and prefer the one that specifies a target URL
* path or has more target URL parameters. Before comparing FlashMap
* instances ensure that they match a given request.
*/
public int compareTo(FlashMap other) {
int thisUrlPath = (this.targetRequestPath != null) ? 1 : 0;
int otherUrlPath = (other.targetRequestPath != null) ? 1 : 0;
if (thisUrlPath != otherUrlPath) {
return otherUrlPath - thisUrlPath;
}
else {
return other.targetRequestParams.size() - this.targetRequestParams.size();
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[Attributes=").append(super.toString());
sb.append(", targetRequestPath=").append(this.targetRequestPath);
sb.append(", targetRequestParams=").append(this.targetRequestParams).append("]");
return sb.toString();
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* A strategy interface for storing, retrieving, and managing {@code FlashMap}
* instances. See {@link FlashMap} for a general overview of flash attributes.
*
* <p>A FlashMapManager is invoked at the beginning and at the end of requests.
* For each request it retrieves an "input" FlashMap with attributes passed
* from a previous request (if any) and creates an "output" FlashMap with
* attributes to pass to a subsequent request. "Input" and "output" FlashMap
* instances are exposed as request attributes and are accessible via methods
* in {@code org.springframework.web.servlet.support.RequestContextUtils}.
*
* <p>Annotated controllers will usually not use this FlashMap directly.
* See {@code org.springframework.web.servlet.mvc.support.RedirectAttributes}.
*
* @author Rossen Stoyanchev
* @since 3.1
*
* @see FlashMap
*/
public interface FlashMapManager {
/**
* Name of request attribute that holds a read-only
* {@code Map<String, Object>} with "input" flash attributes if any.
* @see org.springframework.web.servlet.support.RequestContextUtils#getInputFlashMap(HttpServletRequest)
*/
String INPUT_FLASH_MAP_ATTRIBUTE = FlashMapManager.class.getName() + ".INPUT_FLASH_MAP";
/**
* Name of request attribute that holds the "output" {@link FlashMap} with
* attributes to save for a subsequent request.
* @see org.springframework.web.servlet.support.RequestContextUtils#getOutputFlashMap(HttpServletRequest)
*/
String OUTPUT_FLASH_MAP_ATTRIBUTE = FlashMapManager.class.getName() + ".OUTPUT_FLASH_MAP";
/**
* Perform the following tasks unless the {@link #OUTPUT_FLASH_MAP_ATTRIBUTE}
* request attribute exists:
* <ol>
* <li>Find the "input" FlashMap, expose it under the request attribute
* {@link #INPUT_FLASH_MAP_ATTRIBUTE}, and remove it from underlying storage.
* <li>Create the "output" FlashMap and expose it under the request
* attribute {@link #OUTPUT_FLASH_MAP_ATTRIBUTE}.
* <li>Clean expired FlashMap instances.
* </ol>
* @param request the current request
* @param response the current response
*/
void requestStarted(HttpServletRequest request, HttpServletResponse response);
/**
* Start the expiration period of the "output" FlashMap save it in the
* underlying storage.
* <p>The "output" FlashMap should not be saved if it is empty or if it was
* not created by the current FlashMapManager instance.
* @param request the current request
* @param response the current response
*/
void requestCompleted(HttpServletRequest request, HttpServletResponse response);
}

View File

@@ -0,0 +1,993 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import java.io.IOException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SourceFilteringListener;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.i18n.SimpleLocaleContext;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.ServletRequestHandledEvent;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.util.NestedServletException;
import org.springframework.web.util.WebUtils;
/**
* Base servlet for Spring's web framework. Provides integration with
* a Spring application context, in a JavaBean-based overall solution.
*
* <p>This class offers the following functionality:
* <ul>
* <li>Manages a {@link org.springframework.web.context.WebApplicationContext
* WebApplicationContext} instance per servlet. The servlet's configuration is determined
* by beans in the servlet's namespace.
* <li>Publishes events on request processing, whether or not a request is
* successfully handled.
* </ul>
*
* <p>Subclasses must implement {@link #doService} to handle requests. Because this extends
* {@link HttpServletBean} rather than HttpServlet directly, bean properties are
* automatically mapped onto it. Subclasses can override {@link #initFrameworkServlet()}
* for custom initialization.
*
* <p>Detects a "contextClass" parameter at the servlet init-param level,
* falling back to the default context class,
* {@link org.springframework.web.context.support.XmlWebApplicationContext
* XmlWebApplicationContext}, if not found. Note that, with the default
* {@code FrameworkServlet}, a custom context class needs to implement the
* {@link org.springframework.web.context.ConfigurableWebApplicationContext
* ConfigurableWebApplicationContext} SPI.
*
* <p>Accepts an optional "contextInitializerClasses" servlet init-param that
* specifies one or more {@link org.springframework.context.ApplicationContextInitializer
* ApplicationContextInitializer} classes. The managed web application context will be
* delegated to these initializers, allowing for additional programmatic configuration,
* e.g. adding property sources or activating profiles against the {@linkplain
* org.springframework.context.ConfigurableApplicationContext#getEnvironment() context's
* environment}. See also {@link org.springframework.web.context.ContextLoader} which
* supports a "contextInitializerClasses" context-param with identical semantics for
* the "root" web application context.
*
* <p>Passes a "contextConfigLocation" servlet init-param to the context instance,
* parsing it into potentially multiple file paths which can be separated by any
* number of commas and spaces, like "test-servlet.xml, myServlet.xml".
* If not explicitly specified, the context implementation is supposed to build a
* default location from the namespace of the servlet.
*
* <p>Note: In case of multiple config locations, later bean definitions will
* override ones defined in earlier loaded files, at least when using Spring's
* default ApplicationContext implementation. This can be leveraged to
* deliberately override certain bean definitions via an extra XML file.
*
* <p>The default namespace is "'servlet-name'-servlet", e.g. "test-servlet" for a
* servlet-name "test" (leading to a "/WEB-INF/test-servlet.xml" default location
* with XmlWebApplicationContext). The namespace can also be set explicitly via
* the "namespace" servlet init-param.
*
* <p>As of Spring 3.1, {@code FrameworkServlet} may now be injected with a web
* application context, rather than creating its own internally. This is useful in Servlet
* 3.0+ environments, which support programmatic registration of servlet instances. See
* {@link #FrameworkServlet(WebApplicationContext)} Javadoc for details.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Sam Brannen
* @author Chris Beams
* @see #doService
* @see #setContextClass
* @see #setContextConfigLocation
* @see #setContextInitializerClasses
* @see #setNamespace
*/
@SuppressWarnings("serial")
public abstract class FrameworkServlet extends HttpServletBean {
/**
* Suffix for WebApplicationContext namespaces. If a servlet of this class is
* given the name "test" in a context, the namespace used by the servlet will
* resolve to "test-servlet".
*/
public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";
/**
* Default context class for FrameworkServlet.
* @see org.springframework.web.context.support.XmlWebApplicationContext
*/
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
/**
* Prefix for the ServletContext attribute for the WebApplicationContext.
* The completion is the servlet name.
*/
public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
/**
* Any number of these characters are considered delimiters between
* multiple values in a single init-param String value.
*/
private static final String INIT_PARAM_DELIMITERS = ",; \t\n";
/** ServletContext attribute to find the WebApplicationContext in */
private String contextAttribute;
/** WebApplicationContext implementation class to create */
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
/** WebApplicationContext id to assign */
private String contextId;
/** Namespace for this servlet */
private String namespace;
/** Explicit context config location */
private String contextConfigLocation;
/** Should we publish the context as a ServletContext attribute? */
private boolean publishContext = true;
/** Should we publish a ServletRequestHandledEvent at the end of each request? */
private boolean publishEvents = true;
/** Expose LocaleContext and RequestAttributes as inheritable for child threads? */
private boolean threadContextInheritable = false;
/** Should we dispatch an HTTP OPTIONS request to {@link #doService}? */
private boolean dispatchOptionsRequest = false;
/** Should we dispatch an HTTP TRACE request to {@link #doService}? */
private boolean dispatchTraceRequest = false;
/** WebApplicationContext for this servlet */
private WebApplicationContext webApplicationContext;
/** Flag used to detect whether onRefresh has already been called */
private boolean refreshEventReceived = false;
/** Comma-delimited ApplicationContextInitializer classnames set through init param */
private String contextInitializerClasses;
/** Actual ApplicationContextInitializer instances to apply to the context */
private ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers =
new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();
/**
* Create a new {@code FrameworkServlet} that will create its own internal web
* application context based on defaults and values provided through servlet
* init-params. Typically used in Servlet 2.5 or earlier environments, where the only
* option for servlet registration is through {@code web.xml} which requires the use
* of a no-arg constructor.
* <p>Calling {@link #setContextConfigLocation} (init-param 'contextConfigLocation')
* will dictate which XML files will be loaded by the
* {@linkplain #DEFAULT_CONTEXT_CLASS default XmlWebApplicationContext}
* <p>Calling {@link #setContextClass} (init-param 'contextClass') overrides the
* default {@code XmlWebApplicationContext} and allows for specifying an alternative class,
* such as {@code AnnotationConfigWebApplicationContext}.
* <p>Calling {@link #setContextInitializerClasses} (init-param 'contextInitializerClasses')
* indicates which {@link ApplicationContextInitializer} classes should be used to
* further configure the internal application context prior to refresh().
* @see #FrameworkServlet(WebApplicationContext)
*/
public FrameworkServlet() {
}
/**
* Create a new {@code FrameworkServlet} with the given web application context. This
* constructor is useful in Servlet 3.0+ environments where instance-based registration
* of servlets is possible through the {@link ServletContext#addServlet} API.
* <p>Using this constructor indicates that the following properties / init-params
* will be ignored:
* <ul>
* <li>{@link #setContextClass(Class)} / 'contextClass'</li>
* <li>{@link #setContextConfigLocation(String)} / 'contextConfigLocation'</li>
* <li>{@link #setContextAttribute(String)} / 'contextAttribute'</li>
* <li>{@link #setNamespace(String)} / 'namespace'</li>
* </ul>
* <p>The given web application context may or may not yet be {@linkplain
* ConfigurableApplicationContext#refresh() refreshed}. If it (a) is an implementation
* of {@link ConfigurableWebApplicationContext} and (b) has <strong>not</strong>
* already been refreshed (the recommended approach), then the following will occur:
* <ul>
* <li>If the given context does not already have a {@linkplain
* ConfigurableApplicationContext#setParent parent}, the root application context
* will be set as the parent.</li>
* <li>If the given context has not already been assigned an {@linkplain
* ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
* <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
* the application context</li>
* <li>{@link #postProcessWebApplicationContext} will be called</li>
* <li>Any {@link ApplicationContextInitializer}s specified through the
* "contextInitializerClasses" init-param or through the {@link
* #setContextInitializers} property will be applied.</li>
* <li>{@link ConfigurableApplicationContext#refresh refresh()} will be called</li>
* </ul>
* If the context has already been refreshed or does not implement
* {@code ConfigurableWebApplicationContext}, none of the above will occur under the
* assumption that the user has performed these actions (or not) per his or her
* specific needs.
* <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
* @param webApplicationContext the context to use
* @see #initWebApplicationContext
* @see #configureAndRefreshWebApplicationContext
* @see org.springframework.web.WebApplicationInitializer
*/
public FrameworkServlet(WebApplicationContext webApplicationContext) {
this.webApplicationContext = webApplicationContext;
}
/**
* Set the name of the ServletContext attribute which should be used to retrieve the
* {@link WebApplicationContext} that this servlet is supposed to use.
*/
public void setContextAttribute(String contextAttribute) {
this.contextAttribute = contextAttribute;
}
/**
* Return the name of the ServletContext attribute which should be used to retrieve the
* {@link WebApplicationContext} that this servlet is supposed to use.
*/
public String getContextAttribute() {
return this.contextAttribute;
}
/**
* Set a custom context class. This class must be of type
* {@link org.springframework.web.context.WebApplicationContext}.
* <p>When using the default FrameworkServlet implementation,
* the context class must also implement the
* {@link org.springframework.web.context.ConfigurableWebApplicationContext}
* interface.
* @see #createWebApplicationContext
*/
public void setContextClass(Class<?> contextClass) {
this.contextClass = contextClass;
}
/**
* Return the custom context class.
*/
public Class<?> getContextClass() {
return this.contextClass;
}
/**
* Specify a custom WebApplicationContext id,
* to be used as serialization id for the underlying BeanFactory.
*/
public void setContextId(String contextId) {
this.contextId = contextId;
}
/**
* Return the custom WebApplicationContext id, if any.
*/
public String getContextId() {
return this.contextId;
}
/**
* Set a custom namespace for this servlet,
* to be used for building a default context config location.
*/
public void setNamespace(String namespace) {
this.namespace = namespace;
}
/**
* Return the namespace for this servlet, falling back to default scheme if
* no custom namespace was set: e.g. "test-servlet" for a servlet named "test".
*/
public String getNamespace() {
return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX);
}
/**
* Specify the set of fully-qualified {@link ApplicationContextInitializer} class
* names, per the optional "contextInitializerClasses" servlet init-param.
* @see #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext)
* @see #applyInitializers(ConfigurableWebApplicationContext)
*/
public void setContextInitializerClasses(String contextInitializerClasses) {
this.contextInitializerClasses = contextInitializerClasses;
}
/**
* Specify which {@link ApplicationContextInitializer} instances should be used
* to initialize the application context used by this {@code FrameworkServlet}.
* @see #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext)
* @see #applyInitializers(ConfigurableWebApplicationContext)
*/
public void setContextInitializers(ApplicationContextInitializer<ConfigurableApplicationContext>... contextInitializers) {
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : contextInitializers) {
this.contextInitializers.add(initializer);
}
}
/**
* Set the context config location explicitly, instead of relying on the default
* location built from the namespace. This location string can consist of
* multiple locations separated by any number of commas and spaces.
*/
public void setContextConfigLocation(String contextConfigLocation) {
this.contextConfigLocation = contextConfigLocation;
}
/**
* Return the explicit context config location, if any.
*/
public String getContextConfigLocation() {
return this.contextConfigLocation;
}
/**
* Set whether to publish this servlet's context as a ServletContext attribute,
* available to all objects in the web container. Default is "true".
* <p>This is especially handy during testing, although it is debatable whether
* it's good practice to let other application objects access the context this way.
*/
public void setPublishContext(boolean publishContext) {
this.publishContext = publishContext;
}
/**
* Set whether this servlet should publish a ServletRequestHandledEvent at the end
* of each request. Default is "true"; can be turned off for a slight performance
* improvement, provided that no ApplicationListeners rely on such events.
* @see org.springframework.web.context.support.ServletRequestHandledEvent
*/
public void setPublishEvents(boolean publishEvents) {
this.publishEvents = publishEvents;
}
/**
* Set whether to expose the LocaleContext and RequestAttributes as inheritable
* for child threads (using an {@link java.lang.InheritableThreadLocal}).
* <p>Default is "false", to avoid side effects on spawned background threads.
* Switch this to "true" to enable inheritance for custom child threads which
* are spawned during request processing and only used for this request
* (that is, ending after their initial task, without reuse of the thread).
* <p><b>WARNING:</b> Do not use inheritance for child threads if you are
* accessing a thread pool which is configured to potentially add new threads
* on demand (e.g. a JDK {@link java.util.concurrent.ThreadPoolExecutor}),
* since this will expose the inherited context to such a pooled thread.
*/
public void setThreadContextInheritable(boolean threadContextInheritable) {
this.threadContextInheritable = threadContextInheritable;
}
/**
* Set whether this servlet should dispatch an HTTP OPTIONS request to
* the {@link #doService} method.
* <p>Default is "false", applying {@link javax.servlet.http.HttpServlet}'s
* default behavior (i.e. enumerating all standard HTTP request methods
* as a response to the OPTIONS request).
* <p>Turn this flag on if you prefer OPTIONS requests to go through the
* regular dispatching chain, just like other HTTP requests. This usually
* means that your controllers will receive those requests; make sure
* that those endpoints are actually able to handle an OPTIONS request.
* <p>Note that HttpServlet's default OPTIONS processing will be applied
* in any case if your controllers happen to not set the 'Allow' header
* (as required for an OPTIONS response).
*/
public void setDispatchOptionsRequest(boolean dispatchOptionsRequest) {
this.dispatchOptionsRequest = dispatchOptionsRequest;
}
/**
* Set whether this servlet should dispatch an HTTP TRACE request to
* the {@link #doService} method.
* <p>Default is "false", applying {@link javax.servlet.http.HttpServlet}'s
* default behavior (i.e. reflecting the message received back to the client).
* <p>Turn this flag on if you prefer TRACE requests to go through the
* regular dispatching chain, just like other HTTP requests. This usually
* means that your controllers will receive those requests; make sure
* that those endpoints are actually able to handle a TRACE request.
* <p>Note that HttpServlet's default TRACE processing will be applied
* in any case if your controllers happen to not generate a response
* of content type 'message/http' (as required for a TRACE response).
*/
public void setDispatchTraceRequest(boolean dispatchTraceRequest) {
this.dispatchTraceRequest = dispatchTraceRequest;
}
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
/**
* Initialize and publish the WebApplicationContext for this servlet.
* <p>Delegates to {@link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
/**
* Retrieve a <code>WebApplicationContext</code> from the <code>ServletContext</code>
* attribute with the {@link #setContextAttribute configured name}. The
* <code>WebApplicationContext</code> must have already been loaded and stored in the
* <code>ServletContext</code> before this servlet gets initialized (or invoked).
* <p>Subclasses may override this method to provide a different
* <code>WebApplicationContext</code> retrieval strategy.
* @return the WebApplicationContext for this servlet, or <code>null</code> if not found
* @see #getContextAttribute()
*/
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
if (attrName == null) {
return null;
}
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
/**
* Instantiate the WebApplicationContext for this servlet, either a default
* {@link org.springframework.web.context.support.XmlWebApplicationContext}
* or a {@link #setContextClass custom context class}, if set.
* <p>This implementation expects custom contexts to implement the
* {@link org.springframework.web.context.ConfigurableWebApplicationContext}
* interface. Can be overridden in subclasses.
* <p>Do not forget to register this servlet instance as application listener on the
* created context (for triggering its {@link #onRefresh callback}, and to call
* {@link org.springframework.context.ConfigurableApplicationContext#refresh()}
* before returning the context instance.
* @param parent the parent ApplicationContext to use, or <code>null</code> if none
* @return the WebApplicationContext for this servlet
* @see org.springframework.web.context.support.XmlWebApplicationContext
*/
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
configureAndRefreshWebApplicationContext(wac);
return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
ServletContext sc = getServletContext();
if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
// Servlet <= 2.4: resort to name specified in web.xml, if any.
String servletContextName = sc.getServletContextName();
if (servletContextName != null) {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName +
"." + getServletName());
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName());
}
}
else {
// Servlet 2.5's getContextPath available!
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()) + "/" + getServletName());
}
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
/**
* Instantiate the WebApplicationContext for this servlet, either a default
* {@link org.springframework.web.context.support.XmlWebApplicationContext}
* or a {@link #setContextClass custom context class}, if set.
* Delegates to #createWebApplicationContext(ApplicationContext).
* @param parent the parent WebApplicationContext to use, or <code>null</code> if none
* @return the WebApplicationContext for this servlet
* @see org.springframework.web.context.support.XmlWebApplicationContext
* @see #createWebApplicationContext(ApplicationContext)
*/
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
/**
* Delegate the WebApplicationContext before it is refreshed to any
* {@link ApplicationContextInitializer} instances specified by the
* "contextInitializerClasses" servlet init-param.
* <p>See also {@link #postProcessWebApplicationContext}, which is designed to allow
* subclasses (as opposed to end-users) to modify the application context, and is
* called immediately after this method.
* @param wac the configured WebApplicationContext (not refreshed yet)
* @see #createWebApplicationContext
* @see #postProcessWebApplicationContext
* @see ConfigurableApplicationContext#refresh()
*/
@SuppressWarnings("unchecked")
protected void applyInitializers(ConfigurableApplicationContext wac) {
if (this.contextInitializerClasses != null) {
String[] initializerClassNames =
StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS);
for (String initializerClassName : initializerClassNames) {
ApplicationContextInitializer<ConfigurableApplicationContext> initializer;
try {
Class<?> initializerClass = ClassUtils.forName(initializerClassName, wac.getClassLoader());
initializer = BeanUtils.instantiateClass(initializerClass, ApplicationContextInitializer.class);
}
catch (Exception ex) {
throw new IllegalArgumentException(
String.format("Could not instantiate class [%s] specified via " +
"'contextInitializerClasses' init-param", initializerClassName), ex);
}
this.contextInitializers.add(initializer);
}
}
Collections.sort(this.contextInitializers, new AnnotationAwareOrderComparator());
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
initializer.initialize(wac);
}
}
/**
* Post-process the given WebApplicationContext before it is refreshed
* and activated as context for this servlet.
* <p>The default implementation is empty. <code>refresh()</code> will
* be called automatically after this method returns.
* <p>Note that this method is designed to allow subclasses to modify the application
* context, while {@link #initializeWebApplicationContext} is designed to allow
* end-users to modify the context through the use of
* {@link ApplicationContextInitializer}s.
* @param wac the configured WebApplicationContext (not refreshed yet)
* @see #createWebApplicationContext
* @see #initializeWebApplicationContext
* @see ConfigurableWebApplicationContext#refresh()
*/
protected void postProcessWebApplicationContext(ConfigurableWebApplicationContext wac) {
}
/**
* Return the ServletContext attribute name for this servlet's WebApplicationContext.
* <p>The default implementation returns
* <code>SERVLET_CONTEXT_PREFIX + servlet name</code>.
* @see #SERVLET_CONTEXT_PREFIX
* @see #getServletName
*/
public String getServletContextAttributeName() {
return SERVLET_CONTEXT_PREFIX + getServletName();
}
/**
* Return this servlet's WebApplicationContext.
*/
public final WebApplicationContext getWebApplicationContext() {
return this.webApplicationContext;
}
/**
* This method will be invoked after any bean properties have been set and
* the WebApplicationContext has been loaded. The default implementation is empty;
* subclasses may override this method to perform any initialization they require.
* @throws ServletException in case of an initialization exception
*/
protected void initFrameworkServlet() throws ServletException {
}
/**
* Refresh this servlet's application context, as well as the
* dependent state of the servlet.
* @see #getWebApplicationContext()
* @see org.springframework.context.ConfigurableApplicationContext#refresh()
*/
public void refresh() {
WebApplicationContext wac = getWebApplicationContext();
if (!(wac instanceof ConfigurableApplicationContext)) {
throw new IllegalStateException("WebApplicationContext does not support refresh: " + wac);
}
((ConfigurableApplicationContext) wac).refresh();
}
/**
* Callback that receives refresh events from this servlet's WebApplicationContext.
* <p>The default implementation calls {@link #onRefresh},
* triggering a refresh of this servlet's context-dependent state.
* @param event the incoming ApplicationContext event
*/
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
onRefresh(event.getApplicationContext());
}
/**
* Template method which can be overridden to add servlet-specific refresh work.
* Called after successful context refresh.
* <p>This implementation is empty.
* @param context the current WebApplicationContext
* @see #refresh()
*/
protected void onRefresh(ApplicationContext context) {
// For subclasses: do nothing by default.
}
/**
* Delegate GET requests to processRequest/doService.
* <p>Will also be invoked by HttpServlet's default implementation of <code>doHead</code>,
* with a <code>NoBodyResponse</code> that just captures the content length.
* @see #doService
* @see #doHead
*/
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Delegate POST requests to {@link #processRequest}.
* @see #doService
*/
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Delegate PUT requests to {@link #processRequest}.
* @see #doService
*/
@Override
protected final void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Delegate DELETE requests to {@link #processRequest}.
* @see #doService
*/
@Override
protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Delegate OPTIONS requests to {@link #processRequest}, if desired.
* <p>Applies HttpServlet's standard OPTIONS processing otherwise,
* and also if there is still no 'Allow' header set after dispatching.
* @see #doService
*/
@Override
protected void doOptions(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (this.dispatchOptionsRequest) {
processRequest(request, response);
if (response.containsHeader("Allow")) {
// Proper OPTIONS response coming from a handler - we're done.
return;
}
}
super.doOptions(request, response);
}
/**
* Delegate TRACE requests to {@link #processRequest}, if desired.
* <p>Applies HttpServlet's standard TRACE processing otherwise.
* @see #doService
*/
@Override
protected void doTrace(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (this.dispatchTraceRequest) {
processRequest(request, response);
if ("message/http".equals(response.getContentType())) {
// Proper TRACE response coming from a handler - we're done.
return;
}
}
super.doTrace(request, response);
}
/**
* Process this request, publishing an event regardless of the outcome.
* <p>The actual event handling is performed by the abstract
* {@link #doService} template method.
*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
// Expose current LocaleResolver and request as LocaleContext.
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);
// Expose current RequestAttributes to current thread.
RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = null;
if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) {
requestAttributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
}
if (logger.isTraceEnabled()) {
logger.trace("Bound request context to thread: " + request);
}
try {
doService(request, response);
}
catch (ServletException ex) {
failureCause = ex;
throw ex;
}
catch (IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
// Clear request attributes and reset thread-bound context.
LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
requestAttributes.requestCompleted();
}
if (logger.isTraceEnabled()) {
logger.trace("Cleared thread-bound request context: " + request);
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
this.logger.debug("Successfully completed request");
}
}
if (this.publishEvents) {
// Whether or not we succeeded, publish an event.
long processingTime = System.currentTimeMillis() - startTime;
this.webApplicationContext.publishEvent(
new ServletRequestHandledEvent(this,
request.getRequestURI(), request.getRemoteAddr(),
request.getMethod(), getServletConfig().getServletName(),
WebUtils.getSessionId(request), getUsernameForRequest(request),
processingTime, failureCause));
}
}
}
/**
* Build a LocaleContext for the given request, exposing the request's
* primary locale as current locale.
* @param request current HTTP request
* @return the corresponding LocaleContext
*/
protected LocaleContext buildLocaleContext(HttpServletRequest request) {
return new SimpleLocaleContext(request.getLocale());
}
/**
* Determine the username for the given request.
* <p>The default implementation takes the name of the UserPrincipal, if any.
* Can be overridden in subclasses.
* @param request current HTTP request
* @return the username, or <code>null</code> if none found
* @see javax.servlet.http.HttpServletRequest#getUserPrincipal()
*/
protected String getUsernameForRequest(HttpServletRequest request) {
Principal userPrincipal = request.getUserPrincipal();
return (userPrincipal != null ? userPrincipal.getName() : null);
}
/**
* Subclasses must implement this method to do the work of request handling,
* receiving a centralized callback for GET, POST, PUT and DELETE.
* <p>The contract is essentially the same as that for the commonly overridden
* <code>doGet</code> or <code>doPost</code> methods of HttpServlet.
* <p>This class intercepts calls to ensure that exception handling and
* event publication takes place.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
* @see javax.servlet.http.HttpServlet#doGet
* @see javax.servlet.http.HttpServlet#doPost
*/
protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
throws Exception;
/**
* Close the WebApplicationContext of this servlet.
* @see org.springframework.context.ConfigurableApplicationContext#close()
*/
@Override
public void destroy() {
getServletContext().log("Destroying Spring FrameworkServlet '" + getServletName() + "'");
if (this.webApplicationContext instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) this.webApplicationContext).close();
}
}
/**
* ApplicationListener endpoint that receives events from this servlet's WebApplicationContext
* only, delegating to <code>onApplicationEvent</code> on the FrameworkServlet instance.
*/
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* MVC framework SPI interface, allowing parameterization of core MVC workflow.
*
* <p>Interface that must be implemented for each handler type to handle a request.
* This interface is used to allow the {@link DispatcherServlet} to be indefinitely
* extensible. The DispatcherServlet accesses all installed handlers through this
* interface, meaning that it does not contain code specific to any handler type.
*
* <p>Note that a handler can be of type <code>Object</code>. This is to enable
* handlers from other frameworks to be integrated with this framework without
* custom coding, as well as to allow for annotation handler objects that do
* not obey any specific Java interface.
*
* <p>This interface is not intended for application developers. It is available
* to handlers who want to develop their own web workflow.
*
* <p>Note: HandlerAdaptger implementators may implement the
* {@link org.springframework.core.Ordered} interface to be able to specify a
* sorting order (and thus a priority) for getting applied by DispatcherServlet.
* Non-Ordered instances get treated as lowest priority.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter
* @see org.springframework.web.servlet.handler.SimpleServletHandlerAdapter
*/
public interface HandlerAdapter {
/**
* Given a handler instance, return whether or not this HandlerAdapter can
* support it. Typical HandlerAdapters will base the decision on the handler
* type. HandlerAdapters will usually only support one handler type each.
* <p>A typical implementation:
* <p><code>
* return (handler instanceof MyHandler);
* </code>
* @param handler handler object to check
* @return whether or not this object can use the given handler
*/
boolean supports(Object handler);
/**
* Use the given handler to handle this request.
* The workflow that is required may vary widely.
* @param request current HTTP request
* @param response current HTTP response
* @param handler handler to use. This object must have previously been passed
* to the <code>supports</code> method of this interface, which must have
* returned <code>true</code>.
* @throws Exception in case of errors
* @return ModelAndView object with the name of the view and the required
* model data, or <code>null</code> if the request has been handled directly
*/
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
/**
* Same contract as for HttpServlet's <code>getLastModified</code> method.
* Can simply return -1 if there's no support in the handler class.
* @param request current HTTP request
* @param handler handler to use
* @return the lastModified value for the given handler
* @see javax.servlet.http.HttpServlet#getLastModified
* @see org.springframework.web.servlet.mvc.LastModified#getLastModified
*/
long getLastModified(HttpServletRequest request, Object handler);
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Interface to be implemented by objects than can resolve exceptions thrown
* during handler mapping or execution, in the typical case to error views.
* Implementors are typically registered as beans in the application context.
*
* <p>Error views are analogous to the error page JSPs, but can be used with
* any kind of exception including any checked exception, with potentially
* fine-granular mappings for specific handlers.
*
* @author Juergen Hoeller
* @since 22.11.2003
*/
public interface HandlerExceptionResolver {
/**
* Try to resolve the given exception that got thrown during on handler execution,
* returning a ModelAndView that represents a specific error page if appropriate.
* <p>The returned ModelAndView may be {@linkplain ModelAndView#isEmpty() empty}
* to indicate that the exception has been resolved successfully but that no view
* should be rendered, for instance by setting a status code.
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the
* time of the exception (for example, if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to,
* or <code>null</code> for default processing
*/
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.util.CollectionUtils;
/**
* Handler execution chain, consisting of handler object and any handler interceptors.
* Returned by HandlerMapping's {@link HandlerMapping#getHandler} method.
*
* @author Juergen Hoeller
* @since 20.06.2003
* @see HandlerInterceptor
*/
public class HandlerExecutionChain {
private final Object handler;
private HandlerInterceptor[] interceptors;
private List<HandlerInterceptor> interceptorList;
/**
* Create a new HandlerExecutionChain.
* @param handler the handler object to execute
*/
public HandlerExecutionChain(Object handler) {
this(handler, null);
}
/**
* Create a new HandlerExecutionChain.
* @param handler the handler object to execute
* @param interceptors the array of interceptors to apply
* (in the given order) before the handler itself executes
*/
public HandlerExecutionChain(Object handler, HandlerInterceptor[] interceptors) {
if (handler instanceof HandlerExecutionChain) {
HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
this.handler = originalChain.getHandler();
this.interceptorList = new ArrayList<HandlerInterceptor>();
CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
}
else {
this.handler = handler;
this.interceptors = interceptors;
}
}
/**
* Return the handler object to execute.
* @return the handler object
*/
public Object getHandler() {
return this.handler;
}
public void addInterceptor(HandlerInterceptor interceptor) {
initInterceptorList();
this.interceptorList.add(interceptor);
}
public void addInterceptors(HandlerInterceptor[] interceptors) {
if (interceptors != null) {
initInterceptorList();
this.interceptorList.addAll(Arrays.asList(interceptors));
}
}
private void initInterceptorList() {
if (this.interceptorList == null) {
this.interceptorList = new ArrayList<HandlerInterceptor>();
}
if (this.interceptors != null) {
this.interceptorList.addAll(Arrays.asList(this.interceptors));
this.interceptors = null;
}
}
/**
* Return the array of interceptors to apply (in the given order).
* @return the array of HandlerInterceptors instances (may be <code>null</code>)
*/
public HandlerInterceptor[] getInterceptors() {
if (this.interceptors == null && this.interceptorList != null) {
this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]);
}
return this.interceptors;
}
/**
* Delegates to the handler's <code>toString()</code>.
*/
@Override
public String toString() {
if (this.handler == null) {
return "HandlerExecutionChain with no handler";
}
StringBuilder sb = new StringBuilder();
sb.append("HandlerExecutionChain with handler [").append(this.handler).append("]");
if (!CollectionUtils.isEmpty(this.interceptorList)) {
sb.append(" and ").append(this.interceptorList.size()).append(" interceptor");
if (this.interceptorList.size() > 1) {
sb.append("s");
}
}
return sb.toString();
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Workflow interface that allows for customized handler execution chains.
* Applications can register any number of existing or custom interceptors
* for certain groups of handlers, to add common preprocessing behavior
* without needing to modify each handler implementation.
*
* <p>A HandlerInterceptor gets called before the appropriate HandlerAdapter
* triggers the execution of the handler itself. This mechanism can be used
* for a large field of preprocessing aspects, e.g. for authorization checks,
* or common handler behavior like locale or theme changes. Its main purpose
* is to allow for factoring out repetitive handler code.
*
* <p>Typically an interceptor chain is defined per HandlerMapping bean,
* sharing its granularity. To be able to apply a certain interceptor chain
* to a group of handlers, one needs to map the desired handlers via one
* HandlerMapping bean. The interceptors themselves are defined as beans
* in the application context, referenced by the mapping bean definition
* via its "interceptors" property (in XML: a &lt;list&gt; of &lt;ref&gt;).
*
* <p>HandlerInterceptor is basically similar to a Servlet 2.3 Filter, but in
* contrast to the latter it just allows custom pre-processing with the option
* of prohibiting the execution of the handler itself, and custom post-processing.
* Filters are more powerful, for example they allow for exchanging the request
* and response objects that are handed down the chain. Note that a filter
* gets configured in web.xml, a HandlerInterceptor in the application context.
*
* <p>As a basic guideline, fine-grained handler-related preprocessing tasks are
* candidates for HandlerInterceptor implementations, especially factored-out
* common handler code and authorization checks. On the other hand, a Filter
* is well-suited for request content and view content handling, like multipart
* forms and GZIP compression. This typically shows when one needs to map the
* filter to certain content types (e.g. images), or to all requests.
*
* @author Juergen Hoeller
* @since 20.06.2003
* @see HandlerExecutionChain#getInterceptors
* @see org.springframework.web.servlet.handler.HandlerInterceptorAdapter
* @see org.springframework.web.servlet.handler.AbstractHandlerMapping#setInterceptors
* @see org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor
* @see org.springframework.web.servlet.i18n.LocaleChangeInterceptor
* @see org.springframework.web.servlet.theme.ThemeChangeInterceptor
* @see javax.servlet.Filter
*/
public interface HandlerInterceptor {
/**
* Intercept the execution of a handler. Called after HandlerMapping determined
* an appropriate handler object, but before HandlerAdapter invokes the handler.
* <p>DispatcherServlet processes a handler in an execution chain, consisting
* of any number of interceptors, with the handler itself at the end.
* With this method, each interceptor can decide to abort the execution chain,
* typically sending a HTTP error or writing a custom response.
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance evaluation
* @return <code>true</code> if the execution chain should proceed with the
* next interceptor or the handler itself. Else, DispatcherServlet assumes
* that this interceptor has already dealt with the response itself.
* @throws Exception in case of errors
*/
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
/**
* Intercept the execution of a handler. Called after HandlerAdapter actually
* invoked the handler, but before the DispatcherServlet renders the view.
* Can expose additional model objects to the view via the given ModelAndView.
* <p>DispatcherServlet processes a handler in an execution chain, consisting
* of any number of interceptors, with the handler itself at the end.
* With this method, each interceptor can post-process an execution,
* getting applied in inverse order of the execution chain.
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance examination
* @param modelAndView the <code>ModelAndView</code> that the handler returned
* (can also be <code>null</code>)
* @throws Exception in case of errors
*/
void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
/**
* Callback after completion of request processing, that is, after rendering
* the view. Will be called on any outcome of handler execution, thus allows
* for proper resource cleanup.
* <p>Note: Will only be called if this interceptor's <code>preHandle</code>
* method has successfully completed and returned <code>true</code>!
* <p>As with the {@code postHandle} method, the method will be invoked on each
* interceptor in the chain in reverse order, so the first interceptor will be
* the last to be invoked.
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance examination
* @param ex exception thrown on handler execution, if any
* @throws Exception in case of errors
*/
void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
/**
* Interface to be implemented by objects that define a mapping between
* requests and handler objects.
*
* <p>This class can be implemented by application developers, although this is not
* necessary, as {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping}
* and {@link org.springframework.web.servlet.handler.SimpleUrlHandlerMapping}
* are included in the framework. The former is the default if no
* HandlerMapping bean is registered in the application context.
*
* <p>HandlerMapping implementations can support mapped interceptors but do not
* have to. A handler will always be wrapped in a {@link HandlerExecutionChain}
* instance, optionally accompanied by some {@link HandlerInterceptor} instances.
* The DispatcherServlet will first call each HandlerInterceptor's
* <code>preHandle</code> method in the given order, finally invoking the handler
* itself if all <code>preHandle</code> methods have returned <code>true</code>.
*
* <p>The ability to parameterize this mapping is a powerful and unusual
* capability of this MVC framework. For example, it is possible to write
* a custom mapping based on session state, cookie state or many other
* variables. No other MVC framework seems to be equally flexible.
*
* <p>Note: Implementations can implement the {@link org.springframework.core.Ordered}
* interface to be able to specify a sorting order and thus a priority for getting
* applied by DispatcherServlet. Non-Ordered instances get treated as lowest priority.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see org.springframework.core.Ordered
* @see org.springframework.web.servlet.handler.AbstractHandlerMapping
* @see org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
* @see org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
*/
public interface HandlerMapping {
/**
* Name of the {@link HttpServletRequest} attribute that contains the path
* within the handler mapping, in case of a pattern match, or the full
* relevant URI (typically within the DispatcherServlet's mapping) else.
* <p>Note: This attribute is not required to be supported by all
* HandlerMapping implementations. URL-based HandlerMappings will
* typically support it, but handlers should not necessarily expect
* this request attribute to be present in all scenarios.
*/
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
/**
* Name of the {@link HttpServletRequest} attribute that contains the
* best matching pattern within the handler mapping.
* <p>Note: This attribute is not required to be supported by all
* HandlerMapping implementations. URL-based HandlerMappings will
* typically support it, but handlers should not necessarily expect
* this request attribute to be present in all scenarios.
*/
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
/**
* Name of the boolean {@link HttpServletRequest} attribute that indicates
* whether type-level mappings should be inspected.
* <p>Note: This attribute is not required to be supported by all
* HandlerMapping implementations.
*/
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
/**
* Name of the {@link HttpServletRequest} attribute that contains the URI
* templates map, mapping variable names to values.
* <p>Note: This attribute is not required to be supported by all
* HandlerMapping implementations. URL-based HandlerMappings will
* typically support it, but handlers should not necessarily expect
* this request attribute to be present in all scenarios.
*/
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
/**
* Name of the {@link HttpServletRequest} attribute that contains the set of producible MediaTypes
* applicable to the mapped handler.
* <p>Note: This attribute is not required to be supported by all HandlerMapping implementations.
* Handlers should not necessarily expect this request attribute to be present in all scenarios.
*/
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
/**
* Return a handler and any interceptors for this request. The choice may be made
* on request URL, session state, or any factor the implementing class chooses.
* <p>The returned HandlerExecutionChain contains a handler Object, rather than
* even a tag interface, so that handlers are not constrained in any way.
* For example, a HandlerAdapter could be written to allow another framework's
* handler objects to be used.
* <p>Returns <code>null</code> if no match was found. This is not an error.
* The DispatcherServlet will query all registered HandlerMapping beans to find
* a match, and only decide there is an error if none can find a handler.
* @param request current HTTP request
* @return a HandlerExecutionChain instance containing handler object and
* any interceptors, or <code>null</code> if no mapping found
* @throws Exception if there is an internal error
*/
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

View File

@@ -0,0 +1,231 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceEditor;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.StandardServletEnvironment;
import org.springframework.web.context.support.ServletContextResourceLoader;
/**
* Simple extension of {@link javax.servlet.http.HttpServlet} which treats
* its config parameters (<code>init-param</code> entries within the
* <code>servlet</code> tag in <code>web.xml</code>) as bean properties.
*
* <p>A handy superclass for any type of servlet. Type conversion of config
* parameters is automatic, with the corresponding setter method getting
* invoked with the converted value. It is also possible for subclasses to
* specify required properties. Parameters without matching bean property
* setter will simply be ignored.
*
* <p>This servlet leaves request handling to subclasses, inheriting the default
* behavior of HttpServlet (<code>doGet</code>, <code>doPost</code>, etc).
*
* <p>This generic servlet base class has no dependency on the Spring
* {@link org.springframework.context.ApplicationContext} concept. Simple
* servlets usually don't load their own context but rather access service
* beans from the Spring root application context, accessible via the
* filter's {@link #getServletContext() ServletContext} (see
* {@link org.springframework.web.context.support.WebApplicationContextUtils}).
*
* <p>The {@link FrameworkServlet} class is a more specific servlet base
* class which loads its own application context. FrameworkServlet serves
* as direct base class of Spring's full-fledged {@link DispatcherServlet}.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see #addRequiredProperty
* @see #initServletBean
* @see #doGet
* @see #doPost
*/
@SuppressWarnings("serial")
public abstract class HttpServletBean extends HttpServlet implements EnvironmentAware {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
/**
* Set of required properties (Strings) that must be supplied as
* config parameters to this servlet.
*/
private final Set<String> requiredProperties = new HashSet<String>();
private Environment environment = new StandardServletEnvironment();
/**
* Subclasses can invoke this method to specify that this property
* (which must match a JavaBean property they expose) is mandatory,
* and must be supplied as a config parameter. This should be called
* from the constructor of a subclass.
* <p>This method is only relevant in case of traditional initialization
* driven by a ServletConfig instance.
* @param property name of the required property
*/
protected final void addRequiredProperty(String property) {
this.requiredProperties.add(property);
}
/**
* Map config parameters onto bean properties of this servlet, and
* invoke subclass initialization.
* @throws ServletException if bean properties are invalid (or required
* properties are missing), or if subclass initialization fails.
*/
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// Let subclasses do whatever initialization they like.
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
/**
* Initialize the BeanWrapper for this HttpServletBean,
* possibly with custom editors.
* <p>This default implementation is empty.
* @param bw the BeanWrapper to initialize
* @throws BeansException if thrown by BeanWrapper methods
* @see org.springframework.beans.BeanWrapper#registerCustomEditor
*/
protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
}
/**
* Overridden method that simply returns <code>null</code> when no
* ServletConfig set yet.
* @see #getServletConfig()
*/
@Override
public final String getServletName() {
return (getServletConfig() != null ? getServletConfig().getServletName() : null);
}
/**
* Overridden method that simply returns <code>null</code> when no
* ServletConfig set yet.
* @see #getServletConfig()
*/
@Override
public final ServletContext getServletContext() {
return (getServletConfig() != null ? getServletConfig().getServletContext() : null);
}
/**
* Subclasses may override this to perform custom initialization.
* All bean properties of this servlet will have been set before this
* method is invoked.
* <p>This default implementation is empty.
* @throws ServletException if subclass initialization fails
*/
protected void initServletBean() throws ServletException {
}
/**
* {@inheritDoc}
* <p>Any environment set here overrides the {@link StandardServletEnvironment}
* provided by default.
*/
public void setEnvironment(Environment environment) {
this.environment = environment;
}
/**
* PropertyValues implementation created from ServletConfig init parameters.
*/
private static class ServletConfigPropertyValues extends MutablePropertyValues {
/**
* Create new ServletConfigPropertyValues.
* @param config ServletConfig we'll use to take PropertyValues from
* @param requiredProperties set of property names we need, where
* we can't accept default values
* @throws ServletException if any required properties are missing
*/
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
Set<String> missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?
new HashSet<String>(requiredProperties) : null;
Enumeration en = config.getInitParameterNames();
while (en.hasMoreElements()) {
String property = (String) en.nextElement();
Object value = config.getInitParameter(property);
addPropertyValue(new PropertyValue(property, value));
if (missingProps != null) {
missingProps.remove(property);
}
}
// Fail if we are still missing properties.
if (missingProps != null && missingProps.size() > 0) {
throw new ServletException(
"Initialization from ServletConfig for servlet '" + config.getServletName() +
"' failed; the following required properties were missing: " +
StringUtils.collectionToDelimitedString(missingProps, ", "));
}
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Interface for web-based locale resolution strategies that allows for
* both locale resolution via the request and locale modification via
* request and response.
*
* <p>This interface allows for implementations based on request, session,
* cookies, etc. The default implementation is AcceptHeaderLocaleResolver,
* simply using the request's locale provided by the respective HTTP header.
*
* <p>Use <code>RequestContext.getLocale()</code> to retrieve the current locale
* in controllers or views, independent of the actual resolution strategy.
*
* @author Juergen Hoeller
* @since 27.02.2003
* @see org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
* @see org.springframework.web.servlet.support.RequestContext#getLocale
*/
public interface LocaleResolver {
/**
* Resolve the current locale via the given request.
* Should return a default locale as fallback in any case.
* @param request the request to resolve the locale for
* @return the current locale (never <code>null</code>)
*/
Locale resolveLocale(HttpServletRequest request);
/**
* Set the current locale to the given one.
* @param request the request to be used for locale modification
* @param response the response to be used for locale modification
* @param locale the new locale, or <code>null</code> to clear the locale
* @throws UnsupportedOperationException if the LocaleResolver implementation
* does not support dynamic changing of the theme
*/
void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale);
}

View File

@@ -0,0 +1,304 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import java.util.Map;
import org.springframework.ui.ModelMap;
import org.springframework.util.CollectionUtils;
/**
* Holder for both Model and View in the web MVC framework.
* Note that these are entirely distinct. This class merely holds
* both to make it possible for a controller to return both model
* and view in a single return value.
*
* <p>Represents a model and view returned by a handler, to be resolved
* by a DispatcherServlet. The view can take the form of a String
* view name which will need to be resolved by a ViewResolver object;
* alternatively a View object can be specified directly. The model
* is a Map, allowing the use of multiple objects keyed by name.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Rob Harrop
* @see DispatcherServlet
* @see ViewResolver
* @see HandlerAdapter#handle
* @see org.springframework.web.servlet.mvc.Controller#handleRequest
*/
public class ModelAndView {
/** View instance or view name String */
private Object view;
/** Model Map */
private ModelMap model;
/** Indicates whether or not this instance has been cleared with a call to {@link #clear()} */
private boolean cleared = false;
/**
* Default constructor for bean-style usage: populating bean
* properties instead of passing in constructor arguments.
* @see #setView(View)
* @see #setViewName(String)
*/
public ModelAndView() {
}
/**
* Convenient constructor when there is no model data to expose.
* Can also be used in conjunction with <code>addObject</code>.
* @param viewName name of the View to render, to be resolved
* by the DispatcherServlet's ViewResolver
* @see #addObject
*/
public ModelAndView(String viewName) {
this.view = viewName;
}
/**
* Convenient constructor when there is no model data to expose.
* Can also be used in conjunction with <code>addObject</code>.
* @param view View object to render
* @see #addObject
*/
public ModelAndView(View view) {
this.view = view;
}
/**
* Creates new ModelAndView given a view name and a model.
* @param viewName name of the View to render, to be resolved
* by the DispatcherServlet's ViewResolver
* @param model Map of model names (Strings) to model objects
* (Objects). Model entries may not be <code>null</code>, but the
* model Map may be <code>null</code> if there is no model data.
*/
public ModelAndView(String viewName, Map<String, ?> model) {
this.view = viewName;
if (model != null) {
getModelMap().addAllAttributes(model);
}
}
/**
* Creates new ModelAndView given a View object and a model.
* <emphasis>Note: the supplied model data is copied into the internal
* storage of this class. You should not consider to modify the supplied
* Map after supplying it to this class</emphasis>
* @param view View object to render
* @param model Map of model names (Strings) to model objects
* (Objects). Model entries may not be <code>null</code>, but the
* model Map may be <code>null</code> if there is no model data.
*/
public ModelAndView(View view, Map<String, ?> model) {
this.view = view;
if (model != null) {
getModelMap().addAllAttributes(model);
}
}
/**
* Convenient constructor to take a single model object.
* @param viewName name of the View to render, to be resolved
* by the DispatcherServlet's ViewResolver
* @param modelName name of the single entry in the model
* @param modelObject the single model object
*/
public ModelAndView(String viewName, String modelName, Object modelObject) {
this.view = viewName;
addObject(modelName, modelObject);
}
/**
* Convenient constructor to take a single model object.
* @param view View object to render
* @param modelName name of the single entry in the model
* @param modelObject the single model object
*/
public ModelAndView(View view, String modelName, Object modelObject) {
this.view = view;
addObject(modelName, modelObject);
}
/**
* Set a view name for this ModelAndView, to be resolved by the
* DispatcherServlet via a ViewResolver. Will override any
* pre-existing view name or View.
*/
public void setViewName(String viewName) {
this.view = viewName;
}
/**
* Return the view name to be resolved by the DispatcherServlet
* via a ViewResolver, or <code>null</code> if we are using a View object.
*/
public String getViewName() {
return (this.view instanceof String ? (String) this.view : null);
}
/**
* Set a View object for this ModelAndView. Will override any
* pre-existing view name or View.
*/
public void setView(View view) {
this.view = view;
}
/**
* Return the View object, or <code>null</code> if we are using a view name
* to be resolved by the DispatcherServlet via a ViewResolver.
*/
public View getView() {
return (this.view instanceof View ? (View) this.view : null);
}
/**
* Indicate whether or not this <code>ModelAndView</code> has a view, either
* as a view name or as a direct {@link View} instance.
*/
public boolean hasView() {
return (this.view != null);
}
/**
* Return whether we use a view reference, i.e. <code>true</code>
* if the view has been specified via a name to be resolved by the
* DispatcherServlet via a ViewResolver.
*/
public boolean isReference() {
return (this.view instanceof String);
}
/**
* Return the model map. May return <code>null</code>.
* Called by DispatcherServlet for evaluation of the model.
*/
protected Map<String, Object> getModelInternal() {
return this.model;
}
/**
* Return the underlying <code>ModelMap</code> instance (never <code>null</code>).
*/
public ModelMap getModelMap() {
if (this.model == null) {
this.model = new ModelMap();
}
return this.model;
}
/**
* Return the model map. Never returns <code>null</code>.
* To be called by application code for modifying the model.
*/
public Map<String, Object> getModel() {
return getModelMap();
}
/**
* Add an attribute to the model.
* @param attributeName name of the object to add to the model
* @param attributeValue object to add to the model (never <code>null</code>)
* @see ModelMap#addAttribute(String, Object)
* @see #getModelMap()
*/
public ModelAndView addObject(String attributeName, Object attributeValue) {
getModelMap().addAttribute(attributeName, attributeValue);
return this;
}
/**
* Add an attribute to the model using parameter name generation.
* @param attributeValue the object to add to the model (never <code>null</code>)
* @see ModelMap#addAttribute(Object)
* @see #getModelMap()
*/
public ModelAndView addObject(Object attributeValue) {
getModelMap().addAttribute(attributeValue);
return this;
}
/**
* Add all attributes contained in the provided Map to the model.
* @param modelMap a Map of attributeName -> attributeValue pairs
* @see ModelMap#addAllAttributes(Map)
* @see #getModelMap()
*/
public ModelAndView addAllObjects(Map<String, ?> modelMap) {
getModelMap().addAllAttributes(modelMap);
return this;
}
/**
* Clear the state of this ModelAndView object.
* The object will be empty afterwards.
* <p>Can be used to suppress rendering of a given ModelAndView object
* in the <code>postHandle</code> method of a HandlerInterceptor.
* @see #isEmpty()
* @see HandlerInterceptor#postHandle
*/
public void clear() {
this.view = null;
this.model = null;
this.cleared = true;
}
/**
* Return whether this ModelAndView object is empty,
* i.e. whether it does not hold any view and does not contain a model.
*/
public boolean isEmpty() {
return (this.view == null && CollectionUtils.isEmpty(this.model));
}
/**
* Return whether this ModelAndView object is empty as a result of a call to {@link #clear}
* i.e. whether it does not hold any view and does not contain a model.
* <p>Returns <code>false</code> if any additional state was added to the instance
* <strong>after</strong> the call to {@link #clear}.
* @see #clear()
*/
public boolean wasCleared() {
return (this.cleared && isEmpty());
}
/**
* Return diagnostic information about this model and view.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder("ModelAndView: ");
if (isReference()) {
sb.append("reference to view with name '").append(this.view).append("'");
}
else {
sb.append("materialized View is [").append(this.view).append(']');
}
sb.append("; model is ").append(this.model);
return sb.toString();
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import javax.servlet.ServletException;
import org.springframework.util.Assert;
/**
* Exception to be thrown on error conditions that should forward
* to a specific view with a specific model.
*
* <p>Can be thrown at any time during handler processing.
* This includes any template methods of pre-built controllers.
* For example, a form controller might abort to a specific error page
* if certain parameters do not allow to proceed with the normal workflow.
*
* @author Juergen Hoeller
* @since 22.11.2003
*/
public class ModelAndViewDefiningException extends ServletException {
private ModelAndView modelAndView;
/**
* Create new ModelAndViewDefiningException with the given ModelAndView,
* typically representing a specific error page.
* @param modelAndView ModelAndView with view to forward to and model to expose
*/
public ModelAndViewDefiningException(ModelAndView modelAndView) {
Assert.notNull(modelAndView, "ModelAndView must not be null in ModelAndViewDefiningException");
this.modelAndView = modelAndView;
}
/**
* Return the ModelAndView that this exception contains for forwarding to.
*/
public ModelAndView getModelAndView() {
return modelAndView;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
/**
* Strategy interface for translating an incoming
* {@link javax.servlet.http.HttpServletRequest} into a
* logical view name when no view name is explicitly supplied.
*
* @author Rob Harrop
* @author Juergen Hoeller
* @since 2.0
*/
public interface RequestToViewNameTranslator {
/**
* Translate the given {@link HttpServletRequest} into a view name.
* @param request the incoming {@link HttpServletRequest} providing
* the context from which a view name is to be resolved
* @return the view name (or <code>null</code> if no default found)
* @throws Exception if view name translation fails
*/
String getViewName(HttpServletRequest request) throws Exception;
}

View File

@@ -0,0 +1,345 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.ServletContextResource;
/**
* Simple servlet that can expose an internal resource, including a
* default URL if the specified resource is not found. An alternative,
* for example, to trying and catching exceptions when using JSP include.
*
* <p>A further usage of this servlet is the ability to apply last-modified
* timestamps to quasi-static resources (typically JSPs). This can happen
* as bridge to parameter-specified resources, or as proxy for a specific
* target resource (or a list of specific target resources to combine).
*
* <p>A typical usage would map a URL like "/ResourceServlet" onto an instance
* of this servlet, and use the "JSP include" action to include this URL,
* with the "resource" parameter indicating the actual target path in the WAR.
*
* <p>The <code>defaultUrl</code> property can be set to the internal
* resource path of a default URL, to be rendered when the target resource
* is not found or not specified in the first place.
*
* <p>The "resource" parameter and the <code>defaultUrl</code> property can
* also specify a list of target resources to combine. Those resources will be
* included one by one to build the response. If last-modified determination
* is active, the newest timestamp among those files will be used.
*
* <p>The <code>allowedResources</code> property can be set to a URL
* pattern of resources that should be available via this servlet.
* If not set, any target resource can be requested, including resources
* in the WEB-INF directory!
*
* <p>If using this servlet for direct access rather than via includes,
* the <code>contentType</code> property should be specified to apply a
* proper content type. Note that a content type header in the target JSP will
* be ignored when including the resource via a RequestDispatcher include.
*
* <p>To apply last-modified timestamps for the target resource, set the
* <code>applyLastModified</code> property to true. This servlet will then
* return the file timestamp of the target resource as last-modified value,
* falling back to the startup time of this servlet if not retrievable.
*
* <p>Note that applying the last-modified timestamp in the above fashion
* just makes sense if the target resource does not generate content that
* depends on the HttpSession or cookies; it is just allowed to evaluate
* request parameters.
*
* <p>A typical case for such last-modified usage is a JSP that just makes
* minimal usage of basic means like includes or message resolution to
* build quasi-static content. Regenerating such content on every request
* is unnecessary; it can be cached as long as the file hasn't changed.
*
* <p>Note that this servlet will apply the last-modified timestamp if you
* tell it to do so: It's your decision whether the content of the target
* resource can be cached in such a fashion. Typical use cases are helper
* resources that are not fronted by a controller, like JavaScript files
* that are generated by a JSP (without depending on the HttpSession).
*
* @author Juergen Hoeller
* @author Rod Johnson
* @see #setDefaultUrl
* @see #setAllowedResources
* @see #setApplyLastModified
*/
public class ResourceServlet extends HttpServletBean {
/**
* Any number of these characters are considered delimiters
* between multiple resource paths in a single String value.
*/
public static final String RESOURCE_URL_DELIMITERS = ",; \t\n";
/**
* Name of the parameter that must contain the actual resource path.
*/
public static final String RESOURCE_PARAM_NAME = "resource";
private String defaultUrl;
private String allowedResources;
private String contentType;
private boolean applyLastModified = false;
private PathMatcher pathMatcher;
private long startupTime;
/**
* Set the URL within the current web application from which to
* include content if the requested path isn't found, or if none
* is specified in the first place.
* <p>If specifying multiple URLs, they will be included one by one
* to build the response. If last-modified determination is active,
* the newest timestamp among those files will be used.
* @see #setApplyLastModified
*/
public void setDefaultUrl(String defaultUrl) {
this.defaultUrl = defaultUrl;
}
/**
* Set allowed resources as URL pattern, e.g. "/WEB-INF/res/*.jsp",
* The parameter can be any Ant-style pattern parsable by AntPathMatcher.
* @see org.springframework.util.AntPathMatcher
*/
public void setAllowedResources(String allowedResources) {
this.allowedResources = allowedResources;
}
/**
* Set the content type of the target resource (typically a JSP).
* Default is none, which is appropriate when including resources.
* <p>For directly accessing resources, for example to leverage this
* servlet's last-modified support, specify a content type here.
* Note that a content type header in the target JSP will be ignored
* when including the resource via a RequestDispatcher include.
*/
public void setContentType(String contentType) {
this.contentType = contentType;
}
/**
* Set whether to apply the file timestamp of the target resource
* as last-modified value. Default is "false".
* <p>This is mainly intended for JSP targets that don't generate
* session-specific or database-driven content: Such files can be
* cached by the browser as long as the last-modified timestamp
* of the JSP file doesn't change.
* <p>This will only work correctly with expanded WAR files that
* allow access to the file timestamps. Else, the startup time
* of this servlet is returned.
*/
public void setApplyLastModified(boolean applyLastModified) {
this.applyLastModified = applyLastModified;
}
/**
* Remember the startup time, using no last-modified time before it.
*/
@Override
protected void initServletBean() {
this.pathMatcher = getPathMatcher();
this.startupTime = System.currentTimeMillis();
}
/**
* Return a PathMatcher to use for matching the "allowedResources" URL pattern.
* Default is AntPathMatcher.
* @see #setAllowedResources
* @see org.springframework.util.AntPathMatcher
*/
protected PathMatcher getPathMatcher() {
return new AntPathMatcher();
}
/**
* Determine the URL of the target resource and include it.
* @see #determineResourceUrl
*/
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// determine URL of resource to include
String resourceUrl = determineResourceUrl(request);
if (resourceUrl != null) {
try {
doInclude(request, response, resourceUrl);
}
catch (ServletException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to include content of resource [" + resourceUrl + "]", ex);
}
// Try including default URL if appropriate.
if (!includeDefaultUrl(request, response)) {
throw ex;
}
}
catch (IOException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to include content of resource [" + resourceUrl + "]", ex);
}
// Try including default URL if appropriate.
if (!includeDefaultUrl(request, response)) {
throw ex;
}
}
}
// no resource URL specified -> try to include default URL.
else if (!includeDefaultUrl(request, response)) {
throw new ServletException("No target resource URL found for request");
}
}
/**
* Determine the URL of the target resource of this request.
* <p>Default implementation returns the value of the "resource" parameter.
* Can be overridden in subclasses.
* @param request current HTTP request
* @return the URL of the target resource, or <code>null</code> if none found
* @see #RESOURCE_PARAM_NAME
*/
protected String determineResourceUrl(HttpServletRequest request) {
return request.getParameter(RESOURCE_PARAM_NAME);
}
/**
* Include the specified default URL, if appropriate.
* @param request current HTTP request
* @param response current HTTP response
* @return whether a default URL was included
* @throws ServletException if thrown by the RequestDispatcher
* @throws IOException if thrown by the RequestDispatcher
*/
private boolean includeDefaultUrl(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (this.defaultUrl == null) {
return false;
}
doInclude(request, response, this.defaultUrl);
return true;
}
/**
* Include the specified resource via the RequestDispatcher.
* @param request current HTTP request
* @param response current HTTP response
* @param resourceUrl the URL of the target resource
* @throws ServletException if thrown by the RequestDispatcher
* @throws IOException if thrown by the RequestDispatcher
*/
private void doInclude(HttpServletRequest request, HttpServletResponse response, String resourceUrl)
throws ServletException, IOException {
if (this.contentType != null) {
response.setContentType(this.contentType);
}
String[] resourceUrls =
StringUtils.tokenizeToStringArray(resourceUrl, RESOURCE_URL_DELIMITERS);
for (int i = 0; i < resourceUrls.length; i++) {
// check whether URL matches allowed resources
if (this.allowedResources != null && !this.pathMatcher.match(this.allowedResources, resourceUrls[i])) {
throw new ServletException("Resource [" + resourceUrls[i] +
"] does not match allowed pattern [" + this.allowedResources + "]");
}
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + resourceUrls[i] + "]");
}
RequestDispatcher rd = request.getRequestDispatcher(resourceUrls[i]);
rd.include(request, response);
}
}
/**
* Return the last-modified timestamp of the file that corresponds
* to the target resource URL (i.e. typically the request ".jsp" file).
* Will simply return -1 if "applyLastModified" is false (the default).
* <p>Returns no last-modified date before the startup time of this servlet,
* to allow for message resolution etc that influences JSP contents,
* assuming that those background resources might have changed on restart.
* <p>Returns the startup time of this servlet if the file that corresponds
* to the target resource URL coudln't be resolved (for example, because
* the WAR is not expanded).
* @see #determineResourceUrl
* @see #getFileTimestamp
*/
@Override
protected final long getLastModified(HttpServletRequest request) {
if (this.applyLastModified) {
String resourceUrl = determineResourceUrl(request);
if (resourceUrl == null) {
resourceUrl = this.defaultUrl;
}
if (resourceUrl != null) {
String[] resourceUrls = StringUtils.tokenizeToStringArray(resourceUrl, RESOURCE_URL_DELIMITERS);
long latestTimestamp = -1;
for (int i = 0; i < resourceUrls.length; i++) {
long timestamp = getFileTimestamp(resourceUrls[i]);
if (timestamp > latestTimestamp) {
latestTimestamp = timestamp;
}
}
return (latestTimestamp > this.startupTime ? latestTimestamp : this.startupTime);
}
}
return -1;
}
/**
* Return the file timestamp for the given resource.
* @param resourceUrl the URL of the resource
* @return the file timestamp in milliseconds, or -1 if not determinable
*/
protected long getFileTimestamp(String resourceUrl) {
ServletContextResource resource = new ServletContextResource(getServletContext(), resourceUrl);
try {
long lastModifiedTime = resource.lastModified();
if (logger.isDebugEnabled()) {
logger.debug("Last-modified timestamp of " + resource + " is " + lastModifiedTime);
}
return lastModifiedTime;
}
catch (IOException ex) {
logger.warn("Couldn't retrieve last-modified timestamp of [" + resource +
"] - using ResourceServlet startup time");
return -1;
}
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
/**
* Provides additional information about a View such as whether it
* performs redirects.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public interface SmartView extends View {
/**
* Whether the view performs a redirect.
*/
boolean isRedirectView();
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Interface for web-based theme resolution strategies that allows for
* both theme resolution via the request and theme modification via
* request and response.
*
* <p>This interface allows for implementations based on session,
* cookies, etc. The default implementation is FixedThemeResolver,
* simply using a configured default theme.
*
* <p>Note that this resolver is only responsible for determining the
* current theme name. The Theme instance for the resolved theme name
* gets looked up by DispatcherServlet via the respective ThemeSource,
* i.e. the current WebApplicationContext.
*
* <p>Use RequestContext.getTheme() to retrieve the current theme in
* controllers or views, independent of the actual resolution strategy.
*
* @author Jean-Pierre Pawlak
* @author Juergen Hoeller
* @since 17.06.2003
* @see org.springframework.web.servlet.theme.FixedThemeResolver
* @see org.springframework.ui.context.Theme
* @see org.springframework.ui.context.ThemeSource
* @see org.springframework.web.servlet.support.RequestContext#getTheme
*/
public interface ThemeResolver {
/**
* Resolve the current theme name via the given request.
* Should return a default theme as fallback in any case.
* @param request request to be used for resolution
* @return the current theme name
*/
String resolveThemeName(HttpServletRequest request);
/**
* Set the current theme name to the given one.
* @param request request to be used for theme name modification
* @param response response to be used for theme name modification
* @param themeName the new theme name
* @throws UnsupportedOperationException if the ThemeResolver implementation
* does not support dynamic changing of the theme
*/
void setThemeName(HttpServletRequest request, HttpServletResponse response, String themeName);
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* MVC View for a web interaction. Implementations are responsible for rendering
* content, and exposing the model. A single view exposes multiple model attributes.
*
* <p>This class and the MVC approach associated with it is discussed in Chapter 12 of
* <a href="http://www.amazon.com/exec/obidos/tg/detail/-/0764543857/">Expert One-On-One J2EE Design and Development</a>
* by Rod Johnson (Wrox, 2002).
*
* <p>View implementations may differ widely. An obvious implementation would be
* JSP-based. Other implementations might be XSLT-based, or use an HTML generation library.
* This interface is designed to avoid restricting the range of possible implementations.
*
* <p>Views should be beans. They are likely to be instantiated as beans by a ViewResolver.
* As this interface is stateless, view implementations should be thread-safe.
*
* @author Rod Johnson
* @author Arjen Poutsma
* @see org.springframework.web.servlet.view.AbstractView
* @see org.springframework.web.servlet.view.InternalResourceView
*/
public interface View {
/**
* Name of the {@link HttpServletRequest} attribute that contains the response status code.
* <p>Note: This attribute is not required to be supported by all View implementations.
*/
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
/**
* Name of the {@link HttpServletRequest} attribute that contains a Map with path variables.
* The map consists of String-based URI template variable names as keys and their corresponding
* Object-based values -- extracted from segments of the URL and type converted.
*
* <p>Note: This attribute is not required to be supported by all View implementations.
*/
String PATH_VARIABLES = View.class.getName() + ".pathVariables";
/**
* Return the content type of the view, if predetermined.
* <p>Can be used to check the content type upfront,
* before the actual rendering process.
* @return the content type String (optionally including a character set),
* or <code>null</code> if not predetermined.
*/
String getContentType();
/**
* Render the view given the specified model.
* <p>The first step will be preparing the request: In the JSP case,
* this would mean setting model objects as request attributes.
* The second step will be the actual rendering of the view,
* for example including the JSP via a RequestDispatcher.
* @param model Map with name Strings as keys and corresponding model
* objects as values (Map can also be <code>null</code> in case of empty model)
* @param request current HTTP request
* @param response HTTP response we are building
* @throws Exception if rendering failed
*/
void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import java.io.IOException;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.util.NestedServletException;
/**
* ViewRendererServlet is a bridge servlet, mainly for the Portlet MVC support.
*
* <p>For usage with Portlets, this Servlet is necessary to force the portlet container
* to convert the PortletRequest to a ServletRequest, which it has to do when
* including a resource via the PortletRequestDispatcher. This allows for reuse
* of the entire Servlet-based View support even in a Portlet environment.
*
* <p>The actual mapping of the bridge servlet is configurable in the DispatcherPortlet,
* via a "viewRendererUrl" property. The default is "/WEB-INF/servlet/view", which is
* just available for internal resource dispatching.
*
* @author William G. Thompson, Jr.
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
*/
public class ViewRendererServlet extends HttpServlet {
/**
* Request attribute to hold current web application context.
* Otherwise only the global web app context is obtainable by tags etc.
* @see org.springframework.web.servlet.support.RequestContextUtils#getWebApplicationContext
*/
public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE;
/** Name of request attribute that holds the View object */
public static final String VIEW_ATTRIBUTE = ViewRendererServlet.class.getName() + ".VIEW";
/** Name of request attribute that holds the model Map */
public static final String MODEL_ATTRIBUTE = ViewRendererServlet.class.getName() + ".MODEL";
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Process this request, handling exceptions.
* The actually event handling is performed by the abstract
* <code>renderView()</code> template method.
* @see #renderView
*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
renderView(request, response);
}
catch (ServletException ex) {
throw ex;
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
throw new NestedServletException("View rendering failed", ex);
}
}
/**
* Retrieve the View instance and model Map to render
* and trigger actual rendering.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
* @see org.springframework.web.servlet.View#render
*/
@SuppressWarnings("unchecked")
protected void renderView(HttpServletRequest request, HttpServletResponse response) throws Exception {
View view = (View) request.getAttribute(VIEW_ATTRIBUTE);
if (view == null) {
throw new ServletException("Could not complete render request: View is null");
}
Map<String, Object> model = (Map<String, Object>) request.getAttribute(MODEL_ATTRIBUTE);
view.render(model, request, response);
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import java.util.Locale;
/**
* Interface to be implemented by objects that can resolve views by name.
*
* <p>View state doesn't change during the running of the application,
* so implementations are free to cache views.
*
* <p>Implementations are encouraged to support internationalization,
* i.e. localized view resolution.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see org.springframework.web.servlet.view.InternalResourceViewResolver
* @see org.springframework.web.servlet.view.ResourceBundleViewResolver
* @see org.springframework.web.servlet.view.XmlViewResolver
*/
public interface ViewResolver {
/**
* Resolve the given view by name.
* <p>Note: To allow for ViewResolver chaining, a ViewResolver should
* return <code>null</code> if a view with the given name is not defined in it.
* However, this is not required: Some ViewResolvers will always attempt
* to build View objects with the given name, unable to return <code>null</code>
* (rather throwing an exception when View creation failed).
* @param viewName name of the view to resolve
* @param locale Locale in which to resolve the view.
* ViewResolvers that support internationalization should respect this.
* @return the View object, or <code>null</code> if not found
* (optional, to allow for ViewResolver chaining)
* @throws Exception if the view cannot be resolved
* (typically in case of problems creating an actual View object)
*/
View resolveViewName(String viewName, Locale locale) throws Exception;
}

View File

@@ -0,0 +1,362 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import java.util.List;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionServiceFactoryBean;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
import org.springframework.util.ClassUtils;
import org.springframework.util.xml.DomUtils;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.ServletWebArgumentResolverAdapter;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
import org.w3c.dom.Element;
/**
* A {@link BeanDefinitionParser} that provides the configuration for the
* {@code <annotation-driven/>} MVC namespace element.
*
* <p>This class registers the following {@link HandlerMapping}s:</p>
* <ul>
* <li>{@link RequestMappingHandlerMapping}
* ordered at 0 for mapping requests to annotated controller methods.
* <li>{@link BeanNameUrlHandlerMapping}
* ordered at 2 to map URL paths to controller bean names.
* </ul>
*
* <p><strong>Note:</strong> Additional HandlerMappings may be registered
* as a result of using the {@code <view-controller>} or the
* {@code <resources>} MVC namespace elements.
*
* <p>This class registers the following {@link HandlerAdapter}s:
* <ul>
* <li>{@link RequestMappingHandlerAdapter}
* for processing requests with annotated controller methods.
* <li>{@link HttpRequestHandlerAdapter}
* for processing requests with {@link HttpRequestHandler}s.
* <li>{@link SimpleControllerHandlerAdapter}
* for processing requests with interface-based {@link Controller}s.
* </ul>
*
* <p>This class registers the following {@link HandlerExceptionResolver}s:
* <ul>
* <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions
* through @{@link ExceptionHandler} methods.
* <li>{@link ResponseStatusExceptionResolver} for exceptions annotated
* with @{@link ResponseStatus}.
* <li>{@link DefaultHandlerExceptionResolver} for resolving known Spring
* exception types
* </ul>
*
* <p>Both the {@link RequestMappingHandlerAdapter} and the
* {@link ExceptionHandlerExceptionResolver} are configured with default
* instances of the following kind, unless custom instances are provided:
* <ul>
* <li>A {@link DefaultFormattingConversionService}
* <li>A {@link LocalValidatorFactoryBean} if a JSR-303 implementation is
* available on the classpath
* <li>A range of {@link HttpMessageConverter}s depending on what 3rd party
* libraries are available on the classpath.
* </ul>
*
* @author Keith Donald
* @author Juergen Hoeller
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.0
*/
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
private static final boolean jsr303Present = ClassUtils.isPresent(
"javax.validation.Validator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
private static final boolean jaxb2Present =
ClassUtils.isPresent("javax.xml.bind.Binder", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
private static final boolean jacksonPresent =
ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
private static boolean romePresent =
ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
parserContext.pushContainingComponent(compDefinition);
RootBeanDefinition methodMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
methodMappingDef.setSource(source);
methodMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
methodMappingDef.getPropertyValues().add("order", 0);
String methodMappingName = parserContext.getReaderContext().registerWithGeneratedName(methodMappingDef);
RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
RuntimeBeanReference validator = getValidator(element, source, parserContext);
RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element, source, parserContext);
RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
bindingDef.setSource(source);
bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
bindingDef.getPropertyValues().add("conversionService", conversionService);
bindingDef.getPropertyValues().add("validator", validator);
bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);
ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);
ManagedList<?> argumentResolvers = getArgumentResolvers(element, source, parserContext);
ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, source, parserContext);
RootBeanDefinition methodAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
methodAdapterDef.setSource(source);
methodAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
methodAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
methodAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
if (element.hasAttribute("ignoreDefaultModelOnRedirect")) {
Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignoreDefaultModelOnRedirect"));
methodAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
}
if (argumentResolvers != null) {
methodAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
}
if (returnValueHandlers != null) {
methodAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
}
String methodAdapterName = parserContext.getReaderContext().registerWithGeneratedName(methodAdapterDef);
RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
csInterceptorDef.setSource(source);
csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
RootBeanDefinition mappedCsInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
mappedCsInterceptorDef.setSource(source);
mappedCsInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
String mappedInterceptorName = parserContext.getReaderContext().registerWithGeneratedName(mappedCsInterceptorDef);
RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
methodExceptionResolver.setSource(source);
methodExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
methodExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
methodExceptionResolver.getPropertyValues().add("order", 0);
String methodExceptionResolverName =
parserContext.getReaderContext().registerWithGeneratedName(methodExceptionResolver);
RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
responseStatusExceptionResolver.setSource(source);
responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
responseStatusExceptionResolver.getPropertyValues().add("order", 1);
String responseStatusExceptionResolverName =
parserContext.getReaderContext().registerWithGeneratedName(responseStatusExceptionResolver);
RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
defaultExceptionResolver.setSource(source);
defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
defaultExceptionResolver.getPropertyValues().add("order", 2);
String defaultExceptionResolverName =
parserContext.getReaderContext().registerWithGeneratedName(defaultExceptionResolver);
parserContext.registerComponent(new BeanComponentDefinition(methodMappingDef, methodMappingName));
parserContext.registerComponent(new BeanComponentDefinition(methodAdapterDef, methodAdapterName));
parserContext.registerComponent(new BeanComponentDefinition(methodExceptionResolver, methodExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
parserContext.popAndRegisterContainingComponent();
return null;
}
private RuntimeBeanReference getConversionService(Element element, Object source, ParserContext parserContext) {
RuntimeBeanReference conversionServiceRef;
if (element.hasAttribute("conversion-service")) {
conversionServiceRef = new RuntimeBeanReference(element.getAttribute("conversion-service"));
}
else {
RootBeanDefinition conversionDef = new RootBeanDefinition(FormattingConversionServiceFactoryBean.class);
conversionDef.setSource(source);
conversionDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String conversionName = parserContext.getReaderContext().registerWithGeneratedName(conversionDef);
parserContext.registerComponent(new BeanComponentDefinition(conversionDef, conversionName));
conversionServiceRef = new RuntimeBeanReference(conversionName);
}
return conversionServiceRef;
}
private RuntimeBeanReference getValidator(Element element, Object source, ParserContext parserContext) {
if (element.hasAttribute("validator")) {
return new RuntimeBeanReference(element.getAttribute("validator"));
}
else if (jsr303Present) {
RootBeanDefinition validatorDef = new RootBeanDefinition(LocalValidatorFactoryBean.class);
validatorDef.setSource(source);
validatorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String validatorName = parserContext.getReaderContext().registerWithGeneratedName(validatorDef);
parserContext.registerComponent(new BeanComponentDefinition(validatorDef, validatorName));
return new RuntimeBeanReference(validatorName);
}
else {
return null;
}
}
private RuntimeBeanReference getMessageCodesResolver(Element element, Object source, ParserContext parserContext) {
if (element.hasAttribute("message-codes-resolver")) {
return new RuntimeBeanReference(element.getAttribute("message-codes-resolver"));
} else {
return null;
}
}
private ManagedList<?> getArgumentResolvers(Element element, Object source, ParserContext parserContext) {
Element resolversElement = DomUtils.getChildElementByTagName(element, "argument-resolvers");
if (resolversElement != null) {
ManagedList<BeanDefinitionHolder> argumentResolvers = extractBeanSubElements(resolversElement, parserContext);
return wrapWebArgumentResolverBeanDefs(argumentResolvers);
}
return null;
}
private ManagedList<?> getReturnValueHandlers(Element element, Object source, ParserContext parserContext) {
Element handlersElement = DomUtils.getChildElementByTagName(element, "return-value-handlers");
if (handlersElement != null) {
return extractBeanSubElements(handlersElement, parserContext);
}
return null;
}
private ManagedList<?> getMessageConverters(Element element, Object source, ParserContext parserContext) {
Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
ManagedList<? super Object> messageConverters = new ManagedList<Object>();
if (convertersElement != null) {
messageConverters.setSource(source);
for (Element converter : DomUtils.getChildElementsByTagName(convertersElement, "bean")) {
BeanDefinitionHolder beanDef = parserContext.getDelegate().parseBeanDefinitionElement(converter);
beanDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(converter, beanDef);
messageConverters.add(beanDef);
}
}
if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {
messageConverters.setSource(source);
messageConverters.add(createConverterBeanDefinition(ByteArrayHttpMessageConverter.class, source));
RootBeanDefinition stringConverterDef = createConverterBeanDefinition(StringHttpMessageConverter.class,
source);
stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
messageConverters.add(stringConverterDef);
messageConverters.add(createConverterBeanDefinition(ResourceHttpMessageConverter.class, source));
messageConverters.add(createConverterBeanDefinition(SourceHttpMessageConverter.class, source));
messageConverters.add(createConverterBeanDefinition(XmlAwareFormHttpMessageConverter.class, source));
if (jaxb2Present) {
messageConverters
.add(createConverterBeanDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
}
if (jacksonPresent) {
messageConverters.add(createConverterBeanDefinition(MappingJacksonHttpMessageConverter.class, source));
}
if (romePresent) {
messageConverters.add(createConverterBeanDefinition(AtomFeedHttpMessageConverter.class, source));
messageConverters.add(createConverterBeanDefinition(RssChannelHttpMessageConverter.class, source));
}
}
return messageConverters;
}
private RootBeanDefinition createConverterBeanDefinition(Class<? extends HttpMessageConverter> converterClass,
Object source) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(converterClass);
beanDefinition.setSource(source);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
return beanDefinition;
}
private ManagedList<BeanDefinitionHolder> extractBeanSubElements(Element parentElement, ParserContext parserContext) {
ManagedList<BeanDefinitionHolder> list = new ManagedList<BeanDefinitionHolder>();
list.setSource(parserContext.extractSource(parentElement));
for (Element beanElement : DomUtils.getChildElementsByTagName(parentElement, "bean")) {
BeanDefinitionHolder beanDef = parserContext.getDelegate().parseBeanDefinitionElement(beanElement);
beanDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(beanElement, beanDef);
list.add(beanDef);
}
return list;
}
private ManagedList<BeanDefinitionHolder> wrapWebArgumentResolverBeanDefs(List<BeanDefinitionHolder> beanDefs) {
ManagedList<BeanDefinitionHolder> result = new ManagedList<BeanDefinitionHolder>();
for (BeanDefinitionHolder beanDef : beanDefs) {
String className = beanDef.getBeanDefinition().getBeanClassName();
Class<?> clazz = ClassUtils.resolveClassName(className, ClassUtils.getDefaultClassLoader());
if (WebArgumentResolver.class.isAssignableFrom(clazz)) {
RootBeanDefinition adapter = new RootBeanDefinition(ServletWebArgumentResolverAdapter.class);
adapter.getConstructorArgumentValues().addIndexedArgumentValue(0, beanDef);
result.add(new BeanDefinitionHolder(adapter, beanDef.getBeanName() + "Adapter"));
} else {
result.add(beanDef);
}
}
return result;
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import java.util.Map;
import org.w3c.dom.Element;
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.resource.DefaultServletHttpRequestHandler;
/**
* {@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}.
*
* @author Jeremy Grelle
* @author Rossen Stoyanchev
* @since 3.0.4
*/
class DefaultServletHandlerBeanDefinitionParser implements BeanDefinitionParser {
public BeanDefinition parse(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));
// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
return null;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import java.util.List;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
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.xml.DomUtils;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.w3c.dom.Element;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses a {@code interceptors} element to register
* a set of {@link MappedInterceptor} definitions.
*
* @author Keith Donald
* @since 3.0
*/
class InterceptorsBeanDefinitionParser implements BeanDefinitionParser {
public BeanDefinition parse(Element element, ParserContext parserContext) {
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compDefinition);
List<Element> interceptors = DomUtils.getChildElementsByTagName(element, new String[] { "bean", "ref", "interceptor" });
for (Element interceptor : interceptors) {
RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
mappedInterceptorDef.setSource(parserContext.extractSource(interceptor));
mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String[] pathPatterns;
Object interceptorBean;
if ("interceptor".equals(interceptor.getLocalName())) {
List<Element> paths = DomUtils.getChildElementsByTagName(interceptor, "mapping");
pathPatterns = new String[paths.size()];
for (int i = 0; i < paths.size(); i++) {
pathPatterns[i] = paths.get(i).getAttribute("path");
}
Element beanElem = DomUtils.getChildElementsByTagName(interceptor, new String[] { "bean", "ref"}).get(0);
interceptorBean = parserContext.getDelegate().parsePropertySubElement(beanElem, null);
}
else {
pathPatterns = null;
interceptorBean = parserContext.getDelegate().parsePropertySubElement(interceptor, null);
}
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, pathPatterns);
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, interceptorBean);
String beanName = parserContext.getReaderContext().registerWithGeneratedName(mappedInterceptorDef);
parserContext.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, beanName));
}
parserContext.popAndRegisterContainingComponent();
return null;
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import org.springframework.beans.factory.xml.NamespaceHandler;
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

@@ -0,0 +1,93 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.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.ParserContext;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
/**
* Convenience methods for use in MVC namespace BeanDefinitionParsers.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
abstract class MvcNamespaceUtils {
private static final String BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME =
BeanNameUrlHandlerMapping.class.getName();
private static final String SIMPLE_CONTROLLER_HANDLER_ADAPTER_BEAN_NAME =
SimpleControllerHandlerAdapter.class.getName();
private static final String HTTP_REQUEST_HANDLER_ADAPTER_BEAN_NAME =
HttpRequestHandlerAdapter.class.getName();
public static void registerDefaultComponents(ParserContext parserContext, Object source) {
registerBeanNameUrlHandlerMapping(parserContext, source);
registerHttpRequestHandlerAdapter(parserContext, source);
registerSimpleControllerHandlerAdapter(parserContext, source);
}
/**
* Registers an {@link HttpRequestHandlerAdapter} under a well-known
* name unless already registered.
*/
private static void registerBeanNameUrlHandlerMapping(ParserContext parserContext, Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME)){
RootBeanDefinition beanNameMappingDef = new RootBeanDefinition(BeanNameUrlHandlerMapping.class);
beanNameMappingDef.setSource(source);
beanNameMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
beanNameMappingDef.getPropertyValues().add("order", 2); // consistent with WebMvcConfigurationSupport
parserContext.getRegistry().registerBeanDefinition(BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME, beanNameMappingDef);
parserContext.registerComponent(new BeanComponentDefinition(beanNameMappingDef, BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME));
}
}
/**
* Registers an {@link HttpRequestHandlerAdapter} under a well-known
* name unless already registered.
*/
private static void registerHttpRequestHandlerAdapter(ParserContext parserContext, Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(HTTP_REQUEST_HANDLER_ADAPTER_BEAN_NAME)) {
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(HttpRequestHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(HTTP_REQUEST_HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HTTP_REQUEST_HANDLER_ADAPTER_BEAN_NAME));
}
}
/**
* Registers a {@link SimpleControllerHandlerAdapter} under a well-known
* name unless already registered.
*/
private static void registerSimpleControllerHandlerAdapter(ParserContext parserContext, Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(SIMPLE_CONTROLLER_HANDLER_ADAPTER_BEAN_NAME)) {
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(SimpleControllerHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(SIMPLE_CONTROLLER_HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, SIMPLE_CONTROLLER_HANDLER_ADAPTER_BEAN_NAME));
}
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import java.util.Map;
import org.w3c.dom.Element;
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.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.resource.ResourceHttpRequestHandler;
/**
* {@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,
* and a {@link HttpRequestHandlerAdapter}.
*
* @author Keith Donald
* @author Jeremy Grelle
* @since 3.0.4
*/
class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
String resourceHandlerName = registerResourceHandler(parserContext, element, source);
if (resourceHandlerName == null) {
return null;
}
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 null;
}
urlMap.put(resourceRequestPath, resourceHandlerName);
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
String order = element.getAttribute("order");
// use a default of near-lowest precedence, still allowing for even lower precedence in other mappings
handlerMappingDef.getPropertyValues().add("order", StringUtils.hasText(order) ? order : Ordered.LOWEST_PRECEDENCE - 1);
String beanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
parserContext.getRegistry().registerBeanDefinition(beanName, handlerMappingDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, beanName));
// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
// Register HttpRequestHandlerAdapter
MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
return null;
}
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 null;
}
RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class);
resourceHandlerDef.setSource(source);
resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
resourceHandlerDef.getPropertyValues().add("locations", StringUtils.commaDelimitedListToStringArray(locationAttr));
String cacheSeconds = element.getAttribute("cache-period");
if (StringUtils.hasText(cacheSeconds)) {
resourceHandlerDef.getPropertyValues().add("cacheSeconds", cacheSeconds);
}
String beanName = parserContext.getReaderContext().generateBeanName(resourceHandlerDef);
parserContext.getRegistry().registerBeanDefinition(beanName, resourceHandlerDef);
parserContext.registerComponent(new BeanComponentDefinition(resourceHandlerDef, beanName));
return beanName;
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.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.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.w3c.dom.Element;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses a
* {@code view-controller} element to register a {@link ParameterizableViewController}.
* Will also register a {@link SimpleUrlHandlerMapping} for view controllers.
*
* @author Keith Donald
* @author Christian Dupuis
* @since 3.0
*/
class ViewControllerBeanDefinitionParser implements BeanDefinitionParser {
private static final String HANDLER_MAPPING_BEAN_NAME =
"org.springframework.web.servlet.config.viewControllerHandlerMapping";
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
// Register SimpleUrlHandlerMapping for view controllers
BeanDefinition handlerMappingDef = registerHandlerMapping(parserContext, source);
// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
// Create view controller bean definition
RootBeanDefinition viewControllerDef = new RootBeanDefinition(ParameterizableViewController.class);
viewControllerDef.setSource(source);
if (element.hasAttribute("view-name")) {
viewControllerDef.getPropertyValues().add("viewName", element.getAttribute("view-name"));
}
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);
}
urlMap.put(element.getAttribute("path"), viewControllerDef);
return null;
}
private BeanDefinition registerHandlerMapping(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", "1");
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);
}
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config.annotation;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import org.springframework.util.Assert;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
/**
* Configures a request handler for serving static resources by forwarding the request to the Servlet container's
* "default" Servlet. This is indended to be used when the Spring MVC {@link DispatcherServlet} is mapped to "/"
* thus overriding the Servlet container's default handling of static resources. Since this handler is configured
* at the lowest precedence, effectively it allows all other handler mappings to handle the request, and if none
* of them do, this handler can forward it to the "default" Servlet.
*
* @author Rossen Stoyanchev
* @since 3.1
*
* @see DefaultServletHttpRequestHandler
*/
public class DefaultServletHandlerConfigurer {
private final ServletContext servletContext;
private DefaultServletHttpRequestHandler handler;
/**
* Create a {@link DefaultServletHandlerConfigurer} instance.
* @param servletContext the ServletContext to use to configure the underlying DefaultServletHttpRequestHandler.
*/
public DefaultServletHandlerConfigurer(ServletContext servletContext) {
Assert.notNull(servletContext, "A ServletContext is required to configure default servlet handling");
this.servletContext = servletContext;
}
/**
* Enable forwarding to the "default" Servlet. When this method is used the {@link DefaultServletHttpRequestHandler}
* will try to auto-detect the "default" Servlet name. Alternatively, you can specify the name of the default
* Servlet via {@link #enable(String)}.
* @see DefaultServletHttpRequestHandler
*/
public void enable() {
enable(null);
}
/**
* Enable forwarding to the "default" Servlet identified by the given name.
* This is useful when the default Servlet cannot be auto-detected, for example when it has been manually configured.
* @see DefaultServletHttpRequestHandler
*/
public void enable(String defaultServletName) {
handler = new DefaultServletHttpRequestHandler();
handler.setDefaultServletName(defaultServletName);
handler.setServletContext(servletContext);
}
/**
* Return a handler mapping instance ordered at {@link Integer#MAX_VALUE} containing the
* {@link DefaultServletHttpRequestHandler} instance mapped to {@code "/**"}; or {@code null} if
* default servlet handling was not been enabled.
*/
protected AbstractHandlerMapping getHandlerMapping() {
if (handler == null) {
return null;
}
Map<String, HttpRequestHandler> urlMap = new HashMap<String, HttpRequestHandler>();
urlMap.put("/**", handler);
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
handlerMapping.setOrder(Integer.MAX_VALUE);
handlerMapping.setUrlMap(urlMap);
return handlerMapping;
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config.annotation;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
/**
* Extends {@link WebMvcConfigurationSupport} with the ability to detect beans
* of type {@link WebMvcConfigurer} and give them a chance to customize the
* provided configuration by delegating to them at the appropriate times.
*
* @author Rossen Stoyanchev
* @since 3.1
*
* @see EnableWebMvc
*/
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (configurers == null || configurers.isEmpty()) {
return;
}
this.configurers.addWebMvcConfigurers(configurers);
}
@Override
protected final void addInterceptors(InterceptorRegistry registry) {
configurers.addInterceptors(registry);
}
@Override
protected final void addViewControllers(ViewControllerRegistry registry) {
configurers.addViewControllers(registry);
}
@Override
protected final void addResourceHandlers(ResourceHandlerRegistry registry) {
configurers.addResourceHandlers(registry);
}
@Override
protected final void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurers.configureDefaultServletHandling(configurer);
}
@Override
protected final void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
configurers.addArgumentResolvers(argumentResolvers);
}
@Override
protected final void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
configurers.addReturnValueHandlers(returnValueHandlers);
}
@Override
protected final void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
configurers.configureMessageConverters(converters);
}
@Override
protected final void addFormatters(FormatterRegistry registry) {
configurers.addFormatters(registry);
}
@Override
protected final Validator getValidator() {
return configurers.getValidator();
}
@Override
protected final void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
configurers.configureHandlerExceptionResolvers(exceptionResolvers);
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.springframework.web.servlet.config.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
/**
* Add this annotation to an {@code @Configuration} class to have the Spring MVC
* configuration defined in {@link WebMvcConfigurationSupport} imported:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableWebMvc
* &#064;ComponentScan(basePackageClasses = { MyConfiguration.class })
* public class MyWebConfiguration {
*
* }
* </pre>
* <p>Customize the imported configuration by implementing the
* {@link WebMvcConfigurer} interface or more likely by extending the
* {@link WebMvcConfigurerAdapter} base class and overriding individual methods:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableWebMvc
* &#064;ComponentScan(basePackageClasses = { MyConfiguration.class })
* public class MyConfiguration extends WebMvcConfigurerAdapter {
*
* &#064;Override
* public void addFormatters(FormatterRegistry formatterRegistry) {
* formatterRegistry.addConverter(new MyConverter());
* }
*
* &#064;Override
* public void configureMessageConverters(List&lt;HttpMessageConverter&lt;?&gt;&gt; converters) {
* converters.add(new MyHttpMessageConverter());
* }
*
* // More overridden methods ...
* }
* </pre>
*
* <p>If the customization options of {@link WebMvcConfigurer} do not expose
* something you need to configure, consider removing the {@code @EnableWebMvc}
* annotation and extending directly from {@link WebMvcConfigurationSupport}
* overriding selected {@code @Bean} methods:
*
* <pre class="code">
* &#064;Configuration
* &#064;ComponentScan(basePackageClasses = { MyConfiguration.class })
* public class MyConfiguration extends WebMvcConfigurationSupport {
*
* &#064;Override
* public void addFormatters(FormatterRegistry formatterRegistry) {
* formatterRegistry.addConverter(new MyConverter());
* }
*
* &#064;Bean
* public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
* // Create or delegate to "super" to create and
* // customize properties of RequestMapingHandlerAdapter
* }
* }
* </pre>
*
* @see WebMvcConfigurer
* @see WebMvcConfigurerAdapter
*
* @author Dave Syer
* @author Rossen Stoyanchev
* @since 3.1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config.annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.util.Assert;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.MappedInterceptor;
/**
* Encapsulates a {@link HandlerInterceptor} and an optional list of URL patterns.
* Results in the creation of a {@link MappedInterceptor} if URL patterns are provided.
*
* @author Rossen Stoyanchev
* @author Keith Donald
* @since 3.1
*/
public class InterceptorRegistration {
private final HandlerInterceptor interceptor;
private final List<String> pathPatterns = new ArrayList<String>();
/**
* Creates an {@link InterceptorRegistration} instance.
*/
public InterceptorRegistration(HandlerInterceptor interceptor) {
Assert.notNull(interceptor, "Interceptor is required");
this.interceptor = interceptor;
}
/**
* Adds one or more URL patterns to which the registered interceptor should apply to.
* If no URL patterns are provided, the interceptor applies to all paths.
*/
public void addPathPatterns(String... pathPatterns) {
this.pathPatterns.addAll(Arrays.asList(pathPatterns));
}
/**
* Returns the underlying interceptor. If URL patterns are provided the returned type is
* {@link MappedInterceptor}; otherwise {@link HandlerInterceptor}.
*/
protected Object getInterceptor() {
if (pathPatterns.isEmpty()) {
return interceptor;
}
return new MappedInterceptor(pathPatterns.toArray(new String[pathPatterns.size()]), interceptor);
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config.annotation;
import java.util.ArrayList;
import java.util.List;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter;
/**
* Stores and provides access to a list of interceptors. For each interceptor you can optionally
* specify one or more URL patterns it applies to.
*
* @author Rossen Stoyanchev
* @author Keith Donald
*
* @since 3.1
*/
public class InterceptorRegistry {
private final List<InterceptorRegistration> registrations = new ArrayList<InterceptorRegistration>();
/**
* Adds the provided {@link HandlerInterceptor}.
* @param interceptor the interceptor to add
* @return An {@link InterceptorRegistration} that allows you optionally configure the
* registered interceptor further for example adding URL patterns it should apply to.
*/
public InterceptorRegistration addInterceptor(HandlerInterceptor interceptor) {
InterceptorRegistration registration = new InterceptorRegistration(interceptor);
registrations.add(registration);
return registration;
}
/**
* Adds the provided {@link WebRequestInterceptor}.
* @param interceptor the interceptor to add
* @return An {@link InterceptorRegistration} that allows you optionally configure the
* registered interceptor further for example adding URL patterns it should apply to.
*/
public InterceptorRegistration addWebRequestInterceptor(WebRequestInterceptor interceptor) {
WebRequestHandlerInterceptorAdapter adapted = new WebRequestHandlerInterceptorAdapter(interceptor);
InterceptorRegistration registration = new InterceptorRegistration(adapted);
registrations.add(registration);
return registration;
}
/**
* Returns all registered interceptors.
*/
protected List<Object> getInterceptors() {
List<Object> interceptors = new ArrayList<Object>();
for (InterceptorRegistration registration : registrations) {
interceptors.add(registration.getInterceptor());
}
return interceptors ;
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config.annotation;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
/**
* Encapsulates information required to create a resource handlers.
*
* @author Rossen Stoyanchev
* @author Keith Donald
*
* @since 3.1
*/
public class ResourceHandlerRegistration {
private final ResourceLoader resourceLoader;
private final String[] pathPatterns;
private final List<Resource> locations = new ArrayList<Resource>();
private Integer cachePeriod;
/**
* Create a {@link ResourceHandlerRegistration} instance.
* @param resourceLoader a resource loader for turning a String location into a {@link Resource}
* @param pathPatterns one or more resource URL path patterns
*/
public ResourceHandlerRegistration(ResourceLoader resourceLoader, String... pathPatterns) {
Assert.notEmpty(pathPatterns, "At least one path pattern is required for resource handling.");
this.resourceLoader = resourceLoader;
this.pathPatterns = pathPatterns;
}
/**
* Add one or more resource locations from which to serve static content. Each location must point to a valid
* directory. Multiple locations may be specified as a comma-separated list, and the locations will be checked
* for a given resource in the order specified.
* <p>For example, {{@code "/"}, {@code "classpath:/META-INF/public-web-resources/"}} allows resources to
* be served both from the web application root and from any JAR on the classpath that contains a
* {@code /META-INF/public-web-resources/} directory, with resources in the web application root taking precedence.
* @return the same {@link ResourceHandlerRegistration} instance for chained method invocation
*/
public ResourceHandlerRegistration addResourceLocations(String...resourceLocations) {
for (String location : resourceLocations) {
this.locations.add(resourceLoader.getResource(location));
}
return this;
}
/**
* Specify the cache period for the resources served by the resource handler, in seconds. The default is to not
* send any cache headers but to rely on last-modified timestamps only. Set to 0 in order to send cache headers
* that prevent caching, or to a positive number of seconds to send cache headers with the given max-age value.
* @param cachePeriod the time to cache resources in seconds
* @return the same {@link ResourceHandlerRegistration} instance for chained method invocation
*/
public ResourceHandlerRegistration setCachePeriod(Integer cachePeriod) {
this.cachePeriod = cachePeriod;
return this;
}
/**
* Returns the URL path patterns for the resource handler.
*/
protected String[] getPathPatterns() {
return pathPatterns;
}
/**
* Returns a {@link ResourceHttpRequestHandler} instance.
*/
protected ResourceHttpRequestHandler getRequestHandler() {
Assert.isTrue(!CollectionUtils.isEmpty(locations), "At least one location is required for resource handling.");
ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
requestHandler.setLocations(locations);
if (cachePeriod != null) {
requestHandler.setCacheSeconds(cachePeriod);
}
return requestHandler;
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config.annotation;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
/**
* Stores registrations of resource handlers for serving static resources such as images, css files and others
* through Spring MVC including setting cache headers optimized for efficient loading in a web browser.
* Resources can be served out of locations under web application root, from the classpath, and others.
*
* <p>To create a resource handler, use {@link #addResourceHandler(String...)} providing the URL path patterns
* for which the handler should be invoked to serve static resources (e.g. {@code "/resources/**"}).
*
* <p>Then use additional methods on the returned {@link ResourceHandlerRegistration} to add one or more
* locations from which to serve static content from (e.g. {{@code "/"},
* {@code "classpath:/META-INF/public-web-resources/"}}) or to specify a cache period for served resources.
*
* @author Rossen Stoyanchev
* @since 3.1
*
* @see DefaultServletHandlerConfigurer
*/
public class ResourceHandlerRegistry {
private final ServletContext servletContext;
private final ApplicationContext applicationContext;
private final List<ResourceHandlerRegistration> registrations = new ArrayList<ResourceHandlerRegistration>();
private int order = Integer.MAX_VALUE -1;
public ResourceHandlerRegistry(ApplicationContext applicationContext, ServletContext servletContext) {
Assert.notNull(applicationContext, "ApplicationContext is required");
this.applicationContext = applicationContext;
this.servletContext = servletContext;
}
/**
* Add a resource handler for serving static resources based on the specified URL path patterns.
* The handler will be invoked for every incoming request that matches to one of the specified path patterns.
* @return A {@link ResourceHandlerRegistration} to use to further configure the registered resource handler.
*/
public ResourceHandlerRegistration addResourceHandler(String... pathPatterns) {
ResourceHandlerRegistration registration = new ResourceHandlerRegistration(applicationContext, pathPatterns);
registrations.add(registration);
return registration;
}
/**
* Specify the order to use for resource handling relative to other {@link HandlerMapping}s configured in
* the Spring MVC application context. The default value used is {@code Integer.MAX_VALUE-1}.
*/
public ResourceHandlerRegistry setOrder(int order) {
this.order = order;
return this;
}
/**
* Return a handler mapping with the mapped resource handlers; or {@code null} in case of no registrations.
*/
protected AbstractHandlerMapping getHandlerMapping() {
if (registrations.isEmpty()) {
return null;
}
Map<String, HttpRequestHandler> urlMap = new LinkedHashMap<String, HttpRequestHandler>();
for (ResourceHandlerRegistration registration : registrations) {
for (String pathPattern : registration.getPathPatterns()) {
ResourceHttpRequestHandler requestHandler = registration.getRequestHandler();
requestHandler.setServletContext(servletContext);
requestHandler.setApplicationContext(applicationContext);
urlMap.put(pathPattern, requestHandler);
}
}
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
handlerMapping.setOrder(order);
handlerMapping.setUrlMap(urlMap);
return handlerMapping;
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config.annotation;
import org.springframework.util.Assert;
import org.springframework.web.servlet.RequestToViewNameTranslator;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
/**
* Encapsulates information required to create a view controller.
*
* @author Rossen Stoyanchev
* @author Keith Donald
* @since 3.1
*/
public class ViewControllerRegistration {
private final String urlPath;
private String viewName;
/**
* Creates a {@link ViewControllerRegistration} with the given URL path. When a request matches
* to the given URL path this view controller will process it.
*/
public ViewControllerRegistration(String urlPath) {
Assert.notNull(urlPath, "A URL path is required to create a view controller.");
this.urlPath = urlPath;
}
/**
* Sets the view name to use for this view controller. This field is optional. If not specified the
* view controller will return a {@code null} view name, which will be resolved through the configured
* {@link RequestToViewNameTranslator}. By default that means "/foo/bar" would resolve to "foo/bar".
*/
public void setViewName(String viewName) {
this.viewName = viewName;
}
/**
* Returns the URL path for the view controller.
*/
protected String getUrlPath() {
return urlPath;
}
/**
* Returns the view controllers.
*/
protected Object getViewController() {
ParameterizableViewController controller = new ParameterizableViewController();
controller.setViewName(viewName);
return controller;
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config.annotation;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
/**
* Stores registrations of view controllers. A view controller does nothing more than return a specified
* view name. It saves you from having to write a controller when you want to forward the request straight
* through to a view such as a JSP.
*
* @author Rossen Stoyanchev
* @author Keith Donald
* @since 3.1
*/
public class ViewControllerRegistry {
private final List<ViewControllerRegistration> registrations = new ArrayList<ViewControllerRegistration>();
private int order = 1;
public ViewControllerRegistration addViewController(String urlPath) {
ViewControllerRegistration registration = new ViewControllerRegistration(urlPath);
registrations.add(registration);
return registration;
}
/**
* Specify the order to use for ViewControllers mappings relative to other {@link HandlerMapping}s
* configured in the Spring MVC application context. The default value for view controllers is 1,
* which is 1 higher than the value used for annotated controllers.
*/
public void setOrder(int order) {
this.order = order;
}
/**
* Returns a handler mapping with the mapped ViewControllers; or {@code null} in case of no registrations.
*/
protected AbstractHandlerMapping getHandlerMapping() {
if (registrations.isEmpty()) {
return null;
}
Map<String, Object> urlMap = new LinkedHashMap<String, Object>();
for (ViewControllerRegistration registration : registrations) {
urlMap.put(registration.getUrlPath(), registration.getViewController());
}
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
handlerMapping.setOrder(order);
handlerMapping.setUrlMap(urlMap);
return handlerMapping;
}
}

View File

@@ -0,0 +1,534 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config.annotation;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.xml.transform.Source;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
import org.springframework.util.ClassUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor;
import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
/**
* This is the main class providing the configuration behind the MVC Java config.
* It is typically imported by adding {@link EnableWebMvc @EnableWebMvc} to an
* application {@link Configuration @Configuration} class. An alternative more
* advanced option is to extend directly from this class and override methods as
* necessary remembering to add {@link Configuration @Configuration} to the
* subclass and {@link Bean @Bean} to overridden {@link Bean @Bean} methods.
* For more details see the Javadoc of {@link EnableWebMvc @EnableWebMvc}.
*
* <p>This class registers the following {@link HandlerMapping}s:</p>
* <ul>
* <li>{@link RequestMappingHandlerMapping}
* ordered at 0 for mapping requests to annotated controller methods.
* <li>{@link HandlerMapping}
* ordered at 1 to map URL paths directly to view names.
* <li>{@link BeanNameUrlHandlerMapping}
* ordered at 2 to map URL paths to controller bean names.
* <li>{@link HandlerMapping}
* ordered at {@code Integer.MAX_VALUE-1} to serve static resource requests.
* <li>{@link HandlerMapping}
* ordered at {@code Integer.MAX_VALUE} to forward requests to the default servlet.
* </ul>
*
* <p>Registers these {@link HandlerAdapter}s:
* <ul>
* <li>{@link RequestMappingHandlerAdapter}
* for processing requests with annotated controller methods.
* <li>{@link HttpRequestHandlerAdapter}
* for processing requests with {@link HttpRequestHandler}s.
* <li>{@link SimpleControllerHandlerAdapter}
* for processing requests with interface-based {@link Controller}s.
* </ul>
*
* <p>Registers a {@link HandlerExceptionResolverComposite} with this chain of
* exception resolvers:
* <ul>
* <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions
* through @{@link ExceptionHandler} methods.
* <li>{@link ResponseStatusExceptionResolver} for exceptions annotated
* with @{@link ResponseStatus}.
* <li>{@link DefaultHandlerExceptionResolver} for resolving known Spring
* exception types
* </ul>
*
* <p>Both the {@link RequestMappingHandlerAdapter} and the
* {@link ExceptionHandlerExceptionResolver} are configured with default
* instances of the following kind, unless custom instances are provided:
* <ul>
* <li>A {@link DefaultFormattingConversionService}
* <li>A {@link LocalValidatorFactoryBean} if a JSR-303 implementation is
* available on the classpath
* <li>A range of {@link HttpMessageConverter}s depending on the 3rd party
* libraries available on the classpath.
* </ul>
*
* @see EnableWebMvc
* @see WebMvcConfigurer
* @see WebMvcConfigurerAdapter
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public abstract class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
private ServletContext servletContext;
private ApplicationContext applicationContext;
private List<Object> interceptors;
private List<HttpMessageConverter<?>> messageConverters;
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* Return a {@link RequestMappingHandlerMapping} ordered at 0 for mapping
* requests to annotated controllers.
*/
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping handlerMapping = new RequestMappingHandlerMapping();
handlerMapping.setOrder(0);
handlerMapping.setInterceptors(getInterceptors());
return handlerMapping;
}
/**
* Provide access to the shared handler interceptors used to configure
* {@link HandlerMapping} instances with. This method cannot be overridden,
* use {@link #addInterceptors(InterceptorRegistry)} instead.
*/
protected final Object[] getInterceptors() {
if (interceptors == null) {
InterceptorRegistry registry = new InterceptorRegistry();
addInterceptors(registry);
registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService()));
interceptors = registry.getInterceptors();
}
return interceptors.toArray();
}
/**
* Override this method to add Spring MVC interceptors for
* pre- and post-processing of controller invocation.
* @see InterceptorRegistry
*/
protected void addInterceptors(InterceptorRegistry registry) {
}
/**
* Return a handler mapping ordered at 1 to map URL paths directly to
* view names. To configure view controllers, override
* {@link #addViewControllers}.
*/
@Bean
public HandlerMapping viewControllerHandlerMapping() {
ViewControllerRegistry registry = new ViewControllerRegistry();
addViewControllers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
handlerMapping = handlerMapping != null ? handlerMapping : new EmptyHandlerMapping();
handlerMapping.setInterceptors(getInterceptors());
return handlerMapping;
}
/**
* Override this method to add view controllers.
* @see ViewControllerRegistry
*/
protected void addViewControllers(ViewControllerRegistry registry) {
}
/**
* Return a {@link BeanNameUrlHandlerMapping} ordered at 2 to map URL
* paths to controller bean names.
*/
@Bean
public BeanNameUrlHandlerMapping beanNameHandlerMapping() {
BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
mapping.setOrder(2);
mapping.setInterceptors(getInterceptors());
return mapping;
}
/**
* Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped
* resource handlers. To configure resource handling, override
* {@link #addResourceHandlers}.
*/
@Bean
public HandlerMapping resourceHandlerMapping() {
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(applicationContext, servletContext);
addResourceHandlers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
handlerMapping = handlerMapping != null ? handlerMapping : new EmptyHandlerMapping();
return handlerMapping;
}
/**
* Override this method to add resource handlers for serving static resources.
* @see ResourceHandlerRegistry
*/
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
}
/**
* Return a handler mapping ordered at Integer.MAX_VALUE with a mapped
* default servlet handler. To configure "default" Servlet handling,
* override {@link #configureDefaultServletHandling}.
*/
@Bean
public HandlerMapping defaultServletHandlerMapping() {
DefaultServletHandlerConfigurer configurer = new DefaultServletHandlerConfigurer(servletContext);
configureDefaultServletHandling(configurer);
AbstractHandlerMapping handlerMapping = configurer.getHandlerMapping();
handlerMapping = handlerMapping != null ? handlerMapping : new EmptyHandlerMapping();
return handlerMapping;
}
/**
* Override this method to configure "default" Servlet handling.
* @see DefaultServletHandlerConfigurer
*/
protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
/**
* Returns a {@link RequestMappingHandlerAdapter} for processing requests
* through annotated controller methods. Consider overriding one of these
* other more fine-grained methods:
* <ul>
* <li>{@link #addArgumentResolvers} for adding custom argument resolvers.
* <li>{@link #addReturnValueHandlers} for adding custom return value handlers.
* <li>{@link #configureMessageConverters} for adding custom message converters.
* </ul>
*/
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
ConfigurableWebBindingInitializer webBindingInitializer = new ConfigurableWebBindingInitializer();
webBindingInitializer.setConversionService(mvcConversionService());
webBindingInitializer.setValidator(mvcValidator());
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>();
addArgumentResolvers(argumentResolvers);
List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
addReturnValueHandlers(returnValueHandlers);
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
adapter.setMessageConverters(getMessageConverters());
adapter.setWebBindingInitializer(webBindingInitializer);
adapter.setCustomArgumentResolvers(argumentResolvers);
adapter.setCustomReturnValueHandlers(returnValueHandlers);
return adapter;
}
/**
* Add custom {@link HandlerMethodArgumentResolver}s to use in addition to
* the ones registered by default.
* <p>Custom argument resolvers are invoked before built-in resolvers
* except for those that rely on the presence of annotations (e.g.
* {@code @RequestParameter}, {@code @PathVariable}, etc.).
* The latter can be customized by configuring the
* {@link RequestMappingHandlerAdapter} directly.
* @param argumentResolvers the list of custom converters;
* initially an empty list.
*/
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
}
/**
* Add custom {@link HandlerMethodReturnValueHandler}s in addition to the
* ones registered by default.
* <p>Custom return value handlers are invoked before built-in ones except
* for those that rely on the presence of annotations (e.g.
* {@code @ResponseBody}, {@code @ModelAttribute}, etc.).
* The latter can be customized by configuring the
* {@link RequestMappingHandlerAdapter} directly.
* @param returnValueHandlers the list of custom handlers;
* initially an empty list.
*/
protected void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
}
/**
* Provides access to the shared {@link HttpMessageConverter}s used by the
* {@link RequestMappingHandlerAdapter} and the
* {@link ExceptionHandlerExceptionResolver}.
* This method cannot be overridden.
* Use {@link #configureMessageConverters(List)} instead.
* Also see {@link #addDefaultHttpMessageConverters(List)} that can be
* used to add default message converters.
*/
protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (messageConverters == null) {
messageConverters = new ArrayList<HttpMessageConverter<?>>();
configureMessageConverters(messageConverters);
if (messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(messageConverters);
}
}
return messageConverters;
}
/**
* Override this method to add custom {@link HttpMessageConverter}s to use
* with the {@link RequestMappingHandlerAdapter} and the
* {@link ExceptionHandlerExceptionResolver}. Adding converters to the
* list turns off the default converters that would otherwise be registered
* by default. Also see {@link #addDefaultHttpMessageConverters(List)} that
* can be used to add default message converters.
* @param converters a list to add message converters to;
* initially an empty list.
*/
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
/**
* Adds a set of default HttpMessageConverter instances to the given list.
* Subclasses can call this method from {@link #configureMessageConverters(List)}.
* @param messageConverters the list to add the default message converters to
*/
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setWriteAcceptCharset(false);
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(stringConverter);
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new SourceHttpMessageConverter<Source>());
messageConverters.add(new XmlAwareFormHttpMessageConverter());
ClassLoader classLoader = getClass().getClassLoader();
if (ClassUtils.isPresent("javax.xml.bind.Binder", classLoader)) {
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if (ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", classLoader)) {
messageConverters.add(new MappingJacksonHttpMessageConverter());
}
if (ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", classLoader)) {
messageConverters.add(new AtomFeedHttpMessageConverter());
messageConverters.add(new RssChannelHttpMessageConverter());
}
}
/**
* Returns a {@link FormattingConversionService} for use with annotated
* controller methods and the {@code spring:eval} JSP tag.
* Also see {@link #addFormatters} as an alternative to overriding this method.
*/
@Bean
public FormattingConversionService mvcConversionService() {
FormattingConversionService conversionService = new DefaultFormattingConversionService();
addFormatters(conversionService);
return conversionService;
}
/**
* Override this method to add custom {@link Converter}s and {@link Formatter}s.
*/
protected void addFormatters(FormatterRegistry registry) {
}
/**
* Returns a global {@link Validator} instance for example for validating
* {@code @ModelAttribute} and {@code @RequestBody} method arguments.
* Delegates to {@link #getValidator()} first and if that returns {@code null}
* checks the classpath for the presence of a JSR-303 implementations
* before creating a {@code LocalValidatorFactoryBean}.If a JSR-303
* implementation is not available, a no-op {@link Validator} is returned.
*/
@Bean
public Validator mvcValidator() {
Validator validator = getValidator();
if (validator == null) {
if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) {
Class<?> clazz;
try {
String className = "org.springframework.validation.beanvalidation.LocalValidatorFactoryBean";
clazz = ClassUtils.forName(className, WebMvcConfigurationSupport.class.getClassLoader());
} catch (ClassNotFoundException e) {
throw new BeanInitializationException("Could not find default validator");
} catch (LinkageError e) {
throw new BeanInitializationException("Could not find default validator");
}
validator = (Validator) BeanUtils.instantiate(clazz);
}
else {
validator = new Validator() {
public boolean supports(Class<?> clazz) {
return false;
}
public void validate(Object target, Errors errors) {
}
};
}
}
return validator;
}
/**
* Override this method to provide a custom {@link Validator}.
*/
protected Validator getValidator() {
return null;
}
/**
* Returns a {@link HttpRequestHandlerAdapter} for processing requests
* with {@link HttpRequestHandler}s.
*/
@Bean
public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
return new HttpRequestHandlerAdapter();
}
/**
* Returns a {@link SimpleControllerHandlerAdapter} for processing requests
* with interface-based controllers.
*/
@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
/**
* Returns a {@link HandlerExceptionResolverComposite} containing a list
* of exception resolvers obtained either through
* {@link #configureHandlerExceptionResolvers(List)} or through
* {@link #addDefaultHandlerExceptionResolvers(List)}.
* <p><strong>Note:</strong> This method cannot be made final due to CGLib
* constraints. Rather than overriding it, consider overriding
* {@link #configureHandlerExceptionResolvers(List)}, which allows
* providing a list of resolvers.
*/
@Bean
public HandlerExceptionResolver handlerExceptionResolver() throws Exception {
List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<HandlerExceptionResolver>();
configureHandlerExceptionResolvers(exceptionResolvers);
if (exceptionResolvers.isEmpty()) {
addDefaultHandlerExceptionResolvers(exceptionResolvers);
}
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
composite.setOrder(0);
composite.setExceptionResolvers(exceptionResolvers);
return composite;
}
/**
* Override this method to configure the list of
* {@link HandlerExceptionResolver}s to use. Adding resolvers to the list
* turns off the default resolvers that would otherwise be registered by
* default. Also see {@link #addDefaultHandlerExceptionResolvers(List)}
* that can be used to add the default exception resolvers.
* @param exceptionResolvers a list to add exception resolvers to;
* initially an empty list.
*/
protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
}
/**
* A method available to subclasses for adding default
* {@link HandlerExceptionResolver}s.
* <p>Adds the following exception resolvers:
* <ul>
* <li>{@link ExceptionHandlerExceptionResolver}
* for handling exceptions through @{@link ExceptionHandler} methods.
* <li>{@link ResponseStatusExceptionResolver}
* for exceptions annotated with @{@link ResponseStatus}.
* <li>{@link DefaultHandlerExceptionResolver}
* for resolving known Spring exception types
* </ul>
*/
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver = new ExceptionHandlerExceptionResolver();
exceptionHandlerExceptionResolver.setMessageConverters(getMessageConverters());
exceptionHandlerExceptionResolver.afterPropertiesSet();
exceptionResolvers.add(exceptionHandlerExceptionResolver);
exceptionResolvers.add(new ResponseStatusExceptionResolver());
exceptionResolvers.add(new DefaultHandlerExceptionResolver());
}
private final static class EmptyHandlerMapping extends AbstractHandlerMapping {
@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
return null;
}
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config.annotation;
import java.util.List;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
/**
* Defines callback methods to customize the Java-based configuration for
* Spring MVC enabled via {@code @EnableWebMvc}.
*
* <p>{@code @EnableWebMvc}-annotated configuration classes may implement
* this interface to be called back and given a chance to customize the
* default configuration. Consider extending {@link WebMvcConfigurerAdapter},
* which provides a stub implementation of all interface methods.
*
* @author Rossen Stoyanchev
* @author Keith Donald
* @author David Syer
* @since 3.1
*/
public interface WebMvcConfigurer {
/**
* Add {@link Converter}s and {@link Formatter}s in addition to the ones
* registered by default.
*/
void addFormatters(FormatterRegistry registry);
/**
* Configure the {@link HttpMessageConverter}s to use in argument resolvers
* and return value handlers that support reading and/or writing to the
* body of the request and response. If no message converters are added to
* the list, default converters are added instead.
* @param converters initially an empty list of converters
*/
void configureMessageConverters(List<HttpMessageConverter<?>> converters);
/**
* Provide a custom {@link Validator} instead of the one created by default.
* The default implementation, assuming JSR-303 is on the classpath, is:
* {@link org.springframework.validation.beanvalidation.LocalValidatorFactoryBean}.
* Leave the return value as {@code null} to keep the default.
*/
Validator getValidator();
/**
* Add resolvers to support custom controller method argument types.
* <p>This does not override the built-in support for resolving handler
* method arguments. To customize the built-in support for argument
* resolution, configure {@link RequestMappingHandlerAdapter} directly.
* @param argumentResolvers initially an empty list
*/
void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers);
/**
* Add handlers to support custom controller method return value types.
* <p>Using this option does not override the built-in support for handling
* return values. To customize the built-in support for handling return
* values, configure RequestMappingHandlerAdapter directly.
* @param returnValueHandlers initially an empty list
*/
void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers);
/**
* Configure the {@link HandlerExceptionResolver}s to handle unresolved
* controller exceptions. If no resolvers are added to the list, default
* exception resolvers are added instead.
* @param exceptionResolvers initially an empty list
*/
void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers);
/**
* Add Spring MVC lifecycle interceptors for pre- and post-processing of
* controller method invocations. Interceptors can be registered to apply
* to all requests or be limited to a subset of URL patterns.
*/
void addInterceptors(InterceptorRegistry registry);
/**
* Add view controllers to create a direct mapping between a URL path and
* view name without the need for a controller in between.
*/
void addViewControllers(ViewControllerRegistry registry);
/**
* Add handlers to serve static resources such as images, js, and, css
* files from specific locations under web application root, the classpath,
* and others.
*/
void addResourceHandlers(ResourceHandlerRegistry registry);
/**
* Configure a handler to delegate unhandled requests by forwarding to the
* Servlet container's "default" servlet. A common use case for this is when
* the {@link DispatcherServlet} is mapped to "/" thus overriding the
* Servlet container's default handling of static resources.
*/
void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer);
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config.annotation;
import java.util.List;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
/**
* An convenient base class with empty method implementations of {@link WebMvcConfigurer}.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
/**
* {@inheritDoc}
* <p>This implementation is empty.
*/
public void addFormatters(FormatterRegistry registry) {
}
/**
* {@inheritDoc}
* <p>This implementation is empty.
*/
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
/**
* {@inheritDoc}
* <p>This implementation returns {@code null}
*/
public Validator getValidator() {
return null;
}
/**
* {@inheritDoc}
* <p>This implementation is empty.
*/
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
}
/**
* {@inheritDoc}
* <p>This implementation is empty.
*/
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
}
/**
* {@inheritDoc}
* <p>This implementation is empty.
*/
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
}
/**
* {@inheritDoc}
* <p>This implementation is empty.
*/
public void addInterceptors(InterceptorRegistry registry) {
}
/**
* {@inheritDoc}
* <p>This implementation is empty.
*/
public void addViewControllers(ViewControllerRegistry registry) {
}
/**
* {@inheritDoc}
* <p>This implementation is empty.
*/
public void addResourceHandlers(ResourceHandlerRegistry registry) {
}
/**
* {@inheritDoc}
* <p>This implementation is empty.
*/
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config.annotation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
/**
* An {@link WebMvcConfigurer} implementation that delegates to other {@link WebMvcConfigurer} instances.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
class WebMvcConfigurerComposite implements WebMvcConfigurer {
private final List<WebMvcConfigurer> delegates = new ArrayList<WebMvcConfigurer>();
public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
if (configurers != null) {
this.delegates.addAll(configurers);
}
}
public void addFormatters(FormatterRegistry registry) {
for (WebMvcConfigurer delegate : delegates) {
delegate.addFormatters(registry);
}
}
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
for (WebMvcConfigurer delegate : delegates) {
delegate.configureMessageConverters(converters);
}
}
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
for (WebMvcConfigurer delegate : delegates) {
delegate.addArgumentResolvers(argumentResolvers);
}
}
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
for (WebMvcConfigurer delegate : delegates) {
delegate.addReturnValueHandlers(returnValueHandlers);
}
}
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
for (WebMvcConfigurer delegate : delegates) {
delegate.configureHandlerExceptionResolvers(exceptionResolvers);
}
}
public void addInterceptors(InterceptorRegistry registry) {
for (WebMvcConfigurer delegate : delegates) {
delegate.addInterceptors(registry);
}
}
public void addViewControllers(ViewControllerRegistry registry) {
for (WebMvcConfigurer delegate : delegates) {
delegate.addViewControllers(registry);
}
}
public void addResourceHandlers(ResourceHandlerRegistry registry) {
for (WebMvcConfigurer delegate : delegates) {
delegate.addResourceHandlers(registry);
}
}
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
for (WebMvcConfigurer delegate : delegates) {
delegate.configureDefaultServletHandling(configurer);
}
}
public Validator getValidator() {
Map<WebMvcConfigurer, Validator> validators = new HashMap<WebMvcConfigurer, Validator>();
for (WebMvcConfigurer delegate : delegates) {
Validator validator = delegate.getValidator();
if (validator != null) {
validators.put(delegate, validator);
}
}
if (validators.size() == 0) {
return null;
}
else if (validators.size() == 1) {
return validators.values().iterator().next();
}
else {
throw new IllegalStateException(
"Multiple custom validators provided from [" + validators.keySet() + "]");
}
}
}

View File

@@ -0,0 +1,8 @@
/**
*
* Annotation-based setup for Spring MVC.
*
*/
package org.springframework.web.servlet.config.annotation;

View File

@@ -0,0 +1,8 @@
/**
*
* Defines the XML configuration namespace for Spring MVC.
*
*/
package org.springframework.web.servlet.config;

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContextException;
import org.springframework.util.ObjectUtils;
/**
* Abstract implementation of the {@link org.springframework.web.servlet.HandlerMapping}
* interface, detecting URL mappings for handler beans through introspection of all
* defined beans in the application context.
*
* @author Juergen Hoeller
* @since 2.5
* @see #determineUrlsForHandler
*/
public abstract class AbstractDetectingUrlHandlerMapping extends AbstractUrlHandlerMapping {
private boolean detectHandlersInAncestorContexts = false;
/**
* Set whether to detect handler beans in ancestor ApplicationContexts.
* <p>Default is "false": Only handler beans in the current ApplicationContext
* will be detected, i.e. only in the context that this HandlerMapping itself
* is defined in (typically the current DispatcherServlet's context).
* <p>Switch this flag on to detect handler beans in ancestor contexts
* (typically the Spring root WebApplicationContext) as well.
*/
public void setDetectHandlersInAncestorContexts(boolean detectHandlersInAncestorContexts) {
this.detectHandlersInAncestorContexts = detectHandlersInAncestorContexts;
}
/**
* Calls the {@link #detectHandlers()} method in addition to the
* superclass's initialization.
*/
@Override
public void initApplicationContext() throws ApplicationContextException {
super.initApplicationContext();
detectHandlers();
}
/**
* Register all handlers found in the current ApplicationContext.
* <p>The actual URL determination for a handler is up to the concrete
* {@link #determineUrlsForHandler(String)} implementation. A bean for
* which no such URLs could be determined is simply not considered a handler.
* @throws org.springframework.beans.BeansException if the handler couldn't be registered
* @see #determineUrlsForHandler(String)
*/
protected void detectHandlers() throws BeansException {
if (logger.isDebugEnabled()) {
logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
}
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
// Take any bean name that we can determine URLs for.
for (String beanName : beanNames) {
String[] urls = determineUrlsForHandler(beanName);
if (!ObjectUtils.isEmpty(urls)) {
// URL paths found: Let's consider it a handler.
registerHandler(urls, beanName);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
}
}
}
}
/**
* Determine the URLs for the given handler bean.
* @param beanName the name of the candidate bean
* @return the URLs determined for the bean,
* or <code>null</code> or an empty array if none
*/
protected abstract String[] determineUrlsForHandler(String beanName);
}

View File

@@ -0,0 +1,244 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
/**
* Abstract base class for {@link HandlerExceptionResolver} implementations.
*
* <p>Provides a set of mapped handlers that the resolver should map to,
* and the {@link Ordered} implementation.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 3.0
*/
public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {
private static final String HEADER_PRAGMA = "Pragma";
private static final String HEADER_EXPIRES = "Expires";
private static final String HEADER_CACHE_CONTROL = "Cache-Control";
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private int order = Ordered.LOWEST_PRECEDENCE;
private Set mappedHandlers;
private Class[] mappedHandlerClasses;
private Log warnLogger;
private boolean preventResponseCaching = false;
public void setOrder(int order) {
this.order = order;
}
public int getOrder() {
return this.order;
}
/**
* Specify the set of handlers that this exception resolver should apply to.
* The exception mappings and the default error view will only apply to the specified handlers.
* <p>If no handlers and handler classes are set, the exception mappings and the default error
* view will apply to all handlers. This means that a specified default error view will be used
* as fallback for all exceptions; any further HandlerExceptionResolvers in the chain will be
* ignored in this case.
*/
public void setMappedHandlers(Set mappedHandlers) {
this.mappedHandlers = mappedHandlers;
}
/**
* Specify the set of classes that this exception resolver should apply to.
* The exception mappings and the default error view will only apply to handlers of the
* specified type; the specified types may be interfaces and superclasses of handlers as well.
* <p>If no handlers and handler classes are set, the exception mappings and the default error
* view will apply to all handlers. This means that a specified default error view will be used
* as fallback for all exceptions; any further HandlerExceptionResolvers in the chain will be
* ignored in this case.
*/
public void setMappedHandlerClasses(Class[] mappedHandlerClasses) {
this.mappedHandlerClasses = mappedHandlerClasses;
}
/**
* Set the log category for warn logging. The name will be passed to the underlying logger
* implementation through Commons Logging, getting interpreted as log category according
* to the logger's configuration.
* <p>Default is no warn logging. Specify this setting to activate warn logging into a specific
* category. Alternatively, override the {@link #logException} method for custom logging.
* @see org.apache.commons.logging.LogFactory#getLog(String)
* @see org.apache.log4j.Logger#getLogger(String)
* @see java.util.logging.Logger#getLogger(String)
*/
public void setWarnLogCategory(String loggerName) {
this.warnLogger = LogFactory.getLog(loggerName);
}
/**
* Specify whether to prevent HTTP response caching for any view resolved
* by this HandlerExceptionResolver.
* <p>Default is "false". Switch this to "true" in order to automatically
* generate HTTP response headers that suppress response caching.
*/
public void setPreventResponseCaching(boolean preventResponseCaching) {
this.preventResponseCaching = preventResponseCaching;
}
/**
* Checks whether this resolver is supposed to apply (i.e. the handler matches
* in case of "mappedHandlers" having been specified), then delegates to the
* {@link #doResolveException} template method.
*/
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
// Log exception, both at debug log level and at warn level, if desired.
if (logger.isDebugEnabled()) {
logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
}
logException(ex, request);
prepareResponse(ex, response);
return doResolveException(request, response, handler, ex);
}
else {
return null;
}
}
/**
* Check whether this resolver is supposed to apply to the given handler.
* <p>The default implementation checks against the specified mapped handlers
* and handler classes, if any.
* @param request current HTTP request
* @param handler the executed handler, or <code>null</code> if none chosen
* at the time of the exception (for example, if multipart resolution failed)
* @return whether this resolved should proceed with resolving the exception
* for the given request and handler
* @see #setMappedHandlers
* @see #setMappedHandlerClasses
*/
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
if (handler != null) {
if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
return true;
}
if (this.mappedHandlerClasses != null) {
for (Class handlerClass : this.mappedHandlerClasses) {
if (handlerClass.isInstance(handler)) {
return true;
}
}
}
}
// Else only apply if there are no explicit handler mappings.
return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
}
/**
* Log the given exception at warn level, provided that warn logging has been
* activated through the {@link #setWarnLogCategory "warnLogCategory"} property.
* <p>Calls {@link #buildLogMessage} in order to determine the concrete message to log.
* Always passes the full exception to the logger.
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @see #setWarnLogCategory
* @see #buildLogMessage
* @see org.apache.commons.logging.Log#warn(Object, Throwable)
*/
protected void logException(Exception ex, HttpServletRequest request) {
if (this.warnLogger != null && this.warnLogger.isWarnEnabled()) {
this.warnLogger.warn(buildLogMessage(ex, request), ex);
}
}
/**
* Build a log message for the given exception, occured during processing the given request.
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @return the log message to use
*/
protected String buildLogMessage(Exception ex, HttpServletRequest request) {
return "Handler execution resulted in exception";
}
/**
* Prepare the response for the exceptional case.
* <p>The default implementation prevents the response from being cached,
* if the {@link #setPreventResponseCaching "preventResponseCaching"} property
* has been set to "true".
* @param ex the exception that got thrown during handler execution
* @param response current HTTP response
* @see #preventCaching
*/
protected void prepareResponse(Exception ex, HttpServletResponse response) {
if (this.preventResponseCaching) {
preventCaching(response);
}
}
/**
* Prevents the response from being cached, through setting corresponding
* HTTP headers. See <code>http://www.mnot.net/cache_docs</code>.
* @param response current HTTP response
*/
protected void preventCaching(HttpServletResponse response) {
response.setHeader(HEADER_PRAGMA, "no-cache");
response.setDateHeader(HEADER_EXPIRES, 1L);
response.setHeader(HEADER_CACHE_CONTROL, "no-cache");
response.addHeader(HEADER_CACHE_CONTROL, "no-store");
}
/**
* Actually resolve the given exception that got thrown during on handler execution,
* returning a ModelAndView that represents a specific error page if appropriate.
* <p>May be overridden in subclasses, in order to apply specific exception checks.
* Note that this template method will be invoked <i>after</i> checking whether this
* resolved applies ("mappedHandlers" etc), so an implementation may simply proceed
* with its actual exception handling.
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time
* of the exception (for example, if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to, or <code>null</code> for default processing
*/
protected abstract ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex);
}

View File

@@ -0,0 +1,349 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.core.Ordered;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.context.support.WebApplicationObjectSupport;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.UrlPathHelper;
/**
* Abstract base class for {@link org.springframework.web.servlet.HandlerMapping}
* implementations. Supports ordering, a default handler, handler interceptors,
* including handler interceptors mapped by path patterns.
*
* <p>Note: This base class does <i>not</i> support exposure of the
* {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE}. Support for this attribute
* is up to concrete subclasses, typically based on request URL mappings.
*
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @since 07.04.2003
* @see #getHandlerInternal
* @see #setDefaultHandler
* @see #setAlwaysUseFullPath
* @see #setUrlDecode
* @see org.springframework.util.AntPathMatcher
* @see #setInterceptors
* @see org.springframework.web.servlet.HandlerInterceptor
*/
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
implements HandlerMapping, Ordered {
private int order = Integer.MAX_VALUE; // default: same as non-Ordered
private Object defaultHandler;
private UrlPathHelper urlPathHelper = new UrlPathHelper();
private PathMatcher pathMatcher = new AntPathMatcher();
private final List<Object> interceptors = new ArrayList<Object>();
private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<HandlerInterceptor>();
private final List<MappedInterceptor> mappedInterceptors = new ArrayList<MappedInterceptor>();
/**
* Specify the order value for this HandlerMapping bean.
* <p>Default value is <code>Integer.MAX_VALUE</code>, meaning that it's non-ordered.
* @see org.springframework.core.Ordered#getOrder()
*/
public final void setOrder(int order) {
this.order = order;
}
public final int getOrder() {
return this.order;
}
/**
* Set the default handler for this handler mapping.
* This handler will be returned if no specific mapping was found.
* <p>Default is <code>null</code>, indicating no default handler.
*/
public void setDefaultHandler(Object defaultHandler) {
this.defaultHandler = defaultHandler;
}
/**
* Return the default handler for this handler mapping,
* or <code>null</code> if none.
*/
public Object getDefaultHandler() {
return this.defaultHandler;
}
/**
* Set if URL lookup should always use the full path within the current servlet
* context. Else, the path within the current servlet mapping is used if applicable
* (that is, in the case of a ".../*" servlet mapping in web.xml).
* <p>Default is "false".
* @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
*/
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
}
/**
* Set if context path and request URI should be URL-decoded. Both are returned
* <i>undecoded</i> by the Servlet API, in contrast to the servlet path.
* <p>Uses either the request encoding or the default encoding according
* to the Servlet spec (ISO-8859-1).
* @see org.springframework.web.util.UrlPathHelper#setUrlDecode
*/
public void setUrlDecode(boolean urlDecode) {
this.urlPathHelper.setUrlDecode(urlDecode);
}
/**
* Set the UrlPathHelper to use for resolution of lookup paths.
* <p>Use this to override the default UrlPathHelper with a custom subclass,
* or to share common UrlPathHelper settings across multiple HandlerMappings
* and MethodNameResolvers.
*/
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
this.urlPathHelper = urlPathHelper;
}
/**
* Return the UrlPathHelper implementation to use for resolution of lookup paths.
*/
public UrlPathHelper getUrlPathHelper() {
return urlPathHelper;
}
/**
* Set the PathMatcher implementation to use for matching URL paths
* against registered URL patterns. Default is AntPathMatcher.
* @see org.springframework.util.AntPathMatcher
*/
public void setPathMatcher(PathMatcher pathMatcher) {
Assert.notNull(pathMatcher, "PathMatcher must not be null");
this.pathMatcher = pathMatcher;
}
/**
* Return the PathMatcher implementation to use for matching URL paths
* against registered URL patterns.
*/
public PathMatcher getPathMatcher() {
return this.pathMatcher;
}
/**
* Set the interceptors to apply for all handlers mapped by this handler mapping.
* <p>Supported interceptor types are HandlerInterceptor, WebRequestInterceptor, and MappedInterceptor.
* Mapped interceptors apply only to request URLs that match its path patterns.
* Mapped interceptor beans are also detected by type during initialization.
* @param interceptors array of handler interceptors, or <code>null</code> if none
* @see #adaptInterceptor
* @see org.springframework.web.servlet.HandlerInterceptor
* @see org.springframework.web.context.request.WebRequestInterceptor
*/
public void setInterceptors(Object[] interceptors) {
this.interceptors.addAll(Arrays.asList(interceptors));
}
/**
* Initializes the interceptors.
* @see #extendInterceptors(java.util.List)
* @see #initInterceptors()
*/
@Override
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.mappedInterceptors);
initInterceptors();
}
/**
* Extension hook that subclasses can override to register additional interceptors,
* given the configured interceptors (see {@link #setInterceptors}).
* <p>Will be invoked before {@link #initInterceptors()} adapts the specified
* interceptors into {@link HandlerInterceptor} instances.
* <p>The default implementation is empty.
* @param interceptors the configured interceptor List (never <code>null</code>),
* allowing to add further interceptors before as well as after the existing
* interceptors
*/
protected void extendInterceptors(List<Object> interceptors) {
}
/**
* Detects beans of type {@link MappedInterceptor} and adds them to the list of mapped interceptors.
* This is done in addition to any {@link MappedInterceptor}s that may have been provided via
* {@link #setInterceptors(Object[])}. Subclasses can override this method to change that.
*
* @param mappedInterceptors an empty list to add MappedInterceptor types to
*/
protected void detectMappedInterceptors(List<MappedInterceptor> mappedInterceptors) {
mappedInterceptors.addAll(
BeanFactoryUtils.beansOfTypeIncludingAncestors(
getApplicationContext(),MappedInterceptor.class, true, false).values());
}
/**
* Initialize the specified interceptors, checking for {@link MappedInterceptor}s and adapting
* HandlerInterceptors where necessary.
* @see #setInterceptors
* @see #adaptInterceptor
*/
protected void initInterceptors() {
if (!this.interceptors.isEmpty()) {
for (int i = 0; i < this.interceptors.size(); i++) {
Object interceptor = this.interceptors.get(i);
if (interceptor == null) {
throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
}
if (interceptor instanceof MappedInterceptor) {
mappedInterceptors.add((MappedInterceptor) interceptor);
}
else {
adaptedInterceptors.add(adaptInterceptor(interceptor));
}
}
}
}
/**
* Adapt the given interceptor object to the HandlerInterceptor interface.
* <p>Supported interceptor types are HandlerInterceptor and WebRequestInterceptor.
* Each given WebRequestInterceptor will be wrapped in a WebRequestHandlerInterceptorAdapter.
* Can be overridden in subclasses.
* @param interceptor the specified interceptor object
* @return the interceptor wrapped as HandlerInterceptor
* @see org.springframework.web.servlet.HandlerInterceptor
* @see org.springframework.web.context.request.WebRequestInterceptor
* @see WebRequestHandlerInterceptorAdapter
*/
protected HandlerInterceptor adaptInterceptor(Object interceptor) {
if (interceptor instanceof HandlerInterceptor) {
return (HandlerInterceptor) interceptor;
}
else if (interceptor instanceof WebRequestInterceptor) {
return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
}
else {
throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
}
}
/**
* Return the adapted interceptors as HandlerInterceptor array.
* @return the array of HandlerInterceptors, or <code>null</code> if none
*/
protected final HandlerInterceptor[] getAdaptedInterceptors() {
int count = adaptedInterceptors.size();
return (count > 0) ? adaptedInterceptors.toArray(new HandlerInterceptor[count]) : null;
}
/**
* Return all configured {@link MappedInterceptor}s as an array.
* @return the array of {@link MappedInterceptor}s, or <code>null</code> if none
*/
protected final MappedInterceptor[] getMappedInterceptors() {
int count = mappedInterceptors.size();
return (count > 0) ? mappedInterceptors.toArray(new MappedInterceptor[count]) : null;
}
/**
* Look up a handler for the given request, falling back to the default
* handler if no specific one is found.
* @param request current HTTP request
* @return the corresponding handler instance, or the default handler
* @see #getHandlerInternal
*/
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
return getHandlerExecutionChain(handler, request);
}
/**
* Look up a handler for the given request, returning <code>null</code> if no
* specific one is found. This method is called by {@link #getHandler};
* a <code>null</code> return value will lead to the default handler, if one is set.
* <p>Note: This method may also return a pre-built {@link HandlerExecutionChain},
* combining a handler object with dynamically determined interceptors.
* Statically specified interceptors will get merged into such an existing chain.
* @param request current HTTP request
* @return the corresponding handler instance, or <code>null</code> if none found
* @throws Exception if there is an internal error
*/
protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
/**
* Build a HandlerExecutionChain for the given handler, including applicable interceptors.
* <p>The default implementation simply builds a standard HandlerExecutionChain with
* the given handler, the handler mapping's common interceptors, and any {@link MappedInterceptor}s
* matching to the current request URL. Subclasses may
* override this in order to extend/rearrange the list of interceptors.
* <p><b>NOTE:</b> The passed-in handler object may be a raw handler or a pre-built
* HandlerExecutionChain. This method should handle those two cases explicitly,
* either building a new HandlerExecutionChain or extending the existing chain.
* <p>For simply adding an interceptor, consider calling <code>super.getHandlerExecutionChain</code>
* and invoking {@link HandlerExecutionChain#addInterceptor} on the returned chain object.
* @param handler the resolved handler instance (never <code>null</code>)
* @param request current HTTP request
* @return the HandlerExecutionChain (never <code>null</code>)
* @see #getAdaptedInterceptors()
*/
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain =
(handler instanceof HandlerExecutionChain) ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler);
chain.addInterceptors(getAdaptedInterceptors());
String lookupPath = urlPathHelper.getLookupPathForRequest(request);
for (MappedInterceptor mappedInterceptor : mappedInterceptors) {
if (mappedInterceptor.matches(lookupPath, pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
return chain;
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
/**
* Abstract base class for
* {@link org.springframework.web.servlet.HandlerExceptionResolver HandlerExceptionResolver}
* implementations that support handling exceptions from handlers of type {@link HandlerMethod}.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExceptionResolver {
/**
* Checks if the handler is a {@link HandlerMethod} instance and performs the check against the bean
* instance it contains. If the provided handler is not an instance of {@link HandlerMethod},
* {@code false} is returned instead.
*/
@Override
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
if (handler == null) {
return super.shouldApplyTo(request, handler);
}
else if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
handler = handlerMethod.getBean();
return super.shouldApplyTo(request, handler);
}
else {
return false;
}
}
@Override
protected final ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
}
/**
* Actually resolve the given exception that got thrown during on handler execution,
* returning a ModelAndView that represents a specific error page if appropriate.
* <p>May be overridden in subclasses, in order to apply specific exception checks.
* Note that this template method will be invoked <i>after</i> checking whether this
* resolved applies ("mappedHandlers" etc), so an implementation may simply proceed
* with its actual exception handling.
* @param request current HTTP request
* @param response current HTTP response
* @param handlerMethod the executed handler method, or <code>null</code> if none chosen at the time
* of the exception (for example, if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to, or <code>null</code> for default processing
*/
protected abstract ModelAndView doResolveHandlerMethodException(
HttpServletRequest request, HttpServletResponse response,
HandlerMethod handlerMethod, Exception ex);
}

View File

@@ -0,0 +1,355 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContextException;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.HandlerMethodSelector;
import org.springframework.web.servlet.HandlerMapping;
/**
* Abstract base class for {@link HandlerMapping} implementations that define a
* mapping between a request and a {@link HandlerMethod}.
*
* <p>For each registered handler method, a unique mapping is maintained with
* subclasses defining the details of the mapping type {@code <T>}.
*
* @param <T> The mapping for a {@link HandlerMethod} containing the conditions
* needed to match the handler method to incoming request.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
*/
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping {
private boolean detectHandlerMethodsInAncestorContexts = false;
private final Map<T, HandlerMethod> handlerMethods = new LinkedHashMap<T, HandlerMethod>();
private final MultiValueMap<String, T> urlMap = new LinkedMultiValueMap<String, T>();
/**
* Whether to detect handler methods in beans in ancestor ApplicationContexts.
* <p>Default is "false": Only beans in the current ApplicationContext are
* considered, i.e. only in the context that this HandlerMapping itself
* is defined in (typically the current DispatcherServlet's context).
* <p>Switch this flag on to detect handler beans in ancestor contexts
* (typically the Spring root WebApplicationContext) as well.
*/
public void setDetectHandlerMethodsInAncestorContexts(boolean detectHandlerMethodsInAncestorContexts) {
this.detectHandlerMethodsInAncestorContexts = detectHandlerMethodsInAncestorContexts;
}
/**
* Return a map with all handler methods and their mappings.
*/
public Map<T, HandlerMethod> getHandlerMethods() {
return Collections.unmodifiableMap(handlerMethods);
}
/**
* ApplicationContext initialization and handler method detection.
*/
@Override
public void initApplicationContext() throws ApplicationContextException {
super.initApplicationContext();
initHandlerMethods();
}
/**
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #isHandler(Class)
* @see #getMappingForMethod(Method, Class)
* @see #handlerMethodsInitialized(Map)
*/
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
if (isHandler(getApplicationContext().getType(beanName))){
detectHandlerMethods(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
/**
* Whether the given type is a handler with handler methods.
* @param beanType the type of the bean being checked
* @return "true" if this a handler type, "false" otherwise.
*/
protected abstract boolean isHandler(Class<?> beanType);
/**
* Invoked after all handler methods have been detected.
* @param handlerMethods a read-only map with handler methods and mappings.
*/
protected void handlerMethodsInitialized(Map<T, HandlerMethod> handlerMethods) {
}
/**
* Look for handler methods in a handler.
* @param handler the bean name of a handler or a handler instance
*/
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String) ?
getApplicationContext().getType((String) handler) : handler.getClass();
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
public boolean matches(Method method) {
return getMappingForMethod(method, userType) != null;
}
});
for (Method method : methods) {
T mapping = getMappingForMethod(method, userType);
registerHandlerMethod(handler, method, mapping);
}
}
/**
* Provide the mapping for a handler method. A method for which no
* mapping can be provided is not a handler method.
*
* @param method the method to provide a mapping for
* @param handlerType the handler type, possibly a sub-type of the method's
* declaring class
* @return the mapping, or {@code null} if the method is not mapped
*/
protected abstract T getMappingForMethod(Method method, Class<?> handlerType);
/**
* Register a handler method and its unique mapping.
*
* @param handler the bean name of the handler or the handler instance
* @param method the method to register
* @param mapping the mapping conditions associated with the handler method
* @throws IllegalStateException if another method was already registered
* under the same mapping
*/
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
HandlerMethod handlerMethod;
if (handler instanceof String) {
String beanName = (String) handler;
handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method);
}
else {
handlerMethod = new HandlerMethod(handler, method);
}
HandlerMethod oldHandlerMethod = handlerMethods.get(mapping);
if (oldHandlerMethod != null && !oldHandlerMethod.equals(handlerMethod)) {
throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + handlerMethod.getBean()
+ "' bean method \n" + handlerMethod + "\nto " + mapping + ": There is already '"
+ oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
}
handlerMethods.put(mapping, handlerMethod);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
}
Set<String> patterns = getMappingPathPatterns(mapping);
for (String pattern : patterns) {
if (!getPathMatcher().isPattern(pattern)) {
urlMap.add(pattern, mapping);
}
}
}
/**
* Extract and return the URL paths contained in a mapping.
*/
protected abstract Set<String> getMappingPathPatterns(T mapping);
/**
* Look up a handler method for the given request.
*/
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath);
}
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
if (logger.isDebugEnabled()) {
if (handlerMethod != null) {
logger.debug("Returning handler method [" + handlerMethod + "]");
}
else {
logger.debug("Did not find handler method for [" + lookupPath + "]");
}
}
return (handlerMethod != null) ? handlerMethod.createWithResolvedBean() : null;
}
/**
* Look up the best-matching handler method for the current request.
* If multiple matches are found, the best match is selected.
*
* @param lookupPath mapping lookup path within the current servlet mapping
* @param request the current request
* @return the best-matching handler method, or {@code null} if no match
*
* @see #handleMatch(Object, String, HttpServletRequest)
* @see #handleNoMatch(Set, String, HttpServletRequest)
*/
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<T> mappings = urlMap.get(lookupPath);
if (mappings == null) {
mappings = new ArrayList<T>(handlerMethods.keySet());
}
List<Match> matches = new ArrayList<Match>();
for (T mapping : mappings) {
T match = getMatchingMapping(mapping, request);
if (match != null) {
matches.add(new Match(match, handlerMethods.get(mapping)));
}
}
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
Collections.sort(matches, comparator);
if (logger.isTraceEnabled()) {
logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
}
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
throw new IllegalStateException(
"Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +
m1 + ", " + m2 + "}");
}
}
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(handlerMethods.keySet(), lookupPath, request);
}
}
/**
* Check if a mapping matches the current request and return a (potentially
* new) mapping with conditions relevant to the current request.
*
* @param mapping the mapping to get a match for
* @param request the current HTTP servlet request
* @return the match, or {@code null} if the mapping doesn't match
*/
protected abstract T getMatchingMapping(T mapping, HttpServletRequest request);
/**
* Return a comparator for sorting matching mappings.
* The returned comparator should sort 'better' matches higher.
* @param request the current request
* @return the comparator, never {@code null}
*/
protected abstract Comparator<T> getMappingComparator(HttpServletRequest request);
/**
* Invoked when a matching mapping is found.
* @param mapping the matching mapping
* @param lookupPath mapping lookup path within the current servlet mapping
* @param request the current request
*/
protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
}
/**
* Invoked when no matching mapping is not found.
* @param mappings all registered mappings
* @param lookupPath mapping lookup path within the current servlet mapping
* @param request the current request
* @throws ServletException in case of errors
*/
protected HandlerMethod handleNoMatch(Set<T> mappings, String lookupPath, HttpServletRequest request)
throws Exception {
return null;
}
/**
* A temporary container for a mapping matched to a request.
*/
private class Match {
private final T mapping;
private final HandlerMethod handlerMethod;
private Match(T mapping, HandlerMethod handlerMethod) {
this.mapping = mapping;
this.handlerMethod = handlerMethod;
}
@Override
public String toString() {
return mapping.toString();
}
}
private class MatchComparator implements Comparator<Match> {
private final Comparator<T> comparator;
public MatchComparator(Comparator<T> comparator) {
this.comparator = comparator;
}
public int compare(Match match1, Match match2) {
return comparator.compare(match1.mapping, match2.mapping);
}
}
}

View File

@@ -0,0 +1,388 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeansException;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
/**
* Abstract base class for URL-mapped {@link org.springframework.web.servlet.HandlerMapping}
* implementations. Provides infrastructure for mapping handlers to URLs and configurable
* URL lookup. For information on the latter, see "alwaysUseFullPath" property.
*
* <p>Supports direct matches, e.g. a registered "/test" matches "/test", and
* various Ant-style pattern matches, e.g. a registered "/t*" pattern matches
* both "/test" and "/team", "/test/*" matches all paths in the "/test" directory,
* "/test/**" matches all paths below "/test". For details, see the
* {@link org.springframework.util.AntPathMatcher AntPathMatcher} javadoc.
*
* <p>Will search all path patterns to find the most exact match for the
* current request path. The most exact match is defined as the longest
* path pattern that matches the current request path.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
* @since 16.04.2003
*/
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
private Object rootHandler;
private boolean lazyInitHandlers = false;
private final Map<String, Object> handlerMap = new LinkedHashMap<String, Object>();
/**
* Set the root handler for this handler mapping, that is,
* the handler to be registered for the root path ("/").
* <p>Default is <code>null</code>, indicating no root handler.
*/
public void setRootHandler(Object rootHandler) {
this.rootHandler = rootHandler;
}
/**
* Return the root handler for this handler mapping (registered for "/"),
* or <code>null</code> if none.
*/
public Object getRootHandler() {
return this.rootHandler;
}
/**
* Set whether to lazily initialize handlers. Only applicable to
* singleton handlers, as prototypes are always lazily initialized.
* Default is "false", as eager initialization allows for more efficiency
* through referencing the controller objects directly.
* <p>If you want to allow your controllers to be lazily initialized,
* make them "lazy-init" and set this flag to true. Just making them
* "lazy-init" will not work, as they are initialized through the
* references from the handler mapping in this case.
*/
public void setLazyInitHandlers(boolean lazyInitHandlers) {
this.lazyInitHandlers = lazyInitHandlers;
}
/**
* Look up a handler for the URL path of the given request.
* @param request current HTTP request
* @return the handler instance, or <code>null</code> if none found
*/
@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
Object handler = lookupHandler(lookupPath, request);
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
if ("/".equals(lookupPath)) {
rawHandler = getRootHandler();
}
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = getApplicationContext().getBean(handlerName);
}
validateHandler(rawHandler, request);
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
if (handler != null && logger.isDebugEnabled()) {
logger.debug("Mapping [" + lookupPath + "] to " + handler);
}
else if (handler == null && logger.isTraceEnabled()) {
logger.trace("No handler mapping found for [" + lookupPath + "]");
}
return handler;
}
/**
* Look up a handler instance for the given URL path.
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
* and various Ant-style pattern matches, e.g. a registered "/t*" matches
* both "/test" and "/team". For details, see the AntPathMatcher class.
* <p>Looks for the most exact pattern, where most exact is defined as
* the longest path pattern.
* @param urlPath URL the bean is mapped to
* @param request current HTTP request (to expose the path within the mapping to)
* @return the associated handler instance, or <code>null</code> if not found
* @see #exposePathWithinMapping
* @see org.springframework.util.AntPathMatcher
*/
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// Direct match?
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
// Pattern match?
List<String> matchingPatterns = new ArrayList<String>();
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, urlPath)) {
matchingPatterns.add(registeredPattern);
}
}
String bestPatternMatch = null;
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
if (!matchingPatterns.isEmpty()) {
Collections.sort(matchingPatterns, patternComparator);
if (logger.isDebugEnabled()) {
logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
}
bestPatternMatch = matchingPatterns.get(0);
}
if (bestPatternMatch != null) {
handler = this.handlerMap.get(bestPatternMatch);
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);
// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
// for all of them
Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
uriTemplateVariables
.putAll(getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath));
}
}
if (logger.isDebugEnabled()) {
logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
}
return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}
/**
* Validate the given handler against the current request.
* <p>The default implementation is empty. Can be overridden in subclasses,
* for example to enforce specific preconditions expressed in URL mappings.
* @param handler the handler object to validate
* @param request current HTTP request
* @throws Exception if validation failed
*/
protected void validateHandler(Object handler, HttpServletRequest request) throws Exception {
}
/**
* Build a handler object for the given raw handler, exposing the actual
* handler, the {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE}, as well as
* the {@link #URI_TEMPLATE_VARIABLES_ATTRIBUTE} before executing the handler.
* <p>The default implementation builds a {@link HandlerExecutionChain}
* with a special interceptor that exposes the path attribute and uri template variables
* @param rawHandler the raw handler to expose
* @param pathWithinMapping the path to expose before executing the handler
* @param uriTemplateVariables the URI template variables, can be <code>null</code> if no variables found
* @return the final handler object
*/
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
String pathWithinMapping, Map<String, String> uriTemplateVariables) {
HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
}
return chain;
}
/**
* Expose the path within the current mapping as request attribute.
* @param pathWithinMapping the path within the current mapping
* @param request the request to expose the path to
* @see #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
*/
protected void exposePathWithinMapping(String bestMatchingPattern, String pathWithinMapping, HttpServletRequest request) {
request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, bestMatchingPattern);
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping);
}
/**
* Expose the URI templates variables as request attribute.
* @param uriTemplateVariables the URI template variables
* @param request the request to expose the path to
* @see #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
*/
protected void exposeUriTemplateVariables(Map<String, String> uriTemplateVariables, HttpServletRequest request) {
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
}
/**
* Register the specified handler for the given URL paths.
* @param urlPaths the URLs that the bean should be mapped to
* @param beanName the name of the handler bean
* @throws BeansException if the handler couldn't be registered
* @throws IllegalStateException if there is a conflicting handler registered
*/
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
Assert.notNull(urlPaths, "URL path array must not be null");
for (String urlPath : urlPaths) {
registerHandler(urlPath, beanName);
}
}
/**
* Register the specified handler for the given URL path.
* @param urlPath the URL the bean should be mapped to
* @param handler the handler instance or handler bean name String
* (a bean name will automatically be resolved into the corresponding handler bean)
* @throws BeansException if the handler couldn't be registered
* @throws IllegalStateException if there is a conflicting handler registered
*/
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
// Eagerly resolve handler if referencing singleton via name.
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
if (getApplicationContext().isSingleton(handlerName)) {
resolvedHandler = getApplicationContext().getBean(handlerName);
}
}
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
}
else {
if (urlPath.equals("/")) {
if (logger.isInfoEnabled()) {
logger.info("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
else if (urlPath.equals("/*")) {
if (logger.isInfoEnabled()) {
logger.info("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isInfoEnabled()) {
logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}
private String getHandlerDescription(Object handler) {
return "handler " + (handler instanceof String ? "'" + handler + "'" : "of type [" + handler.getClass() + "]");
}
/**
* Return the registered handlers as an unmodifiable Map, with the registered path
* as key and the handler object (or handler bean name in case of a lazy-init handler)
* as value.
* @see #getDefaultHandler()
*/
public final Map<String, Object> getHandlerMap() {
return Collections.unmodifiableMap(this.handlerMap);
}
/**
* Indicates whether this handler mapping support type-level mappings. Default to {@code false}.
*/
protected boolean supportsTypeLevelMappings() {
return false;
}
/**
* Special interceptor for exposing the
* {@link AbstractUrlHandlerMapping#PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE} attribute.
* @see AbstractUrlHandlerMapping#exposePathWithinMapping
*/
private class PathExposingHandlerInterceptor extends HandlerInterceptorAdapter {
private final String bestMatchingPattern;
private final String pathWithinMapping;
public PathExposingHandlerInterceptor(String bestMatchingPattern, String pathWithinMapping) {
this.bestMatchingPattern = bestMatchingPattern;
this.pathWithinMapping = pathWithinMapping;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
exposePathWithinMapping(this.bestMatchingPattern, this.pathWithinMapping, request);
request.setAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings());
return true;
}
}
/**
* Special interceptor for exposing the
* {@link AbstractUrlHandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE} attribute.
* @see AbstractUrlHandlerMapping#exposePathWithinMapping
*/
private class UriTemplateVariablesHandlerInterceptor extends HandlerInterceptorAdapter {
private final Map<String, String> uriTemplateVariables;
public UriTemplateVariablesHandlerInterceptor(Map<String, String> uriTemplateVariables) {
this.uriTemplateVariables = uriTemplateVariables;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
exposeUriTemplateVariables(this.uriTemplateVariables, request);
return true;
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.ArrayList;
import java.util.List;
import org.springframework.util.StringUtils;
/**
* Implementation of the {@link org.springframework.web.servlet.HandlerMapping}
* interface that map from URLs to beans with names that start with a slash ("/"),
* similar to how Struts maps URLs to action names.
*
* <p>This is the default implementation used by the
* {@link org.springframework.web.servlet.DispatcherServlet}, along with
* {@link org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping}
* (on Java 5 and higher). Alternatively, {@link SimpleUrlHandlerMapping} allows for
* customizing a handler mapping declaratively.
*
* <p>The mapping is from URL to bean name. Thus an incoming URL "/foo" would map
* to a handler named "/foo", or to "/foo /foo2" in case of multiple mappings to
* a single handler. Note: In XML definitions, you'll need to use an alias
* name="/foo" in the bean definition, as the XML id may not contain slashes.
*
* <p>Supports direct matches (given "/test" -> registered "/test") and "*"
* matches (given "/test" -> registered "/t*"). Note that the default is
* to map within the current servlet mapping if applicable; see the
* {@link #setAlwaysUseFullPath "alwaysUseFullPath"} property for details.
* For details on the pattern options, see the
* {@link org.springframework.util.AntPathMatcher} javadoc.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see SimpleUrlHandlerMapping
*/
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
/**
* Checks name and aliases of the given bean for URLs, starting with "/".
*/
@Override
protected String[] determineUrlsForHandler(String beanName) {
List<String> urls = new ArrayList<String>();
if (beanName.startsWith("/")) {
urls.add(beanName);
}
String[] aliases = getApplicationContext().getAliases(beanName);
for (String alias : aliases) {
if (alias.startsWith("/")) {
urls.add(alias);
}
}
return StringUtils.toStringArray(urls);
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.convert.ConversionService;
import org.springframework.util.Assert;
/**
* Interceptor that places the configured {@link ConversionService} in request scope
* so it's available during request processing. The request attribute name is
* "org.springframework.core.convert.ConversionService", the value of
* <code>ConversionService.class.getName()</code>.
*
* <p>Mainly for use within JSP tags such as the spring:eval tag.
*
* @author Keith Donald
* @since 3.0.1
*/
public class ConversionServiceExposingInterceptor extends HandlerInterceptorAdapter {
private final ConversionService conversionService;
/**
* Creates a new {@link ConversionServiceExposingInterceptor}.
* @param conversionService the conversion service to export to request scope when this interceptor is invoked
*/
public ConversionServiceExposingInterceptor(ConversionService conversionService) {
Assert.notNull(conversionService, "The ConversionService may not be null");
this.conversionService = conversionService;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws ServletException, IOException {
request.setAttribute(ConversionService.class.getName(), this.conversionService);
return true;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.support.RequestContextUtils;
/**
* {@link ServletWebRequest} subclass that is aware of
* {@link org.springframework.web.servlet.DispatcherServlet}'s
* request context, such as the Locale determined by the configured
* {@link org.springframework.web.servlet.LocaleResolver}.
*
* @author Juergen Hoeller
* @since 2.0
* @see #getLocale()
* @see org.springframework.web.servlet.LocaleResolver
*/
public class DispatcherServletWebRequest extends ServletWebRequest {
/**
* Create a new DispatcherServletWebRequest instance for the given request.
* @param request current HTTP request
*/
public DispatcherServletWebRequest(HttpServletRequest request) {
super(request);
}
/**
* Create a new DispatcherServletWebRequest instance for the given request and response.
* @param request current HTTP request
* @param request current HTTP response
*/
public DispatcherServletWebRequest(HttpServletRequest request, HttpServletResponse response) {
super(request, response);
}
@Override
public Locale getLocale() {
return RequestContextUtils.getLocale(getRequest());
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
/**
* A {@link HandlerExceptionResolver} that delegates to a list of other {@link HandlerExceptionResolver}s.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class HandlerExceptionResolverComposite implements HandlerExceptionResolver, Ordered {
private List<HandlerExceptionResolver> resolvers;
private int order = Ordered.LOWEST_PRECEDENCE;
public void setOrder(int order) {
this.order = order;
}
public int getOrder() {
return this.order;
}
/**
* Set the list of exception resolvers to delegate to.
*/
public void setExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
this.resolvers = exceptionResolvers;
}
/**
* Return the list of exception resolvers to delegate to.
*/
public List<HandlerExceptionResolver> getExceptionResolvers() {
return Collections.unmodifiableList(resolvers);
}
/**
* Resolve the exception by iterating over the list of configured exception resolvers.
* The first one to return a ModelAndView instance wins. Otherwise {@code null} is returned.
*/
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
if (resolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : resolvers) {
ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (mav != null) {
return mav;
}
}
}
return null;
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
* Abstract adapter class for the HandlerInterceptor interface,
* for simplified implementation of pre-only/post-only interceptors.
*
* @author Juergen Hoeller
* @since 05.12.2003
*/
public abstract class HandlerInterceptorAdapter implements HandlerInterceptor {
/**
* This implementation always returns <code>true</code>.
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
/**
* This implementation is empty.
*/
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
}
/**
* This implementation is empty.
*/
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import org.springframework.util.PathMatcher;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* Holds information about a HandlerInterceptor mapped to a path into the application.
* Provides a method to match a request path to the mapped path patterns.
*
* @author Keith Donald
* @author Rossen Stoyanchev
* @since 3.0
*/
public final class MappedInterceptor {
private final String[] pathPatterns;
private final HandlerInterceptor interceptor;
/**
* Create a new MappedInterceptor instance.
* @param pathPatterns the path patterns to map with a {@code null} value matching to all paths
* @param interceptor the HandlerInterceptor instance to map to the given patterns
*/
public MappedInterceptor(String[] pathPatterns, HandlerInterceptor interceptor) {
this.pathPatterns = pathPatterns;
this.interceptor = interceptor;
}
/**
* Create a new MappedInterceptor instance.
* @param pathPatterns the path patterns to map with a {@code null} value matching to all paths
* @param interceptor the WebRequestInterceptor instance to map to the given patterns
*/
public MappedInterceptor(String[] pathPatterns, WebRequestInterceptor interceptor) {
this.pathPatterns = pathPatterns;
this.interceptor = new WebRequestHandlerInterceptorAdapter(interceptor);
}
/**
* The path into the application the interceptor is mapped to.
*/
public String[] getPathPatterns() {
return this.pathPatterns;
}
/**
* The actual Interceptor reference.
*/
public HandlerInterceptor getInterceptor() {
return this.interceptor;
}
/**
* Returns {@code true} if the interceptor applies to the given request path.
* @param lookupPath the current request path
* @param pathMatcher a path matcher for path pattern matching
*/
public boolean matches(String lookupPath, PathMatcher pathMatcher) {
if (pathPatterns == null) {
return true;
}
else {
for (String pathPattern : pathPatterns) {
if (pathMatcher.match(pathPattern, lookupPath)) {
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,318 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.WebUtils;
/**
* {@link org.springframework.web.servlet.HandlerExceptionResolver} implementation that allows for mapping exception
* class names to view names, either for a set of given handlers or for all handlers in the DispatcherServlet.
*
* <p>Error views are analogous to error page JSPs, but can be used with any kind of exception including any checked
* one, with fine-granular mappings for specific handlers.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
* @since 22.11.2003
* @see org.springframework.web.servlet.DispatcherServlet
*/
public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionResolver {
/** The default name of the exception attribute: "exception". */
public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";
private Properties exceptionMappings;
private String defaultErrorView;
private Integer defaultStatusCode;
private Map<String, Integer> statusCodes = new HashMap<String, Integer>();
private String exceptionAttribute = DEFAULT_EXCEPTION_ATTRIBUTE;
/**
* Set the mappings between exception class names and error view names.
* The exception class name can be a substring, with no wildcard support at present.
* A value of "ServletException" would match <code>javax.servlet.ServletException</code>
* and subclasses, for example.
* <p><b>NB:</b> Consider carefully how
* specific the pattern is, and whether to include package information (which isn't mandatory).
* For example, "Exception" will match nearly anything, and will probably hide other rules.
* "java.lang.Exception" would be correct if "Exception" was meant to define a rule for all
* checked exceptions. With more unusual exception names such as "BaseBusinessException"
* there's no need to use a FQN.
* @param mappings exception patterns (can also be fully qualified class names) as keys,
* and error view names as values
*/
public void setExceptionMappings(Properties mappings) {
this.exceptionMappings = mappings;
}
/**
* Set the name of the default error view. This view will be returned if no specific mapping was found. <p>Default is
* none.
*/
public void setDefaultErrorView(String defaultErrorView) {
this.defaultErrorView = defaultErrorView;
}
/**
* Set the HTTP status code that this exception resolver will apply for a given resolved error view. Keys are
* view names; values are status codes.
* <p>Note that this error code will only get applied in case of a top-level request. It will not be set for an include
* request, since the HTTP status cannot be modified from within an include.
* <p>If not specified, the default status code will be applied.
* @see #setDefaultStatusCode(int)
*/
public void setStatusCodes(Properties statusCodes) {
for (Enumeration<?> enumeration = statusCodes.propertyNames(); enumeration.hasMoreElements();) {
String viewName = (String) enumeration.nextElement();
Integer statusCode = new Integer(statusCodes.getProperty(viewName));
this.statusCodes.put(viewName, statusCode);
}
}
/**
* An alternative to {@link #setStatusCodes(Properties)} for use with
* Java-based configuration.
*/
public void addStatusCode(String viewName, int statusCode) {
this.statusCodes.put(viewName, statusCode);
}
/**
* Returns the HTTP status codes provided via {@link #setStatusCodes(Properties)}.
* Keys are view names; values are status codes.
*/
public Map<String, Integer> getStatusCodesAsMap() {
return Collections.unmodifiableMap(statusCodes);
}
/**
* Set the default HTTP status code that this exception resolver will apply if it resolves an error view and if there
* is no status code mapping defined.
* <p>Note that this error code will only get applied in case of a top-level request. It will not be set for an
* include request, since the HTTP status cannot be modified from within an include.
* <p>If not specified, no status code will be applied, either leaving this to the controller or view, or keeping
* the servlet engine's default of 200 (OK).
* @param defaultStatusCode HTTP status code value, for example 500
* ({@link HttpServletResponse#SC_INTERNAL_SERVER_ERROR}) or 404 ({@link HttpServletResponse#SC_NOT_FOUND})
* @see #setStatusCodes(Properties)
*/
public void setDefaultStatusCode(int defaultStatusCode) {
this.defaultStatusCode = defaultStatusCode;
}
/**
* Set the name of the model attribute as which the exception should be exposed. Default is "exception". <p>This can be
* either set to a different attribute name or to <code>null</code> for not exposing an exception attribute at all.
* @see #DEFAULT_EXCEPTION_ATTRIBUTE
*/
public void setExceptionAttribute(String exceptionAttribute) {
this.exceptionAttribute = exceptionAttribute;
}
/**
* Actually resolve the given exception that got thrown during on handler execution, returning a ModelAndView that
* represents a specific error page if appropriate. <p>May be overridden in subclasses, in order to apply specific
* exception checks. Note that this template method will be invoked <i>after</i> checking whether this resolved applies
* ("mappedHandlers" etc), so an implementation may simply proceed with its actual exception handling.
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception (for example,
* if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to, or <code>null</code> for default processing
*/
@Override
protected ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
// Expose ModelAndView for chosen error view.
String viewName = determineViewName(ex, request);
if (viewName != null) {
// Apply HTTP status code for error views, if specified.
// Only apply it if we're processing a top-level request.
Integer statusCode = determineStatusCode(request, viewName);
if (statusCode != null) {
applyStatusCodeIfPossible(request, response, statusCode);
}
return getModelAndView(viewName, ex, request);
}
else {
return null;
}
}
/**
* Determine the view name for the given exception, searching the {@link #setExceptionMappings "exceptionMappings"},
* using the {@link #setDefaultErrorView "defaultErrorView"} as fallback.
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @return the resolved view name, or <code>null</code> if none found
*/
protected String determineViewName(Exception ex, HttpServletRequest request) {
String viewName = null;
// Check for specific exception mappings.
if (this.exceptionMappings != null) {
viewName = findMatchingViewName(this.exceptionMappings, ex);
}
// Return default error view else, if defined.
if (viewName == null && this.defaultErrorView != null) {
if (logger.isDebugEnabled()) {
logger.debug("Resolving to default view '" + this.defaultErrorView + "' for exception of type [" +
ex.getClass().getName() + "]");
}
viewName = this.defaultErrorView;
}
return viewName;
}
/**
* Find a matching view name in the given exception mappings.
* @param exceptionMappings mappings between exception class names and error view names
* @param ex the exception that got thrown during handler execution
* @return the view name, or <code>null</code> if none found
* @see #setExceptionMappings
*/
protected String findMatchingViewName(Properties exceptionMappings, Exception ex) {
String viewName = null;
String dominantMapping = null;
int deepest = Integer.MAX_VALUE;
for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements();) {
String exceptionMapping = (String) names.nextElement();
int depth = getDepth(exceptionMapping, ex);
if (depth >= 0 && depth < deepest) {
deepest = depth;
dominantMapping = exceptionMapping;
viewName = exceptionMappings.getProperty(exceptionMapping);
}
}
if (viewName != null && logger.isDebugEnabled()) {
logger.debug("Resolving to view '" + viewName + "' for exception of type [" + ex.getClass().getName() +
"], based on exception mapping [" + dominantMapping + "]");
}
return viewName;
}
/**
* Return the depth to the superclass matching.
* <p>0 means ex matches exactly. Returns -1 if there's no match.
* Otherwise, returns depth. Lowest depth wins.
*/
protected int getDepth(String exceptionMapping, Exception ex) {
return getDepth(exceptionMapping, ex.getClass(), 0);
}
private int getDepth(String exceptionMapping, Class<?> exceptionClass, int depth) {
if (exceptionClass.getName().contains(exceptionMapping)) {
// Found it!
return depth;
}
// If we've gone as far as we can go and haven't found it...
if (exceptionClass.equals(Throwable.class)) {
return -1;
}
return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
}
/**
* Determine the HTTP status code to apply for the given error view.
* <p>The default implementation returns the status code for the given view name (specified through the
* {@link #setStatusCodes(Properties) statusCodes} property), or falls back to the
* {@link #setDefaultStatusCode defaultStatusCode} if there is no match.
* <p>Override this in a custom subclass to customize this behavior.
* @param request current HTTP request
* @param viewName the name of the error view
* @return the HTTP status code to use, or <code>null</code> for the servlet container's default
* (200 in case of a standard error view)
* @see #setDefaultStatusCode
* @see #applyStatusCodeIfPossible
*/
protected Integer determineStatusCode(HttpServletRequest request, String viewName) {
if (this.statusCodes.containsKey(viewName)) {
return this.statusCodes.get(viewName);
}
return this.defaultStatusCode;
}
/**
* Apply the specified HTTP status code to the given response, if possible (that is,
* if not executing within an include request).
* @param request current HTTP request
* @param response current HTTP response
* @param statusCode the status code to apply
* @see #determineStatusCode
* @see #setDefaultStatusCode
* @see HttpServletResponse#setStatus
*/
protected void applyStatusCodeIfPossible(HttpServletRequest request, HttpServletResponse response, int statusCode) {
if (!WebUtils.isIncludeRequest(request)) {
if (logger.isDebugEnabled()) {
logger.debug("Applying HTTP status code " + statusCode);
}
response.setStatus(statusCode);
request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, statusCode);
}
}
/**
* Return a ModelAndView for the given request, view name and exception.
* <p>The default implementation delegates to {@link #getModelAndView(String, Exception)}.
* @param viewName the name of the error view
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @return the ModelAndView instance
*/
protected ModelAndView getModelAndView(String viewName, Exception ex, HttpServletRequest request) {
return getModelAndView(viewName, ex);
}
/**
* Return a ModelAndView for the given view name and exception.
* <p>The default implementation adds the specified exception attribute.
* Can be overridden in subclasses.
* @param viewName the name of the error view
* @param ex the exception that got thrown during handler execution
* @return the ModelAndView instance
* @see #setExceptionAttribute
*/
protected ModelAndView getModelAndView(String viewName, Exception ex) {
ModelAndView mv = new ModelAndView(viewName);
if (this.exceptionAttribute != null) {
if (logger.isDebugEnabled()) {
logger.debug("Exposing Exception as model attribute '" + this.exceptionAttribute + "'");
}
mv.addObject(this.exceptionAttribute, ex);
}
return mv;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import javax.servlet.Servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ModelAndView;
/**
* Adapter to use the Servlet interface with the generic DispatcherServlet.
* Calls the Servlet's <code>service</code> method to handle a request.
*
* <p>Last-modified checking is not explicitly supported: This is typically
* handled by the Servlet implementation itself (usually deriving from
* the HttpServlet base class).
*
* <p>This adapter is not activated by default; it needs to be defined as a
* bean in the DispatcherServlet context. It will automatically apply to
* mapped handler beans that implement the Servlet interface then.
*
* <p>Note that Servlet instances defined as bean will not receive initialization
* and destruction callbacks, unless a special post-processor such as
* SimpleServletPostProcessor is defined in the DispatcherServlet context.
*
* <p><b>Alternatively, consider wrapping a Servlet with Spring's
* ServletWrappingController.</b> This is particularly appropriate for
* existing Servlet classes, allowing to specify Servlet initialization
* parameters etc.
*
* @author Juergen Hoeller
* @since 1.1.5
* @see javax.servlet.Servlet
* @see javax.servlet.http.HttpServlet
* @see SimpleServletPostProcessor
* @see org.springframework.web.servlet.mvc.ServletWrappingController
*/
public class SimpleServletHandlerAdapter implements HandlerAdapter {
public boolean supports(Object handler) {
return (handler instanceof Servlet);
}
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((Servlet) handler).service(request, response);
return null;
}
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.web.context.ServletConfigAware;
import org.springframework.web.context.ServletContextAware;
/**
* {@link org.springframework.beans.factory.config.BeanPostProcessor}
* that applies initialization and destruction callbacks to beans that
* implement the {@link javax.servlet.Servlet} interface.
*
* <p>After initialization of the bean instance, the Servlet <code>init</code>
* method will be called with a ServletConfig that contains the bean name
* of the Servlet and the ServletContext that it is running in.
*
* <p>Before destruction of the bean instance, the Servlet <code>destroy</code>
* will be called.
*
* <p><b>Note that this post-processor does not support Servlet initialization
* parameters.</b> Bean instances that implement the Servlet interface are
* supposed to be configured like any other Spring bean, that is, through
* constructor arguments or bean properties.
*
* <p>For reuse of a Servlet implementation in a plain Servlet container
* and as a bean in a Spring context, consider deriving from Spring's
* {@link org.springframework.web.servlet.HttpServletBean} base class that
* applies Servlet initialization parameters as bean properties, supporting
* both the standard Servlet and the Spring bean initialization style.
*
* <p><b>Alternatively, consider wrapping a Servlet with Spring's
* {@link org.springframework.web.servlet.mvc.ServletWrappingController}.</b>
* This is particularly appropriate for existing Servlet classes,
* allowing to specify Servlet initialization parameters etc.
*
* @author Juergen Hoeller
* @since 1.1.5
* @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
* @see javax.servlet.Servlet#destroy()
* @see SimpleServletHandlerAdapter
*/
public class SimpleServletPostProcessor implements
DestructionAwareBeanPostProcessor, ServletContextAware, ServletConfigAware {
private boolean useSharedServletConfig = true;
private ServletContext servletContext;
private ServletConfig servletConfig;
/**
* Set whether to use the shared ServletConfig object passed in
* through <code>setServletConfig</code>, if available.
* <p>Default is "true". Turn this setting to "false" to pass in
* a mock ServletConfig object with the bean name as servlet name,
* holding the current ServletContext.
* @see #setServletConfig
*/
public void setUseSharedServletConfig(boolean useSharedServletConfig) {
this.useSharedServletConfig = useSharedServletConfig;
}
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
public void setServletConfig(ServletConfig servletConfig) {
this.servletConfig = servletConfig;
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Servlet) {
ServletConfig config = this.servletConfig;
if (config == null || !this.useSharedServletConfig) {
config = new DelegatingServletConfig(beanName, this.servletContext);
}
try {
((Servlet) bean).init(config);
}
catch (ServletException ex) {
throw new BeanInitializationException("Servlet.init threw exception", ex);
}
}
return bean;
}
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
if (bean instanceof Servlet) {
((Servlet) bean).destroy();
}
}
/**
* Internal implementation of the {@link ServletConfig} interface,
* to be passed to the wrapped servlet.
*/
private static class DelegatingServletConfig implements ServletConfig {
private final String servletName;
private final ServletContext servletContext;
public DelegatingServletConfig(String servletName, ServletContext servletContext) {
this.servletName = servletName;
this.servletContext = servletContext;
}
public String getServletName() {
return this.servletName;
}
public ServletContext getServletContext() {
return this.servletContext;
}
public String getInitParameter(String paramName) {
return null;
}
public Enumeration<String> getInitParameterNames() {
return Collections.enumeration(new HashSet<String>());
}
}
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.springframework.beans.BeansException;
import org.springframework.util.CollectionUtils;
/**
* Implementation of the {@link org.springframework.web.servlet.HandlerMapping}
* interface to map from URLs to request handler beans. Supports both mapping to bean
* instances and mapping to bean names; the latter is required for non-singleton handlers.
*
* <p>The "urlMap" property is suitable for populating the handler map with
* bean references, e.g. via the map element in XML bean definitions.
*
* <p>Mappings to bean names can be set via the "mappings" property, in a form
* accepted by the <code>java.util.Properties</code> class, like as follows:<br>
* <code>
* /welcome.html=ticketController
* /show.html=ticketController
* </code><br>
* The syntax is <code>PATH=HANDLER_BEAN_NAME</code>.
* If the path doesn't begin with a slash, one is prepended.
*
* <p>Supports direct matches (given "/test" -> registered "/test") and "*"
* matches (given "/test" -> registered "/t*"). Note that the default is
* to map within the current servlet mapping if applicable; see the
* {@link #setAlwaysUseFullPath "alwaysUseFullPath"} property for details.
* For details on the pattern options, see the
* {@link org.springframework.util.AntPathMatcher} javadoc.
* @author Rod Johnson
* @author Juergen Hoeller
* @see #setMappings
* @see #setUrlMap
* @see BeanNameUrlHandlerMapping
*/
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
private final Map<String, Object> urlMap = new HashMap<String, Object>();
/**
* Map URL paths to handler bean names.
* This is the typical way of configuring this HandlerMapping.
* <p>Supports direct URL matches and Ant-style pattern matches. For syntax
* details, see the {@link org.springframework.util.AntPathMatcher} javadoc.
* @param mappings properties with URLs as keys and bean names as values
* @see #setUrlMap
*/
public void setMappings(Properties mappings) {
CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap);
}
/**
* Set a Map with URL paths as keys and handler beans (or handler bean names)
* as values. Convenient for population with bean references.
* <p>Supports direct URL matches and Ant-style pattern matches. For syntax
* details, see the {@link org.springframework.util.AntPathMatcher} javadoc.
* @param urlMap map with URLs as keys and beans as values
* @see #setMappings
*/
public void setUrlMap(Map<String, ?> urlMap) {
this.urlMap.putAll(urlMap);
}
/**
* Allow Map access to the URL path mappings, with the option to add or
* override specific entries.
* <p>Useful for specifying entries directly, for example via "urlMap[myKey]".
* This is particularly useful for adding or overriding entries in child
* bean definitions.
*/
public Map<String, ?> getUrlMap() {
return this.urlMap;
}
/**
* Calls the {@link #registerHandlers} method in addition to the
* superclass's initialization.
*/
@Override
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
registerHandlers(this.urlMap);
}
/**
* Register all handlers specified in the URL map for the corresponding paths.
* @param urlMap Map with URL paths as keys and handler beans or bean names as values
* @throws BeansException if a handler couldn't be registered
* @throws IllegalStateException if there is a conflicting handler registered
*/
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
if (urlMap.isEmpty()) {
logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
}
else {
for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
String url = entry.getKey();
Object handler = entry.getValue();
// Prepend with slash if not already present.
if (!url.startsWith("/")) {
url = "/" + url;
}
// Remove whitespace from handler bean name.
if (handler instanceof String) {
handler = ((String) handler).trim();
}
registerHandler(url, handler);
}
}
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Interceptor that checks the authorization of the current user via the
* user's roles, as evaluated by HttpServletRequest's isUserInRole method.
*
* @author Juergen Hoeller
* @since 20.06.2003
* @see javax.servlet.http.HttpServletRequest#isUserInRole
*/
public class UserRoleAuthorizationInterceptor extends HandlerInterceptorAdapter {
private String[] authorizedRoles;
/**
* Set the roles that this interceptor should treat as authorized.
* @param authorizedRoles array of role names
*/
public final void setAuthorizedRoles(String[] authorizedRoles) {
this.authorizedRoles = authorizedRoles;
}
@Override
public final boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws ServletException, IOException {
if (this.authorizedRoles != null) {
for (String role : this.authorizedRoles) {
if (request.isUserInRole(role)) {
return true;
}
}
}
handleNotAuthorized(request, response, handler);
return false;
}
/**
* Handle a request that is not authorized according to this interceptor.
* Default implementation sends HTTP status code 403 ("forbidden").
* <p>This method can be overridden to write a custom message, forward or
* redirect to some error page or login page, or throw a ServletException.
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance evaluation
* @throws javax.servlet.ServletException if there is an internal error
* @throws java.io.IOException in case of an I/O error when writing the response
*/
protected void handleNotAuthorized(HttpServletRequest request, HttpServletResponse response, Object handler)
throws ServletException, IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.Assert;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
* Adapter that implements the Servlet HandlerInterceptor interface
* and wraps an underlying WebRequestInterceptor.
*
* @author Juergen Hoeller
* @since 2.0
* @see org.springframework.web.context.request.WebRequestInterceptor
* @see org.springframework.web.servlet.HandlerInterceptor
*/
public class WebRequestHandlerInterceptorAdapter implements HandlerInterceptor {
private final WebRequestInterceptor requestInterceptor;
/**
* Create a new WebRequestHandlerInterceptorAdapter for the given WebRequestInterceptor.
* @param requestInterceptor the WebRequestInterceptor to wrap
*/
public WebRequestHandlerInterceptorAdapter(WebRequestInterceptor requestInterceptor) {
Assert.notNull(requestInterceptor, "WebRequestInterceptor must not be null");
this.requestInterceptor = requestInterceptor;
}
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
this.requestInterceptor.preHandle(new DispatcherServletWebRequest(request, response));
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
this.requestInterceptor.postHandle(new DispatcherServletWebRequest(request, response),
(modelAndView != null && !modelAndView.wasCleared() ? modelAndView.getModelMap() : null));
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
this.requestInterceptor.afterCompletion(new DispatcherServletWebRequest(request, response), ex);
}
}

View File

@@ -0,0 +1,9 @@
/**
*
* Provides standard HandlerMapping implementations,
* including abstract base classes for custom implementations.
*
*/
package org.springframework.web.servlet.handler;

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.i18n;
import java.util.Locale;
import org.springframework.web.servlet.LocaleResolver;
/**
* Abstract base class for {@link LocaleResolver} implementations.
* Provides support for a default locale.
*
* @author Juergen Hoeller
* @since 1.2.9
*/
public abstract class AbstractLocaleResolver implements LocaleResolver {
private Locale defaultLocale;
/**
* Set a default Locale that this resolver will return if no other locale found.
*/
public void setDefaultLocale(Locale defaultLocale) {
this.defaultLocale = defaultLocale;
}
/**
* Return the default Locale that this resolver is supposed to fall back to, if any.
*/
protected Locale getDefaultLocale() {
return this.defaultLocale;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.i18n;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.LocaleResolver;
/**
* Implementation of LocaleResolver that simply uses the primary locale
* specified in the "accept-language" header of the HTTP request (that is,
* the locale sent by the client browser, normally that of the client's OS).
*
* <p>Note: Does not support <code>setLocale</code>, since the accept header
* can only be changed through changing the client's locale settings.
*
* @author Juergen Hoeller
* @since 27.02.2003
* @see javax.servlet.http.HttpServletRequest#getLocale()
*/
public class AcceptHeaderLocaleResolver implements LocaleResolver {
public Locale resolveLocale(HttpServletRequest request) {
return request.getLocale();
}
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
throw new UnsupportedOperationException(
"Cannot change HTTP accept header - use a different locale resolution strategy");
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.i18n;
import java.util.Locale;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.util.CookieGenerator;
import org.springframework.web.util.WebUtils;
/**
* {@link LocaleResolver} implementation that uses a cookie sent back to the user
* in case of a custom setting, with a fallback to the specified default locale
* or the request's accept-header locale.
*
* <p>This is particularly useful for stateless applications without user sessions.
*
* <p>Custom controllers can thus override the user's locale by calling
* {@link #setLocale(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.util.Locale)},
* for example responding to a certain locale change request.
*
* @author Juergen Hoeller
* @author Jean-Pierre Pawlak
* @since 27.02.2003
* @see #setDefaultLocale
* @see #setLocale
*/
public class CookieLocaleResolver extends CookieGenerator implements LocaleResolver {
/**
* The name of the request attribute that holds the locale.
* <p>Only used for overriding a cookie value if the locale has been
* changed in the course of the current request! Use
* {@link org.springframework.web.servlet.support.RequestContext#getLocale}
* to retrieve the current locale in controllers or views.
* @see org.springframework.web.servlet.support.RequestContext#getLocale
*/
public static final String LOCALE_REQUEST_ATTRIBUTE_NAME = CookieLocaleResolver.class.getName() + ".LOCALE";
/**
* The default cookie name used if none is explicitly set.
*/
public static final String DEFAULT_COOKIE_NAME = CookieLocaleResolver.class.getName() + ".LOCALE";
private Locale defaultLocale;
/**
* Creates a new instance of the {@link CookieLocaleResolver} class
* using the {@link #DEFAULT_COOKIE_NAME default cookie name}.
*/
public CookieLocaleResolver() {
setCookieName(DEFAULT_COOKIE_NAME);
}
/**
* Set a fixed Locale that this resolver will return if no cookie found.
*/
public void setDefaultLocale(Locale defaultLocale) {
this.defaultLocale = defaultLocale;
}
/**
* Return the fixed Locale that this resolver will return if no cookie found,
* if any.
*/
protected Locale getDefaultLocale() {
return this.defaultLocale;
}
public Locale resolveLocale(HttpServletRequest request) {
// Check request for pre-parsed or preset locale.
Locale locale = (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
if (locale != null) {
return locale;
}
// Retrieve and parse cookie value.
Cookie cookie = WebUtils.getCookie(request, getCookieName());
if (cookie != null) {
locale = StringUtils.parseLocaleString(cookie.getValue());
if (logger.isDebugEnabled()) {
logger.debug("Parsed cookie value [" + cookie.getValue() + "] into locale '" + locale + "'");
}
if (locale != null) {
request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME, locale);
return locale;
}
}
return determineDefaultLocale(request);
}
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
if (locale != null) {
// Set request attribute and add cookie.
request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME, locale);
addCookie(response, locale.toString());
}
else {
// Set request attribute to fallback locale and remove cookie.
request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME, determineDefaultLocale(request));
removeCookie(response);
}
}
/**
* Determine the default locale for the given request,
* Called if no locale cookie has been found.
* <p>The default implementation returns the specified default locale,
* if any, else falls back to the request's accept-header locale.
* @param request the request to resolve the locale for
* @return the default locale (never <code>null</code>)
* @see #setDefaultLocale
* @see javax.servlet.http.HttpServletRequest#getLocale()
*/
protected Locale determineDefaultLocale(HttpServletRequest request) {
Locale defaultLocale = getDefaultLocale();
if (defaultLocale == null) {
defaultLocale = request.getLocale();
}
return defaultLocale;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.i18n;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* {@link org.springframework.web.servlet.LocaleResolver} implementation
* that always returns a fixed default locale. Default is the current
* JVM's default locale.
*
* <p>Note: Does not support <code>setLocale</code>, as the fixed locale
* cannot be changed.
*
* @author Juergen Hoeller
* @since 1.1
* @see #setDefaultLocale
*/
public class FixedLocaleResolver extends AbstractLocaleResolver {
/**
* Create a default FixedLocaleResolver, exposing a configured default
* locale (or the JVM's default locale as fallback).
* @see #setDefaultLocale
*/
public FixedLocaleResolver() {
}
/**
* Create a FixedLocaleResolver that exposes the given locale.
* @param locale the locale to expose
*/
public FixedLocaleResolver(Locale locale) {
setDefaultLocale(locale);
}
public Locale resolveLocale(HttpServletRequest request) {
Locale locale = getDefaultLocale();
if (locale == null) {
locale = Locale.getDefault();
}
return locale;
}
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
throw new UnsupportedOperationException(
"Cannot change fixed locale - use a different locale resolution strategy");
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.i18n;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.support.RequestContextUtils;
/**
* Interceptor that allows for changing the current locale on every request,
* via a configurable request parameter.
*
* @author Juergen Hoeller
* @since 20.06.2003
* @see org.springframework.web.servlet.LocaleResolver
*/
public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {
/**
* Default name of the locale specification parameter: "locale".
*/
public static final String DEFAULT_PARAM_NAME = "locale";
private String paramName = DEFAULT_PARAM_NAME;
/**
* Set the name of the parameter that contains a locale specification
* in a locale change request. Default is "locale".
*/
public void setParamName(String paramName) {
this.paramName = paramName;
}
/**
* Return the name of the parameter that contains a locale specification
* in a locale change request.
*/
public String getParamName() {
return this.paramName;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws ServletException {
String newLocale = request.getParameter(this.paramName);
if (newLocale != null) {
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
if (localeResolver == null) {
throw new IllegalStateException("No LocaleResolver found: not in a DispatcherServlet request?");
}
localeResolver.setLocale(request, response, StringUtils.parseLocaleString(newLocale));
}
// Proceed in any case.
return true;
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.i18n;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.util.WebUtils;
/**
* Implementation of LocaleResolver that uses a locale attribute in the user's
* session in case of a custom setting, with a fallback to the specified default
* locale or the request's accept-header locale.
*
* <p>This is most appropriate if the application needs user sessions anyway,
* that is, when the HttpSession does not have to be created for the locale.
*
* <p>Custom controllers can override the user's locale by calling
* <code>setLocale</code>, e.g. responding to a locale change request.
*
* @author Juergen Hoeller
* @since 27.02.2003
* @see #setDefaultLocale
* @see #setLocale
*/
public class SessionLocaleResolver extends AbstractLocaleResolver {
/**
* Name of the session attribute that holds the locale.
* Only used internally by this implementation.
* Use <code>RequestContext(Utils).getLocale()</code>
* to retrieve the current locale in controllers or views.
* @see org.springframework.web.servlet.support.RequestContext#getLocale
* @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
*/
public static final String LOCALE_SESSION_ATTRIBUTE_NAME = SessionLocaleResolver.class.getName() + ".LOCALE";
public Locale resolveLocale(HttpServletRequest request) {
Locale locale = (Locale) WebUtils.getSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME);
if (locale == null) {
locale = determineDefaultLocale(request);
}
return locale;
}
/**
* Determine the default locale for the given request,
* Called if no locale session attribute has been found.
* <p>The default implementation returns the specified default locale,
* if any, else falls back to the request's accept-header locale.
* @param request the request to resolve the locale for
* @return the default locale (never <code>null</code>)
* @see #setDefaultLocale
* @see javax.servlet.http.HttpServletRequest#getLocale()
*/
protected Locale determineDefaultLocale(HttpServletRequest request) {
Locale defaultLocale = getDefaultLocale();
if (defaultLocale == null) {
defaultLocale = request.getLocale();
}
return defaultLocale;
}
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
WebUtils.setSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME, locale);
}
}

View File

@@ -0,0 +1,10 @@
/**
*
* Locale support classes for Spring's web MVC framework.
* Provides standard LocaleResolver implementations,
* and a HandlerInterceptor for locale changes.
*
*/
package org.springframework.web.servlet.i18n;

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.servlet.ModelAndView;
/**
* Abstract base class for custom command controllers.
*
* <p>Autopopulates a command bean from the request. For command validation,
* a validator (property inherited from {@link BaseCommandController}) can be
* used.
*
* <p>In most cases this command controller should not be used to handle form
* submission, because functionality for forms is offered in more detail by the
* {@link org.springframework.web.servlet.mvc.AbstractFormController} and its
* corresponding implementations.
*
* <p><b><a name="config">Exposed configuration properties</a>
* (<a href="BaseCommandController.html#config">and those defined by superclass</a>):</b><br>
* <i>none</i> (so only those available in superclass).</p>
*
* <p><b><a name="workflow">Workflow
* (<a name="BaseCommandController.html#workflow">and that defined by superclass</a>):</b><br>
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see #setCommandClass
* @see #setCommandName
* @see #setValidator
* @deprecated as of Spring 3.0, in favor of annotated controllers
*/
@Deprecated
public abstract class AbstractCommandController extends BaseCommandController {
/**
* Create a new AbstractCommandController.
*/
public AbstractCommandController() {
}
/**
* Create a new AbstractCommandController.
* @param commandClass class of the command bean
*/
public AbstractCommandController(Class commandClass) {
setCommandClass(commandClass);
}
/**
* Create a new AbstractCommandController.
* @param commandClass class of the command bean
* @param commandName name of the command bean
*/
public AbstractCommandController(Class commandClass, String commandName) {
setCommandClass(commandClass);
setCommandName(commandName);
}
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
Object command = getCommand(request);
ServletRequestDataBinder binder = bindAndValidate(request, command);
BindException errors = new BindException(binder.getBindingResult());
return handle(request, response, command, errors);
}
/**
* Template method for request handling, providing a populated and validated instance
* of the command class, and an Errors object containing binding and validation errors.
* <p>Call <code>errors.getModel()</code> to populate the ModelAndView model
* with the command and the Errors instance, under the specified command name,
* as expected by the "spring:bind" tag.
* @param request current HTTP request
* @param response current HTTP response
* @param command the populated command object
* @param errors validation errors holder
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
*/
protected abstract ModelAndView handle(
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
throws Exception;
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.support.WebContentGenerator;
import org.springframework.web.util.WebUtils;
/**
* <p>Convenient superclass for controller implementations, using the Template
* Method design pattern.</p>
*
* <p>As stated in the {@link org.springframework.web.servlet.mvc.Controller Controller}
* interface, a lot of functionality is already provided by certain abstract
* base controllers. The AbstractController is one of the most important
* abstract base controller providing basic features such as the generation
* of caching headers and the enabling or disabling of
* supported methods (GET/POST).</p>
*
* <p><b><a name="workflow">Workflow
* (<a href="Controller.html#workflow">and that defined by interface</a>):</b><br>
* <ol>
* <li>{@link #handleRequest(HttpServletRequest,HttpServletResponse) handleRequest()}
* will be called by the DispatcherServlet</li>
* <li>Inspection of supported methods (ServletException if request method
* is not support)</li>
* <li>If session is required, try to get it (ServletException if not found)</li>
* <li>Set caching headers if needed according to the cacheSeconds property</li>
* <li>Call abstract method {@link #handleRequestInternal(HttpServletRequest,HttpServletResponse) handleRequestInternal()}
* (optionally synchronizing around the call on the HttpSession),
* which should be implemented by extending classes to provide actual
* functionality to return {@link org.springframework.web.servlet.ModelAndView ModelAndView} objects.</li>
* </ol>
* </p>
*
* <p><b><a name="config">Exposed configuration properties</a>
* (<a href="Controller.html#config">and those defined by interface</a>):</b><br>
* <table border="1">
* <tr>
* <td><b>name</b></th>
* <td><b>default</b></td>
* <td><b>description</b></td>
* </tr>
* <tr>
* <td>supportedMethods</td>
* <td>GET,POST</td>
* <td>comma-separated (CSV) list of methods supported by this controller,
* such as GET, POST and PUT</td>
* </tr>
* <tr>
* <td>requireSession</td>
* <td>false</td>
* <td>whether a session should be required for requests to be able to
* be handled by this controller. This ensures that derived controller
* can - without fear of null pointers - call request.getSession() to
* retrieve a session. If no session can be found while processing
* the request, a ServletException will be thrown</td>
* </tr>
* <tr>
* <td>cacheSeconds</td>
* <td>-1</td>
* <td>indicates the amount of seconds to include in the cache header
* for the response following on this request. 0 (zero) will include
* headers for no caching at all, -1 (the default) will not generate
* <i>any headers</i> and any positive number will generate headers
* that state the amount indicated as seconds to cache the content</td>
* </tr>
* <tr>
* <td>synchronizeOnSession</td>
* <td>false</td>
* <td>whether the call to <code>handleRequestInternal</code> should be
* synchronized around the HttpSession, to serialize invocations
* from the same client. No effect if there is no HttpSession.
* </td>
* </tr>
* </table>
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see WebContentInterceptor
*/
public abstract class AbstractController extends WebContentGenerator implements Controller {
private boolean synchronizeOnSession = false;
/**
* Set if controller execution should be synchronized on the session,
* to serialize parallel invocations from the same client.
* <p>More specifically, the execution of the <code>handleRequestInternal</code>
* method will get synchronized if this flag is "true". The best available
* session mutex will be used for the synchronization; ideally, this will
* be a mutex exposed by HttpSessionMutexListener.
* <p>The session mutex is guaranteed to be the same object during
* the entire lifetime of the session, available under the key defined
* by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a
* safe reference to synchronize on for locking on the current session.
* <p>In many cases, the HttpSession reference itself is a safe mutex
* as well, since it will always be the same object reference for the
* same active logical session. However, this is not guaranteed across
* different servlet containers; the only 100% safe way is a session mutex.
* @see org.springframework.web.servlet.mvc.AbstractController#handleRequestInternal
* @see org.springframework.web.util.HttpSessionMutexListener
* @see org.springframework.web.util.WebUtils#getSessionMutex(javax.servlet.http.HttpSession)
*/
public final void setSynchronizeOnSession(boolean synchronizeOnSession) {
this.synchronizeOnSession = synchronizeOnSession;
}
/**
* Return whether controller execution should be synchronized on the session.
*/
public final boolean isSynchronizeOnSession() {
return this.synchronizeOnSession;
}
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception {
// Delegate to WebContentGenerator for checking and preparing.
checkAndPrepare(request, response, this instanceof LastModified);
// Execute handleRequestInternal in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
return handleRequestInternal(request, response);
}
}
}
return handleRequestInternal(request, response);
}
/**
* Template method. Subclasses must implement this.
* The contract is the same as for <code>handleRequest</code>.
* @see #handleRequest
*/
protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception;
}

View File

@@ -0,0 +1,678 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.HttpSessionRequiredException;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.servlet.ModelAndView;
/**
* <p>Form controller that auto-populates a form bean from the request.
* This, either using a new bean instance per request, or using the same bean
* when the <code>sessionForm</code> property has been set to <code>true</code>.</p>
*
* <p>This class is the base class for both framework subclasses such as
* {@link SimpleFormController} and {@link AbstractWizardFormController}
* and custom form controllers that you may provide yourself.</p>
*
* <p>A form-input view and an after-submission view have to be provided
* programmatically. To provide those views using configuration properties,
* use the {@link SimpleFormController}.</p>
*
* <p>Subclasses need to override <code>showForm</code> to prepare the form view,
* and <code>processFormSubmission</code> to handle submit requests. For the latter,
* binding errors like type mismatches will be reported via the given "errors" holder.
* For additional custom form validation, a validator (property inherited from
* BaseCommandController) can be used, reporting via the same "errors" instance.</p>
*
* <p>Comparing this Controller to the Struts notion of the <code>Action</code>
* shows us that with Spring, you can use any ordinary JavaBeans or database-
* backed JavaBeans without having to implement a framework-specific class
* (like Struts' <code>ActionForm</code>). More complex properties of JavaBeans
* (Dates, Locales, but also your own application-specific or compound types)
* can be represented and submitted to the controller, by using the notion of
* a <code>java.beans.PropertyEditor</code>. For more information on that
* subject, see the workflow of this controller and the explanation of the
* {@link BaseCommandController}.</p>
*
* <p><b><a name="workflow">Workflow
* (<a href="BaseCommandController.html#workflow">and that defined by superclass</a>):</b><br>
* <ol>
* <li><b>The controller receives a request for a new form (typically a GET).</b></li>
* <li>Call to {@link #formBackingObject formBackingObject()} which by default,
* returns an instance of the commandClass that has been configured
* (see the properties the superclass exposes), but can also be overridden
* to e.g. retrieve an object from the database (that needs to be modified
* using the form).</li>
* <li>Call to {@link #initBinder initBinder()} which allows you to register
* custom editors for certain fields (often properties of non-primitive
* or non-String types) of the command class. This will render appropriate
* Strings for those property values, e.g. locale-specific date strings.</li>
* <li><em>Only if <code>bindOnNewForm</code> is set to <code>true</code></em>, then
* {@link org.springframework.web.bind.ServletRequestDataBinder ServletRequestDataBinder}
* gets applied to populate the new form object with initial request parameters and the
* {@link #onBindOnNewForm(HttpServletRequest, Object, BindException)} callback method is
* called. <em>Note:</em> any defined Validators are not applied at this point, to allow
* partial binding. However be aware that any Binder customizations applied via
* initBinder() (such as
* {@link org.springframework.validation.DataBinder#setRequiredFields(String[])} will
* still apply. As such, if using bindOnNewForm=true and initBinder() customizations are
* used to validate fields instead of using Validators, in the case that only some fields
* will be populated for the new form, there will potentially be some bind errors for
* missing fields in the errors object. Any view (JSP, etc.) that displays binder errors
* needs to be intelligent and for this case take into account whether it is displaying the
* initial form view or subsequent post results, skipping error display for the former.</li>
* <li>Call to {@link #showForm(HttpServletRequest, HttpServletResponse, BindException) showForm()}
* to return a View that should be rendered (typically the view that renders
* the form). This method has to be implemented in subclasses.</li>
* <li>The showForm() implementation will call {@link #referenceData referenceData()},
* which you can implement to provide any relevant reference data you might need
* when editing a form (e.g. a List of Locale objects you're going to let the
* user select one from).</li>
* <li>Model gets exposed and view gets rendered, to let the user fill in the form.</li>
* <li><b>The controller receives a form submission (typically a POST).</b>
* To use a different way of detecting a form submission, override the
* {@link #isFormSubmission isFormSubmission} method.
* </li>
* <li>If <code>sessionForm</code> is not set, {@link #formBackingObject formBackingObject()}
* is called to retrieve a form object. Otherwise, the controller tries to
* find the command object which is already bound in the session. If it cannot
* find the object, it does a call to {@link #handleInvalidSubmit handleInvalidSubmit}
* which - by default - tries to create a new form object and resubmit the form.</li>
* <li>The {@link org.springframework.web.bind.ServletRequestDataBinder ServletRequestDataBinder}
* gets applied to populate the form object with current request parameters.
* <li>Call to {@link #onBind onBind(HttpServletRequest, Object, Errors)} which allows
* you to do custom processing after binding but before validation (e.g. to manually
* bind request parameters to bean properties, to be seen by the Validator).</li>
* <li>If <code>validateOnBinding</code> is set, a registered Validator will be invoked.
* The Validator will check the form object properties, and register corresponding
* errors via the given {@link org.springframework.validation.Errors Errors}</li> object.
* <li>Call to {@link #onBindAndValidate onBindAndValidate()} which allows you
* to do custom processing after binding and validation (e.g. to manually
* bind request parameters, and to validate them outside a Validator).</li>
* <li>Call {@link #processFormSubmission(HttpServletRequest, HttpServletResponse,
* Object, BindException) processFormSubmission()} to process the submission, with
* or without binding errors. This method has to be implemented in subclasses.</li>
* </ol>
* </p>
*
* <p>In session form mode, a submission without an existing form object in the
* session is considered invalid, like in case of a resubmit/reload by the browser.
* The {@link #handleInvalidSubmit handleInvalidSubmit} method is invoked then,
* by default trying to resubmit. It can be overridden in subclasses to show
* corresponding messages or to redirect to a new form, in order to avoid duplicate
* submissions. The form object in the session can be considered a transaction
* token in that case.</p>
*
* <p>Note that views should never retrieve form beans from the session but always
* from the request, as prepared by the form controller. Remember that some view
* technologies like Velocity cannot even access a HTTP session.</p>
*
* <p><b><a name="config">Exposed configuration properties</a>
* (<a href="BaseCommandController.html#config">and those defined by superclass</a>):</b><br>
* <table border="1">
* <tr>
* <td><b>name</b></td>
* <td><b>default</b></td>
* <td><b>description</b></td>
* </tr>
* <tr>
* <td>bindOnNewForm</td>
* <td>false</td>
* <td>Indicates whether to bind servlet request parameters when
* creating a new form. Otherwise, the parameters will only be
* bound on form submission attempts.</td>
* </tr>
* <tr>
* <td>sessionForm</td>
* <td>false</td>
* <td>Indicates whether the form object should be kept in the session
* when a user asks for a new form. This allows you e.g. to retrieve
* an object from the database, let the user edit it, and then persist
* it again. Otherwise, a new command object will be created for each
* request (even when showing the form again after validation errors).</td>
* </tr>
* </table>
* </p>
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Alef Arendsen
* @author Rob Harrop
* @author Colin Sampaleanu
* @see #showForm(HttpServletRequest, HttpServletResponse, BindException)
* @see #processFormSubmission
* @see SimpleFormController
* @see AbstractWizardFormController
* @deprecated as of Spring 3.0, in favor of annotated controllers
*/
@Deprecated
public abstract class AbstractFormController extends BaseCommandController {
private boolean bindOnNewForm = false;
private boolean sessionForm = false;
/**
* Create a new AbstractFormController.
* <p>Subclasses should set the following properties, either in the constructor
* or via a BeanFactory: commandName, commandClass, bindOnNewForm, sessionForm.
* Note that "commandClass" doesn't need to be set when overriding
* {@link #formBackingObject}, since the latter determines the class anyway.
* <p>"cacheSeconds" is by default set to 0 (-> no caching for all form controllers).
* @see #setCommandName
* @see #setCommandClass
* @see #setBindOnNewForm
* @see #setSessionForm
* @see #formBackingObject
*/
public AbstractFormController() {
setCacheSeconds(0);
}
/**
* Set whether request parameters should be bound to the form object
* in case of a non-submitting request, that is, a new form.
*/
public final void setBindOnNewForm(boolean bindOnNewForm) {
this.bindOnNewForm = bindOnNewForm;
}
/**
* Return <code>true</code> if request parameters should be bound in case of a new form.
*/
public final boolean isBindOnNewForm() {
return this.bindOnNewForm;
}
/**
* Activate/deactivate session form mode. In session form mode,
* the form is stored in the session to keep the form object instance
* between requests, instead of creating a new one on each request.
* <p>This is necessary for either wizard-style controllers that populate a
* single form object from multiple pages, or forms that populate a persistent
* object that needs to be identical to allow for tracking changes.
* <p>Please note that the {@link AbstractFormController} class (and all
* subclasses of it unless stated to the contrary) do <i>not</i> support
* the notion of a conversation. This is important in the context of this
* property, because it means that there is only <i>one</i> form per session:
* this means that if session form mode is activated and a user opens up
* say two tabs in their browser and attempts to edit two distinct objects
* using the same form, then the <i>shared</i> session state can potentially
* (and most probably will) be overwritten by the last tab to be opened,
* which can lead to errors when either of the forms in each is finally
* submitted.
* <p>If you need to have per-form, per-session state management (that is,
* stateful web conversations), the recommendation is to use
* <a href="http://www.springframework.org/webflow">Spring WebFlow</a>,
* which has full support for conversations and has a much more flexible
* usage model overall.
* @param sessionForm <code>true</code> if session form mode is to be activated
*/
public final void setSessionForm(boolean sessionForm) {
this.sessionForm = sessionForm;
}
/**
* Return <code>true</code> if session form mode is activated.
*/
public final boolean isSessionForm() {
return this.sessionForm;
}
/**
* Handles two cases: form submissions and showing a new form.
* Delegates the decision between the two to {@link #isFormSubmission},
* always treating requests without existing form session attribute
* as new form when using session form mode.
* @see #isFormSubmission
* @see #showNewForm
* @see #processFormSubmission
*/
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
// Form submission or new form to show?
if (isFormSubmission(request)) {
// Fetch form object from HTTP session, bind, validate, process submission.
try {
Object command = getCommand(request);
ServletRequestDataBinder binder = bindAndValidate(request, command);
BindException errors = new BindException(binder.getBindingResult());
return processFormSubmission(request, response, command, errors);
}
catch (HttpSessionRequiredException ex) {
// Cannot submit a session form if no form object is in the session.
if (logger.isDebugEnabled()) {
logger.debug("Invalid submit detected: " + ex.getMessage());
}
return handleInvalidSubmit(request, response);
}
}
else {
// New form to show: render form view.
return showNewForm(request, response);
}
}
/**
* Determine if the given request represents a form submission.
* <p>The default implementation treats a POST request as form submission.
* Note: If the form session attribute doesn't exist when using session form
* mode, the request is always treated as new form by handleRequestInternal.
* <p>Subclasses can override this to use a custom strategy, e.g. a specific
* request parameter (assumably a hidden field or submit button name).
* @param request current HTTP request
* @return if the request represents a form submission
*/
protected boolean isFormSubmission(HttpServletRequest request) {
return "POST".equals(request.getMethod());
}
/**
* Return the name of the HttpSession attribute that holds the form object
* for this form controller.
* <p>The default implementation delegates to the {@link #getFormSessionAttributeName()}
* variant without arguments.
* @param request current HTTP request
* @return the name of the form session attribute, or <code>null</code> if not in session form mode
* @see #getFormSessionAttributeName
* @see javax.servlet.http.HttpSession#getAttribute
*/
protected String getFormSessionAttributeName(HttpServletRequest request) {
return getFormSessionAttributeName();
}
/**
* Return the name of the HttpSession attribute that holds the form object
* for this form controller.
* <p>Default is an internal name, of no relevance to applications, as the form
* session attribute is not usually accessed directly. Can be overridden to use
* an application-specific attribute name, which allows other code to access
* the session attribute directly.
* @return the name of the form session attribute
* @see javax.servlet.http.HttpSession#getAttribute
*/
protected String getFormSessionAttributeName() {
return getClass().getName() + ".FORM." + getCommandName();
}
/**
* Show a new form. Prepares a backing object for the current form
* and the given request, including checking its validity.
* @param request current HTTP request
* @param response current HTTP response
* @return the prepared form view
* @throws Exception in case of an invalid new form object
* @see #getErrorsForNewForm
*/
protected final ModelAndView showNewForm(HttpServletRequest request, HttpServletResponse response)
throws Exception {
logger.debug("Displaying new form");
return showForm(request, response, getErrorsForNewForm(request));
}
/**
* Create a BindException instance for a new form.
* Called by {@link #showNewForm}.
* <p>Can be used directly when intending to show a new form but with
* special errors registered on it (for example, on invalid submit).
* Usually, the resulting BindException will be passed to
* {@link #showForm(HttpServletRequest, HttpServletResponse, BindException)},
* after registering the errors on it.
* @param request current HTTP request
* @return the BindException instance
* @throws Exception in case of an invalid new form object
* @see #showNewForm
* @see #showForm(HttpServletRequest, HttpServletResponse, BindException)
* @see #handleInvalidSubmit
*/
protected final BindException getErrorsForNewForm(HttpServletRequest request) throws Exception {
// Create form-backing object for new form.
Object command = formBackingObject(request);
if (command == null) {
throw new ServletException("Form object returned by formBackingObject() must not be null");
}
if (!checkCommand(command)) {
throw new ServletException("Form object returned by formBackingObject() must match commandClass");
}
// Bind without validation, to allow for prepopulating a form, and for
// convenient error evaluation in views (on both first attempt and resubmit).
ServletRequestDataBinder binder = createBinder(request, command);
BindException errors = new BindException(binder.getBindingResult());
if (isBindOnNewForm()) {
logger.debug("Binding to new form");
binder.bind(request);
onBindOnNewForm(request, command, errors);
}
// Return BindException object that resulted from binding.
return errors;
}
/**
* Callback for custom post-processing in terms of binding for a new form.
* Called when preparing a new form if <code>bindOnNewForm</code> is <code>true</code>.
* <p>The default implementation delegates to <code>onBindOnNewForm(request, command)</code>.
* @param request current HTTP request
* @param command the command object to perform further binding on
* @param errors validation errors holder, allowing for additional
* custom registration of binding errors
* @throws Exception in case of invalid state or arguments
* @see #onBindOnNewForm(javax.servlet.http.HttpServletRequest, Object)
* @see #setBindOnNewForm
*/
protected void onBindOnNewForm(HttpServletRequest request, Object command, BindException errors)
throws Exception {
onBindOnNewForm(request, command);
}
/**
* Callback for custom post-processing in terms of binding for a new form.
* <p>Called by the default implementation of the
* {@link #onBindOnNewForm(HttpServletRequest, Object, BindException)} variant
* with all parameters, after standard binding when displaying the form view.
* Only called if <code>bindOnNewForm</code> is set to <code>true</code>.
* <p>The default implementation is empty.
* @param request current HTTP request
* @param command the command object to perform further binding on
* @throws Exception in case of invalid state or arguments
* @see #onBindOnNewForm(HttpServletRequest, Object, BindException)
* @see #setBindOnNewForm(boolean)
*/
protected void onBindOnNewForm(HttpServletRequest request, Object command) throws Exception {
}
/**
* Return the form object for the given request.
* <p>Calls {@link #formBackingObject} if not in session form mode.
* Else, retrieves the form object from the session. Note that the form object
* gets removed from the session, but it will be re-added when showing the
* form for resubmission.
* @param request current HTTP request
* @return object form to bind onto
* @throws org.springframework.web.HttpSessionRequiredException
* if a session was expected but no active session (or session form object) found
* @throws Exception in case of invalid state or arguments
* @see #formBackingObject
*/
@Override
protected final Object getCommand(HttpServletRequest request) throws Exception {
// If not in session-form mode, create a new form-backing object.
if (!isSessionForm()) {
return formBackingObject(request);
}
// Session-form mode: retrieve form object from HTTP session attribute.
HttpSession session = request.getSession(false);
if (session == null) {
throw new HttpSessionRequiredException("Must have session when trying to bind (in session-form mode)");
}
String formAttrName = getFormSessionAttributeName(request);
Object sessionFormObject = session.getAttribute(formAttrName);
if (sessionFormObject == null) {
throw new HttpSessionRequiredException("Form object not found in session (in session-form mode)");
}
// Remove form object from HTTP session: we might finish the form workflow
// in this request. If it turns out that we need to show the form view again,
// we'll re-bind the form object to the HTTP session.
if (logger.isDebugEnabled()) {
logger.debug("Removing form session attribute [" + formAttrName + "]");
}
session.removeAttribute(formAttrName);
return currentFormObject(request, sessionFormObject);
}
/**
* Retrieve a backing object for the current form from the given request.
* <p>The properties of the form object will correspond to the form field values
* in your form view. This object will be exposed in the model under the specified
* command name, to be accessed under that name in the view: for example, with
* a "spring:bind" tag. The default command name is "command".
* <p>Note that you need to activate session form mode to reuse the form-backing
* object across the entire form workflow. Else, a new instance of the command
* class will be created for each submission attempt, just using this backing
* object as template for the initial form.
* <p>The default implementation calls {@link #createCommand()},
* creating a new empty instance of the specified command class.
* Subclasses can override this to provide a preinitialized backing object.
* @param request current HTTP request
* @return the backing object
* @throws Exception in case of invalid state or arguments
* @see #setCommandName
* @see #setCommandClass
* @see #createCommand
*/
protected Object formBackingObject(HttpServletRequest request) throws Exception {
return createCommand();
}
/**
* Return the current form object to use for binding and further processing,
* based on the passed-in form object as found in the HttpSession.
* <p>The default implementation simply returns the session form object as-is.
* Subclasses can override this to post-process the session form object,
* for example reattaching it to a persistence manager.
* @param sessionFormObject the form object retrieved from the HttpSession
* @return the form object to use for binding and further processing
* @throws Exception in case of invalid state or arguments
*/
protected Object currentFormObject(HttpServletRequest request, Object sessionFormObject) throws Exception {
return sessionFormObject;
}
/**
* Prepare the form model and view, including reference and error data.
* Can show a configured form page, or generate a form view programmatically.
* <p>A typical implementation will call
* <code>showForm(request, errors, "myView")</code>
* to prepare the form view for a specific view name, returning the
* ModelAndView provided there.
* <p>For building a custom ModelAndView, call <code>errors.getModel()</code>
* to populate the ModelAndView model with the command and the Errors instance,
* under the specified command name, as expected by the "spring:bind" tag.
* You also need to include the model returned by {@link #referenceData}.
* <p>Note: If you decide to have a "formView" property specifying the
* view name, consider using SimpleFormController.
* @param request current HTTP request
* @param response current HTTP response
* @param errors validation errors holder
* @return the prepared form view, or <code>null</code> if handled directly
* @throws Exception in case of invalid state or arguments
* @see #showForm(HttpServletRequest, BindException, String)
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
* @see #referenceData(HttpServletRequest, Object, Errors)
* @see SimpleFormController#setFormView
*/
protected abstract ModelAndView showForm(
HttpServletRequest request, HttpServletResponse response, BindException errors)
throws Exception;
/**
* Prepare model and view for the given form, including reference and errors.
* <p>In session form mode: Re-puts the form object in the session when
* returning to the form, as it has been removed by getCommand.
* <p>Can be used in subclasses to redirect back to a specific form page.
* @param request current HTTP request
* @param errors validation errors holder
* @param viewName name of the form view
* @return the prepared form view
* @throws Exception in case of invalid state or arguments
*/
protected final ModelAndView showForm(HttpServletRequest request, BindException errors, String viewName)
throws Exception {
return showForm(request, errors, viewName, null);
}
/**
* Prepare model and view for the given form, including reference and errors,
* adding a controller-specific control model.
* <p>In session form mode: Re-puts the form object in the session when returning
* to the form, as it has been removed by getCommand.
* <p>Can be used in subclasses to redirect back to a specific form page.
* @param request current HTTP request
* @param errors validation errors holder
* @param viewName name of the form view
* @param controlModel model map containing controller-specific control data
* (e.g. current page in wizard-style controllers or special error message)
* @return the prepared form view
* @throws Exception in case of invalid state or arguments
*/
protected final ModelAndView showForm(
HttpServletRequest request, BindException errors, String viewName, Map controlModel)
throws Exception {
// In session form mode, re-expose form object as HTTP session attribute.
// Re-binding is necessary for proper state handling in a cluster,
// to notify other nodes of changes in the form object.
if (isSessionForm()) {
String formAttrName = getFormSessionAttributeName(request);
if (logger.isDebugEnabled()) {
logger.debug("Setting form session attribute [" + formAttrName + "] to: " + errors.getTarget());
}
request.getSession().setAttribute(formAttrName, errors.getTarget());
}
// Fetch errors model as starting point, containing form object under
// "commandName", and corresponding Errors instance under internal key.
Map model = errors.getModel();
// Merge reference data into model, if any.
Map referenceData = referenceData(request, errors.getTarget(), errors);
if (referenceData != null) {
model.putAll(referenceData);
}
// Merge control attributes into model, if any.
if (controlModel != null) {
model.putAll(controlModel);
}
// Trigger rendering of the specified view, using the final model.
return new ModelAndView(viewName, model);
}
/**
* Create a reference data map for the given request, consisting of
* bean name/bean instance pairs as expected by ModelAndView.
* <p>The default implementation returns <code>null</code>.
* Subclasses can override this to set reference data used in the view.
* @param request current HTTP request
* @param command form object with request parameters bound onto it
* @param errors validation errors holder
* @return a Map with reference data entries, or <code>null</code> if none
* @throws Exception in case of invalid state or arguments
* @see ModelAndView
*/
protected Map referenceData(HttpServletRequest request, Object command, Errors errors) throws Exception {
return null;
}
/**
* Process form submission request. Called by {@link #handleRequestInternal}
* in case of a form submission, with or without binding errors. Implementations
* need to proceed properly, typically showing a form view in case of binding
* errors or performing a submit action else.
* <p>Subclasses can implement this to provide custom submission handling like
* triggering a custom action. They can also provide custom validation and call
* {@link #showForm(HttpServletRequest, HttpServletResponse, BindException)}
* or proceed with the submission accordingly.
* <p>For a success view, call <code>errors.getModel()</code> to populate the
* ModelAndView model with the command and the Errors instance, under the
* specified command name, as expected by the "spring:bind" tag. For a form view,
* simply return the ModelAndView object provided by
* {@link #showForm(HttpServletRequest, HttpServletResponse, BindException)}.
* @param request current servlet request
* @param response current servlet response
* @param command form object with request parameters bound onto it
* @param errors holder without errors (subclass can add errors if it wants to)
* @return the prepared model and view, or <code>null</code>
* @throws Exception in case of errors
* @see #handleRequestInternal
* @see #isFormSubmission
* @see #showForm(HttpServletRequest, HttpServletResponse, BindException)
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
*/
protected abstract ModelAndView processFormSubmission(
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
throws Exception;
/**
* Handle an invalid submit request, e.g. when in session form mode but no form object
* was found in the session (like in case of an invalid resubmit by the browser).
* <p>The default implementation simply tries to resubmit the form with a new
* form object. This should also work if the user hit the back button, changed
* some form data, and resubmitted the form.
* <p>Note: To avoid duplicate submissions, you need to override this method.
* Either show some "invalid submit" message, or call {@link #showNewForm} for
* resetting the form (prepopulating it with the current values if "bindOnNewForm"
* is true). In this case, the form object in the session serves as transaction token.
* <pre>
* protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response) throws Exception {
* return showNewForm(request, response);
* }</pre>
* You can also show a new form but with special errors registered on it:
* <pre class="code">
* protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response) throws Exception {
* BindException errors = getErrorsForNewForm(request);
* errors.reject("duplicateFormSubmission", "Duplicate form submission");
* return showForm(request, response, errors);
* }</pre>
* @param request current HTTP request
* @param response current HTTP response
* @return a prepared view, or <code>null</code> if handled directly
* @throws Exception in case of errors
* @see #showNewForm
* @see #getErrorsForNewForm
* @see #showForm(HttpServletRequest, HttpServletResponse, BindException)
* @see #setBindOnNewForm
*/
protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response)
throws Exception {
Object command = formBackingObject(request);
ServletRequestDataBinder binder = bindAndValidate(request, command);
BindException errors = new BindException(binder.getBindingResult());
return processFormSubmission(request, response, command, errors);
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.Assert;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.util.UrlPathHelper;
/**
* Abstract base class for <code>Controllers</code> that return a view name
* based on the request URL.
*
* <p>Provides infrastructure for determining view names from URLs and configurable
* URL lookup. For information on the latter, see <code>alwaysUseFullPath</code>
* and <code>urlDecode</code> properties.
*
* @author Juergen Hoeller
* @since 1.2.6
* @see #setAlwaysUseFullPath
* @see #setUrlDecode
*/
public abstract class AbstractUrlViewController extends AbstractController {
private UrlPathHelper urlPathHelper = new UrlPathHelper();
/**
* Set if URL lookup should always use full path within current servlet
* context. Else, the path within the current servlet mapping is used
* if applicable (i.e. in the case of a ".../*" servlet mapping in web.xml).
* Default is "false".
* @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
*/
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
}
/**
* Set if context path and request URI should be URL-decoded.
* Both are returned <i>undecoded</i> by the Servlet API,
* in contrast to the servlet path.
* <p>Uses either the request encoding or the default encoding according
* to the Servlet spec (ISO-8859-1).
* @see org.springframework.web.util.UrlPathHelper#setUrlDecode
*/
public void setUrlDecode(boolean urlDecode) {
this.urlPathHelper.setUrlDecode(urlDecode);
}
/**
* Set the UrlPathHelper to use for the resolution of lookup paths.
* <p>Use this to override the default UrlPathHelper with a custom subclass,
* or to share common UrlPathHelper settings across multiple MethodNameResolvers
* and HandlerMappings.
* @see org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#setUrlPathHelper
*/
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
this.urlPathHelper = urlPathHelper;
}
/**
* Return the UrlPathHelper to use for the resolution of lookup paths.
*/
protected UrlPathHelper getUrlPathHelper() {
return this.urlPathHelper;
}
/**
* Retrieves the URL path to use for lookup and delegates to
* {@link #getViewNameForRequest}. Also adds the content of
* {@link RequestContextUtils#getInputFlashMap} to the model.
*/
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
String viewName = getViewNameForRequest(request);
if (logger.isDebugEnabled()) {
logger.debug("Returning view name '" + viewName + "' for lookup path [" + lookupPath + "]");
}
return new ModelAndView(viewName, RequestContextUtils.getInputFlashMap(request));
}
/**
* Return the name of the view to render for this request, based on the
* given lookup path. Called by {@link #handleRequestInternal}.
* @param request current HTTP request
* @return a view name for this request (never <code>null</code>)
* @see #handleRequestInternal
* @see #setAlwaysUseFullPath
* @see #setUrlDecode
*/
protected abstract String getViewNameForRequest(HttpServletRequest request);
}

View File

@@ -0,0 +1,751 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.WebUtils;
/**
* Form controller for typical wizard-style workflows.
*
* <p>In contrast to classic forms, wizards have more than one form view page.
* Therefore, there are various actions instead of one single submit action:
* <ul>
* <li>finish: trying to leave the wizard successfully, that is, perform its
* final action, and thus requiring a valid state;
* <li>cancel: leaving the wizard without performing its final action, and
* thus without regard to the validity of its current state;
* <li>page change: showing another wizard page, e.g. the next or previous
* one, with regard to "dirty back" and "dirty forward".
* </ul>
*
* <p>Finish and cancel actions can be triggered by request parameters, named
* PARAM_FINISH ("_finish") and PARAM_CANCEL ("_cancel"), ignoring parameter
* values to allow for HTML buttons. The target page for page changes can be
* specified by PARAM_TARGET, appending the page number to the parameter name
* (e.g. "_target1"). The action parameters are recognized when triggered by
* image buttons too (via "_finish.x", "_abort.x", or "_target1.x").
*
* <p>The current page number will be stored in the session. It can also be
* specified as request parameter PARAM_PAGE ("_page") in order to properly handle
* usage of the back button in a browser: In this case, a submission will always
* contain the correct page number, even if the user submitted from an old view.
*
* <p>The page can only be changed if it validates correctly, except if a
* "dirty back" or "dirty forward" is allowed. At finish, all pages get
* validated again to guarantee a consistent state.
*
* <p>Note that a validator's default validate method is not executed when using
* this class! Rather, the {@link #validatePage} implementation should call
* special <code>validateXXX</code> methods that the validator needs to provide,
* validating certain pieces of the object. These can be combined to validate
* the elements of individual pages.
*
* <p>Note: Page numbering starts with 0, to be able to pass an array
* consisting of the corresponding view names to the "pages" bean property.
*
* @author Juergen Hoeller
* @since 25.04.2003
* @see #setPages
* @see #validatePage
* @see #processFinish
* @see #processCancel
* @deprecated as of Spring 3.0, in favor of annotated controllers
*/
@Deprecated
public abstract class AbstractWizardFormController extends AbstractFormController {
/**
* Parameter triggering the finish action.
* Can be called from any wizard page!
*/
public static final String PARAM_FINISH = "_finish";
/**
* Parameter triggering the cancel action.
* Can be called from any wizard page!
*/
public static final String PARAM_CANCEL = "_cancel";
/**
* Parameter specifying the target page,
* appending the page number to the name.
*/
public static final String PARAM_TARGET = "_target";
/**
* Parameter specifying the current page as value. Not necessary on
* form pages, but allows to properly handle usage of the back button.
* @see #setPageAttribute
*/
public static final String PARAM_PAGE = "_page";
private String[] pages;
private String pageAttribute;
private boolean allowDirtyBack = true;
private boolean allowDirtyForward = false;
/**
* Create a new AbstractWizardFormController.
* <p>"sessionForm" is automatically turned on, "validateOnBinding"
* turned off, and "cacheSeconds" set to 0 by the base class
* (-> no caching for all form controllers).
*/
public AbstractWizardFormController() {
// AbstractFormController sets default cache seconds to 0.
super();
// Always needs session to keep data from all pages.
setSessionForm(true);
// Never validate everything on binding ->
// wizards validate individual pages.
setValidateOnBinding(false);
}
/**
* Set the wizard pages, i.e. the view names for the pages.
* The array index is interpreted as page number.
* @param pages view names for the pages
*/
public final void setPages(String[] pages) {
if (pages == null || pages.length == 0) {
throw new IllegalArgumentException("No wizard pages defined");
}
this.pages = pages;
}
/**
* Return the wizard pages, i.e. the view names for the pages.
* The array index corresponds to the page number.
* <p>Note that a concrete wizard form controller might override
* {@link #getViewName(HttpServletRequest, Object, int)} to
* determine the view name for each page dynamically.
* @see #getViewName(javax.servlet.http.HttpServletRequest, Object, int)
*/
public final String[] getPages() {
return this.pages;
}
/**
* Return the number of wizard pages.
* Useful to check whether the last page has been reached.
* <p>Note that a concrete wizard form controller might override
* {@link #getPageCount(HttpServletRequest, Object)} to determine
* the page count dynamically. The default implementation of that extended
* <code>getPageCount</code> variant returns the static page count as
* determined by this <code>getPageCount()</code> method.
* @see #getPageCount(javax.servlet.http.HttpServletRequest, Object)
*/
protected final int getPageCount() {
return this.pages.length;
}
/**
* Set the name of the page attribute in the model, containing
* an Integer with the current page number.
* <p>This will be necessary for single views rendering multiple view pages.
* It also allows for specifying the optional "_page" parameter.
* @param pageAttribute name of the page attribute
* @see #PARAM_PAGE
*/
public final void setPageAttribute(String pageAttribute) {
this.pageAttribute = pageAttribute;
}
/**
* Return the name of the page attribute in the model.
*/
public final String getPageAttribute() {
return this.pageAttribute;
}
/**
* Set if "dirty back" is allowed, that is, if moving to a former wizard
* page is allowed in case of validation errors for the current page.
* @param allowDirtyBack if "dirty back" is allowed
*/
public final void setAllowDirtyBack(boolean allowDirtyBack) {
this.allowDirtyBack = allowDirtyBack;
}
/**
* Return whether "dirty back" is allowed.
*/
public final boolean isAllowDirtyBack() {
return this.allowDirtyBack;
}
/**
* Set if "dirty forward" is allowed, that is, if moving to a later wizard
* page is allowed in case of validation errors for the current page.
* @param allowDirtyForward if "dirty forward" is allowed
*/
public final void setAllowDirtyForward(boolean allowDirtyForward) {
this.allowDirtyForward = allowDirtyForward;
}
/**
* Return whether "dirty forward" is allowed.
*/
public final boolean isAllowDirtyForward() {
return this.allowDirtyForward;
}
/**
* Calls page-specific onBindAndValidate method.
*/
@Override
protected final void onBindAndValidate(HttpServletRequest request, Object command, BindException errors)
throws Exception {
onBindAndValidate(request, command, errors, getCurrentPage(request));
}
/**
* Callback for custom post-processing in terms of binding and validation.
* Called on each submit, after standard binding but before page-specific
* validation of this wizard form controller.
* <p>Note: AbstractWizardFormController does not perform standand
* validation on binding but rather applies page-specific validation
* on processing the form submission.
* @param request current HTTP request
* @param command bound command
* @param errors Errors instance for additional custom validation
* @param page current wizard page
* @throws Exception in case of invalid state or arguments
* @see #bindAndValidate
* @see #processFormSubmission
* @see org.springframework.validation.Errors
*/
protected void onBindAndValidate(HttpServletRequest request, Object command, BindException errors, int page)
throws Exception {
}
/**
* Consider an explicit finish or cancel request as a form submission too.
* @see #isFinishRequest(javax.servlet.http.HttpServletRequest)
* @see #isCancelRequest(javax.servlet.http.HttpServletRequest)
*/
@Override
protected boolean isFormSubmission(HttpServletRequest request) {
return super.isFormSubmission(request) || isFinishRequest(request) || isCancelRequest(request);
}
/**
* Calls page-specific referenceData method.
*/
@Override
protected final Map referenceData(HttpServletRequest request, Object command, Errors errors)
throws Exception {
return referenceData(request, command, errors, getCurrentPage(request));
}
/**
* Create a reference data map for the given request, consisting of
* bean name/bean instance pairs as expected by ModelAndView.
* <p>The default implementation delegates to referenceData(HttpServletRequest, int).
* Subclasses can override this to set reference data used in the view.
* @param request current HTTP request
* @param command form object with request parameters bound onto it
* @param errors validation errors holder
* @param page current wizard page
* @return a Map with reference data entries, or <code>null</code> if none
* @throws Exception in case of invalid state or arguments
* @see #referenceData(HttpServletRequest, int)
* @see ModelAndView
*/
protected Map referenceData(HttpServletRequest request, Object command, Errors errors, int page)
throws Exception {
return referenceData(request, page);
}
/**
* Create a reference data map for the given request, consisting of
* bean name/bean instance pairs as expected by ModelAndView.
* <p>The default implementation returns <code>null</code>.
* Subclasses can override this to set reference data used in the view.
* @param request current HTTP request
* @param page current wizard page
* @return a Map with reference data entries, or <code>null</code> if none
* @throws Exception in case of invalid state or arguments
* @see ModelAndView
*/
protected Map referenceData(HttpServletRequest request, int page) throws Exception {
return null;
}
/**
* Show the first page as form view.
* <p>This can be overridden in subclasses, e.g. to prepare wizard-specific
* error views in case of an Exception.
*/
@Override
protected ModelAndView showForm(
HttpServletRequest request, HttpServletResponse response, BindException errors)
throws Exception {
return showPage(request, errors, getInitialPage(request, errors.getTarget()));
}
/**
* Prepare the form model and view, including reference and error data,
* for the given page. Can be used in {@link #processFinish} implementations,
* to show the corresponding page in case of validation errors.
* @param request current HTTP request
* @param errors validation errors holder
* @param page number of page to show
* @return the prepared form view
* @throws Exception in case of invalid state or arguments
*/
protected final ModelAndView showPage(HttpServletRequest request, BindException errors, int page)
throws Exception {
if (page >= 0 && page < getPageCount(request, errors.getTarget())) {
if (logger.isDebugEnabled()) {
logger.debug("Showing wizard page " + page + " for form bean '" + getCommandName() + "'");
}
// Set page session attribute, expose overriding request attribute.
Integer pageInteger = new Integer(page);
String pageAttrName = getPageSessionAttributeName(request);
if (isSessionForm()) {
if (logger.isDebugEnabled()) {
logger.debug("Setting page session attribute [" + pageAttrName + "] to: " + pageInteger);
}
request.getSession().setAttribute(pageAttrName, pageInteger);
}
request.setAttribute(pageAttrName, pageInteger);
// Set page request attribute for evaluation by views.
Map controlModel = new HashMap();
if (this.pageAttribute != null) {
controlModel.put(this.pageAttribute, new Integer(page));
}
String viewName = getViewName(request, errors.getTarget(), page);
return showForm(request, errors, viewName, controlModel);
}
else {
throw new ServletException("Invalid wizard page number: " + page);
}
}
/**
* Return the page count for this wizard form controller.
* The default implementation delegates to {@link #getPageCount()}.
* <p>Can be overridden to dynamically adapt the page count.
* @param request current HTTP request
* @param command the command object as returned by formBackingObject
* @return the current page count
* @see #getPageCount
*/
protected int getPageCount(HttpServletRequest request, Object command) {
return getPageCount();
}
/**
* Return the name of the view for the specified page of this wizard form controller.
* <p>The default implementation takes the view name from the {@link #getPages()} array.
* <p>Can be overridden to dynamically switch the page view or to return view names
* for dynamically defined pages.
* @param request current HTTP request
* @param command the command object as returned by formBackingObject
* @param page the current page number
* @return the current page count
* @see #getPageCount
*/
protected String getViewName(HttpServletRequest request, Object command, int page) {
return getPages()[page];
}
/**
* Return the initial page of the wizard, that is, the page shown at wizard startup.
* <p>The default implementation delegates to {@link #getInitialPage(HttpServletRequest)}.
* @param request current HTTP request
* @param command the command object as returned by formBackingObject
* @return the initial page number
* @see #getInitialPage(HttpServletRequest)
* @see #formBackingObject
*/
protected int getInitialPage(HttpServletRequest request, Object command) {
return getInitialPage(request);
}
/**
* Return the initial page of the wizard, that is, the page shown at wizard startup.
* <p>The default implementation returns 0 for first page.
* @param request current HTTP request
* @return the initial page number
*/
protected int getInitialPage(HttpServletRequest request) {
return 0;
}
/**
* Return the name of the HttpSession attribute that holds the page object
* for this wizard form controller.
* <p>The default implementation delegates to the {@link #getPageSessionAttributeName()}
* variant without arguments.
* @param request current HTTP request
* @return the name of the form session attribute, or <code>null</code> if not in session form mode
* @see #getPageSessionAttributeName
* @see #getFormSessionAttributeName(javax.servlet.http.HttpServletRequest)
* @see javax.servlet.http.HttpSession#getAttribute
*/
protected String getPageSessionAttributeName(HttpServletRequest request) {
return getPageSessionAttributeName();
}
/**
* Return the name of the HttpSession attribute that holds the page object
* for this wizard form controller.
* <p>Default is an internal name, of no relevance to applications, as the form
* session attribute is not usually accessed directly. Can be overridden to use
* an application-specific attribute name, which allows other code to access
* the session attribute directly.
* @return the name of the page session attribute
* @see #getFormSessionAttributeName
* @see javax.servlet.http.HttpSession#getAttribute
*/
protected String getPageSessionAttributeName() {
return getClass().getName() + ".PAGE." + getCommandName();
}
/**
* Handle an invalid submit request, e.g. when in session form mode but no form object
* was found in the session (like in case of an invalid resubmit by the browser).
* <p>The default implementation for wizard form controllers simply shows the initial page
* of a new wizard form. If you want to show some "invalid submit" message, you need
* to override this method.
* @param request current HTTP request
* @param response current HTTP response
* @return a prepared view, or <code>null</code> if handled directly
* @throws Exception in case of errors
* @see #showNewForm
* @see #setBindOnNewForm
*/
@Override
protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response)
throws Exception {
return showNewForm(request, response);
}
/**
* Apply wizard workflow: finish, cancel, page change.
*/
@Override
protected final ModelAndView processFormSubmission(
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
throws Exception {
int currentPage = getCurrentPage(request);
// Remove page session attribute, provide copy as request attribute.
String pageAttrName = getPageSessionAttributeName(request);
if (isSessionForm()) {
if (logger.isDebugEnabled()) {
logger.debug("Removing page session attribute [" + pageAttrName + "]");
}
request.getSession().removeAttribute(pageAttrName);
}
request.setAttribute(pageAttrName, new Integer(currentPage));
// cancel?
if (isCancelRequest(request)) {
if (logger.isDebugEnabled()) {
logger.debug("Cancelling wizard for form bean '" + getCommandName() + "'");
}
return processCancel(request, response, command, errors);
}
// finish?
if (isFinishRequest(request)) {
if (logger.isDebugEnabled()) {
logger.debug("Finishing wizard for form bean '" + getCommandName() + "'");
}
return validatePagesAndFinish(request, response, command, errors, currentPage);
}
// Normal submit: validate current page and show specified target page.
if (!suppressValidation(request, command, errors)) {
if (logger.isDebugEnabled()) {
logger.debug("Validating wizard page " + currentPage + " for form bean '" + getCommandName() + "'");
}
validatePage(command, errors, currentPage, false);
}
// Give subclasses a change to perform custom post-procession
// of the current page and its command object.
postProcessPage(request, command, errors, currentPage);
int targetPage = getTargetPage(request, command, errors, currentPage);
if (logger.isDebugEnabled()) {
logger.debug("Target page " + targetPage + " requested");
}
if (targetPage != currentPage) {
if (!errors.hasErrors() || (this.allowDirtyBack && targetPage < currentPage) ||
(this.allowDirtyForward && targetPage > currentPage)) {
// Allowed to go to target page.
return showPage(request, errors, targetPage);
}
}
// Show current page again.
return showPage(request, errors, currentPage);
}
/**
* Return the current page number. Used by {@link #processFormSubmission}.
* <p>The default implementation checks the page session attribute.
* Subclasses can override this for customized page determination.
* @param request current HTTP request
* @return the current page number
* @see #getPageSessionAttributeName()
*/
protected int getCurrentPage(HttpServletRequest request) {
// Check for overriding attribute in request.
String pageAttrName = getPageSessionAttributeName(request);
Integer pageAttr = (Integer) request.getAttribute(pageAttrName);
if (pageAttr != null) {
return pageAttr.intValue();
}
// Check for explicit request parameter.
String pageParam = request.getParameter(PARAM_PAGE);
if (pageParam != null) {
return Integer.parseInt(pageParam);
}
// Check for original attribute in session.
if (isSessionForm()) {
pageAttr = (Integer) request.getSession().getAttribute(pageAttrName);
if (pageAttr != null) {
return pageAttr.intValue();
}
}
throw new IllegalStateException(
"Page attribute [" + pageAttrName + "] neither found in session nor in request");
}
/**
* Determine whether the incoming request is a request to finish the
* processing of the current form.
* <p>By default, this method returns <code>true</code> if a parameter
* matching the "_finish" key is present in the request, otherwise it
* returns <code>false</code>. Subclasses may override this method
* to provide custom logic to detect a finish request.
* <p>The parameter is recognized both when sent as a plain parameter
* ("_finish") or when triggered by an image button ("_finish.x").
* @param request current HTTP request
* @return whether the request indicates to finish form processing
* @see #PARAM_FINISH
*/
protected boolean isFinishRequest(HttpServletRequest request) {
return WebUtils.hasSubmitParameter(request, PARAM_FINISH);
}
/**
* Determine whether the incoming request is a request to cancel the
* processing of the current form.
* <p>By default, this method returns <code>true</code> if a parameter
* matching the "_cancel" key is present in the request, otherwise it
* returns <code>false</code>. Subclasses may override this method
* to provide custom logic to detect a cancel request.
* <p>The parameter is recognized both when sent as a plain parameter
* ("_cancel") or when triggered by an image button ("_cancel.x").
* @return whether the request indicates to cancel form processing
* @param request current HTTP request
* @see #PARAM_CANCEL
*/
protected boolean isCancelRequest(HttpServletRequest request) {
return WebUtils.hasSubmitParameter(request, PARAM_CANCEL);
}
/**
* Return the target page specified in the request.
* <p>The default implementation delegates to {@link #getTargetPage(HttpServletRequest, int)}.
* Subclasses can override this for customized target page determination.
* @param request current HTTP request
* @param command form object with request parameters bound onto it
* @param errors validation errors holder
* @param currentPage the current page, to be returned as fallback
* if no target page specified
* @return the page specified in the request, or current page if not found
* @see #getTargetPage(HttpServletRequest, int)
*/
protected int getTargetPage(HttpServletRequest request, Object command, Errors errors, int currentPage) {
return getTargetPage(request, currentPage);
}
/**
* Return the target page specified in the request.
* <p>The default implementation examines "_target" parameter (e.g. "_target1").
* Subclasses can override this for customized target page determination.
* @param request current HTTP request
* @param currentPage the current page, to be returned as fallback
* if no target page specified
* @return the page specified in the request, or current page if not found
* @see #PARAM_TARGET
*/
protected int getTargetPage(HttpServletRequest request, int currentPage) {
return WebUtils.getTargetPage(request, PARAM_TARGET, currentPage);
}
/**
* Validate all pages and process finish.
* If there are page validation errors, show the corresponding view page.
*/
private ModelAndView validatePagesAndFinish(
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors,
int currentPage) throws Exception {
// In case of binding errors -> show current page.
if (errors.hasErrors()) {
return showPage(request, errors, currentPage);
}
if (!suppressValidation(request, command, errors)) {
// In case of remaining errors on a page -> show the page.
for (int page = 0; page < getPageCount(request, command); page++) {
validatePage(command, errors, page, true);
if (errors.hasErrors()) {
return showPage(request, errors, page);
}
}
}
// No remaining errors -> proceed with finish.
return processFinish(request, response, command, errors);
}
/**
* Template method for custom validation logic for individual pages.
* The default implementation calls {@link #validatePage(Object, Errors, int)}.
* <p>Implementations will typically call fine-granular <code>validateXXX</code>
* methods of this instance's Validator, combining them to validation of the
* corresponding pages. The Validator's default <code>validate</code> method
* will not be called by a wizard form controller!
* @param command form object with the current wizard state
* @param errors validation errors holder
* @param page number of page to validate
* @param finish whether this method is called during final revalidation on finish
* (else, it is called for validating the current page)
* @see #validatePage(Object, Errors, int)
* @see org.springframework.validation.Validator#validate
*/
protected void validatePage(Object command, Errors errors, int page, boolean finish) {
validatePage(command, errors, page);
}
/**
* Template method for custom validation logic for individual pages.
* The default implementation is empty.
* <p>Implementations will typically call fine-granular validateXXX methods of this
* instance's validator, combining them to validation of the corresponding pages.
* The validator's default <code>validate</code> method will not be called by a
* wizard form controller!
* @param command form object with the current wizard state
* @param errors validation errors holder
* @param page number of page to validate
* @see org.springframework.validation.Validator#validate
*/
protected void validatePage(Object command, Errors errors, int page) {
}
/**
* Post-process the given page after binding and validation, potentially
* updating its command object. The passed-in request might contain special
* parameters sent by the page.
* <p>Only invoked when displaying another page or the same page again,
* not when finishing or cancelling.
* @param request current HTTP request
* @param command form object with request parameters bound onto it
* @param errors validation errors holder
* @param page number of page to post-process
* @throws Exception in case of invalid state or arguments
*/
protected void postProcessPage(HttpServletRequest request, Object command, Errors errors, int page)
throws Exception {
}
/**
* Template method for processing the final action of this wizard.
* <p>Call <code>errors.getModel()</code> to populate the ModelAndView model
* with the command and the Errors instance, under the specified command name,
* as expected by the "spring:bind" tag.
* <p>You can call the {@link #showPage} method to return back to the wizard,
* in case of last-minute validation errors having been found that you would
* like to present to the user within the original wizard form.
* @param request current HTTP request
* @param response current HTTP response
* @param command form object with the current wizard state
* @param errors validation errors holder
* @return the finish view
* @throws Exception in case of invalid state or arguments
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
* @see #showPage(javax.servlet.http.HttpServletRequest, org.springframework.validation.BindException, int)
*/
protected abstract ModelAndView processFinish(
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
throws Exception;
/**
* Template method for processing the cancel action of this wizard.
* <p>The default implementation throws a ServletException, saying that a cancel
* operation is not supported by this controller. Thus, you do not need to
* implement this template method if you do not support a cancel operation.
* <p>Call <code>errors.getModel()</code> to populate the ModelAndView model
* with the command and the Errors instance, under the specified command name,
* as expected by the "spring:bind" tag.
* @param request current HTTP request
* @param response current HTTP response
* @param command form object with the current wizard state
* @param errors Errors instance containing errors
* @return the cancellation view
* @throws Exception in case of invalid state or arguments
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
*/
protected ModelAndView processCancel(
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
throws Exception {
throw new ServletException(
"Wizard form controller class [" + getClass().getName() + "] does not support a cancel operation");
}
}

View File

@@ -0,0 +1,595 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingErrorProcessor;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.request.ServletWebRequest;
/**
* <p>Controller implementation which creates an object (the command object) on
* receipt of a request and attempts to populate this object with request parameters.</p>
*
* <p>This controller is the base for all controllers wishing to populate
* JavaBeans based on request parameters, validate the content of such
* JavaBeans using {@link org.springframework.validation.Validator Validators}
* and use custom editors (in the form of
* {@link java.beans.PropertyEditor PropertyEditors}) to transform
* objects into strings and vice versa, for example. Three notions are mentioned here:</p>
*
* <p><b>Command class:</b><br>
* An instance of the command class will be created for each request and populated
* with request parameters. A command class can basically be any Java class; the only
* requirement is a no-arg constructor. The command class should preferably be a
* JavaBean in order to be able to populate bean properties with request parameters.</p>
*
* <p><b>Populating using request parameters and PropertyEditors:</b><br>
* Upon receiving a request, any BaseCommandController will attempt to fill the
* command object using the request parameters. This is done using the typical
* and well-known JavaBeans property notation. When a request parameter named
* <code>'firstName'</code> exists, the framework will attempt to call
* <code>setFirstName([value])</code> passing the value of the parameter. Nested properties
* are of course supported. For instance a parameter named <code>'address.city'</code>
* will result in a <code>getAddress().setCity([value])</code> call on the
* command class.</p>
*
* <p>It's important to realise that you are not limited to String arguments in
* your JavaBeans. Using the PropertyEditor-notion as supplied by the
* java.beans package, you will be able to transform Strings to Objects and
* the other way around. For instance <code>setLocale(Locale loc)</code> is
* perfectly possible for a request parameter named <code>locale</code> having
* a value of <code>en</code>, as long as you register the appropriate
* PropertyEditor in the Controller (see {@link #initBinder initBinder()}
* for more information on that matter.</p>
*
* <p><b>Validators:</b>
* After the controller has successfully populated the command object with
* parameters from the request, it will use any configured validators to
* validate the object. Validation results will be put in a
* {@link org.springframework.validation.Errors Errors} object which can be
* used in a View to render any input problems.</p>
*
* <p><b><a name="workflow">Workflow
* (<a href="AbstractController.html#workflow">and that defined by superclass</a>):</b><br>
* Since this class is an abstract base class for more specific implementation,
* it does not override the handleRequestInternal() method and also has no
* actual workflow. Implementing classes like
* {@link AbstractFormController AbstractFormController},
* {@link AbstractCommandController AbstractcommandController},
* {@link SimpleFormController SimpleFormController} and
* {@link AbstractWizardFormController AbstractWizardFormController}
* provide actual functionality and workflow.
* More information on workflow performed by superclasses can be found
* <a href="AbstractController.html#workflow">here</a>.</p>
*
* <p><b><a name="config">Exposed configuration properties</a>
* (<a href="AbstractController.html#config">and those defined by superclass</a>):</b><br>
* <table border="1">
* <tr>
* <td><b>name</b></th>
* <td><b>default</b></td>
* <td><b>description</b></td>
* </tr>
* <tr>
* <td>commandName</td>
* <td>command</td>
* <td>the name to use when binding the instantiated command class
* to the request</td>
* </tr>
* <tr>
* <td>commandClass</td>
* <td><i>null</i></td>
* <td>the class to use upon receiving a request and which to fill
* using the request parameters. What object is used and whether
* or not it should be created is defined by extending classes
* and their configuration properties and methods.</td>
* </tr>
* <tr>
* <td>validators</td>
* <td><i>null</i></td>
* <td>Array of Validator beans. The validator will be called at appropriate
* places in the workflow of subclasses (have a look at those for more info)
* to validate the command object.</td>
* </tr>
* <tr>
* <td>validator</td>
* <td><i>null</i></td>
* <td>Short-form property for setting only one Validator bean (usually passed in
* using a &lt;ref bean="beanId"/&gt; property.</td>
* </tr>
* <tr>
* <td>validateOnBinding</td>
* <td>true</td>
* <td>Indicates whether or not to validate the command object after the
* object has been populated with request parameters.</td>
* </tr>
* </table>
* </p>
*
* @author Rod Johnson
* @author Juergen Hoeller
* @deprecated as of Spring 3.0, in favor of annotated controllers
*/
@Deprecated
public abstract class BaseCommandController extends AbstractController {
/** Default command name used for binding command objects: "command" */
public static final String DEFAULT_COMMAND_NAME = "command";
private String commandName = DEFAULT_COMMAND_NAME;
private Class commandClass;
private Validator[] validators;
private boolean validateOnBinding = true;
private MessageCodesResolver messageCodesResolver;
private BindingErrorProcessor bindingErrorProcessor;
private PropertyEditorRegistrar[] propertyEditorRegistrars;
private WebBindingInitializer webBindingInitializer;
/**
* Set the name of the command in the model.
* The command object will be included in the model under this name.
*/
public final void setCommandName(String commandName) {
this.commandName = commandName;
}
/**
* Return the name of the command in the model.
*/
public final String getCommandName() {
return this.commandName;
}
/**
* Set the command class for this controller.
* An instance of this class gets populated and validated on each request.
*/
public final void setCommandClass(Class commandClass) {
this.commandClass = commandClass;
}
/**
* Return the command class for this controller.
*/
public final Class getCommandClass() {
return this.commandClass;
}
/**
* Set the primary Validator for this controller. The Validator
* must support the specified command class. If there are one
* or more existing validators set already when this method is
* called, only the specified validator will be kept. Use
* {@link #setValidators(Validator[])} to set multiple validators.
*/
public final void setValidator(Validator validator) {
this.validators = new Validator[] {validator};
}
/**
* Return the primary Validator for this controller.
*/
public final Validator getValidator() {
return (this.validators != null && this.validators.length > 0 ? this.validators[0] : null);
}
/**
* Set the Validators for this controller.
* The Validator must support the specified command class.
*/
public final void setValidators(Validator[] validators) {
this.validators = validators;
}
/**
* Return the Validators for this controller.
*/
public final Validator[] getValidators() {
return this.validators;
}
/**
* Set if the Validator should get applied when binding.
*/
public final void setValidateOnBinding(boolean validateOnBinding) {
this.validateOnBinding = validateOnBinding;
}
/**
* Return if the Validator should get applied when binding.
*/
public final boolean isValidateOnBinding() {
return this.validateOnBinding;
}
/**
* Set the strategy to use for resolving errors into message codes.
* Applies the given strategy to all data binders used by this controller.
* <p>Default is <code>null</code>, i.e. using the default strategy of
* the data binder.
* @see #createBinder
* @see org.springframework.validation.DataBinder#setMessageCodesResolver
*/
public final void setMessageCodesResolver(MessageCodesResolver messageCodesResolver) {
this.messageCodesResolver = messageCodesResolver;
}
/**
* Return the strategy to use for resolving errors into message codes (if any).
*/
public final MessageCodesResolver getMessageCodesResolver() {
return this.messageCodesResolver;
}
/**
* Set the strategy to use for processing binding errors, that is,
* required field errors and <code>PropertyAccessException</code>s.
* <p>Default is <code>null</code>, that is, using the default strategy
* of the data binder.
* @see #createBinder
* @see org.springframework.validation.DataBinder#setBindingErrorProcessor
*/
public final void setBindingErrorProcessor(BindingErrorProcessor bindingErrorProcessor) {
this.bindingErrorProcessor = bindingErrorProcessor;
}
/**
* Return the strategy to use for processing binding errors (if any).
*/
public final BindingErrorProcessor getBindingErrorProcessor() {
return this.bindingErrorProcessor;
}
/**
* Specify a single PropertyEditorRegistrar to be applied
* to every DataBinder that this controller uses.
* <p>Allows for factoring out the registration of PropertyEditors
* to separate objects, as an alternative to {@link #initBinder}.
* @see #initBinder
*/
public final void setPropertyEditorRegistrar(PropertyEditorRegistrar propertyEditorRegistrar) {
this.propertyEditorRegistrars = new PropertyEditorRegistrar[] {propertyEditorRegistrar};
}
/**
* Specify multiple PropertyEditorRegistrars to be applied
* to every DataBinder that this controller uses.
* <p>Allows for factoring out the registration of PropertyEditors
* to separate objects, as an alternative to {@link #initBinder}.
* @see #initBinder
*/
public final void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) {
this.propertyEditorRegistrars = propertyEditorRegistrars;
}
/**
* Return the PropertyEditorRegistrars (if any) to be applied
* to every DataBinder that this controller uses.
*/
public final PropertyEditorRegistrar[] getPropertyEditorRegistrars() {
return this.propertyEditorRegistrars;
}
/**
* Specify a WebBindingInitializer which will apply pre-configured
* configuration to every DataBinder that this controller uses.
* <p>Allows for factoring out the entire binder configuration
* to separate objects, as an alternative to {@link #initBinder}.
*/
public final void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
this.webBindingInitializer = webBindingInitializer;
}
/**
* Return the WebBindingInitializer (if any) which will apply pre-configured
* configuration to every DataBinder that this controller uses.
*/
public final WebBindingInitializer getWebBindingInitializer() {
return this.webBindingInitializer;
}
@Override
protected void initApplicationContext() {
if (this.validators != null) {
for (int i = 0; i < this.validators.length; i++) {
if (this.commandClass != null && !this.validators[i].supports(this.commandClass))
throw new IllegalArgumentException("Validator [" + this.validators[i] +
"] does not support command class [" +
this.commandClass.getName() + "]");
}
}
}
/**
* Retrieve a command object for the given request.
* <p>The default implementation calls {@link #createCommand}.
* Subclasses can override this.
* @param request current HTTP request
* @return object command to bind onto
* @throws Exception if the command object could not be obtained
* @see #createCommand
*/
protected Object getCommand(HttpServletRequest request) throws Exception {
return createCommand();
}
/**
* Create a new command instance for the command class of this controller.
* <p>This implementation uses <code>BeanUtils.instantiateClass</code>,
* so the command needs to have a no-arg constructor (supposed to be
* public, but not required to).
* @return the new command instance
* @throws Exception if the command object could not be instantiated
* @see org.springframework.beans.BeanUtils#instantiateClass(Class)
*/
protected final Object createCommand() throws Exception {
if (this.commandClass == null) {
throw new IllegalStateException("Cannot create command without commandClass being set - " +
"either set commandClass or (in a form controller) override formBackingObject");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating new command of class [" + this.commandClass.getName() + "]");
}
return BeanUtils.instantiateClass(this.commandClass);
}
/**
* Check if the given command object is a valid for this controller,
* i.e. its command class.
* @param command the command object to check
* @return if the command object is valid for this controller
*/
protected final boolean checkCommand(Object command) {
return (this.commandClass == null || this.commandClass.isInstance(command));
}
/**
* Bind the parameters of the given request to the given command object.
* @param request current HTTP request
* @param command the command to bind onto
* @return the ServletRequestDataBinder instance for additional custom validation
* @throws Exception in case of invalid state or arguments
*/
protected final ServletRequestDataBinder bindAndValidate(HttpServletRequest request, Object command)
throws Exception {
ServletRequestDataBinder binder = createBinder(request, command);
BindException errors = new BindException(binder.getBindingResult());
if (!suppressBinding(request)) {
binder.bind(request);
onBind(request, command, errors);
if (this.validators != null && isValidateOnBinding() && !suppressValidation(request, command, errors)) {
for (int i = 0; i < this.validators.length; i++) {
ValidationUtils.invokeValidator(this.validators[i], command, errors);
}
}
onBindAndValidate(request, command, errors);
}
return binder;
}
/**
* Return whether to suppress binding for the given request.
* <p>The default implementation always returns "false". Can be overridden
* in subclasses to suppress validation, for example, if a special
* request parameter is set.
* @param request current HTTP request
* @return whether to suppress binding for the given request
* @see #suppressValidation
*/
protected boolean suppressBinding(HttpServletRequest request) {
return false;
}
/**
* Create a new binder instance for the given command and request.
* <p>Called by {@link #bindAndValidate}. Can be overridden to plug in
* custom ServletRequestDataBinder instances.
* <p>The default implementation creates a standard ServletRequestDataBinder
* and invokes {@link #prepareBinder} and {@link #initBinder}.
* <p>Note that neither {@link #prepareBinder} nor {@link #initBinder} will
* be invoked automatically if you override this method! Call those methods
* at appropriate points of your overridden method.
* @param request current HTTP request
* @param command the command to bind onto
* @return the new binder instance
* @throws Exception in case of invalid state or arguments
* @see #bindAndValidate
* @see #prepareBinder
* @see #initBinder
*/
protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object command)
throws Exception {
ServletRequestDataBinder binder = new ServletRequestDataBinder(command, getCommandName());
prepareBinder(binder);
initBinder(request, binder);
return binder;
}
/**
* Prepare the given binder, applying the specified MessageCodesResolver,
* BindingErrorProcessor and PropertyEditorRegistrars (if any).
* Called by {@link #createBinder}.
* @param binder the new binder instance
* @see #createBinder
* @see #setMessageCodesResolver
* @see #setBindingErrorProcessor
*/
protected final void prepareBinder(ServletRequestDataBinder binder) {
if (useDirectFieldAccess()) {
binder.initDirectFieldAccess();
}
if (this.messageCodesResolver != null) {
binder.setMessageCodesResolver(this.messageCodesResolver);
}
if (this.bindingErrorProcessor != null) {
binder.setBindingErrorProcessor(this.bindingErrorProcessor);
}
if (this.propertyEditorRegistrars != null) {
for (int i = 0; i < this.propertyEditorRegistrars.length; i++) {
this.propertyEditorRegistrars[i].registerCustomEditors(binder);
}
}
}
/**
* Determine whether to use direct field access instead of bean property access.
* Applied by {@link #prepareBinder}.
* <p>Default is "false". Can be overridden in subclasses.
* @return whether to use direct field access (<code>true</code>)
* or bean property access (<code>false</code>)
* @see #prepareBinder
* @see org.springframework.validation.DataBinder#initDirectFieldAccess()
*/
protected boolean useDirectFieldAccess() {
return false;
}
/**
* Initialize the given binder instance, for example with custom editors.
* Called by {@link #createBinder}.
* <p>This method allows you to register custom editors for certain fields of your
* command class. For instance, you will be able to transform Date objects into a
* String pattern and back, in order to allow your JavaBeans to have Date properties
* and still be able to set and display them in an HTML interface.
* <p>The default implementation is empty.
* @param request current HTTP request
* @param binder the new binder instance
* @throws Exception in case of invalid state or arguments
* @see #createBinder
* @see org.springframework.validation.DataBinder#registerCustomEditor
* @see org.springframework.beans.propertyeditors.CustomDateEditor
*/
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
if (this.webBindingInitializer != null) {
this.webBindingInitializer.initBinder(binder, new ServletWebRequest(request));
}
}
/**
* Callback for custom post-processing in terms of binding.
* Called on each submit, after standard binding but before validation.
* <p>The default implementation delegates to {@link #onBind(HttpServletRequest, Object)}.
* @param request current HTTP request
* @param command the command object to perform further binding on
* @param errors validation errors holder, allowing for additional
* custom registration of binding errors
* @throws Exception in case of invalid state or arguments
* @see #bindAndValidate
* @see #onBind(HttpServletRequest, Object)
*/
protected void onBind(HttpServletRequest request, Object command, BindException errors) throws Exception {
onBind(request, command);
}
/**
* Callback for custom post-processing in terms of binding.
* <p>Called by the default implementation of the
* {@link #onBind(HttpServletRequest, Object, BindException)} variant
* with all parameters, after standard binding but before validation.
* <p>The default implementation is empty.
* @param request current HTTP request
* @param command the command object to perform further binding on
* @throws Exception in case of invalid state or arguments
* @see #onBind(HttpServletRequest, Object, BindException)
*/
protected void onBind(HttpServletRequest request, Object command) throws Exception {
}
/**
* Return whether to suppress validation for the given request.
* <p>The default implementation delegates to {@link #suppressValidation(HttpServletRequest, Object)}.
* @param request current HTTP request
* @param command the command object to validate
* @param errors validation errors holder, allowing for additional
* custom registration of binding errors
* @return whether to suppress validation for the given request
*/
protected boolean suppressValidation(HttpServletRequest request, Object command, BindException errors) {
return suppressValidation(request, command);
}
/**
* Return whether to suppress validation for the given request.
* <p>Called by the default implementation of the
* {@link #suppressValidation(HttpServletRequest, Object, BindException)} variant
* with all parameters.
* <p>The default implementation delegates to {@link #suppressValidation(HttpServletRequest)}.
* @param request current HTTP request
* @param command the command object to validate
* @return whether to suppress validation for the given request
*/
protected boolean suppressValidation(HttpServletRequest request, Object command) {
return suppressValidation(request);
}
/**
* Return whether to suppress validation for the given request.
* <p>Called by the default implementation of the
* {@link #suppressValidation(HttpServletRequest, Object)} variant
* with all parameters.
* <p>The default implementation is empty.
* @param request current HTTP request
* @return whether to suppress validation for the given request
* @deprecated as of Spring 2.0.4, in favor of the
* {@link #suppressValidation(HttpServletRequest, Object)} variant
*/
@Deprecated
protected boolean suppressValidation(HttpServletRequest request) {
return false;
}
/**
* Callback for custom post-processing in terms of binding and validation.
* Called on each submit, after standard binding and validation,
* but before error evaluation.
* <p>The default implementation is empty.
* @param request current HTTP request
* @param command the command object, still allowing for further binding
* @param errors validation errors holder, allowing for additional
* custom validation
* @throws Exception in case of invalid state or arguments
* @see #bindAndValidate
* @see org.springframework.validation.Errors
*/
protected void onBindAndValidate(HttpServletRequest request, Object command, BindException errors)
throws Exception {
}
}

View File

@@ -0,0 +1,210 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.WebUtils;
/**
* <p>Extension of <code>SimpleFormController</code> that supports "cancellation"
* of form processing. By default, this controller looks for a given parameter in the
* request, identified by the <code>cancelParamKey<code>. If this parameter is present,
* then the controller will return the configured <code>cancelView</code>, otherwise
* processing is passed back to the superclass.</p>
*
* <p><b><a name="workflow">Workflow
* (<a href="SimpleFormController.html#workflow">in addition to the superclass</a>):</b><br>
* <ol>
* <li>Call to {@link #processFormSubmission processFormSubmission} which calls
* {@link #isCancelRequest} to see if the incoming request is to cancel the
* current form entry. By default, {@link #isCancelRequest} returns <code>true</code>
* if the configured <code>cancelParamKey</code> exists in the request.
* This behavior can be overridden in subclasses.</li>
* <li>If {@link #isCancelRequest} returns <code>false</code>, then the controller
* will delegate all processing back to {@link SimpleFormController SimpleFormController},
* otherwise it will call the {@link #onCancel} version with all parameters.
* By default, that method will delegate to the {@link #onCancel} version with just
* the command object, which will in turn simply return the configured
* <code>cancelView</code>. This behavior can be overridden in subclasses.</li>
* </ol>
* </p>
*
* <p>Thanks to Erwin Bolwidt for submitting the original prototype
* of such a cancellable form controller!</p>
*
* @author Rob Harrop
* @author Juergen Hoeller
* @since 1.2.3
* @see #setCancelParamKey
* @see #setCancelView
* @see #isCancelRequest(javax.servlet.http.HttpServletRequest)
* @see #onCancel(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, Object)
* @deprecated as of Spring 3.0, in favor of annotated controllers
*/
@Deprecated
public class CancellableFormController extends SimpleFormController {
/**
* Default parameter triggering the cancel action.
* Can be called even with validation errors on the form.
*/
private static final String PARAM_CANCEL = "_cancel";
private String cancelParamKey = PARAM_CANCEL;
private String cancelView;
/**
* Set the key of the request parameter used to identify a cancel request.
* Default is "_cancel".
* <p>The parameter is recognized both when sent as a plain parameter
* ("_cancel") or when triggered by an image button ("_cancel.x").
*/
public final void setCancelParamKey(String cancelParamKey) {
this.cancelParamKey = cancelParamKey;
}
/**
* Return the key of the request parameter used to identify a cancel request.
*/
public final String getCancelParamKey() {
return this.cancelParamKey;
}
/**
* Sets the name of the cancel view.
*/
public final void setCancelView(String cancelView) {
this.cancelView = cancelView;
}
/**
* Gets the name of the cancel view.
*/
public final String getCancelView() {
return this.cancelView;
}
/**
* Consider an explicit cancel request as a form submission too.
* @see #isCancelRequest(javax.servlet.http.HttpServletRequest)
*/
@Override
protected boolean isFormSubmission(HttpServletRequest request) {
return super.isFormSubmission(request) || isCancelRequest(request);
}
/**
* Suppress validation for an explicit cancel request too.
* @see #isCancelRequest(javax.servlet.http.HttpServletRequest)
*/
@Override
protected boolean suppressValidation(HttpServletRequest request, Object command) {
return super.suppressValidation(request, command) || isCancelRequest(request);
}
/**
* This implementation first checks to see if the incoming is a cancel request,
* through a call to {@link #isCancelRequest}. If so, control is passed to
* {@link #onCancel}; otherwise, control is passed up to
* {@link SimpleFormController#processFormSubmission}.
* @see #isCancelRequest
* @see #onCancel(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, Object)
* @see SimpleFormController#processFormSubmission
*/
@Override
protected ModelAndView processFormSubmission(
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
throws Exception {
if (isCancelRequest(request)) {
return onCancel(request, response, command);
}
else {
return super.processFormSubmission(request, response, command, errors);
}
}
/**
* Determine whether the incoming request is a request to cancel the
* processing of the current form.
* <p>By default, this method returns <code>true</code> if a parameter
* matching the configured <code>cancelParamKey</code> is present in
* the request, otherwise it returns <code>false</code>. Subclasses may
* override this method to provide custom logic to detect a cancel request.
* <p>The parameter is recognized both when sent as a plain parameter
* ("_cancel") or when triggered by an image button ("_cancel.x").
* @param request current HTTP request
* @see #setCancelParamKey
* @see #PARAM_CANCEL
*/
protected boolean isCancelRequest(HttpServletRequest request) {
return WebUtils.hasSubmitParameter(request, getCancelParamKey());
}
/**
* Callback method for handling a cancel request. Called if {@link #isCancelRequest}
* returns <code>true</code>.
* <p>Default implementation delegates to <code>onCancel(Object)</code> to return
* the configured <code>cancelView</code>. Subclasses may override either of the two
* methods to build a custom {@link ModelAndView ModelAndView} that may contain model
* parameters used in the cancel view.
* <p>If you simply want to move the user to a new view and you don't want to add
* additional model parameters, use {@link #setCancelView(String)} rather than
* overriding an <code>onCancel</code> method.
* @param request current servlet request
* @param response current servlet response
* @param command form object with request parameters bound onto it
* @return the prepared model and view, or <code>null</code>
* @throws Exception in case of errors
* @see #isCancelRequest(javax.servlet.http.HttpServletRequest)
* @see #onCancel(Object)
* @see #setCancelView
*/
protected ModelAndView onCancel(HttpServletRequest request, HttpServletResponse response, Object command)
throws Exception {
return onCancel(command);
}
/**
* Simple <code>onCancel</code> version. Called by the default implementation
* of the <code>onCancel</code> version with all parameters.
* <p>Default implementation returns eturns the configured <code>cancelView</code>.
* Subclasses may override this method to build a custom {@link ModelAndView ModelAndView}
* that may contain model parameters used in the cancel view.
* <p>If you simply want to move the user to a new view and you don't want to add
* additional model parameters, use {@link #setCancelView(String)} rather than
* overriding an <code>onCancel</code> method.
* @param command form object with request parameters bound onto it
* @return the prepared model and view, or <code>null</code>
* @throws Exception in case of errors
* @see #onCancel(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, Object)
* @see #setCancelView
*/
protected ModelAndView onCancel(Object command) throws Exception {
return new ModelAndView(getCancelView());
}
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
/**
* Base Controller interface, representing a component that receives
* <code>HttpServletRequest</code> and <code>HttpServletResponse</code>
* instances just like a <code>HttpServlet</code> but is able to
* participate in an MVC workflow. Controllers are comparable to the
* notion of a Struts <code>Action</code>.
*
* <p>Any implementation of the Controller interface should be a
* <i>reusable, thread-safe</i> class, capable of handling multiple
* HTTP requests throughout the lifecycle of an application. To be able to
* configure a Controller easily, Controller implementations are encouraged
* to be (and usually are) JavaBeans.
* </p>
*
* <p><b><a name="workflow">Workflow</a></b></p>
*
* <p>
* After a <cde>DispatcherServlet</code> has received a request and has
* done its work to resolve locales, themes and suchlike, it then tries
* to resolve a Controller, using a
* {@link org.springframework.web.servlet.HandlerMapping HandlerMapping}.
* When a Controller has been found to handle the request, the
* {@link #handleRequest(HttpServletRequest, HttpServletResponse) handleRequest}
* method of the located Controller will be invoked; the located Controller
* is then responsible for handling the actual request and - if applicable -
* returning an appropriate
* {@link org.springframework.web.servlet.ModelAndView ModelAndView}.
* So actually, this method is the main entrypoint for the
* {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet}
* which delegates requests to controllers.</p>
*
* <p>So basically any <i>direct</i> implementation of the Controller interface
* just handles HttpServletRequests and should return a ModelAndView, to be further
* interpreted by the DispatcherServlet. Any additional functionality such as
* optional validation, form handling, etc should be obtained through extending
* one of the abstract controller classes mentioned above.</p>
*
* <p><b>Notes on design and testing</b></p>
*
* <p>The Controller interface is explicitly designed to operate on HttpServletRequest
* and HttpServletResponse objects, just like an HttpServlet. It does not aim to
* decouple itself from the Servlet API, in contrast to, for example, WebWork, JSF or Tapestry.
* Instead, the full power of the Servlet API is available, allowing Controllers to be
* general-purpose: a Controller is able to not only handle web user interface
* requests but also to process remoting protocols or to generate reports on demand.</p>
*
* <p>Controllers can easily be tested by passing in mock objects for the
* HttpServletRequest and HttpServletResponse objects as parameters to the
* {@link #handleRequest(HttpServletRequest, HttpServletResponse) handleRequest}
* method. As a convenience, Spring ships with a set of Servlet API mocks
* that are suitable for testing any kind of web components, but are particularly
* suitable for testing Spring web controllers. In contrast to a Struts Action,
* there is no need to mock the ActionServlet or any other infrastructure;
* HttpServletRequest and HttpServletResponse are sufficient.</p>
*
* <p>If Controllers need to be aware of specific environment references, they can
* choose to implement specific awareness interfaces, just like any other bean in a
* Spring (web) application context can do, for example:</p>
* <ul>
* <li><code>org.springframework.context.ApplicationContextAware</code></li>
* <li><code>org.springframework.context.ResourceLoaderAware</code></li>
* <li><code>org.springframework.web.context.ServletContextAware</code></li>
* </ul>
*
* <p>Such environment references can easily be passed in testing environments,
* through the corresponding setters defined in the respective awareness interfaces.
* In general, it is recommended to keep the dependencies as minimal as possible:
* for example, if all you need is resource loading, implement ResourceLoaderAware only.
* Alternatively, derive from the WebApplicationObjectSupport base class, which gives
* you all those references through convenient accessors - but requires an
* ApplicationContext reference on initialization.
*
* <p>Controllers can optionally implement the {@link LastModified} interface.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see LastModified
* @see SimpleControllerHandlerAdapter
* @see AbstractController
* @see org.springframework.mock.web.MockHttpServletRequest
* @see org.springframework.mock.web.MockHttpServletResponse
* @see org.springframework.context.ApplicationContextAware
* @see org.springframework.context.ResourceLoaderAware
* @see org.springframework.web.context.ServletContextAware
* @see org.springframework.web.context.support.WebApplicationObjectSupport
*/
public interface Controller {
/**
* Process the request and return a ModelAndView object which the DispatcherServlet
* will render. A <code>null</code> return value is not an error: It indicates that
* this object completed request processing itself, thus there is no ModelAndView
* to render.
* @param request current HTTP request
* @param response current HTTP response
* @return a ModelAndView to render, or <code>null</code> if handled directly
* @throws Exception in case of errors
*/
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ModelAndView;
/**
* Adapter to use the plain {@link org.springframework.web.HttpRequestHandler}
* interface with the generic {@link org.springframework.web.servlet.DispatcherServlet}.
* Supports handlers that implement the {@link LastModified} interface.
*
* <p>This is an SPI class, not used directly by application code.
*
* @author Juergen Hoeller
* @since 2.0
* @see org.springframework.web.servlet.DispatcherServlet
* @see org.springframework.web.HttpRequestHandler
* @see LastModified
* @see SimpleControllerHandlerAdapter
*/
public class HttpRequestHandlerAdapter implements HandlerAdapter {
public boolean supports(Object handler) {
return (handler instanceof HttpRequestHandler);
}
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
}
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified) {
return ((LastModified) handler).getLastModified(request);
}
return -1L;
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc;
import javax.servlet.http.HttpServletRequest;
/**
* Supports last-modified HTTP requests to facilitate content caching.
* Same contract as for the Servlet API's <code>getLastModified</code> method.
*
* <p>Delegated to by a {@link org.springframework.web.servlet.HandlerAdapter#getLastModified}
* implementation. By default, any Controller or HttpRequestHandler within Spring's
* default framework can implement this interface to enable last-modified checking.
*
* <p><b>Note:</b> Alternative handler implementation approaches have different
* last-modified handling styles. For example, Spring 2.5's annotated controller
* approach (using <code>@RequestMapping</code>) provides last-modified support
* through the {@link org.springframework.web.context.request.WebRequest#checkNotModified}
* method, allowing for last-modified checking within the main handler method.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see javax.servlet.http.HttpServlet#getLastModified
* @see Controller
* @see SimpleControllerHandlerAdapter
* @see org.springframework.web.HttpRequestHandler
* @see HttpRequestHandlerAdapter
*/
public interface LastModified {
/**
* Same contract as for HttpServlet's <code>getLastModified</code> method.
* Invoked <b>before</b> request processing.
* <p>The return value will be sent to the HTTP client as Last-Modified header,
* and compared with If-Modified-Since headers that the client sends back.
* The content will only get regenerated if there has been a modification.
* @param request current HTTP request
* @return the time the underlying resource was last modified, or -1
* meaning that the content must always be regenerated
* @see org.springframework.web.servlet.HandlerAdapter#getLastModified
* @see javax.servlet.http.HttpServlet#getLastModified
*/
long getLastModified(HttpServletRequest request);
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.support.RequestContextUtils;
/**
* <p>Trivial controller that always returns a named view. The view
* can be configured using an exposed configuration property. This
* controller offers an alternative to sending a request straight to a view
* such as a JSP. The advantage here is that the client is not exposed to
* the concrete view technology but rather just to the controller URL;
* the concrete view will be determined by the ViewResolver.
*
* <p>An alternative to the ParameterizableViewController is a
* {@link org.springframework.web.servlet.mvc.multiaction.MultiActionController MultiActionController},
* which can define a variety of handler methods that just return a plain
* ModelAndView instance for a given view name.
*
* <p><b><a name="workflow">Workflow
* (<a href="AbstractController.html#workflow">and that defined by superclass</a>):</b><br>
* <ol>
* <li>Request is received by the controller</li>
* <li>call to {@link #handleRequestInternal handleRequestInternal} which
* just returns the view, named by the configuration property
* <code>viewName</code>. Nothing more, nothing less</li>
* </ol>
* </p>
*
* <p><b><a name="config">Exposed configuration properties</a>
* (<a href="AbstractController.html#config">and those defined by superclass</a>):</b><br>
* <table border="1">
* <tr>
* <td><b>name</b></td>
* <td><b>default</b></td>
* <td><b>description</b></td>
* </tr>
* <tr>
* <td>viewName</td>
* <td><i>null</i></td>
* <td>the name of the view the viewResolver will use to forward to
* (if this property is not set, a null view name will be returned
* directing the caller to calculate the view name from the current request)</td>
* </tr>
* </table>
* </p>
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Keith Donald
*/
public class ParameterizableViewController extends AbstractController {
private String viewName;
/**
* Set the name of the view to delegate to.
*/
public void setViewName(String viewName) {
this.viewName = viewName;
}
/**
* Return the name of the view to delegate to.
*/
public String getViewName() {
return this.viewName;
}
/**
* Return a ModelAndView object with the specified view name.
* The content of {@link RequestContextUtils#getInputFlashMap} is also added to the model.
* @see #getViewName()
*/
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
return new ModelAndView(getViewName(), RequestContextUtils.getInputFlashMap(request));
}
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.WebUtils;
/**
* Spring Controller implementation that forwards to a named servlet,
* i.e. the "servlet-name" in web.xml rather than a URL path mapping.
* A target servlet doesn't even need a "servlet-mapping" in web.xml
* in the first place: A "servlet" declaration is sufficient.
*
* <p>Useful to invoke an existing servlet via Spring's dispatching infrastructure,
* for example to apply Spring HandlerInterceptors to its requests. This will work
* even in a minimal Servlet container that does not support Servlet filters.
*
* <p><b>Example:</b> web.xml, mapping all "/myservlet" requests to a Spring dispatcher.
* Also defines a custom "myServlet", but <i>without</i> servlet mapping.
*
* <pre>
* &lt;servlet&gt;
* &lt;servlet-name&gt;myServlet&lt;/servlet-name&gt;
* &lt;servlet-class&gt;mypackage.TestServlet&lt;/servlet-class&gt;
* &lt;/servlet&gt;
*
* &lt;servlet&gt;
* &lt;servlet-name&gt;myDispatcher&lt;/servlet-name&gt;
* &lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt;
* &lt;/servlet&gt;
*
* &lt;servlet-mapping&gt;
* &lt;servlet-name&gt;myDispatcher&lt;/servlet-name&gt;
* &lt;url-pattern&gt;/myservlet&lt;/url-pattern&gt;
* &lt;/servlet-mapping&gt;</pre>
*
* <b>Example:</b> myDispatcher-servlet.xml, in turn forwarding "/myservlet" to your
* servlet (identified by servlet name). All such requests will go through the
* configured HandlerInterceptor chain (e.g. an OpenSessionInViewInterceptor).
* From the servlet point of view, everything will work as usual.
*
* <pre>
* &lt;bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"&gt;
* &lt;property name="interceptors"&gt;
* &lt;list&gt;
* &lt;ref bean="openSessionInViewInterceptor"/&gt;
* &lt;/list&gt;
* &lt;/property&gt;
* &lt;property name="mappings"&gt;
* &lt;props&gt;
* &lt;prop key="/myservlet"&gt;myServletForwardingController&lt;/prop&gt;
* &lt;/props&gt;
* &lt;/property&gt;
* &lt;/bean&gt;
*
* &lt;bean id="myServletForwardingController" class="org.springframework.web.servlet.mvc.ServletForwardingController"&gt;
* &lt;property name="servletName"&gt;&lt;value&gt;myServlet&lt;/value&gt;&lt;/property&gt;
* &lt;/bean&gt;</pre>
*
* @author Juergen Hoeller
* @since 1.1.1
* @see ServletWrappingController
* @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor
* @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
* @see org.springframework.orm.jdo.support.OpenPersistenceManagerInViewInterceptor
* @see org.springframework.orm.jdo.support.OpenPersistenceManagerInViewFilter
*/
public class ServletForwardingController extends AbstractController implements BeanNameAware {
private String servletName;
private String beanName;
/**
* Set the name of the servlet to forward to,
* i.e. the "servlet-name" of the target servlet in web.xml.
* <p>Default is the bean name of this controller.
*/
public void setServletName(String servletName) {
this.servletName = servletName;
}
public void setBeanName(String name) {
this.beanName = name;
if (this.servletName == null) {
this.servletName = name;
}
}
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
RequestDispatcher rd = getServletContext().getNamedDispatcher(this.servletName);
if (rd == null) {
throw new ServletException("No servlet with name '" + this.servletName + "' defined in web.xml");
}
// If already included, include again, else forward.
if (useInclude(request, response)) {
rd.include(request, response);
if (logger.isDebugEnabled()) {
logger.debug("Included servlet [" + this.servletName +
"] in ServletForwardingController '" + this.beanName + "'");
}
}
else {
rd.forward(request, response);
if (logger.isDebugEnabled()) {
logger.debug("Forwarded to servlet [" + this.servletName +
"] in ServletForwardingController '" + this.beanName + "'");
}
}
return null;
}
/**
* Determine whether to use RequestDispatcher's <code>include</code> or
* <code>forward</code> method.
* <p>Performs a check whether an include URI attribute is found in the request,
* indicating an include request, and whether the response has already been committed.
* In both cases, an include will be performed, as a forward is not possible anymore.
* @param request current HTTP request
* @param response current HTTP response
* @return <code>true</code> for include, <code>false</code> for forward
* @see javax.servlet.RequestDispatcher#forward
* @see javax.servlet.RequestDispatcher#include
* @see javax.servlet.ServletResponse#isCommitted
* @see org.springframework.web.util.WebUtils#isIncludeRequest
*/
protected boolean useInclude(HttpServletRequest request, HttpServletResponse response) {
return (WebUtils.isIncludeRequest(request) || response.isCommitted());
}
}

View File

@@ -0,0 +1,197 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc;
import java.util.Enumeration;
import java.util.Properties;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.servlet.ModelAndView;
/**
* Spring Controller implementation that wraps a servlet instance which it manages
* internally. Such a wrapped servlet is not known outside of this controller;
* its entire lifecycle is covered here (in contrast to {@link ServletForwardingController}).
*
* <p>Useful to invoke an existing servlet via Spring's dispatching infrastructure,
* for example to apply Spring HandlerInterceptors to its requests.
*
* <p>Note that Struts has a special requirement in that it parses <code>web.xml</code>
* to find its servlet mapping. Therefore, you need to specify the DispatcherServlet's
* servlet name as "servletName" on this controller, so that Struts finds the
* DispatcherServlet's mapping (thinking that it refers to the ActionServlet).
*
* <p><b>Example:</b> a DispatcherServlet XML context, forwarding "*.do" to the Struts
* ActionServlet wrapped by a ServletWrappingController. All such requests will go
* through the configured HandlerInterceptor chain (e.g. an OpenSessionInViewInterceptor).
* From the Struts point of view, everything will work as usual.
*
* <pre>
* &lt;bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"&gt;
* &lt;property name="interceptors"&gt;
* &lt;list&gt;
* &lt;ref bean="openSessionInViewInterceptor"/&gt;
* &lt;/list&gt;
* &lt;/property&gt;
* &lt;property name="mappings"&gt;
* &lt;props&gt;
* &lt;prop key="*.do"&gt;strutsWrappingController&lt;/prop&gt;
* &lt;/props&gt;
* &lt;/property&gt;
* &lt;/bean&gt;
*
* &lt;bean id="strutsWrappingController" class="org.springframework.web.servlet.mvc.ServletWrappingController"&gt;
* &lt;property name="servletClass"&gt;
* &lt;value&gt;org.apache.struts.action.ActionServlet&lt;/value&gt;
* &lt;/property&gt;
* &lt;property name="servletName"&gt;
* &lt;value&gt;action&lt;/value&gt;
* &lt;/property&gt;
* &lt;property name="initParameters"&gt;
* &lt;props&gt;
* &lt;prop key="config"&gt;/WEB-INF/struts-config.xml&lt;/prop&gt;
* &lt;/props&gt;
* &lt;/property&gt;
* &lt;/bean&gt;</pre>
*
* @author Juergen Hoeller
* @since 1.1.1
* @see ServletForwardingController
* @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor
* @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
* @see org.springframework.orm.jdo.support.OpenPersistenceManagerInViewInterceptor
* @see org.springframework.orm.jdo.support.OpenPersistenceManagerInViewFilter
*/
public class ServletWrappingController extends AbstractController
implements BeanNameAware, InitializingBean, DisposableBean {
private Class servletClass;
private String servletName;
private Properties initParameters = new Properties();
private String beanName;
private Servlet servletInstance;
/**
* Set the class of the servlet to wrap.
* Needs to implement <code>javax.servlet.Servlet</code>.
* @see javax.servlet.Servlet
*/
public void setServletClass(Class servletClass) {
this.servletClass = servletClass;
}
/**
* Set the name of the servlet to wrap.
* Default is the bean name of this controller.
*/
public void setServletName(String servletName) {
this.servletName = servletName;
}
/**
* Specify init parameters for the servlet to wrap,
* as name-value pairs.
*/
public void setInitParameters(Properties initParameters) {
this.initParameters = initParameters;
}
public void setBeanName(String name) {
this.beanName = name;
}
/**
* Initialize the wrapped Servlet instance.
* @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
*/
public void afterPropertiesSet() throws Exception {
if (this.servletClass == null) {
throw new IllegalArgumentException("servletClass is required");
}
if (!Servlet.class.isAssignableFrom(this.servletClass)) {
throw new IllegalArgumentException("servletClass [" + this.servletClass.getName() +
"] needs to implement interface [javax.servlet.Servlet]");
}
if (this.servletName == null) {
this.servletName = this.beanName;
}
this.servletInstance = (Servlet) this.servletClass.newInstance();
this.servletInstance.init(new DelegatingServletConfig());
}
/**
* Invoke the the wrapped Servlet instance.
* @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
*/
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
this.servletInstance.service(request, response);
return null;
}
/**
* Destroy the wrapped Servlet instance.
* @see javax.servlet.Servlet#destroy()
*/
public void destroy() {
this.servletInstance.destroy();
}
/**
* Internal implementation of the ServletConfig interface, to be passed
* to the wrapped servlet. Delegates to ServletWrappingController fields
* and methods to provide init parameters and other environment info.
*/
private class DelegatingServletConfig implements ServletConfig {
public String getServletName() {
return servletName;
}
public ServletContext getServletContext() {
return ServletWrappingController.this.getServletContext();
}
public String getInitParameter(String paramName) {
return initParameters.getProperty(paramName);
}
public Enumeration getInitParameterNames() {
return initParameters.keys();
}
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ModelAndView;
/**
* Adapter to use the plain {@link Controller} workflow interface with
* the generic {@link org.springframework.web.servlet.DispatcherServlet}.
* Supports handlers that implement the {@link LastModified} interface.
*
* <p>This is an SPI class, not used directly by application code.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see org.springframework.web.servlet.DispatcherServlet
* @see Controller
* @see LastModified
* @see HttpRequestHandlerAdapter
*/
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified) {
return ((LastModified) handler).getLastModified(request);
}
return -1L;
}
}

View File

@@ -0,0 +1,468 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.servlet.ModelAndView;
/**
* <p>Concrete FormController implementation that provides configurable
* form and success views, and an onSubmit chain for convenient overriding.
* Automatically resubmits to the form view in case of validation errors,
* and renders the success view in case of a valid submission.</p>
*
* <p>The workflow of this Controller does not differ much from the one described
* in the {@link AbstractFormController AbstractFormController}. The difference
* is that you do not need to implement {@link #showForm showForm} and
* {@link #processFormSubmission processFormSubmission}: A form view and a
* success view can be configured declaratively.</p>
*
* <p><b><a name="workflow">Workflow
* (<a href="AbstractFormController.html#workflow">in addition to the superclass</a>):</b><br>
* <ol>
* <li>Call to {@link #processFormSubmission processFormSubmission} which inspects
* the {@link org.springframework.validation.Errors Errors} object to see if
* any errors have occurred during binding and validation.</li>
* <li>If errors occured, the controller will return the configured formView,
* showing the form again (possibly rendering according error messages).</li>
* <li>If {@link #isFormChangeRequest isFormChangeRequest} is overridden and returns
* true for the given request, the controller will return the formView too.
* In that case, the controller will also suppress validation. Before returning the formView,
* the controller will invoke {@link #onFormChange}, giving sub-classes a chance
* to make modification to the command object.
* This is intended for requests that change the structure of the form,
* which should not cause validation and show the form in any case.</li>
* <li>If no errors occurred, the controller will call
* {@link #onSubmit(HttpServletRequest, HttpServletResponse, Object, BindException) onSubmit}
* using all parameters, which in case of the default implementation delegates to
* {@link #onSubmit(Object, BindException) onSubmit} with just the command object.
* The default implementation of the latter method will return the configured
* <code>successView</code>. Consider implementing {@link #doSubmitAction} doSubmitAction
* for simply performing a submit action and rendering the success view.</li>
* </ol>
* </p>
*
* <p>The submit behavior can be customized by overriding one of the
* {@link #onSubmit onSubmit} methods. Submit actions can also perform
* custom validation if necessary (typically database-driven checks), calling
* {@link #showForm(HttpServletRequest, HttpServletResponse, BindException) showForm}
* in case of validation errors to show the form view again.</p>
*
* <p><b><a name="config">Exposed configuration properties</a>
* (<a href="AbstractFormController.html#config">and those defined by superclass</a>):</b><br>
* <table border="1">
* <tr>
* <td><b>name</b></td>
* <td><b>default</b></td>
* <td><b>description</b></td>
* </tr>
* <tr>
* <td>formView</td>
* <td><i>null</i></td>
* <td>Indicates what view to use when the user asks for a new form
* or when validation errors have occurred on form submission.</td>
* </tr>
* <tr>
* <td>successView</td>
* <td><i>null</i></td>
* <td>Indicates what view to use when successful form submissions have
* occurred. Such a success view could e.g. display a submission summary.
* More sophisticated actions can be implemented by overriding one of
* the {@link #onSubmit(Object) onSubmit()} methods.</td>
* </tr>
* <table>
* </p>
*
* @author Juergen Hoeller
* @author Rob Harrop
* @since 05.05.2003
* @deprecated as of Spring 3.0, in favor of annotated controllers
*/
@Deprecated
public class SimpleFormController extends AbstractFormController {
private String formView;
private String successView;
/**
* Create a new SimpleFormController.
* <p>Subclasses should set the following properties, either in the constructor
* or via a BeanFactory: commandName, commandClass, sessionForm, formView,
* successView. Note that commandClass doesn't need to be set when overriding
* <code>formBackingObject</code>, as this determines the class anyway.
* @see #setCommandClass
* @see #setCommandName
* @see #setSessionForm
* @see #setFormView
* @see #setSuccessView
* @see #formBackingObject
*/
public SimpleFormController() {
// AbstractFormController sets default cache seconds to 0.
super();
}
/**
* Set the name of the view that should be used for form display.
*/
public final void setFormView(String formView) {
this.formView = formView;
}
/**
* Return the name of the view that should be used for form display.
*/
public final String getFormView() {
return this.formView;
}
/**
* Set the name of the view that should be shown on successful submit.
*/
public final void setSuccessView(String successView) {
this.successView = successView;
}
/**
* Return the name of the view that should be shown on successful submit.
*/
public final String getSuccessView() {
return this.successView;
}
/**
* This implementation shows the configured form view, delegating to the analogous
* {@link #showForm(HttpServletRequest, HttpServletResponse, BindException, Map)}
* variant with a "controlModel" argument.
* <p>Can be called within
* {@link #onSubmit(HttpServletRequest, HttpServletResponse, Object, BindException)}
* implementations, to redirect back to the form in case of custom validation errors
* (errors not determined by the validator).
* <p>Can be overridden in subclasses to show a custom view, writing directly
* to the response or preparing the response before rendering a view.
* <p>If calling showForm with a custom control model in subclasses, it's preferable
* to override the analogous showForm version with a controlModel argument
* (which will handle both standard form showing and custom form showing then).
* @see #setFormView
* @see #showForm(HttpServletRequest, HttpServletResponse, BindException, Map)
*/
@Override
protected ModelAndView showForm(
HttpServletRequest request, HttpServletResponse response, BindException errors)
throws Exception {
return showForm(request, response, errors, null);
}
/**
* This implementation shows the configured form view.
* <p>Can be called within
* {@link #onSubmit(HttpServletRequest, HttpServletResponse, Object, BindException)}
* implementations, to redirect back to the form in case of custom validation errors
* (errors not determined by the validator).
* <p>Can be overridden in subclasses to show a custom view, writing directly
* to the response or preparing the response before rendering a view.
* @param request current HTTP request
* @param errors validation errors holder
* @param controlModel model map containing controller-specific control data
* (e.g. current page in wizard-style controllers or special error message)
* @return the prepared form view
* @throws Exception in case of invalid state or arguments
* @see #setFormView
*/
protected ModelAndView showForm(
HttpServletRequest request, HttpServletResponse response, BindException errors, Map controlModel)
throws Exception {
return showForm(request, errors, getFormView(), controlModel);
}
/**
* Create a reference data map for the given request and command,
* consisting of bean name/bean instance pairs as expected by ModelAndView.
* <p>The default implementation delegates to {@link #referenceData(HttpServletRequest)}.
* Subclasses can override this to set reference data used in the view.
* @param request current HTTP request
* @param command form object with request parameters bound onto it
* @param errors validation errors holder
* @return a Map with reference data entries, or <code>null</code> if none
* @throws Exception in case of invalid state or arguments
* @see ModelAndView
*/
@Override
protected Map referenceData(HttpServletRequest request, Object command, Errors errors) throws Exception {
return referenceData(request);
}
/**
* Create a reference data map for the given request.
* Called by the {@link #referenceData(HttpServletRequest, Object, Errors)}
* variant with all parameters.
* <p>The default implementation returns <code>null</code>.
* Subclasses can override this to set reference data used in the view.
* @param request current HTTP request
* @return a Map with reference data entries, or <code>null</code> if none
* @throws Exception in case of invalid state or arguments
* @see #referenceData(HttpServletRequest, Object, Errors)
* @see ModelAndView
*/
protected Map referenceData(HttpServletRequest request) throws Exception {
return null;
}
/**
* This implementation calls
* {@link #showForm(HttpServletRequest, HttpServletResponse, BindException)}
* in case of errors, and delegates to the full
* {@link #onSubmit(HttpServletRequest, HttpServletResponse, Object, BindException)}'s
* variant else.
* <p>This can only be overridden to check for an action that should be executed
* without respect to binding errors, like a cancel action. To just handle successful
* submissions without binding errors, override one of the <code>onSubmit</code>
* methods or {@link #doSubmitAction}.
* @see #showForm(HttpServletRequest, HttpServletResponse, BindException)
* @see #onSubmit(HttpServletRequest, HttpServletResponse, Object, BindException)
* @see #onSubmit(Object, BindException)
* @see #onSubmit(Object)
* @see #doSubmitAction(Object)
*/
@Override
protected ModelAndView processFormSubmission(
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
throws Exception {
if (errors.hasErrors()) {
if (logger.isDebugEnabled()) {
logger.debug("Data binding errors: " + errors.getErrorCount());
}
return showForm(request, response, errors);
}
else if (isFormChangeRequest(request, command)) {
logger.debug("Detected form change request -> routing request to onFormChange");
onFormChange(request, response, command, errors);
return showForm(request, response, errors);
}
else {
logger.debug("No errors -> processing submit");
return onSubmit(request, response, command, errors);
}
}
/**
* This implementation delegates to {@link #isFormChangeRequest(HttpServletRequest, Object)}:
* A form change request changes the appearance of the form and should not get
* validated but just show the new form.
* @see #isFormChangeRequest
*/
@Override
protected boolean suppressValidation(HttpServletRequest request, Object command) {
return isFormChangeRequest(request, command);
}
/**
* Determine whether the given request is a form change request.
* A form change request changes the appearance of the form
* and should always show the new form, without validation.
* <p>Gets called by {@link #suppressValidation} and {@link #processFormSubmission}.
* Consequently, this single method determines to suppress validation
* <i>and</i> to show the form view in any case.
* <p>The default implementation delegates to
* {@link #isFormChangeRequest(javax.servlet.http.HttpServletRequest)}.
* @param request current HTTP request
* @param command form object with request parameters bound onto it
* @return whether the given request is a form change request
* @see #suppressValidation
* @see #processFormSubmission
*/
protected boolean isFormChangeRequest(HttpServletRequest request, Object command) {
return isFormChangeRequest(request);
}
/**
* Simpler <code>isFormChangeRequest</code> variant, called by the full
* variant {@link #isFormChangeRequest(HttpServletRequest, Object)}.
* <p>The default implementation returns <code>false</code.
* @param request current HTTP request
* @return whether the given request is a form change request
* @see #suppressValidation
* @see #processFormSubmission
*/
protected boolean isFormChangeRequest(HttpServletRequest request) {
return false;
}
/**
* Called during form submission if
* {@link #isFormChangeRequest(javax.servlet.http.HttpServletRequest)}
* returns <code>true</code>. Allows subclasses to implement custom logic
* to modify the command object to directly modify data in the form.
* <p>The default implementation delegates to
* {@link #onFormChange(HttpServletRequest, HttpServletResponse, Object, BindException)}.
* @param request current servlet request
* @param response current servlet response
* @param command form object with request parameters bound onto it
* @param errors validation errors holder, allowing for additional
* custom validation
* @throws Exception in case of errors
* @see #isFormChangeRequest(HttpServletRequest)
* @see #onFormChange(HttpServletRequest, HttpServletResponse, Object)
*/
protected void onFormChange(
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
throws Exception {
onFormChange(request, response, command);
}
/**
* Simpler <code>onFormChange</code> variant, called by the full variant
* {@link #onFormChange(HttpServletRequest, HttpServletResponse, Object, BindException)}.
* <p>The default implementation is empty.
* @param request current servlet request
* @param response current servlet response
* @param command form object with request parameters bound onto it
* @throws Exception in case of errors
* @see #onFormChange(HttpServletRequest, HttpServletResponse, Object, BindException)
*/
protected void onFormChange(HttpServletRequest request, HttpServletResponse response, Object command)
throws Exception {
}
/**
* Submit callback with all parameters. Called in case of submit without errors
* reported by the registered validator, or on every submit if no validator.
* <p>The default implementation delegates to {@link #onSubmit(Object, BindException)}.
* For simply performing a submit action and rendering the specified success
* view, consider implementing {@link #doSubmitAction} rather than an
* <code>onSubmit</code> variant.
* <p>Subclasses can override this to provide custom submission handling like storing
* the object to the database. Implementations can also perform custom validation and
* call showForm to return to the form. Do <i>not</i> implement multiple onSubmit
* methods: In that case, just this method will be called by the controller.
* <p>Call <code>errors.getModel()</code> to populate the ModelAndView model
* with the command and the Errors instance, under the specified command name,
* as expected by the "spring:bind" tag.
* @param request current servlet request
* @param response current servlet response
* @param command form object with request parameters bound onto it
* @param errors Errors instance without errors (subclass can add errors if it wants to)
* @return the prepared model and view, or <code>null</code>
* @throws Exception in case of errors
* @see #onSubmit(Object, BindException)
* @see #doSubmitAction
* @see #showForm
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
*/
protected ModelAndView onSubmit(
HttpServletRequest request, HttpServletResponse response, Object command, BindException errors)
throws Exception {
return onSubmit(command, errors);
}
/**
* Simpler <code>onSubmit</code> variant.
* Called by the default implementation of the
* {@link #onSubmit(HttpServletRequest, HttpServletResponse, Object, BindException)}
* variant with all parameters.
* <p>The default implementation calls {@link #onSubmit(Object)}, using the
* returned ModelAndView if actually implemented in a subclass. Else, the
* default behavior will apply: rendering the success view with the command
* and Errors instance as model.
* <p>Subclasses can override this to provide custom submission handling that
* does not need request and response.
* <p>Call <code>errors.getModel()</code> to populate the ModelAndView model
* with the command and the Errors instance, under the specified command name,
* as expected by the "spring:bind" tag.
* @param command form object with request parameters bound onto it
* @param errors Errors instance without errors
* @return the prepared model and view
* @throws Exception in case of errors
* @see #onSubmit(HttpServletRequest, HttpServletResponse, Object, BindException)
* @see #onSubmit(Object)
* @see #setSuccessView
* @see org.springframework.validation.Errors
* @see org.springframework.validation.BindException#getModel
*/
protected ModelAndView onSubmit(Object command, BindException errors) throws Exception {
ModelAndView mv = onSubmit(command);
if (mv != null) {
// simplest onSubmit variant implemented in custom subclass
return mv;
}
else {
// default behavior: render success view
if (getSuccessView() == null) {
throw new ServletException("successView isn't set");
}
return new ModelAndView(getSuccessView(), errors.getModel());
}
}
/**
* Simplest <code>onSubmit</code> variant. Called by the default implementation
* of the {@link #onSubmit(Object, BindException)} variant.
* <p>This implementation calls {@link #doSubmitAction(Object)} and returns
* <code>null</code> as ModelAndView, making the calling <code>onSubmit</code>
* method perform its default rendering of the success view.
* <p>Subclasses can override this to provide custom submission handling
* that just depends on the command object. It's preferable to use either
* {@link #onSubmit(Object, BindException)} or {@link #doSubmitAction(Object)},
* though: Use the former when you want to build your own ModelAndView; use the
* latter when you want to perform an action and forward to the successView.
* @param command form object with request parameters bound onto it
* @return the prepared model and view, or <code>null</code> for default
* (that is, rendering the configured "successView")
* @throws Exception in case of errors
* @see #onSubmit(Object, BindException)
* @see #doSubmitAction
* @see #setSuccessView
*/
protected ModelAndView onSubmit(Object command) throws Exception {
doSubmitAction(command);
return null;
}
/**
* Template method for submit actions. Called by the default implementation
* of the simplest {@link #onSubmit(Object)} variant.
* <p><b>This is the preferred submit callback to implement if you want to
* perform an action (like storing changes to the database) and then render
* the success view with the command and Errors instance as model.</b>
* You don't need to care about the success ModelAndView here.
* @param command form object with request parameters bound onto it
* @throws Exception in case of errors
* @see #onSubmit(Object)
* @see #setSuccessView
*/
protected void doSubmitAction(Object command) throws Exception {
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.http.HttpServletRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerMapping;
/**
* Simple <code>Controller</code> implementation that transforms the virtual
* path of a URL into a view name and returns that view.
*
* <p>Can optionally prepend a {@link #setPrefix prefix} and/or append a
* {@link #setSuffix suffix} to build the viewname from the URL filename.
*
* <p>Find below some examples:
*
* <ol>
* <li><code>"/index" -> "index"</code></li>
* <li><code>"/index.html" -> "index"</code></li>
* <li><code>"/index.html"</code> + prefix <code>"pre_"</code> and suffix <code>"_suf" -> "pre_index_suf"</code></li>
* <li><code>"/products/view.html" -> "products/view"</code></li>
* </ol>
*
* <p>Thanks to David Barri for suggesting prefix/suffix support!
*
* @author Alef Arendsen
* @author Juergen Hoeller
* @author Rob Harrop
* @see #setPrefix
* @see #setSuffix
*/
public class UrlFilenameViewController extends AbstractUrlViewController {
private String prefix = "";
private String suffix = "";
/** Request URL path String --> view name String */
private final Map<String, String> viewNameCache = new ConcurrentHashMap<String, String>();
/**
* Set the prefix to prepend to the request URL filename
* to build a view name.
*/
public void setPrefix(String prefix) {
this.prefix = (prefix != null ? prefix : "");
}
/**
* Return the prefix to prepend to the request URL filename.
*/
protected String getPrefix() {
return this.prefix;
}
/**
* Set the suffix to append to the request URL filename
* to build a view name.
*/
public void setSuffix(String suffix) {
this.suffix = (suffix != null ? suffix : "");
}
/**
* Return the suffix to append to the request URL filename.
*/
protected String getSuffix() {
return this.suffix;
}
/**
* Returns view name based on the URL filename,
* with prefix/suffix applied when appropriate.
* @see #extractViewNameFromUrlPath
* @see #setPrefix
* @see #setSuffix
*/
@Override
protected String getViewNameForRequest(HttpServletRequest request) {
String uri = extractOperableUrl(request);
return getViewNameForUrlPath(uri);
}
/**
* Extract a URL path from the given request,
* suitable for view name extraction.
* @param request current HTTP request
* @return the URL to use for view name extraction
*/
protected String extractOperableUrl(HttpServletRequest request) {
String urlPath = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
if (!StringUtils.hasText(urlPath)) {
urlPath = getUrlPathHelper().getLookupPathForRequest(request);
}
return urlPath;
}
/**
* Returns view name based on the URL filename,
* with prefix/suffix applied when appropriate.
* @param uri the request URI; for example <code>"/index.html"</code>
* @return the extracted URI filename; for example <code>"index"</code>
* @see #extractViewNameFromUrlPath
* @see #postProcessViewName
*/
protected String getViewNameForUrlPath(String uri) {
String viewName = this.viewNameCache.get(uri);
if (viewName == null) {
viewName = extractViewNameFromUrlPath(uri);
viewName = postProcessViewName(viewName);
this.viewNameCache.put(uri, viewName);
}
return viewName;
}
/**
* Extract the URL filename from the given request URI.
* @param uri the request URI; for example <code>"/index.html"</code>
* @return the extracted URI filename; for example <code>"index"</code>
*/
protected String extractViewNameFromUrlPath(String uri) {
int start = (uri.charAt(0) == '/' ? 1 : 0);
int lastIndex = uri.lastIndexOf(".");
int end = (lastIndex < 0 ? uri.length() : lastIndex);
return uri.substring(start, end);
}
/**
* Build the full view name based on the given view name
* as indicated by the URL path.
* <p>The default implementation simply applies prefix and suffix.
* This can be overridden, for example, to manipulate upper case
* / lower case, etc.
* @param viewName the original view name, as indicated by the URL path
* @return the full view name to use
* @see #getPrefix()
* @see #getSuffix()
*/
protected String postProcessViewName(String viewName) {
return getPrefix() + viewName + getSuffix();
}
}

View File

@@ -0,0 +1,205 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.support.WebContentGenerator;
import org.springframework.web.util.UrlPathHelper;
/**
* Interceptor that checks and prepares request and response. Checks for supported
* methods and a required session, and applies the specified number of cache seconds.
* See superclass bean properties for configuration options.
*
* <p>All the settings supported by this interceptor can also be set on AbstractController.
* This interceptor is mainly intended for applying checks and preparations to a set of
* controllers mapped by a HandlerMapping.
*
* @author Juergen Hoeller
* @since 27.11.2003
* @see AbstractController
*/
public class WebContentInterceptor extends WebContentGenerator implements HandlerInterceptor {
private UrlPathHelper urlPathHelper = new UrlPathHelper();
private Map<String, Integer> cacheMappings = new HashMap<String, Integer>();
private PathMatcher pathMatcher = new AntPathMatcher();
public WebContentInterceptor() {
// no restriction of HTTP methods by default,
// in particular for use with annotated controllers
super(false);
}
/**
* Set if URL lookup should always use full path within current servlet
* context. Else, the path within the current servlet mapping is used
* if applicable (i.e. in the case of a ".../*" servlet mapping in web.xml).
* Default is "false".
* <p>Only relevant for the "cacheMappings" setting.
* @see #setCacheMappings
* @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
*/
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
}
/**
* Set if context path and request URI should be URL-decoded.
* Both are returned <i>undecoded</i> by the Servlet API,
* in contrast to the servlet path.
* <p>Uses either the request encoding or the default encoding according
* to the Servlet spec (ISO-8859-1).
* <p>Only relevant for the "cacheMappings" setting.
* @see #setCacheMappings
* @see org.springframework.web.util.UrlPathHelper#setUrlDecode
*/
public void setUrlDecode(boolean urlDecode) {
this.urlPathHelper.setUrlDecode(urlDecode);
}
/**
* Set the UrlPathHelper to use for resolution of lookup paths.
* <p>Use this to override the default UrlPathHelper with a custom subclass,
* or to share common UrlPathHelper settings across multiple HandlerMappings
* and MethodNameResolvers.
* <p>Only relevant for the "cacheMappings" setting.
* @see #setCacheMappings
* @see org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#setUrlPathHelper
* @see org.springframework.web.servlet.mvc.multiaction.AbstractUrlMethodNameResolver#setUrlPathHelper
*/
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
this.urlPathHelper = urlPathHelper;
}
/**
* Map specific URL paths to specific cache seconds.
* <p>Overrides the default cache seconds setting of this interceptor.
* Can specify "-1" to exclude a URL path from default caching.
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
* and a various Ant-style pattern matches, e.g. a registered "/t*" matches
* both "/test" and "/team". For details, see the AntPathMatcher javadoc.
* @param cacheMappings a mapping between URL paths (as keys) and
* cache seconds (as values, need to be integer-parsable)
* @see #setCacheSeconds
* @see org.springframework.util.AntPathMatcher
*/
public void setCacheMappings(Properties cacheMappings) {
this.cacheMappings.clear();
Enumeration propNames = cacheMappings.propertyNames();
while (propNames.hasMoreElements()) {
String path = (String) propNames.nextElement();
this.cacheMappings.put(path, Integer.valueOf(cacheMappings.getProperty(path)));
}
}
/**
* Set the PathMatcher implementation to use for matching URL paths
* against registered URL patterns, for determining cache mappings.
* Default is AntPathMatcher.
* @see #setCacheMappings
* @see org.springframework.util.AntPathMatcher
*/
public void setPathMatcher(PathMatcher pathMatcher) {
Assert.notNull(pathMatcher, "PathMatcher must not be null");
this.pathMatcher = pathMatcher;
}
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws ServletException {
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
if (logger.isDebugEnabled()) {
logger.debug("Looking up cache seconds for [" + lookupPath + "]");
}
Integer cacheSeconds = lookupCacheSeconds(lookupPath);
if (cacheSeconds != null) {
if (logger.isDebugEnabled()) {
logger.debug("Applying " + cacheSeconds + " cache seconds to [" + lookupPath + "]");
}
checkAndPrepare(request, response, cacheSeconds, handler instanceof LastModified);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Applying default cache seconds to [" + lookupPath + "]");
}
checkAndPrepare(request, response, handler instanceof LastModified);
}
return true;
}
/**
* Look up a cache seconds value for the given URL path.
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
* and various Ant-style pattern matches, e.g. a registered "/t*" matches
* both "/test" and "/team". For details, see the AntPathMatcher class.
* @param urlPath URL the bean is mapped to
* @return the associated cache seconds, or <code>null</code> if not found
* @see org.springframework.util.AntPathMatcher
*/
protected Integer lookupCacheSeconds(String urlPath) {
// direct match?
Integer cacheSeconds = this.cacheMappings.get(urlPath);
if (cacheSeconds == null) {
// pattern match?
for (String registeredPath : this.cacheMappings.keySet()) {
if (this.pathMatcher.match(registeredPath, urlPath)) {
cacheSeconds = this.cacheMappings.get(registeredPath);
}
}
}
return cacheSeconds;
}
/**
* This implementation is empty.
*/
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
}
/**
* This implementation is empty.
*/
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}

View File

@@ -0,0 +1,443 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.annotation;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.core.ExceptionDepthComparator;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.ui.Model;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.springframework.web.servlet.support.RequestContextUtils;
/**
* Implementation of the {@link org.springframework.web.servlet.HandlerExceptionResolver} interface that handles
* exceptions through the {@link ExceptionHandler} annotation.
*
* <p>This exception resolver is enabled by default in the {@link org.springframework.web.servlet.DispatcherServlet}.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 3.0
*/
public class AnnotationMethodHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
// dummy method placeholder
private static final Method NO_METHOD_FOUND = ClassUtils.getMethodIfAvailable(System.class, "currentTimeMillis", null);
private final Map<Class<?>, Map<Class<? extends Throwable>, Method>> exceptionHandlerCache =
new ConcurrentHashMap<Class<?>, Map<Class<? extends Throwable>, Method>>();
private WebArgumentResolver[] customArgumentResolvers;
private HttpMessageConverter<?>[] messageConverters =
new HttpMessageConverter[] {new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(),
new SourceHttpMessageConverter(), new XmlAwareFormHttpMessageConverter()};
/**
* Set a custom ArgumentResolvers to use for special method parameter types.
* <p>Such a custom ArgumentResolver will kick in first, having a chance to resolve
* an argument value before the standard argument handling kicks in.
*/
public void setCustomArgumentResolver(WebArgumentResolver argumentResolver) {
this.customArgumentResolvers = new WebArgumentResolver[]{argumentResolver};
}
/**
* Set one or more custom ArgumentResolvers to use for special method parameter types.
* <p>Any such custom ArgumentResolver will kick in first, having a chance to resolve
* an argument value before the standard argument handling kicks in.
*/
public void setCustomArgumentResolvers(WebArgumentResolver[] argumentResolvers) {
this.customArgumentResolvers = argumentResolvers;
}
/**
* Set the message body converters to use.
* <p>These converters are used to convert from and to HTTP requests and responses.
*/
public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) {
this.messageConverters = messageConverters;
}
@Override
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (handler != null) {
Method handlerMethod = findBestExceptionHandlerMethod(handler, ex);
if (handlerMethod != null) {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
Object[] args = resolveHandlerArguments(handlerMethod, handler, webRequest, ex);
if (logger.isDebugEnabled()) {
logger.debug("Invoking request handler method: " + handlerMethod);
}
Object retVal = doInvokeMethod(handlerMethod, handler, args);
return getModelAndView(handlerMethod, retVal, webRequest);
}
catch (Exception invocationEx) {
logger.error("Invoking request method resulted in exception : " + handlerMethod, invocationEx);
}
}
}
return null;
}
/**
* Finds the handler method that matches the thrown exception best.
* @param handler the handler object
* @param thrownException the exception to be handled
* @return the best matching method; or <code>null</code> if none is found
*/
private Method findBestExceptionHandlerMethod(Object handler, final Exception thrownException) {
final Class<?> handlerType = ClassUtils.getUserClass(handler);
final Class<? extends Throwable> thrownExceptionType = thrownException.getClass();
Method handlerMethod = null;
Map<Class<? extends Throwable>, Method> handlers = exceptionHandlerCache.get(handlerType);
if (handlers != null) {
handlerMethod = handlers.get(thrownExceptionType);
if (handlerMethod != null) {
return (handlerMethod == NO_METHOD_FOUND ? null : handlerMethod);
}
}
else {
handlers = new ConcurrentHashMap<Class<? extends Throwable>, Method>();
exceptionHandlerCache.put(handlerType, handlers);
}
final Map<Class<? extends Throwable>, Method> resolverMethods = handlers;
ReflectionUtils.doWithMethods(handlerType, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) {
method = ClassUtils.getMostSpecificMethod(method, handlerType);
List<Class<? extends Throwable>> handledExceptions = getHandledExceptions(method);
for (Class<? extends Throwable> handledException : handledExceptions) {
if (handledException.isAssignableFrom(thrownExceptionType)) {
if (!resolverMethods.containsKey(handledException)) {
resolverMethods.put(handledException, method);
}
else {
Method oldMappedMethod = resolverMethods.get(handledException);
if (!oldMappedMethod.equals(method)) {
throw new IllegalStateException(
"Ambiguous exception handler mapped for " + handledException + "]: {" +
oldMappedMethod + ", " + method + "}.");
}
}
}
}
}
});
handlerMethod = getBestMatchingMethod(resolverMethods, thrownException);
handlers.put(thrownExceptionType, (handlerMethod == null ? NO_METHOD_FOUND : handlerMethod));
return handlerMethod;
}
/**
* Returns all the exception classes handled by the given method.
* <p>The default implementation looks for exceptions in the {@linkplain ExceptionHandler#value() annotation},
* or - if that annotation element is empty - any exceptions listed in the method parameters if the method
* is annotated with {@code @ExceptionHandler}.
* @param method the method
* @return the handled exceptions
*/
@SuppressWarnings("unchecked")
protected List<Class<? extends Throwable>> getHandledExceptions(Method method) {
List<Class<? extends Throwable>> result = new ArrayList<Class<? extends Throwable>>();
ExceptionHandler exceptionHandler = AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
if (exceptionHandler != null) {
if (!ObjectUtils.isEmpty(exceptionHandler.value())) {
result.addAll(Arrays.asList(exceptionHandler.value()));
}
else {
for (Class<?> param : method.getParameterTypes()) {
if (Throwable.class.isAssignableFrom(param)) {
result.add((Class<? extends Throwable>) param);
}
}
}
}
return result;
}
/**
* Returns the best matching method. Uses the {@link DepthComparator}.
*/
private Method getBestMatchingMethod(
Map<Class<? extends Throwable>, Method> resolverMethods, Exception thrownException) {
if (!resolverMethods.isEmpty()) {
Class<? extends Throwable> closestMatch =
ExceptionDepthComparator.findClosestMatch(resolverMethods.keySet(), thrownException);
return resolverMethods.get(closestMatch);
}
else {
return null;
}
}
/**
* Resolves the arguments for the given method. Delegates to {@link #resolveCommonArgument}.
*/
private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,
NativeWebRequest webRequest, Exception thrownException) throws Exception {
Class[] paramTypes = handlerMethod.getParameterTypes();
Object[] args = new Object[paramTypes.length];
Class<?> handlerType = handler.getClass();
for (int i = 0; i < args.length; i++) {
MethodParameter methodParam = new MethodParameter(handlerMethod, i);
GenericTypeResolver.resolveParameterType(methodParam, handlerType);
Class paramType = methodParam.getParameterType();
Object argValue = resolveCommonArgument(methodParam, webRequest, thrownException);
if (argValue != WebArgumentResolver.UNRESOLVED) {
args[i] = argValue;
}
else {
throw new IllegalStateException("Unsupported argument [" + paramType.getName() +
"] for @ExceptionHandler method: " + handlerMethod);
}
}
return args;
}
/**
* Resolves common method arguments. Delegates to registered {@link #setCustomArgumentResolver(WebArgumentResolver)
* argumentResolvers} first, then checking {@link #resolveStandardArgument}.
* @param methodParameter the method parameter
* @param webRequest the request
* @param thrownException the exception thrown
* @return the argument value, or {@link WebArgumentResolver#UNRESOLVED}
*/
protected Object resolveCommonArgument(MethodParameter methodParameter, NativeWebRequest webRequest,
Exception thrownException) throws Exception {
// Invoke custom argument resolvers if present...
if (this.customArgumentResolvers != null) {
for (WebArgumentResolver argumentResolver : this.customArgumentResolvers) {
Object value = argumentResolver.resolveArgument(methodParameter, webRequest);
if (value != WebArgumentResolver.UNRESOLVED) {
return value;
}
}
}
// Resolution of standard parameter types...
Class paramType = methodParameter.getParameterType();
Object value = resolveStandardArgument(paramType, webRequest, thrownException);
if (value != WebArgumentResolver.UNRESOLVED && !ClassUtils.isAssignableValue(paramType, value)) {
throw new IllegalStateException(
"Standard argument type [" + paramType.getName() + "] resolved to incompatible value of type [" +
(value != null ? value.getClass() : null) +
"]. Consider declaring the argument type in a less specific fashion.");
}
return value;
}
/**
* Resolves standard method arguments. The default implementation handles {@link NativeWebRequest},
* {@link ServletRequest}, {@link ServletResponse}, {@link HttpSession}, {@link Principal},
* {@link Locale}, request {@link InputStream}, request {@link Reader}, response {@link OutputStream},
* response {@link Writer}, and the given {@code thrownException}.
* @param parameterType the method parameter type
* @param webRequest the request
* @param thrownException the exception thrown
* @return the argument value, or {@link WebArgumentResolver#UNRESOLVED}
*/
protected Object resolveStandardArgument(Class parameterType, NativeWebRequest webRequest,
Exception thrownException) throws Exception {
if (parameterType.isInstance(thrownException)) {
return thrownException;
}
else if (WebRequest.class.isAssignableFrom(parameterType)) {
return webRequest;
}
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
if (ServletRequest.class.isAssignableFrom(parameterType)) {
return request;
}
else if (ServletResponse.class.isAssignableFrom(parameterType)) {
return response;
}
else if (HttpSession.class.isAssignableFrom(parameterType)) {
return request.getSession();
}
else if (Principal.class.isAssignableFrom(parameterType)) {
return request.getUserPrincipal();
}
else if (Locale.class.equals(parameterType)) {
return RequestContextUtils.getLocale(request);
}
else if (InputStream.class.isAssignableFrom(parameterType)) {
return request.getInputStream();
}
else if (Reader.class.isAssignableFrom(parameterType)) {
return request.getReader();
}
else if (OutputStream.class.isAssignableFrom(parameterType)) {
return response.getOutputStream();
}
else if (Writer.class.isAssignableFrom(parameterType)) {
return response.getWriter();
}
else {
return WebArgumentResolver.UNRESOLVED;
}
}
private Object doInvokeMethod(Method method, Object target, Object[] args) throws Exception {
ReflectionUtils.makeAccessible(method);
try {
return method.invoke(target, args);
}
catch (InvocationTargetException ex) {
ReflectionUtils.rethrowException(ex.getTargetException());
}
throw new IllegalStateException("Should never get here");
}
@SuppressWarnings("unchecked")
private ModelAndView getModelAndView(Method handlerMethod, Object returnValue, ServletWebRequest webRequest)
throws Exception {
ResponseStatus responseStatusAnn = AnnotationUtils.findAnnotation(handlerMethod, ResponseStatus.class);
if (responseStatusAnn != null) {
HttpStatus responseStatus = responseStatusAnn.value();
String reason = responseStatusAnn.reason();
if (!StringUtils.hasText(reason)) {
webRequest.getResponse().setStatus(responseStatus.value());
}
else {
webRequest.getResponse().sendError(responseStatus.value(), reason);
}
}
if (returnValue != null && AnnotationUtils.findAnnotation(handlerMethod, ResponseBody.class) != null) {
return handleResponseBody(returnValue, webRequest);
}
if (returnValue instanceof ModelAndView) {
return (ModelAndView) returnValue;
}
else if (returnValue instanceof Model) {
return new ModelAndView().addAllObjects(((Model) returnValue).asMap());
}
else if (returnValue instanceof Map) {
return new ModelAndView().addAllObjects((Map) returnValue);
}
else if (returnValue instanceof View) {
return new ModelAndView((View) returnValue);
}
else if (returnValue instanceof String) {
return new ModelAndView((String) returnValue);
}
else if (returnValue == null) {
return new ModelAndView();
}
else {
throw new IllegalArgumentException("Invalid handler method return value: " + returnValue);
}
}
@SuppressWarnings("unchecked")
private ModelAndView handleResponseBody(Object returnValue, ServletWebRequest webRequest)
throws ServletException, IOException {
HttpInputMessage inputMessage = new ServletServerHttpRequest(webRequest.getRequest());
List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
if (acceptedMediaTypes.isEmpty()) {
acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
}
MediaType.sortByQualityValue(acceptedMediaTypes);
HttpOutputMessage outputMessage = new ServletServerHttpResponse(webRequest.getResponse());
Class<?> returnValueType = returnValue.getClass();
if (this.messageConverters != null) {
for (MediaType acceptedMediaType : acceptedMediaTypes) {
for (HttpMessageConverter messageConverter : this.messageConverters) {
if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {
messageConverter.write(returnValue, acceptedMediaType, outputMessage);
return new ModelAndView();
}
}
}
}
if (logger.isWarnEnabled()) {
logger.warn("Could not find HttpMessageConverter that supports return type [" + returnValueType + "] and " +
acceptedMediaTypes);
}
return null;
}
}

View File

@@ -0,0 +1,268 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Controller;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping;
/**
* Implementation of the {@link org.springframework.web.servlet.HandlerMapping}
* interface that maps handlers based on HTTP paths expressed through the
* {@link RequestMapping} annotation at the type or method level.
*
* <p>Registered by default in {@link org.springframework.web.servlet.DispatcherServlet}
* on Java 5+. <b>NOTE:</b> If you define custom HandlerMapping beans in your
* DispatcherServlet context, you need to add a DefaultAnnotationHandlerMapping bean
* explicitly, since custom HandlerMapping beans replace the default mapping strategies.
* Defining a DefaultAnnotationHandlerMapping also allows for registering custom
* interceptors:
*
* <pre class="code">
* &lt;bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"&gt;
* &lt;property name="interceptors"&gt;
* ...
* &lt;/property&gt;
* &lt;/bean&gt;</pre>
*
* Annotated controllers are usually marked with the {@link Controller} stereotype
* at the type level. This is not strictly necessary when {@link RequestMapping} is
* applied at the type level (since such a handler usually implements the
* {@link org.springframework.web.servlet.mvc.Controller} interface). However,
* {@link Controller} is required for detecting {@link RequestMapping} annotations
* at the method level if {@link RequestMapping} is not present at the type level.
*
* <p><b>NOTE:</b> Method-level mappings are only allowed to narrow the mapping
* expressed at the class level (if any). HTTP paths need to uniquely map onto
* specific handler beans, with any given HTTP path only allowed to be mapped
* onto one specific handler bean (not spread across multiple handler beans).
* It is strongly recommended to co-locate related handler methods into the same bean.
*
* <p>The {@link AnnotationMethodHandlerAdapter} is responsible for processing
* annotated handler methods, as mapped by this HandlerMapping. For
* {@link RequestMapping} at the type level, specific HandlerAdapters such as
* {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter} apply.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
* @since 2.5
* @see RequestMapping
* @see AnnotationMethodHandlerAdapter
*/
public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandlerMapping {
private boolean useDefaultSuffixPattern = true;
private final Map<Class, RequestMapping> cachedMappings = new HashMap<Class, RequestMapping>();
/**
* Set whether to register paths using the default suffix pattern as well:
* i.e. whether "/users" should be registered as "/users.*" and "/users/" too.
* <p>Default is "true". Turn this convention off if you intend to interpret
* your <code>@RequestMapping</code> paths strictly.
* <p>Note that paths which include a ".xxx" suffix or end with "/" already will not be
* transformed using the default suffix pattern in any case.
*/
public void setUseDefaultSuffixPattern(boolean useDefaultSuffixPattern) {
this.useDefaultSuffixPattern = useDefaultSuffixPattern;
}
/**
* Checks for presence of the {@link org.springframework.web.bind.annotation.RequestMapping}
* annotation on the handler class and on any of its methods.
*/
@Override
protected String[] determineUrlsForHandler(String beanName) {
ApplicationContext context = getApplicationContext();
Class<?> handlerType = context.getType(beanName);
RequestMapping mapping = context.findAnnotationOnBean(beanName, RequestMapping.class);
if (mapping != null) {
// @RequestMapping found at type level
this.cachedMappings.put(handlerType, mapping);
Set<String> urls = new LinkedHashSet<String>();
String[] typeLevelPatterns = mapping.value();
if (typeLevelPatterns.length > 0) {
// @RequestMapping specifies paths at type level
String[] methodLevelPatterns = determineUrlsForHandlerMethods(handlerType, true);
for (String typeLevelPattern : typeLevelPatterns) {
if (!typeLevelPattern.startsWith("/")) {
typeLevelPattern = "/" + typeLevelPattern;
}
boolean hasEmptyMethodLevelMappings = false;
for (String methodLevelPattern : methodLevelPatterns) {
if (methodLevelPattern == null) {
hasEmptyMethodLevelMappings = true;
}
else {
String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern);
addUrlsForPath(urls, combinedPattern);
}
}
if (hasEmptyMethodLevelMappings ||
org.springframework.web.servlet.mvc.Controller.class.isAssignableFrom(handlerType)) {
addUrlsForPath(urls, typeLevelPattern);
}
}
return StringUtils.toStringArray(urls);
}
else {
// actual paths specified by @RequestMapping at method level
return determineUrlsForHandlerMethods(handlerType, false);
}
}
else if (AnnotationUtils.findAnnotation(handlerType, Controller.class) != null) {
// @RequestMapping to be introspected at method level
return determineUrlsForHandlerMethods(handlerType, false);
}
else {
return null;
}
}
/**
* Derive URL mappings from the handler's method-level mappings.
* @param handlerType the handler type to introspect
* @param hasTypeLevelMapping whether the method-level mappings are nested
* within a type-level mapping
* @return the array of mapped URLs
*/
protected String[] determineUrlsForHandlerMethods(Class<?> handlerType, final boolean hasTypeLevelMapping) {
String[] subclassResult = determineUrlsForHandlerMethods(handlerType);
if (subclassResult != null) {
return subclassResult;
}
final Set<String> urls = new LinkedHashSet<String>();
Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
handlerTypes.add(handlerType);
handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces()));
for (Class<?> currentHandlerType : handlerTypes) {
ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) {
RequestMapping mapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (mapping != null) {
String[] mappedPatterns = mapping.value();
if (mappedPatterns.length > 0) {
for (String mappedPattern : mappedPatterns) {
if (!hasTypeLevelMapping && !mappedPattern.startsWith("/")) {
mappedPattern = "/" + mappedPattern;
}
addUrlsForPath(urls, mappedPattern);
}
}
else if (hasTypeLevelMapping) {
// empty method-level RequestMapping
urls.add(null);
}
}
}
}, ReflectionUtils.USER_DECLARED_METHODS);
}
return StringUtils.toStringArray(urls);
}
/**
* Derive URL mappings from the handler's method-level mappings.
* @param handlerType the handler type to introspect
* @return the array of mapped URLs
*/
protected String[] determineUrlsForHandlerMethods(Class<?> handlerType) {
return null;
}
/**
* Add URLs and/or URL patterns for the given path.
* @param urls the Set of URLs for the current bean
* @param path the currently introspected path
*/
protected void addUrlsForPath(Set<String> urls, String path) {
urls.add(path);
if (this.useDefaultSuffixPattern && path.indexOf('.') == -1 && !path.endsWith("/")) {
urls.add(path + ".*");
urls.add(path + "/");
}
}
/**
* Validate the given annotated handler against the current request.
* @see #validateMapping
*/
@Override
protected void validateHandler(Object handler, HttpServletRequest request) throws Exception {
RequestMapping mapping = this.cachedMappings.get(handler.getClass());
if (mapping == null) {
mapping = AnnotationUtils.findAnnotation(handler.getClass(), RequestMapping.class);
}
if (mapping != null) {
validateMapping(mapping, request);
}
}
/**
* Validate the given type-level mapping metadata against the current request,
* checking HTTP request method and parameter conditions.
* @param mapping the mapping metadata to validate
* @param request current HTTP request
* @throws Exception if validation failed
*/
protected void validateMapping(RequestMapping mapping, HttpServletRequest request) throws Exception {
RequestMethod[] mappedMethods = mapping.method();
if (!ServletAnnotationMappingUtils.checkRequestMethod(mappedMethods, request)) {
String[] supportedMethods = new String[mappedMethods.length];
for (int i = 0; i < mappedMethods.length; i++) {
supportedMethods[i] = mappedMethods[i].name();
}
throw new HttpRequestMethodNotSupportedException(request.getMethod(), supportedMethods);
}
String[] mappedParams = mapping.params();
if (!ServletAnnotationMappingUtils.checkParameters(mappedParams, request)) {
throw new UnsatisfiedServletRequestParameterException(mappedParams, request.getParameterMap());
}
String[] mappedHeaders = mapping.headers();
if (!ServletAnnotationMappingUtils.checkHeaders(mappedHeaders, request)) {
throw new ServletRequestBindingException("Header conditions \"" +
StringUtils.arrayToDelimitedString(mappedHeaders, ", ") +
"\" not met for actual request");
}
}
@Override
protected boolean supportsTypeLevelMappings() {
return true;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.annotation;
import java.lang.reflect.Method;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.servlet.ModelAndView;
/**
* SPI for resolving custom return values from a specific handler method. Typically implemented to detect special return
* types, resolving well-known result values for them.
*
* <p>A typical implementation could look like as follows:
*
* <pre class="code">
* public class MyModelAndViewResolver implements ModelAndViewResolver {
*
* public ModelAndView resolveModelAndView(Method handlerMethod,
* Class handlerType,
* Object returnValue,
* ExtendedModelMap implicitModel,
* NativeWebRequest webRequest) {
* if (returnValue instanceof MySpecialRetVal.class)) {
* return new MySpecialRetVal(returnValue);
* }
* return UNRESOLVED;
* }
* }</pre>
*
* @author Arjen Poutsma
* @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#setCustomModelAndViewResolvers
* @see org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter#setCustomModelAndViewResolvers
* @since 3.0
*/
public interface ModelAndViewResolver {
/** Marker to be returned when the resolver does not know how to handle the given method parameter. */
ModelAndView UNRESOLVED = new ModelAndView();
ModelAndView resolveModelAndView(Method handlerMethod,
Class handlerType,
Object returnValue,
ExtendedModelMap implicitModel,
NativeWebRequest webRequest);
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.annotation;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
/**
* Implementation of the {@link org.springframework.web.servlet.HandlerExceptionResolver HandlerExceptionResolver}
* interface that uses the {@link ResponseStatus @ResponseStatus} annotation to map exceptions to HTTP status codes.
*
* <p>This exception resolver is enabled by default in the {@link org.springframework.web.servlet.DispatcherServlet}.
*
* @author Arjen Poutsma
* @since 3.0
*/
public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver {
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
ResponseStatus responseStatus = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);
if (responseStatus != null) {
try {
return resolveResponseStatus(responseStatus, request, response, handler, ex);
}
catch (Exception resolveEx) {
logger.warn("Handling of @ResponseStatus resulted in Exception", resolveEx);
}
}
return null;
}
/**
* Template method that handles {@link ResponseStatus @ResponseStatus} annotation. <p>Default implementation send a
* response error using {@link HttpServletResponse#sendError(int)}, or {@link HttpServletResponse#sendError(int,
* String)} if the annotation has a {@linkplain ResponseStatus#reason() reason}. Returns an empty ModelAndView.
* @param responseStatus the annotation
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the time of the exception
* (for example, if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to, or <code>null</code> for default processing
*/
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) throws Exception {
int statusCode = responseStatus.value().value();
String reason = responseStatus.reason();
if (!StringUtils.hasLength(reason)) {
response.sendError(statusCode);
}
else {
response.sendError(statusCode, reason);
}
return new ModelAndView();
}
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.annotation;
import java.util.Iterator;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.util.WebUtils;
/**
* Helper class for annotation-based request mapping.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
* @since 2.5.2
*/
abstract class ServletAnnotationMappingUtils {
/**
* Check whether the given request matches the specified request methods.
* @param methods the HTTP request methods to check against
* @param request the current HTTP request to check
*/
public static boolean checkRequestMethod(RequestMethod[] methods, HttpServletRequest request) {
if (ObjectUtils.isEmpty(methods)) {
return true;
}
for (RequestMethod method : methods) {
if (method.name().equals(request.getMethod())) {
return true;
}
}
return false;
}
/**
* Check whether the given request matches the specified parameter conditions.
* @param params the parameter conditions, following
* {@link org.springframework.web.bind.annotation.RequestMapping#params() RequestMapping.#params()}
* @param request the current HTTP request to check
*/
public static boolean checkParameters(String[] params, HttpServletRequest request) {
if (!ObjectUtils.isEmpty(params)) {
for (String param : params) {
int separator = param.indexOf('=');
if (separator == -1) {
if (param.startsWith("!")) {
if (WebUtils.hasSubmitParameter(request, param.substring(1))) {
return false;
}
}
else if (!WebUtils.hasSubmitParameter(request, param)) {
return false;
}
}
else {
boolean negated = separator > 0 && param.charAt(separator - 1) == '!';
String key = !negated ? param.substring(0, separator) : param.substring(0, separator - 1);
String value = param.substring(separator + 1);
boolean match = value.equals(request.getParameter(key));
if (negated) {
match = !match;
}
if (!match) {
return false;
}
}
}
}
return true;
}
/**
* Check whether the given request matches the specified header conditions.
* @param headers the header conditions, following
* {@link org.springframework.web.bind.annotation.RequestMapping#headers() RequestMapping.headers()}
* @param request the current HTTP request to check
*/
public static boolean checkHeaders(String[] headers, HttpServletRequest request) {
if (!ObjectUtils.isEmpty(headers)) {
for (String header : headers) {
int separator = header.indexOf('=');
if (separator == -1) {
if (header.startsWith("!")) {
if (request.getHeader(header.substring(1)) != null) {
return false;
}
}
else if (request.getHeader(header) == null) {
return false;
}
}
else {
boolean negated = (separator > 0 && header.charAt(separator - 1) == '!');
String key = !negated ? header.substring(0, separator) : header.substring(0, separator - 1);
String value = header.substring(separator + 1);
if (isMediaTypeHeader(key)) {
List<MediaType> requestMediaTypes = MediaType.parseMediaTypes(request.getHeader(key));
List<MediaType> valueMediaTypes = MediaType.parseMediaTypes(value);
boolean found = false;
for (Iterator<MediaType> valIter = valueMediaTypes.iterator(); valIter.hasNext() && !found;) {
MediaType valueMediaType = valIter.next();
for (Iterator<MediaType> reqIter = requestMediaTypes.iterator();
reqIter.hasNext() && !found;) {
MediaType requestMediaType = reqIter.next();
if (valueMediaType.includes(requestMediaType)) {
found = true;
}
}
}
if (negated) {
found = !found;
}
if (!found) {
return false;
}
}
else {
boolean match = value.equals(request.getHeader(key));
if (negated) {
match = !match;
}
if (!match) {
return false;
}
}
}
}
}
return true;
}
private static boolean isMediaTypeHeader(String headerName) {
return "Accept".equalsIgnoreCase(headerName) || "Content-Type".equalsIgnoreCase(headerName);
}
}

View File

@@ -0,0 +1,8 @@
/**
*
* Support package for annotation-based Servlet MVC controllers.
*
*/
package org.springframework.web.servlet.mvc.annotation;

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.condition;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Supports media type expressions as described in:
* {@link RequestMapping#consumes()} and {@link RequestMapping#produces()}.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
*/
abstract class AbstractMediaTypeExpression implements Comparable<AbstractMediaTypeExpression>, MediaTypeExpression {
private final MediaType mediaType;
private final boolean isNegated;
AbstractMediaTypeExpression(String expression) {
if (expression.startsWith("!")) {
isNegated = true;
expression = expression.substring(1);
}
else {
isNegated = false;
}
this.mediaType = MediaType.parseMediaType(expression);
}
AbstractMediaTypeExpression(MediaType mediaType, boolean negated) {
this.mediaType = mediaType;
isNegated = negated;
}
public MediaType getMediaType() {
return mediaType;
}
public boolean isNegated() {
return isNegated;
}
public final boolean match(HttpServletRequest request) {
boolean match = matchMediaType(request);
return !isNegated ? match : !match;
}
protected abstract boolean matchMediaType(HttpServletRequest request);
public int compareTo(AbstractMediaTypeExpression other) {
return MediaType.SPECIFICITY_COMPARATOR.compare(this.getMediaType(), other.getMediaType());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null && getClass().equals(obj.getClass())) {
AbstractMediaTypeExpression other = (AbstractMediaTypeExpression) obj;
return (this.mediaType.equals(other.mediaType)) && (this.isNegated == other.isNegated);
}
return false;
}
@Override
public int hashCode() {
return mediaType.hashCode();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (isNegated) {
builder.append('!');
}
builder.append(mediaType.toString());
return builder.toString();
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.condition;
import javax.servlet.http.HttpServletRequest;
/**
* Supports "name=value" style expressions as described in:
* {@link org.springframework.web.bind.annotation.RequestMapping#params()} and
* {@link org.springframework.web.bind.annotation.RequestMapping#headers()}.
*
* @author Rossen Stoyanchev
* @author Arjen Poutsma
* @since 3.1
*/
abstract class AbstractNameValueExpression<T> implements NameValueExpression<T> {
protected final String name;
protected final T value;
protected final boolean isNegated;
AbstractNameValueExpression(String expression) {
int separator = expression.indexOf('=');
if (separator == -1) {
this.isNegated = expression.startsWith("!");
this.name = isNegated ? expression.substring(1) : expression;
this.value = null;
}
else {
this.isNegated = (separator > 0) && (expression.charAt(separator - 1) == '!');
this.name = isNegated ? expression.substring(0, separator - 1) : expression.substring(0, separator);
this.value = parseValue(expression.substring(separator + 1));
}
}
public String getName() {
return this.name;
}
public T getValue() {
return this.value;
}
public boolean isNegated() {
return this.isNegated;
}
protected abstract T parseValue(String valueExpression);
public final boolean match(HttpServletRequest request) {
boolean isMatch;
if (this.value != null) {
isMatch = matchValue(request);
}
else {
isMatch = matchName(request);
}
return isNegated ? !isMatch : isMatch;
}
protected abstract boolean matchName(HttpServletRequest request);
protected abstract boolean matchValue(HttpServletRequest request);
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null && obj instanceof AbstractNameValueExpression) {
AbstractNameValueExpression<?> other = (AbstractNameValueExpression<?>) obj;
return ((this.name.equalsIgnoreCase(other.name)) &&
(this.value != null ? this.value.equals(other.value) : other.value == null) &&
this.isNegated == other.isNegated);
}
return false;
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + (value != null ? value.hashCode() : 0);
result = 31 * result + (isNegated ? 1 : 0);
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (value != null) {
builder.append(name);
if (isNegated) {
builder.append('!');
}
builder.append('=');
builder.append(value);
}
else {
if (isNegated) {
builder.append('!');
}
builder.append(name);
}
return builder.toString();
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.condition;
import java.util.Collection;
import java.util.Iterator;
/**
* A base class for {@link RequestCondition} types providing implementations of
* {@link #equals(Object)}, {@link #hashCode()}, and {@link #toString()}.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public abstract class AbstractRequestCondition<T extends AbstractRequestCondition<T>> implements RequestCondition<T> {
/**
* Return the discrete items a request condition is composed of.
* For example URL patterns, HTTP request methods, param expressions, etc.
* @return a collection of objects, never {@code null}
*/
protected abstract Collection<?> getContent();
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o != null && getClass().equals(o.getClass())) {
AbstractRequestCondition<?> other = (AbstractRequestCondition<?>) o;
return getContent().equals(other.getContent());
}
return false;
}
@Override
public int hashCode() {
return getContent().hashCode();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("[");
for (Iterator<?> iterator = getContent().iterator(); iterator.hasNext();) {
Object expression = iterator.next();
builder.append(expression.toString());
if (iterator.hasNext()) {
builder.append(getToStringInfix());
}
}
builder.append("]");
return builder.toString();
}
/**
* The notation to use when printing discrete items of content.
* For example " || " for URL patterns or " && " for param expressions.
*/
protected abstract String getToStringInfix();
}

View File

@@ -0,0 +1,221 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.condition;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression;
/**
* A logical disjunction (' || ') request condition to match a request's
* 'Content-Type' header to a list of media type expressions. Two kinds of
* media type expressions are supported, which are described in
* {@link RequestMapping#consumes()} and {@link RequestMapping#headers()}
* where the header name is 'Content-Type'. Regardless of which syntax is
* used, the semantics are the same.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
*/
public final class ConsumesRequestCondition extends AbstractRequestCondition<ConsumesRequestCondition> {
private final List<ConsumeMediaTypeExpression> expressions;
/**
* Creates a new instance from 0 or more "consumes" expressions.
* @param consumes expressions with the syntax described in
* {@link RequestMapping#consumes()}; if 0 expressions are provided,
* the condition will match to every request.
*/
public ConsumesRequestCondition(String... consumes) {
this(consumes, null);
}
/**
* Creates a new instance with "consumes" and "header" expressions.
* "Header" expressions where the header name is not 'Content-Type' or have
* no header value defined are ignored. If 0 expressions are provided in
* total, the condition will match to every request
* @param consumes as described in {@link RequestMapping#consumes()}
* @param headers as described in {@link RequestMapping#headers()}
*/
public ConsumesRequestCondition(String[] consumes, String[] headers) {
this(parseExpressions(consumes, headers));
}
/**
* Private constructor accepting parsed media type expressions.
*/
private ConsumesRequestCondition(Collection<ConsumeMediaTypeExpression> expressions) {
this.expressions = new ArrayList<ConsumeMediaTypeExpression>(expressions);
Collections.sort(this.expressions);
}
private static Set<ConsumeMediaTypeExpression> parseExpressions(String[] consumes, String[] headers) {
Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<ConsumeMediaTypeExpression>();
if (headers != null) {
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
if ("Content-Type".equalsIgnoreCase(expr.name)) {
for (MediaType mediaType : MediaType.parseMediaTypes(expr.value)) {
result.add(new ConsumeMediaTypeExpression(mediaType, expr.isNegated));
}
}
}
}
if (consumes != null) {
for (String consume : consumes) {
result.add(new ConsumeMediaTypeExpression(consume));
}
}
return result;
}
/**
* Return the contained MediaType expressions.
*/
public Set<MediaTypeExpression> getExpressions() {
return new LinkedHashSet<MediaTypeExpression>(this.expressions);
}
/**
* Returns the media types for this condition excluding negated expressions.
*/
public Set<MediaType> getConsumableMediaTypes() {
Set<MediaType> result = new LinkedHashSet<MediaType>();
for (ConsumeMediaTypeExpression expression : this.expressions) {
if (!expression.isNegated()) {
result.add(expression.getMediaType());
}
}
return result;
}
/**
* Whether the condition has any media type expressions.
*/
public boolean isEmpty() {
return this.expressions.isEmpty();
}
@Override
protected Collection<ConsumeMediaTypeExpression> getContent() {
return this.expressions;
}
@Override
protected String getToStringInfix() {
return " || ";
}
/**
* Returns the "other" instance if it has any expressions; returns "this"
* instance otherwise. Practically that means a method-level "consumes"
* overrides a type-level "consumes" condition.
*/
public ConsumesRequestCondition combine(ConsumesRequestCondition other) {
return !other.expressions.isEmpty() ? other : this;
}
/**
* Checks if any of the contained media type expressions match the given
* request 'Content-Type' header and returns an instance that is guaranteed
* to contain matching expressions only. The match is performed via
* {@link MediaType#includes(MediaType)}.
*
* @param request the current request
*
* @return the same instance if the condition contains no expressions;
* or a new condition with matching expressions only;
* or {@code null} if no expressions match.
*/
public ConsumesRequestCondition getMatchingCondition(HttpServletRequest request) {
if (isEmpty()) {
return this;
}
Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<ConsumeMediaTypeExpression>(expressions);
for (Iterator<ConsumeMediaTypeExpression> iterator = result.iterator(); iterator.hasNext();) {
ConsumeMediaTypeExpression expression = iterator.next();
if (!expression.match(request)) {
iterator.remove();
}
}
return (result.isEmpty()) ? null : new ConsumesRequestCondition(result);
}
/**
* Returns:
* <ul>
* <li>0 if the two conditions have the same number of expressions
* <li>Less than 0 if "this" has more or more specific media type expressions
* <li>Greater than 0 if "other" has more or more specific media type expressions
* </ul>
*
* <p>It is assumed that both instances have been obtained via
* {@link #getMatchingCondition(HttpServletRequest)} and each instance contains
* the matching consumable media type expression only or is otherwise empty.
*/
public int compareTo(ConsumesRequestCondition other, HttpServletRequest request) {
if (this.expressions.isEmpty() && other.expressions.isEmpty()) {
return 0;
}
else if (this.expressions.isEmpty()) {
return 1;
}
else if (other.expressions.isEmpty()) {
return -1;
}
else {
return this.expressions.get(0).compareTo(other.expressions.get(0));
}
}
/**
* Parses and matches a single media type expression to a request's 'Content-Type' header.
*/
static class ConsumeMediaTypeExpression extends AbstractMediaTypeExpression {
ConsumeMediaTypeExpression(String expression) {
super(expression);
}
ConsumeMediaTypeExpression(MediaType mediaType, boolean negated) {
super(mediaType, negated);
}
@Override
protected boolean matchMediaType(HttpServletRequest request) {
MediaType contentType = StringUtils.hasLength(request.getContentType()) ?
MediaType.parseMediaType(request.getContentType()) :
MediaType.APPLICATION_OCTET_STREAM ;
return getMediaType().includes(contentType);
}
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.condition;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* A logical conjunction (' && ') request condition that matches a request against
* a set of header expressions with syntax defined in {@link RequestMapping#headers()}.
*
* <p>Expressions passed to the constructor with header names 'Accept' or
* 'Content-Type' are ignored. See {@link ConsumesRequestCondition} and
* {@link ProducesRequestCondition} for those.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
*/
public final class HeadersRequestCondition extends AbstractRequestCondition<HeadersRequestCondition> {
private final Set<HeaderExpression> expressions;
/**
* Create a new instance from the given header expressions. Expressions with
* header names 'Accept' or 'Content-Type' are ignored. See {@link ConsumesRequestCondition}
* and {@link ProducesRequestCondition} for those.
* @param headers media type expressions with syntax defined in {@link RequestMapping#headers()};
* if 0, the condition will match to every request.
*/
public HeadersRequestCondition(String... headers) {
this(parseExpressions(headers));
}
private HeadersRequestCondition(Collection<HeaderExpression> conditions) {
this.expressions = Collections.unmodifiableSet(new LinkedHashSet<HeaderExpression>(conditions));
}
private static Collection<HeaderExpression> parseExpressions(String... headers) {
Set<HeaderExpression> expressions = new LinkedHashSet<HeaderExpression>();
if (headers != null) {
for (String header : headers) {
HeaderExpression expr = new HeaderExpression(header);
if ("Accept".equalsIgnoreCase(expr.name) || "Content-Type".equalsIgnoreCase(expr.name)) {
continue;
}
expressions.add(expr);
}
}
return expressions;
}
/**
* Return the contained request header expressions.
*/
public Set<NameValueExpression<String>> getExpressions() {
return new LinkedHashSet<NameValueExpression<String>>(this.expressions);
}
@Override
protected Collection<HeaderExpression> getContent() {
return this.expressions;
}
@Override
protected String getToStringInfix() {
return " && ";
}
/**
* Returns a new instance with the union of the header expressions
* from "this" and the "other" instance.
*/
public HeadersRequestCondition combine(HeadersRequestCondition other) {
Set<HeaderExpression> set = new LinkedHashSet<HeaderExpression>(this.expressions);
set.addAll(other.expressions);
return new HeadersRequestCondition(set);
}
/**
* Returns "this" instance if the request matches all expressions;
* or {@code null} otherwise.
*/
public HeadersRequestCondition getMatchingCondition(HttpServletRequest request) {
for (HeaderExpression expression : expressions) {
if (!expression.match(request)) {
return null;
}
}
return this;
}
/**
* Returns:
* <ul>
* <li>0 if the two conditions have the same number of header expressions
* <li>Less than 0 if "this" instance has more header expressions
* <li>Greater than 0 if the "other" instance has more header expressions
* </ul>
*
* <p>It is assumed that both instances have been obtained via
* {@link #getMatchingCondition(HttpServletRequest)} and each instance
* contains the matching header expression only or is otherwise empty.
*/
public int compareTo(HeadersRequestCondition other, HttpServletRequest request) {
return other.expressions.size() - this.expressions.size();
}
/**
* Parses and matches a single header expression to a request.
*/
static class HeaderExpression extends AbstractNameValueExpression<String> {
public HeaderExpression(String expression) {
super(expression);
}
@Override
protected String parseValue(String valueExpression) {
return valueExpression;
}
@Override
protected boolean matchName(HttpServletRequest request) {
return request.getHeader(name) != null;
}
@Override
protected boolean matchValue(HttpServletRequest request) {
return value.equals(request.getHeader(name));
}
@Override
public int hashCode() {
int result = name.toLowerCase().hashCode();
result = 31 * result + (value != null ? value.hashCode() : 0);
result = 31 * result + (isNegated ? 1 : 0);
return result;
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.condition;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* A contract for media type expressions (e.g. "text/plain", "!text/plain") as
* defined in the {@code @RequestMapping} annotation for "consumes" and
* "produces" conditions.
*
* @author Rossen Stoyanchev
* @since 3.1
*
* @see RequestMapping#consumes()
* @see RequestMapping#produces()
*/
public interface MediaTypeExpression {
MediaType getMediaType();
boolean isNegated();
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.condition;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* A contract for {@code "name!=value"} style expression used to specify request
* parameters and request header conditions in {@code @RequestMapping}.
*
* @author Rossen Stoyanchev
* @since 3.1
*
* @see RequestMapping#params()
* @see RequestMapping#headers()
*/
public interface NameValueExpression<T> {
String getName();
T getValue();
boolean isNegated();
}

Some files were not shown because too many files have changed in this diff Show More