From af47a8b79b0fa07ff2c038197a07c09b745802ac Mon Sep 17 00:00:00 2001
From: Arjen Poutsma Note that BindTag does not use this class to avoid unnecessary
+ * creation of ObjectError instances. It just escapes the messages and values
+ * that get copied into the respective BindStatus instance.
+ *
+ * @author Juergen Hoeller
+ * @since 01.03.2003
+ * @see org.springframework.web.servlet.support.RequestContext#getErrors
+ * @see org.springframework.web.servlet.tags.BindTag
+ */
+public class EscapedErrors implements Errors {
+
+ private final Errors source;
+
+
+ /**
+ * Create a new EscapedErrors instance for the given source instance.
+ */
+ public EscapedErrors(Errors source) {
+ if (source == null) {
+ throw new IllegalArgumentException("Cannot wrap a null instance");
+ }
+ this.source = source;
+ }
+
+ public Errors getSource() {
+ return this.source;
+ }
+
+
+ public String getObjectName() {
+ return this.source.getObjectName();
+ }
+
+ public void setNestedPath(String nestedPath) {
+ this.source.setNestedPath(nestedPath);
+ }
+
+ public String getNestedPath() {
+ return this.source.getNestedPath();
+ }
+
+ public void pushNestedPath(String subPath) {
+ this.source.pushNestedPath(subPath);
+ }
+
+ public void popNestedPath() throws IllegalStateException {
+ this.source.popNestedPath();
+ }
+
+
+ public void reject(String errorCode) {
+ this.source.reject(errorCode);
+ }
+
+ public void reject(String errorCode, String defaultMessage) {
+ this.source.reject(errorCode, defaultMessage);
+ }
+
+ public void reject(String errorCode, Object[] errorArgs, String defaultMessage) {
+ this.source.reject(errorCode, errorArgs, defaultMessage);
+ }
+
+ public void rejectValue(String field, String errorCode) {
+ this.source.rejectValue(field, errorCode);
+ }
+
+ public void rejectValue(String field, String errorCode, String defaultMessage) {
+ this.source.rejectValue(field, errorCode, defaultMessage);
+ }
+
+ public void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) {
+ this.source.rejectValue(field, errorCode, errorArgs, defaultMessage);
+ }
+
+ public void addAllErrors(Errors errors) {
+ this.source.addAllErrors(errors);
+ }
+
+
+ public boolean hasErrors() {
+ return this.source.hasErrors();
+ }
+
+ public int getErrorCount() {
+ return this.source.getErrorCount();
+ }
+
+ public List getAllErrors() {
+ return escapeObjectErrors(this.source.getAllErrors());
+ }
+
+ public boolean hasGlobalErrors() {
+ return this.source.hasGlobalErrors();
+ }
+
+ public int getGlobalErrorCount() {
+ return this.source.getGlobalErrorCount();
+ }
+
+ public List getGlobalErrors() {
+ return escapeObjectErrors(this.source.getGlobalErrors());
+ }
+
+ public ObjectError getGlobalError() {
+ return escapeObjectError(this.source.getGlobalError());
+ }
+
+ public boolean hasFieldErrors() {
+ return this.source.hasFieldErrors();
+ }
+
+ public int getFieldErrorCount() {
+ return this.source.getFieldErrorCount();
+ }
+
+ public List getFieldErrors() {
+ return this.source.getFieldErrors();
+ }
+
+ public FieldError getFieldError() {
+ return this.source.getFieldError();
+ }
+
+ public boolean hasFieldErrors(String field) {
+ return this.source.hasFieldErrors(field);
+ }
+
+ public int getFieldErrorCount(String field) {
+ return this.source.getFieldErrorCount(field);
+ }
+
+ public List getFieldErrors(String field) {
+ return escapeObjectErrors(this.source.getFieldErrors(field));
+ }
+
+ public FieldError getFieldError(String field) {
+ return (FieldError) escapeObjectError(this.source.getFieldError(field));
+ }
+
+ public Object getFieldValue(String field) {
+ Object value = this.source.getFieldValue(field);
+ return (value instanceof String ? HtmlUtils.htmlEscape((String) value) : value);
+ }
+
+ public Class getFieldType(String field) {
+ return this.source.getFieldType(field);
+ }
+
+ private ObjectError escapeObjectError(ObjectError source) {
+ if (source == null) {
+ return null;
+ }
+ if (source instanceof FieldError) {
+ FieldError fieldError = (FieldError) source;
+ Object value = fieldError.getRejectedValue();
+ if (value instanceof String) {
+ value = HtmlUtils.htmlEscape((String) value);
+ }
+ return new FieldError(
+ fieldError.getObjectName(), fieldError.getField(), value,
+ fieldError.isBindingFailure(), fieldError.getCodes(),
+ fieldError.getArguments(), HtmlUtils.htmlEscape(fieldError.getDefaultMessage()));
+ }
+ return new ObjectError(
+ source.getObjectName(), source.getCodes(), source.getArguments(),
+ HtmlUtils.htmlEscape(source.getDefaultMessage()));
+ }
+
+ private List escapeObjectErrors(List source) {
+ List escaped = new ArrayList(source.size());
+ for (Iterator it = source.iterator(); it.hasNext();) {
+ ObjectError objectError = (ObjectError)it.next();
+ escaped.add(escapeObjectError(objectError));
+ }
+ return escaped;
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java
new file mode 100644
index 00000000000..6c1f68bd333
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java
@@ -0,0 +1,62 @@
+/*
+ * 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.bind;
+
+/**
+ * {@link ServletRequestBindingException} subclass that indicates a missing parameter.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0.2
+ */
+public class MissingServletRequestParameterException extends ServletRequestBindingException {
+
+ private String parameterName;
+
+ private String parameterType;
+
+
+ /**
+ * Constructor for MissingServletRequestParameterException.
+ * @param parameterName the name of the missing parameter
+ * @param parameterType the expected type of the missing parameter
+ */
+ public MissingServletRequestParameterException(String parameterName, String parameterType) {
+ super("");
+ this.parameterName = parameterName;
+ this.parameterType = parameterType;
+ }
+
+
+ public String getMessage() {
+ return "Required " + this.parameterType + " parameter '" + this.parameterName + "' is not present";
+ }
+
+ /**
+ * Return the name of the offending parameter.
+ */
+ public String getParameterName() {
+ return this.parameterName;
+ }
+
+ /**
+ * Return the expected type of the offending parameter.
+ */
+ public String getParameterType() {
+ return this.parameterType;
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/RequestUtils.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/RequestUtils.java
new file mode 100644
index 00000000000..a76ee8abde1
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/RequestUtils.java
@@ -0,0 +1,491 @@
+/*
+ * 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.bind;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+
+/**
+ * Parameter extraction methods, for an approach distinct from data binding,
+ * in which parameters of specific types are required.
+ *
+ * This approach is very useful for simple submissions, where binding
+ * request parameters to a command object would be overkill.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Keith Donald
+ * @deprecated as of Spring 2.0: use ServletRequestUtils instead
+ * @see ServletRequestUtils
+ */
+public abstract class RequestUtils {
+
+ /**
+ * Throw a ServletException if the given HTTP request method should be rejected.
+ * @param request request to check
+ * @param method method (such as "GET") which should be rejected
+ * @throws ServletException if the given HTTP request is rejected
+ */
+ public static void rejectRequestMethod(HttpServletRequest request, String method) throws ServletException {
+ if (request.getMethod().equals(method)) {
+ throw new HttpRequestMethodNotSupportedException(method);
+ }
+ }
+
+
+ /**
+ * Get an Integer parameter, or Accepts "true", "on", "yes" (any case) and "1" as values for true;
+ * treats every other non-empty value as false (i.e. parses leniently).
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @return the Boolean value, or Accepts "true", "on", "yes" (any case) and "1" as values for true;
+ * treats every other non-empty value as false (i.e. parses leniently).
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @param defaultVal the default value to use as fallback
+ */
+ public static boolean getBooleanParameter(HttpServletRequest request, String name, boolean defaultVal) {
+ if (request.getParameter(name) == null) {
+ return defaultVal;
+ }
+ try {
+ return getRequiredBooleanParameter(request, name);
+ }
+ catch (ServletRequestBindingException ex) {
+ return defaultVal;
+ }
+ }
+
+ /**
+ * Get an array of boolean parameters, return an empty array if not found.
+ * Accepts "true", "on", "yes" (any case) and "1" as values for true;
+ * treats every other non-empty value as false (i.e. parses leniently).
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ */
+ public static boolean[] getBooleanParameters(HttpServletRequest request, String name) {
+ try {
+ return getRequiredBooleanParameters(request, name);
+ }
+ catch (ServletRequestBindingException ex) {
+ return new boolean[0];
+ }
+ }
+
+ /**
+ * Get a boolean parameter, throwing an exception if it isn't found
+ * or isn't a boolean.
+ * Accepts "true", "on", "yes" (any case) and "1" as values for true;
+ * treats every other non-empty value as false (i.e. parses leniently).
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static boolean getRequiredBooleanParameter(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ boolean value = ServletRequestUtils.getRequiredBooleanParameter(request, name);
+ if (!value && "".equals(request.getParameter(name))) {
+ throw new ServletRequestBindingException(
+ "Required boolean parameter '" + name + "' contains no value");
+ }
+ return value;
+ }
+
+ /**
+ * Get an array of boolean parameters, throwing an exception if not found
+ * or one isn't a boolean.
+ * Accepts "true", "on", "yes" (any case) and "1" as values for true;
+ * treats every other non-empty value as false (i.e. parses leniently).
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static boolean[] getRequiredBooleanParameters(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ boolean[] values = ServletRequestUtils.getRequiredBooleanParameters(request, name);
+ for (int i = 0; i < values.length; i++) {
+ if (!values[i] && "".equals(request.getParameterValues(name)[i])) {
+ throw new ServletRequestBindingException(
+ "Required boolean parameter '" + name + "' contains no value");
+ }
+ }
+ return values;
+ }
+
+
+ /**
+ * Get a String parameter, or Extends ServletException for convenient throwing in any Servlet resource
+ * (such as a Filter), and NestedServletException for proper root cause handling
+ * (as the plain ServletException doesn't expose its root cause at all).
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ */
+public class ServletRequestBindingException extends NestedServletException {
+
+ /**
+ * Constructor for ServletRequestBindingException.
+ * @param msg the detail message
+ */
+ public ServletRequestBindingException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructor for ServletRequestBindingException.
+ * @param msg the detail message
+ * @param cause the root cause
+ */
+ public ServletRequestBindingException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java
new file mode 100644
index 00000000000..5bc4cd7b622
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java
@@ -0,0 +1,127 @@
+/*
+ * 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.bind;
+
+import javax.servlet.ServletRequest;
+
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.validation.BindException;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+
+/**
+ * Special {@link org.springframework.validation.DataBinder} to perform data binding
+ * from servlet request parameters to JavaBeans, including support for multipart files.
+ *
+ * See the DataBinder/WebDataBinder superclasses for customization options,
+ * which include specifying allowed/required fields, and registering custom
+ * property editors.
+ *
+ * Used by Spring Web MVC's BaseCommandController and MultiActionController.
+ * Note that BaseCommandController and its subclasses allow for easy customization
+ * of the binder instances that they use through overriding Can also be used for manual data binding in custom web controllers:
+ * for example, in a plain Controller implementation or in a MultiActionController
+ * handler method. Simply instantiate a ServletRequestDataBinder for each binding
+ * process, and invoke This call can create field errors, representing basic binding
+ * errors like a required field (code "required"), or type mismatch
+ * between value and bean property (code "typeMismatch").
+ * Multipart files are bound via their parameter name, just like normal
+ * HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property,
+ * invoking a "setUploadedFile" setter method.
+ * The type of the target property for a multipart file can be MultipartFile,
+ * byte[], or String. The latter two receive the contents of the uploaded file;
+ * all metadata like original file name, content type, etc are lost in those cases.
+ * @param request request with parameters to bind (can be multipart)
+ * @see org.springframework.web.multipart.MultipartHttpServletRequest
+ * @see org.springframework.web.multipart.MultipartFile
+ * @see #bindMultipartFiles
+ * @see #bind(org.springframework.beans.PropertyValues)
+ */
+ public void bind(ServletRequest request) {
+ MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
+ if (request instanceof MultipartHttpServletRequest) {
+ MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
+ bindMultipartFiles(multipartRequest.getFileMap(), mpvs);
+ }
+ doBind(mpvs);
+ }
+
+ /**
+ * Treats errors as fatal.
+ * Use this method only if it's an error if the input isn't valid.
+ * This might be appropriate if all input is from dropdowns, for example.
+ * @throws ServletRequestBindingException subclass of ServletException on any binding problem
+ */
+ public void closeNoCatch() throws ServletRequestBindingException {
+ if (getBindingResult().hasErrors()) {
+ throw new ServletRequestBindingException(
+ "Errors binding onto object '" + getBindingResult().getObjectName() + "'",
+ new BindException(getBindingResult()));
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/ServletRequestParameterPropertyValues.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/ServletRequestParameterPropertyValues.java
new file mode 100644
index 00000000000..a5e36bb89df
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/ServletRequestParameterPropertyValues.java
@@ -0,0 +1,80 @@
+/*
+ * 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.bind;
+
+import javax.servlet.ServletRequest;
+
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.web.util.WebUtils;
+
+/**
+ * PropertyValues implementation created from parameters in a ServletRequest.
+ * Can look for all property values beginning with a certain prefix and
+ * prefix separator (default is "_").
+ *
+ * For example, with a prefix of "spring", "spring_param1" and
+ * "spring_param2" result in a Map with "param1" and "param2" as keys.
+ *
+ * This class is not immutable to be able to efficiently remove property
+ * values that should be ignored for binding.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @see org.springframework.web.util.WebUtils#getParametersStartingWith
+ */
+public class ServletRequestParameterPropertyValues extends MutablePropertyValues {
+
+ /** Default prefix separator */
+ public static final String DEFAULT_PREFIX_SEPARATOR = "_";
+
+
+ /**
+ * Create new ServletRequestPropertyValues using no prefix
+ * (and hence, no prefix separator).
+ * @param request HTTP request
+ */
+ public ServletRequestParameterPropertyValues(ServletRequest request) {
+ this(request, null, null);
+ }
+
+ /**
+ * Create new ServletRequestPropertyValues using the given prefix and
+ * the default prefix separator (the underscore character "_").
+ * @param request HTTP request
+ * @param prefix the prefix for parameters (the full prefix will
+ * consist of this plus the separator)
+ * @see #DEFAULT_PREFIX_SEPARATOR
+ */
+ public ServletRequestParameterPropertyValues(ServletRequest request, String prefix) {
+ this(request, prefix, DEFAULT_PREFIX_SEPARATOR);
+ }
+
+ /**
+ * Create new ServletRequestPropertyValues supplying both prefix and
+ * prefix separator.
+ * @param request HTTP request
+ * @param prefix the prefix for parameters (the full prefix will
+ * consist of this plus the separator)
+ * @param prefixSeparator separator delimiting prefix (e.g. "spring")
+ * and the rest of the parameter name ("param1", "param2")
+ */
+ public ServletRequestParameterPropertyValues(ServletRequest request, String prefix, String prefixSeparator) {
+ super(WebUtils.getParametersStartingWith(
+ request, (prefix != null) ? prefix + prefixSeparator : null));
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/ServletRequestUtils.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/ServletRequestUtils.java
new file mode 100644
index 00000000000..b6e8be351eb
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/ServletRequestUtils.java
@@ -0,0 +1,697 @@
+/*
+ * 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.bind;
+
+import javax.servlet.ServletRequest;
+
+/**
+ * Parameter extraction methods, for an approach distinct from data binding,
+ * in which parameters of specific types are required.
+ *
+ * This approach is very useful for simple submissions, where binding
+ * request parameters to a command object would be overkill.
+ *
+ * @author Juergen Hoeller
+ * @author Keith Donald
+ * @since 2.0
+ */
+public abstract class ServletRequestUtils {
+
+ private static final IntParser INT_PARSER = new IntParser();
+
+ private static final LongParser LONG_PARSER = new LongParser();
+
+ private static final FloatParser FLOAT_PARSER = new FloatParser();
+
+ private static final DoubleParser DOUBLE_PARSER = new DoubleParser();
+
+ private static final BooleanParser BOOLEAN_PARSER = new BooleanParser();
+
+ private static final StringParser STRING_PARSER = new StringParser();
+
+
+ /**
+ * Get an Integer parameter, or Accepts "true", "on", "yes" (any case) and "1" as values for true;
+ * treats every other non-empty value as false (i.e. parses leniently).
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @return the Boolean value, or Accepts "true", "on", "yes" (any case) and "1" as values for true;
+ * treats every other non-empty value as false (i.e. parses leniently).
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @param defaultVal the default value to use as fallback
+ */
+ public static boolean getBooleanParameter(ServletRequest request, String name, boolean defaultVal) {
+ if (request.getParameter(name) == null) {
+ return defaultVal;
+ }
+ try {
+ return getRequiredBooleanParameter(request, name);
+ }
+ catch (ServletRequestBindingException ex) {
+ return defaultVal;
+ }
+ }
+
+ /**
+ * Get an array of boolean parameters, return an empty array if not found.
+ * Accepts "true", "on", "yes" (any case) and "1" as values for true;
+ * treats every other non-empty value as false (i.e. parses leniently).
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ */
+ public static boolean[] getBooleanParameters(ServletRequest request, String name) {
+ try {
+ return getRequiredBooleanParameters(request, name);
+ }
+ catch (ServletRequestBindingException ex) {
+ return new boolean[0];
+ }
+ }
+
+ /**
+ * Get a boolean parameter, throwing an exception if it isn't found
+ * or isn't a boolean.
+ * Accepts "true", "on", "yes" (any case) and "1" as values for true;
+ * treats every other non-empty value as false (i.e. parses leniently).
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static boolean getRequiredBooleanParameter(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return BOOLEAN_PARSER.parseBoolean(name, request.getParameter(name));
+ }
+
+ /**
+ * Get an array of boolean parameters, throwing an exception if not found
+ * or one isn't a boolean.
+ * Accepts "true", "on", "yes" (any case) and "1" as values for true;
+ * treats every other non-empty value as false (i.e. parses leniently).
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static boolean[] getRequiredBooleanParameters(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return BOOLEAN_PARSER.parseBooleans(name, request.getParameterValues(name));
+ }
+
+
+ /**
+ * Get a String parameter, or Includes support for field markers which address a common problem with
+ * HTML checkboxes and select options: detecting that a field was part of
+ * the form, but did not generate a request parameter because it was empty.
+ * A field marker allows to detect that state and reset the corresponding
+ * bean property accordingly.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see #registerCustomEditor
+ * @see #setAllowedFields
+ * @see #setRequiredFields
+ * @see #setFieldMarkerPrefix
+ * @see ServletRequestDataBinder
+ */
+public class WebDataBinder extends DataBinder {
+
+ /**
+ * Default prefix that field marker parameters start with, followed by the field
+ * name: e.g. "_subscribeToNewsletter" for a field "subscribeToNewsletter".
+ * Such a marker parameter indicates that the field was visible, that is,
+ * existed in the form that caused the submission. If no corresponding field
+ * value parameter was found, the field will be reset. The value of the field
+ * marker parameter does not matter in this case; an arbitrary value can be used.
+ * This is particularly useful for HTML checkboxes and select options.
+ * @see #setFieldMarkerPrefix
+ */
+ public static final String DEFAULT_FIELD_MARKER_PREFIX = "_";
+
+
+ private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX;
+
+ private boolean bindEmptyMultipartFiles = true;
+
+
+ /**
+ * Create a new WebDataBinder instance, with default object name.
+ * @param target the target object to bind onto (or Default is "_", for "_FIELD" parameters (e.g. "_subscribeToNewsletter").
+ * Set this to null if you want to turn off the empty field check completely.
+ * HTML checkboxes only send a value when they're checked, so it is not
+ * possible to detect that a formerly checked box has just been unchecked,
+ * at least not with standard HTML means.
+ * One way to address this is to look for a checkbox parameter value if
+ * you know that the checkbox has been visible in the form, resetting the
+ * checkbox if no value found. In Spring web MVC, this typically happens
+ * in a custom This auto-reset mechanism addresses this deficiency, provided
+ * that a marker parameter is sent for each checkbox field, like
+ * "_subscribeToNewsletter" for a "subscribeToNewsletter" field.
+ * As the marker parameter is sent in any case, the data binder can
+ * detect an empty field and automatically reset its value.
+ * @see #DEFAULT_FIELD_MARKER_PREFIX
+ * @see org.springframework.web.servlet.mvc.BaseCommandController#onBind
+ */
+ public void setFieldMarkerPrefix(String fieldMarkerPrefix) {
+ this.fieldMarkerPrefix = fieldMarkerPrefix;
+ }
+
+ /**
+ * Return the prefix for parameters that mark potentially empty fields.
+ */
+ public String getFieldMarkerPrefix() {
+ return this.fieldMarkerPrefix;
+ }
+
+ /**
+ * Set whether to bind empty MultipartFile parameters. Default is "true".
+ * Turn this off if you want to keep an already bound MultipartFile
+ * when the user resubmits the form without choosing a different file.
+ * Else, the already bound MultipartFile will be replaced by an empty
+ * MultipartFile holder.
+ * @see org.springframework.web.multipart.MultipartFile
+ */
+ public void setBindEmptyMultipartFiles(boolean bindEmptyMultipartFiles) {
+ this.bindEmptyMultipartFiles = bindEmptyMultipartFiles;
+ }
+
+ /**
+ * Return whether to bind empty MultipartFile parameters.
+ */
+ public boolean isBindEmptyMultipartFiles() {
+ return this.bindEmptyMultipartFiles;
+ }
+
+
+ /**
+ * This implementation performs a field marker check
+ * before delegating to the superclass binding process.
+ * @see #checkFieldMarkers
+ */
+ protected void doBind(MutablePropertyValues mpvs) {
+ checkFieldMarkers(mpvs);
+ super.doBind(mpvs);
+ }
+
+ /**
+ * Check the given property values for field markers,
+ * i.e. for fields that start with the field marker prefix.
+ * The existence of a field marker indicates that the specified
+ * field existed in the form. If the property values do not contain
+ * a corresponding field value, the field will be considered as empty
+ * and will be reset appropriately.
+ * @param mpvs the property values to be bound (can be modified)
+ * @see #getFieldMarkerPrefix
+ * @see #getEmptyValue(String, Class)
+ */
+ protected void checkFieldMarkers(MutablePropertyValues mpvs) {
+ if (getFieldMarkerPrefix() != null) {
+ String fieldMarkerPrefix = getFieldMarkerPrefix();
+ PropertyValue[] pvArray = mpvs.getPropertyValues();
+ for (int i = 0; i < pvArray.length; i++) {
+ PropertyValue pv = pvArray[i];
+ if (pv.getName().startsWith(fieldMarkerPrefix)) {
+ String field = pv.getName().substring(fieldMarkerPrefix.length());
+ if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
+ Class fieldType = getPropertyAccessor().getPropertyType(field);
+ mpvs.addPropertyValue(field, getEmptyValue(field, fieldType));
+ }
+ mpvs.removePropertyValue(pv);
+ }
+ }
+ }
+ }
+
+ /**
+ * Determine an empty value for the specified field.
+ * Default implementation returns Multipart files will only be added to the property values if they
+ * are not empty or if we're configured to bind empty multipart files too.
+ * @param multipartFiles Map of field name String to MultipartFile object
+ * @param mpvs the property values to be bound (can be modified)
+ * @see org.springframework.web.multipart.MultipartFile
+ * @see #setBindEmptyMultipartFiles
+ */
+ protected void bindMultipartFiles(Map multipartFiles, MutablePropertyValues mpvs) {
+ for (Iterator it = multipartFiles.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ String key = (String) entry.getKey();
+ MultipartFile value = (MultipartFile) entry.getValue();
+ if (isBindEmptyMultipartFiles() || !value.isEmpty()) {
+ mpvs.addPropertyValue(key, value);
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/InitBinder.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/InitBinder.java
new file mode 100644
index 00000000000..9b22b261c4f
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/InitBinder.java
@@ -0,0 +1,61 @@
+/*
+ * 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.bind.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;
+
+/**
+ * Annotation that identifies methods which initialize the
+ * {@link org.springframework.web.bind.WebDataBinder} which
+ * will be used for populating command and form object arguments
+ * of annotated handler methods.
+ *
+ * Such init-binder methods support all arguments that {@link RequestMapping}
+ * supports, except for command/form objects and corresponding validation result
+ * objects. Init-binder methods must not have a return value; they are usually
+ * declared as Typical arguments are {@link org.springframework.web.bind.WebDataBinder}
+ * in combination with {@link org.springframework.web.context.request.WebRequest}
+ * or {@link java.util.Locale}, allowing to register context-specific editors.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see org.springframework.web.bind.WebDataBinder
+ * @see org.springframework.web.context.request.WebRequest
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface InitBinder {
+
+ /**
+ * The names of command/form attributes and/or request parameters
+ * that this init-binder method is supposed to apply to.
+ * Default is to apply to all command/form attributes and all request parameters
+ * processed by the annotated handler class. Specifying model attribute names or
+ * request parameter names here restricts the init-binder method to those specific
+ * attributes/parameters, with different init-binder methods typically applying to
+ * different groups of attributes or parameters.
+ */
+ String[] value() default {};
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java
new file mode 100644
index 00000000000..dbd293ff867
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java
@@ -0,0 +1,59 @@
+/*
+ * 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.bind.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;
+
+/**
+ * Annotation that binds a method parameter or method return value
+ * to a named model attribute, exposed to a web view. Supported
+ * for {@link RequestMapping} annotated handler classes.
+ *
+ * Can be used to expose command objects to a web view, using
+ * specific attribute names, through annotating corresponding
+ * parameters of a {@link RequestMapping} annotated handler method).
+ *
+ * Can also be used to expose reference data to a web view
+ * through annotating accessor methods in a controller class which
+ * is based on {@link RequestMapping} annotated handler methods,
+ * with such accessor methods allowed to have any arguments that
+ * {@link RequestMapping} supports for handler methods, returning
+ * the model attribute value to expose.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+@Target({ElementType.PARAMETER, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface ModelAttribute {
+
+ /**
+ * The name of the model attribute to bind to.
+ * The default model attribute name is inferred from the declared
+ * attribute type (i.e. the method parameter type or method return type),
+ * based on the non-qualified class name:
+ * e.g. "orderAddress" for class "mypackage.OrderAddress",
+ * or "orderAddressList" for "List<mypackage.OrderAddress>".
+ */
+ String value() default "";
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
new file mode 100644
index 00000000000..75ed5560eeb
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
@@ -0,0 +1,230 @@
+/*
+ * 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.bind.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;
+
+/**
+ * Annotation for mapping web requests onto specific handler classes and/or
+ * handler methods. Provides consistent style between Servlet and Portlet
+ * environments, with the semantics adapting to the concrete environment.
+ *
+ * NOTE: Method-level mappings are only allowed to narrow the mapping
+ * expressed at the class level (if any). HTTP paths / portlet modes need to
+ * uniquely map onto specific handler beans, with any given path / mode 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.
+ *
+ * Handler methods which are annotated with this annotation are allowed
+ * to have very flexible signatures. They may have arguments of the following
+ * types, in arbitrary order (except for validation results, which need to
+ * follow right after the corresponding command object, if desired):
+ * The following return types are supported for handler methods:
+ * NOTE: In a Servlet environment: the path mapping URIs (e.g. "/myPath.do").
+ * Ant-style path patterns are also supported (e.g. "/myPath/*.do").
+ * At the method level, relative paths (e.g. "edit.do") are supported
+ * within the primary mapping expressed at the type level.
+ * In a Portlet environment: the mapped portlet modes
+ * (i.e. "EDIT", "VIEW", "HELP" or any custom modes).
+ * Supported at the type level as well as at the method level!
+ * When used at the type level, all method-level mappings inherit
+ * this primary mapping, narrowing it for a specific handler method.
+ * In case of Servlet-based handler methods, the method names are
+ * taken into account for narrowing if no path was specified explicitly,
+ * according to the specified
+ * {@link org.springframework.web.servlet.mvc.multiaction.MethodNameResolver}
+ * (by default an
+ * {@link org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver}).
+ * Note that this only applies in case of ambiguous annotation mappings
+ * that do not specify a path mapping explicitly. In other words,
+ * the method name is only used for narrowing among a set of matching
+ * methods; it does not constitute a primary path mapping itself.
+ * If you have a single default method (without explicit path mapping),
+ * then all requests without a more specific mapped method found will
+ * be dispatched to it. If you have multiple such default methods, then
+ * the method name will be taken into account for choosing between them.
+ */
+ String[] value() default {};
+
+ /**
+ * The HTTP request methods to map to, narrowing the primary mapping:
+ * GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE.
+ * Supported at the type level as well as at the method level!
+ * When used at the type level, all method-level mappings inherit
+ * this HTTP method restriction (i.e. the type-level restriction
+ * gets checked before the handler method is even resolved).
+ * Currently only supported in Servlet environments!
+ * To be supported for Portlet 2.0 resource requests in Spring 3.0 as well.
+ */
+ RequestMethod[] method() default {};
+
+ /**
+ * The parameters of the mapped request, narrowing the primary mapping.
+ * Same format for any environment: a sequence of "myParam=myValue" style
+ * expressions, with a request only mapped if each such parameter is found
+ * to have the given value. "myParam" style expressions are also supported,
+ * with such parameters having to be present in the request (allowed to have
+ * any value). Finally, "!myParam" style expressions indicate that the
+ * specified parameter is not supposed to be present in the request.
+ * Supported at the type level as well as at the method level!
+ * When used at the type level, all method-level mappings inherit
+ * this parameter restriction (i.e. the type-level restriction
+ * gets checked before the handler method is even resolved).
+ * In a Servlet environment, parameter mappings are considered as restrictions
+ * that are enforced at the type level. The primary path mapping (i.e. the
+ * specified URI value) still has to uniquely identify the target handler, with
+ * parameter mappings simply expressing preconditions for invoking the handler.
+ * In a Portlet environment, parameters are taken into account as mapping
+ * differentiators, i.e. the primary portlet mode mapping plus the parameter
+ * conditions uniquely identify the target handler. Different handlers may be
+ * mapped onto the same portlet mode, as long as their parameter mappings differ.
+ */
+ String[] params() default {};
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestMethod.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestMethod.java
new file mode 100644
index 00000000000..91218e4f817
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestMethod.java
@@ -0,0 +1,41 @@
+/*
+ * 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.bind.annotation;
+
+/**
+ * Java 5 enumeration of HTTP request methods. Intended for use
+ * with the {@link RequestMapping#method()} attribute of the
+ * {@link RequestMapping} annotation.
+ *
+ * Note that, by default, {@link org.springframework.web.servlet.DispatcherServlet}
+ * supports GET, HEAD, POST, PUT and DELETE only. DispatcherServlet will
+ * process TRACE and OPTIONS with the default HttpServlet behavior unless
+ * explicitly told to dispatch those request types as well: Check out
+ * the "dispatchOptionsRequest" and "dispatchTraceRequest" properties,
+ * switching them to "true" if necessary.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see RequestMapping
+ * @see org.springframework.web.servlet.DispatcherServlet#setDispatchOptionsRequest
+ * @see org.springframework.web.servlet.DispatcherServlet#setDispatchTraceRequest
+ */
+public enum RequestMethod {
+
+ GET, HEAD, POST, PUT, DELETE, OPTIONS, TRACE;
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestParam.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestParam.java
new file mode 100644
index 00000000000..1f7ea2e9cf1
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestParam.java
@@ -0,0 +1,55 @@
+/*
+ * 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.bind.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;
+
+/**
+ * Annotation which indicates that a method parameter should be bound
+ * to a web request parameter. Supported for {@link RequestMapping}
+ * annotated handler methods in Servlet and Portlet environments.
+ *
+ * @author Arjen Poutsma
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see RequestMapping
+ * @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
+ * @see org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RequestParam {
+
+ /**
+ * The request parameter to bind to.
+ */
+ String value() default "";
+
+ /**
+ * Whether the parameter is required.
+ * Default is NOTE: Session attributes as indicated using this annotation
+ * correspond to a specific handler's model attributes, getting transparently
+ * stored in a conversational session. Those attributes will be removed once
+ * the handler indicates completion of its conversational session. Therefore,
+ * use this facility for such conversational attributes which are supposed
+ * to be stored in the session temporarily during the course of a
+ * specific handler's conversation.
+ *
+ * For permanent session attributes, e.g. a user authentication object,
+ * use the traditional Note: This indicates the model attribute names. The session attribute
+ * names may or may not match the model attribute names; applications should
+ * not rely on the session attribute names but rather operate on the model only.
+ */
+ String[] value() default {};
+
+ /**
+ * The types of session attributes in the model, to be stored in the
+ * session or some conversational storage. All model attributes of this
+ * type will be stored in the session, regardless of attribute name.
+ */
+ Class[] types() default {};
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/package.html b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/package.html
new file mode 100644
index 00000000000..fc2b6efadbf
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/package.html
@@ -0,0 +1,8 @@
+
+ Used by {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter}
+ * and {@link org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter}.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.2
+ * @see #invokeHandlerMethod
+ */
+public class HandlerMethodInvoker {
+
+ /**
+ * We'll create a lot of these objects, so we don't want a new logger every time.
+ */
+ private static final Log logger = LogFactory.getLog(HandlerMethodInvoker.class);
+
+ private final HandlerMethodResolver methodResolver;
+
+ private final WebBindingInitializer bindingInitializer;
+
+ private final SessionAttributeStore sessionAttributeStore;
+
+ private final ParameterNameDiscoverer parameterNameDiscoverer;
+
+ private final WebArgumentResolver[] customArgumentResolvers;
+
+ private final SimpleSessionStatus sessionStatus = new SimpleSessionStatus();
+
+
+ public HandlerMethodInvoker(HandlerMethodResolver methodResolver) {
+ this(methodResolver, null);
+ }
+
+ public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer) {
+ this(methodResolver, bindingInitializer, new DefaultSessionAttributeStore(), null);
+ }
+
+ public HandlerMethodInvoker(
+ HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer,
+ SessionAttributeStore sessionAttributeStore, ParameterNameDiscoverer parameterNameDiscoverer,
+ WebArgumentResolver... customArgumentResolvers) {
+
+ this.methodResolver = methodResolver;
+ this.bindingInitializer = bindingInitializer;
+ this.sessionAttributeStore = sessionAttributeStore;
+ this.parameterNameDiscoverer = parameterNameDiscoverer;
+ this.customArgumentResolvers = customArgumentResolvers;
+ }
+
+
+ public final Object invokeHandlerMethod(
+ Method handlerMethod, Object handler, NativeWebRequest webRequest, ExtendedModelMap implicitModel)
+ throws Exception {
+
+ Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);
+ try {
+ boolean debug = logger.isDebugEnabled();
+ for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) {
+ Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod);
+ Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);
+ if (debug) {
+ logger.debug("Invoking model attribute method: " + attributeMethodToInvoke);
+ }
+ Object attrValue = doInvokeMethod(attributeMethodToInvoke, handler, args);
+ String attrName = AnnotationUtils.findAnnotation(attributeMethodToInvoke, ModelAttribute.class).value();
+ if ("".equals(attrName)) {
+ Class resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass());
+ attrName = Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue);
+ }
+ implicitModel.addAttribute(attrName, attrValue);
+ }
+ Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);
+ if (debug) {
+ logger.debug("Invoking request handler method: " + handlerMethodToInvoke);
+ }
+ return doInvokeMethod(handlerMethodToInvoke, handler, args);
+ }
+ catch (IllegalStateException ex) {
+ // Throw exception with full handler method context...
+ throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private Object[] resolveHandlerArguments(
+ Method handlerMethod, Object handler, NativeWebRequest webRequest, ExtendedModelMap implicitModel)
+ throws Exception {
+
+ Class[] paramTypes = handlerMethod.getParameterTypes();
+ Object[] args = new Object[paramTypes.length];
+
+ for (int i = 0; i < args.length; i++) {
+ MethodParameter methodParam = new MethodParameter(handlerMethod, i);
+ methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);
+ GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());
+ String paramName = null;
+ boolean paramRequired = false;
+ String attrName = null;
+ Object[] paramAnns = methodParam.getParameterAnnotations();
+
+ for (int j = 0; j < paramAnns.length; j++) {
+ Object paramAnn = paramAnns[j];
+ if (RequestParam.class.isInstance(paramAnn)) {
+ RequestParam requestParam = (RequestParam) paramAnn;
+ paramName = requestParam.value();
+ paramRequired = requestParam.required();
+ break;
+ }
+ else if (ModelAttribute.class.isInstance(paramAnn)) {
+ ModelAttribute attr = (ModelAttribute) paramAnn;
+ attrName = attr.value();
+ }
+ }
+ if (paramName != null && attrName != null) {
+ throw new IllegalStateException("@RequestParam and @ModelAttribute are an exclusive choice -" +
+ "do not specify both on the same parameter: " + handlerMethod);
+ }
+
+ Class paramType = methodParam.getParameterType();
+
+ if (paramName == null && attrName == null) {
+ Object argValue = resolveCommonArgument(methodParam, webRequest);
+ if (argValue != WebArgumentResolver.UNRESOLVED) {
+ args[i] = argValue;
+ }
+ else {
+ if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {
+ args[i] = implicitModel;
+ }
+ else if (SessionStatus.class.isAssignableFrom(paramType)) {
+ args[i] = this.sessionStatus;
+ }
+ else if (Errors.class.isAssignableFrom(paramType)) {
+ throw new IllegalStateException("Errors/BindingResult argument declared " +
+ "without preceding model attribute. Check your handler method signature!");
+ }
+ else if (BeanUtils.isSimpleProperty(paramType)) {
+ paramName = "";
+ }
+ else {
+ attrName = "";
+ }
+ }
+ }
+
+ if (paramName != null) {
+ args[i] = resolveRequestParam(paramName, paramRequired, methodParam, webRequest, handler);
+ }
+ else if (attrName != null) {
+ WebDataBinder binder = resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);
+ boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
+ if (binder.getTarget() != null) {
+ doBind(webRequest, binder, !assignBindingResult);
+ }
+ args[i] = binder.getTarget();
+ if (assignBindingResult) {
+ args[i + 1] = binder.getBindingResult();
+ i++;
+ }
+ implicitModel.putAll(binder.getBindingResult().getModel());
+ }
+ }
+
+ return args;
+ }
+
+ private void initBinder(Object handler, String attrName, WebDataBinder binder, NativeWebRequest webRequest)
+ throws Exception {
+
+ if (this.bindingInitializer != null) {
+ this.bindingInitializer.initBinder(binder, webRequest);
+ }
+ if (handler != null) {
+ Set Used by {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter}
+ * and {@link org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter}.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.2
+ * @see org.springframework.web.bind.annotation.RequestMapping
+ * @see org.springframework.web.bind.annotation.InitBinder
+ * @see org.springframework.web.bind.annotation.ModelAttribute
+ * @see org.springframework.web.bind.annotation.SessionAttributes
+ */
+public class HandlerMethodResolver {
+
+ private final Set Default is Default is Default is Default is to use no prefix, storing the session attributes with the
+ * same name as in the model.
+ */
+ public void setAttributeNamePrefix(String attributeNamePrefix) {
+ this.attributeNamePrefix = (attributeNamePrefix != null ? attributeNamePrefix : "");
+ }
+
+
+ public void storeAttribute(WebRequest request, String attributeName, Object attributeValue) {
+ Assert.notNull(request, "WebRequest must not be null");
+ Assert.notNull(attributeName, "Attribute name must not be null");
+ Assert.notNull(attributeValue, "Attribute value must not be null");
+ String storeAttributeName = getAttributeNameInSession(request, attributeName);
+ request.setAttribute(storeAttributeName, attributeValue, WebRequest.SCOPE_SESSION);
+ }
+
+ public Object retrieveAttribute(WebRequest request, String attributeName) {
+ Assert.notNull(request, "WebRequest must not be null");
+ Assert.notNull(attributeName, "Attribute name must not be null");
+ String storeAttributeName = getAttributeNameInSession(request, attributeName);
+ return request.getAttribute(storeAttributeName, WebRequest.SCOPE_SESSION);
+ }
+
+ public void cleanupAttribute(WebRequest request, String attributeName) {
+ Assert.notNull(request, "WebRequest must not be null");
+ Assert.notNull(attributeName, "Attribute name must not be null");
+ String storeAttributeName = getAttributeNameInSession(request, attributeName);
+ request.removeAttribute(storeAttributeName, WebRequest.SCOPE_SESSION);
+ }
+
+
+ /**
+ * Calculate the attribute name in the backend session.
+ * The default implementation simply prepends the configured
+ * {@link #setAttributeNamePrefix "attributeNamePrefix"}, if any.
+ * @param request the current request
+ * @param attributeName the name of the attribute
+ * @return the attribute name in the backend session
+ */
+ protected String getAttributeNameInSession(WebRequest request, String attributeName) {
+ return this.attributeNamePrefix + attributeName;
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/SessionAttributeStore.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/SessionAttributeStore.java
new file mode 100644
index 00000000000..e428074dad5
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/SessionAttributeStore.java
@@ -0,0 +1,59 @@
+/*
+ * 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.bind.support;
+
+import org.springframework.web.context.request.WebRequest;
+
+/**
+ * Strategy interface for storing model attributes in a backend session.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see org.springframework.web.bind.annotation.SessionAttributes
+ */
+public interface SessionAttributeStore {
+
+ /**
+ * Store the supplied attribute in the backend session.
+ * Can be called for new attributes as well as for existing attributes.
+ * In the latter case, this signals that the attribute value may have been modified.
+ * @param request the current request
+ * @param attributeName the name of the attribute
+ * @param attributeValue the attribute value to store
+ */
+ void storeAttribute(WebRequest request, String attributeName, Object attributeValue);
+
+ /**
+ * Retrieve the specified attribute from the backend session.
+ * This will typically be called with the expectation that the
+ * attribute is already present, with an exception to be thrown
+ * if this method returns Indicates that the attribute name will not be used anymore.
+ * @param request the current request
+ * @param attributeName the name of the attribute
+ */
+ void cleanupAttribute(WebRequest request, String attributeName);
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/SessionStatus.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/SessionStatus.java
new file mode 100644
index 00000000000..cf48169e9f1
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/SessionStatus.java
@@ -0,0 +1,47 @@
+/*
+ * 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.bind.support;
+
+/**
+ * Simple interface that can be injected into handler methods, allowing them to
+ * signal that their session processing is complete. The handler invoker may
+ * then follow up with appropriate cleanup, e.g. of session attributes which
+ * have been implicitly created during this handler's processing (according to
+ * the
+ * {@link org.springframework.web.bind.annotation.SessionAttributes @SessionAttributes}
+ * annotation).
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see org.springframework.web.bind.annotation.RequestMapping
+ * @see org.springframework.web.bind.annotation.SessionAttributes
+ */
+public interface SessionStatus {
+
+ /**
+ * Mark the current handler's session processing as complete, allowing for
+ * cleanup of session attributes.
+ */
+ void setComplete();
+
+ /**
+ * Return whether the current handler's session processing has been marked
+ * as complete.
+ */
+ boolean isComplete();
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/SimpleSessionStatus.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/SimpleSessionStatus.java
new file mode 100644
index 00000000000..1839316574c
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/SimpleSessionStatus.java
@@ -0,0 +1,39 @@
+/*
+ * 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.bind.support;
+
+/**
+ * Simple implementation of the {@link SessionStatus} interface,
+ * keeping the A typical implementation could look like as follows:
+ *
+ * See the DataBinder/WebDataBinder superclasses for customization options,
+ * which include specifying allowed/required fields, and registering custom
+ * property editors.
+ *
+ * Can also used for manual data binding in custom web controllers or interceptors
+ * that build on Spring's {@link org.springframework.web.context.request.WebRequest}
+ * abstraction: e.g. in a {@link org.springframework.web.context.request.WebRequestInterceptor}
+ * implementation. Simply instantiate a WebRequestDataBinder for each binding
+ * process, and invoke This call can create field errors, representing basic binding
+ * errors like a required field (code "required"), or type mismatch
+ * between value and bean property (code "typeMismatch").
+ * Multipart files are bound via their parameter name, just like normal
+ * HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property,
+ * invoking a "setUploadedFile" setter method.
+ * The type of the target property for a multipart file can be MultipartFile,
+ * byte[], or String. The latter two receive the contents of the uploaded file;
+ * all metadata like original file name, content type, etc are lost in those cases.
+ * @param request request with parameters to bind (can be multipart)
+ * @see org.springframework.web.multipart.MultipartRequest
+ * @see org.springframework.web.multipart.MultipartFile
+ * @see #bindMultipartFiles
+ * @see #bind(org.springframework.beans.PropertyValues)
+ */
+ public void bind(WebRequest request) {
+ MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap());
+ if (request instanceof NativeWebRequest) {
+ Object nativeRequest = ((NativeWebRequest) request).getNativeRequest();
+ if (nativeRequest instanceof MultipartRequest) {
+ MultipartRequest multipartRequest = (MultipartRequest) request;
+ bindMultipartFiles(multipartRequest.getFileMap(), mpvs);
+ }
+ }
+ doBind(mpvs);
+ }
+
+ /**
+ * Treats errors as fatal.
+ * Use this method only if it's an error if the input isn't valid.
+ * This might be appropriate if all input is from dropdowns, for example.
+ * @throws BindException if binding errors have been encountered
+ */
+ public void closeNoCatch() throws BindException {
+ if (getBindingResult().hasErrors()) {
+ throw new BindException(getBindingResult());
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/package.html b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/package.html
new file mode 100644
index 00000000000..eba7e423cae
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/package.html
@@ -0,0 +1,7 @@
+
+ The file contents are either stored in memory or temporarily on disk.
+ * In either case, the user is responsible for copying file contents to a
+ * session-level or persistent store as and if desired. The temporary storages
+ * will be cleared at the end of request processing.
+ *
+ * @author Juergen Hoeller
+ * @author Trevor D. Cook
+ * @since 29.09.2003
+ * @see org.springframework.web.multipart.MultipartHttpServletRequest
+ * @see org.springframework.web.multipart.MultipartResolver
+ */
+public interface MultipartFile {
+
+ /**
+ * Return the name of the parameter in the multipart form.
+ * @return the name of the parameter (never This may contain path information depending on the browser used,
+ * but it typically will not with any other than Opera.
+ * @return the original filename, or the empty String if no file
+ * has been chosen in the multipart form
+ */
+ String getOriginalFilename();
+
+ /**
+ * Return the content type of the file.
+ * @return the content type, or This may either move the file in the filesystem, copy the file in the
+ * filesystem, or save memory-held contents to the destination file.
+ * If the destination file already exists, it will be deleted first.
+ * If the file has been moved in the filesystem, this operation cannot
+ * be invoked again. Therefore, call this method just once to be able to
+ * work with any storage mechanism.
+ * @param dest the destination file
+ * @throws IOException in case of reading or writing errors
+ * @throws IllegalStateException if the file has already been moved
+ * in the filesystem and is not available anymore for another transfer
+ */
+ void transferTo(File dest) throws IOException, IllegalStateException;
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/MultipartHttpServletRequest.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/MultipartHttpServletRequest.java
new file mode 100644
index 00000000000..249e508b3eb
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/MultipartHttpServletRequest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.multipart;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Provides additional methods for dealing with multipart content within a
+ * servlet request, allowing to access uploaded files.
+ * Implementations also need to override the standard
+ * {@link javax.servlet.ServletRequest} methods for parameter access, making
+ * multipart parameters available.
+ *
+ * A concrete implementation is
+ * {@link org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest}.
+ * As an intermediate step,
+ * {@link org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest}
+ * can be subclassed.
+ *
+ * @author Juergen Hoeller
+ * @author Trevor D. Cook
+ * @since 29.09.2003
+ * @see MultipartResolver
+ * @see MultipartFile
+ * @see javax.servlet.http.HttpServletRequest#getParameter
+ * @see javax.servlet.http.HttpServletRequest#getParameterNames
+ * @see javax.servlet.http.HttpServletRequest#getParameterMap
+ * @see org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest
+ * @see org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest
+ */
+public interface MultipartHttpServletRequest extends HttpServletRequest, MultipartRequest {
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/MultipartRequest.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/MultipartRequest.java
new file mode 100644
index 00000000000..a7591051bc7
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/MultipartRequest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.multipart;
+
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * This interface defines the multipart request access operations
+ * that are exposed for actual multipart requests. It is extended
+ * by {@link MultipartHttpServletRequest} and the Portlet
+ * {@link org.springframework.web.portlet.multipart.MultipartActionRequest}.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.2
+ */
+public interface MultipartRequest {
+
+ /**
+ * Return an {@link java.util.Iterator} of String objects containing the
+ * parameter names of the multipart files contained in this request. These
+ * are the field names of the form (like with normal parameters), not the
+ * original file names.
+ * @return the names of the files
+ */
+ Iterator getFileNames();
+
+ /**
+ * Return the contents plus description of an uploaded file in this request,
+ * or There is only one concrete implementation included in Spring,
+ * as of Spring 2.5:
+ * There is no default resolver implementation used for Spring
+ * {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlets},
+ * as an application might choose to parse its multipart requests itself. To define
+ * an implementation, create a bean with the id "multipartResolver" in a
+ * {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet's}
+ * application context. Such a resolver gets applied to all requests handled
+ * by that {@link org.springframework.web.servlet.DispatcherServlet}.
+ *
+ * If a {@link org.springframework.web.servlet.DispatcherServlet} detects
+ * a multipart request, it will resolve it via the configured
+ * {@link org.springframework.web.multipart.MultipartResolver} and pass on a
+ * wrapped {@link javax.servlet.http.HttpServletRequest}.
+ * Controllers can then cast their given request to the
+ * {@link org.springframework.web.multipart.MultipartHttpServletRequest}
+ * interface, which permits access to any
+ * {@link org.springframework.web.multipart.MultipartFile MultipartFiles}.
+ * Note that this cast is only supported in case of an actual multipart request.
+ *
+ * As an alternative to using a
+ * {@link org.springframework.web.multipart.MultipartResolver} with a
+ * {@link org.springframework.web.servlet.DispatcherServlet},
+ * a {@link org.springframework.web.multipart.support.MultipartFilter} can be
+ * registered in Note: There is hardly ever a need to access the
+ * {@link org.springframework.web.multipart.MultipartResolver} itself
+ * from application code. It will simply do its work behind the scenes,
+ * making
+ * {@link org.springframework.web.multipart.MultipartHttpServletRequest MultipartHttpServletRequests}
+ * available to controllers.
+ *
+ * @author Juergen Hoeller
+ * @author Trevor D. Cook
+ * @since 29.09.2003
+ * @see MultipartHttpServletRequest
+ * @see MultipartFile
+ * @see org.springframework.web.multipart.commons.CommonsMultipartResolver
+ * @see org.springframework.web.multipart.support.ByteArrayMultipartFileEditor
+ * @see org.springframework.web.multipart.support.StringMultipartFileEditor
+ * @see org.springframework.web.servlet.DispatcherServlet
+ */
+public interface MultipartResolver {
+
+ /**
+ * Determine if the given request contains multipart content.
+ * Will typically check for content type "multipart/form-data", but the actually
+ * accepted requests might depend on the capabilities of the resolver implementation.
+ * @param request the servlet request to be evaluated
+ * @return whether the request contains multipart content
+ */
+ boolean isMultipart(HttpServletRequest request);
+
+ /**
+ * Parse the given HTTP request into multipart files and parameters,
+ * and wrap the request inside a
+ * {@link org.springframework.web.multipart.MultipartHttpServletRequest} object
+ * that provides access to file descriptors and makes contained
+ * parameters accessible via the standard ServletRequest methods.
+ * @param request the servlet request to wrap (must be of a multipart content type)
+ * @return the wrapped servlet request
+ * @throws MultipartException if the servlet request is not multipart, or if
+ * implementation-specific problems are encountered (such as exceeding file size limits)
+ * @see MultipartHttpServletRequest#getFile
+ * @see MultipartHttpServletRequest#getFileNames
+ * @see MultipartHttpServletRequest#getFileMap
+ * @see javax.servlet.http.HttpServletRequest#getParameter
+ * @see javax.servlet.http.HttpServletRequest#getParameterNames
+ * @see javax.servlet.http.HttpServletRequest#getParameterMap
+ */
+ MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
+
+ /**
+ * Cleanup any resources used for the multipart handling,
+ * like a storage for the uploaded files.
+ * @param request the request to cleanup resources for
+ */
+ void cleanupMultipart(MultipartHttpServletRequest request);
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/commons/CommonsFileUploadSupport.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/commons/CommonsFileUploadSupport.java
new file mode 100644
index 00000000000..733047a5817
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/commons/CommonsFileUploadSupport.java
@@ -0,0 +1,321 @@
+/*
+ * 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.multipart.commons;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileItemFactory;
+import org.apache.commons.fileupload.FileUpload;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.core.io.Resource;
+import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartException;
+import org.springframework.web.util.WebUtils;
+
+/**
+ * Base class for multipart resolvers that use Jakarta Commons FileUpload
+ * 1.1 or higher.
+ *
+ * Provides common configuration properties and parsing functionality
+ * for multipart requests, using a Map of Spring CommonsMultipartFile instances
+ * as representation of uploaded files and a String-based parameter Map as
+ * representation of uploaded form fields.
+ *
+ * Subclasses implement concrete resolution strategies for Servlet or Portlet
+ * environments: see CommonsMultipartResolver and CommonsPortletMultipartResolver,
+ * respectively. This base class is not tied to either of those APIs, factoring
+ * out common functionality.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see CommonsMultipartFile
+ * @see CommonsMultipartResolver
+ * @see org.springframework.web.portlet.multipart.CommonsPortletMultipartResolver
+ */
+public abstract class CommonsFileUploadSupport {
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ private final DiskFileItemFactory fileItemFactory;
+
+ private final FileUpload fileUpload;
+
+ private boolean uploadTempDirSpecified = false;
+
+
+ /**
+ * Instantiate a new CommonsFileUploadSupport with its
+ * corresponding FileItemFactory and FileUpload instances.
+ * @see #newFileItemFactory
+ * @see #newFileUpload
+ */
+ public CommonsFileUploadSupport() {
+ this.fileItemFactory = newFileItemFactory();
+ this.fileUpload = newFileUpload(getFileItemFactory());
+ }
+
+
+ /**
+ * Return the underlying If the request specifies a character encoding itself, the request
+ * encoding will override this setting. This also allows for generically
+ * overriding the character encoding in a filter that invokes the
+ * Default implementation returns a standard DiskFileItemFactory.
+ * Can be overridden to use a custom subclass, e.g. for testing purposes.
+ * @return the new DiskFileItemFactory instance
+ */
+ protected DiskFileItemFactory newFileItemFactory() {
+ return new DiskFileItemFactory();
+ }
+
+ /**
+ * Factory method for a Commons FileUpload instance.
+ * To be implemented by subclasses.
+ * @param fileItemFactory the Commons FileItemFactory to build upon
+ * @return the Commons FileUpload instance
+ */
+ protected abstract FileUpload newFileUpload(FileItemFactory fileItemFactory);
+
+
+ /**
+ * Determine an appropriate FileUpload instance for the given encoding.
+ * Default implementation returns the shared FileUpload instance
+ * if the encoding matches, else creates a new FileUpload instance
+ * with the same configuration other than the desired encoding.
+ * @param encoding the character encoding to use
+ * @return an appropriate FileUpload instance.
+ */
+ protected FileUpload prepareFileUpload(String encoding) {
+ FileUpload fileUpload = getFileUpload();
+ FileUpload actualFileUpload = fileUpload;
+
+ // Use new temporary FileUpload instance if the request specifies
+ // its own encoding that does not match the default encoding.
+ if (encoding != null && !encoding.equals(fileUpload.getHeaderEncoding())) {
+ actualFileUpload = newFileUpload(getFileItemFactory());
+ actualFileUpload.setSizeMax(fileUpload.getSizeMax());
+ actualFileUpload.setHeaderEncoding(encoding);
+ }
+
+ return actualFileUpload;
+ }
+
+ /**
+ * Parse the given List of Commons FileItems into a Spring MultipartParsingResult,
+ * containing Spring MultipartFile instances and a Map of multipart parameter.
+ * @param fileItems the Commons FileIterms to parse
+ * @param encoding the encoding to use for form fields
+ * @return the Spring MultipartParsingResult
+ * @see CommonsMultipartFile#CommonsMultipartFile(org.apache.commons.fileupload.FileItem)
+ */
+ protected MultipartParsingResult parseFileItems(List fileItems, String encoding) {
+ Map multipartFiles = new HashMap();
+ Map multipartParameters = new HashMap();
+
+ // Extract multipart files and multipart parameters.
+ for (Iterator it = fileItems.iterator(); it.hasNext();) {
+ FileItem fileItem = (FileItem) it.next();
+ if (fileItem.isFormField()) {
+ String value = null;
+ if (encoding != null) {
+ try {
+ value = fileItem.getString(encoding);
+ }
+ catch (UnsupportedEncodingException ex) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Could not decode multipart item '" + fileItem.getFieldName() +
+ "' with encoding '" + encoding + "': using platform default");
+ }
+ value = fileItem.getString();
+ }
+ }
+ else {
+ value = fileItem.getString();
+ }
+ String[] curParam = (String[]) multipartParameters.get(fileItem.getFieldName());
+ if (curParam == null) {
+ // simple form field
+ multipartParameters.put(fileItem.getFieldName(), new String[] { value });
+ }
+ else {
+ // array of simple form fields
+ String[] newParam = StringUtils.addStringToArray(curParam, value);
+ multipartParameters.put(fileItem.getFieldName(), newParam);
+ }
+ }
+ else {
+ // multipart file field
+ CommonsMultipartFile file = new CommonsMultipartFile(fileItem);
+ if (multipartFiles.put(file.getName(), file) != null) {
+ throw new MultipartException(
+ "Multiple files for field name [" + file.getName() + "] found - not supported by MultipartResolver");
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Found multipart file [" + file.getName() + "] of size " + file.getSize() +
+ " bytes with original filename [" + file.getOriginalFilename() + "], stored " +
+ file.getStorageDescription());
+ }
+ }
+ }
+ return new MultipartParsingResult(multipartFiles, multipartParameters);
+ }
+
+ /**
+ * Cleanup the Spring MultipartFiles created during multipart parsing,
+ * potentially holding temporary data on disk.
+ * Deletes the underlying Commons FileItem instances.
+ * @param multipartFiles Collection of MultipartFile instances
+ * @see org.apache.commons.fileupload.FileItem#delete()
+ */
+ protected void cleanupFileItems(Collection multipartFiles) {
+ for (Iterator it = multipartFiles.iterator(); it.hasNext();) {
+ CommonsMultipartFile file = (CommonsMultipartFile) it.next();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Cleaning up multipart file [" + file.getName() + "] with original filename [" +
+ file.getOriginalFilename() + "], stored " + file.getStorageDescription());
+ }
+ file.getFileItem().delete();
+ }
+ }
+
+
+ /**
+ * Holder for a Map of Spring MultipartFiles and a Map of
+ * multipart parameters.
+ */
+ protected static class MultipartParsingResult {
+
+ private final Map multipartFiles;
+
+ private final Map multipartParameters;
+
+ /**
+ * Create a new MultipartParsingResult.
+ * @param multipartFiles Map of field name to MultipartFile instance
+ * @param multipartParameters Map of field name to form field String value
+ */
+ public MultipartParsingResult(Map multipartFiles, Map multipartParameters) {
+ this.multipartFiles = multipartFiles;
+ this.multipartParameters = multipartParameters;
+ }
+
+ /**
+ * Return the multipart files as Map of field name to MultipartFile instance.
+ */
+ public Map getMultipartFiles() {
+ return this.multipartFiles;
+ }
+
+ /**
+ * Return the multipart parameters as Map of field name to form field String value.
+ */
+ public Map getMultipartParameters() {
+ return this.multipartParameters;
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartFile.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartFile.java
new file mode 100644
index 00000000000..8c042953eef
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartFile.java
@@ -0,0 +1,194 @@
+/*
+ * 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.multipart.commons;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.disk.DiskFileItem;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * MultipartFile implementation for Jakarta Commons FileUpload.
+ *
+ * NOTE: As of Spring 2.0, this class requires Commons FileUpload 1.1
+ * or higher. The implementation does not use any deprecated FileUpload 1.0 API
+ * anymore, to be compatible with future Commons FileUpload releases.
+ *
+ * @author Trevor D. Cook
+ * @author Juergen Hoeller
+ * @since 29.09.2003
+ * @see CommonsMultipartResolver
+ */
+public class CommonsMultipartFile implements MultipartFile, Serializable {
+
+ protected static final Log logger = LogFactory.getLog(CommonsMultipartFile.class);
+
+ private final FileItem fileItem;
+
+ private final long size;
+
+
+ /**
+ * Create an instance wrapping the given FileItem.
+ * @param fileItem the FileItem to wrap
+ */
+ public CommonsMultipartFile(FileItem fileItem) {
+ this.fileItem = fileItem;
+ this.size = this.fileItem.getSize();
+ }
+
+ /**
+ * Return the underlying Provides "maxUploadSize", "maxInMemorySize" and "defaultEncoding" settings as
+ * bean properties (inherited from {@link CommonsFileUploadSupport}). See corresponding
+ * ServletFileUpload / DiskFileItemFactory properties ("sizeMax", "sizeThreshold",
+ * "headerEncoding") for details in terms of defaults and accepted values.
+ *
+ * Saves temporary files to the servlet container's temporary directory.
+ * Needs to be initialized either by an application context or
+ * via the constructor that takes a ServletContext (for standalone usage).
+ *
+ * @author Trevor D. Cook
+ * @author Juergen Hoeller
+ * @since 29.09.2003
+ * @see #CommonsMultipartResolver(ServletContext)
+ * @see #setResolveLazily
+ * @see org.springframework.web.portlet.multipart.CommonsPortletMultipartResolver
+ * @see org.apache.commons.fileupload.servlet.ServletFileUpload
+ * @see org.apache.commons.fileupload.disk.DiskFileItemFactory
+ */
+public class CommonsMultipartResolver extends CommonsFileUploadSupport
+ implements MultipartResolver, ServletContextAware {
+
+ private final boolean commonsFileUpload12Present =
+ ClassUtils.hasMethod(ServletFileUpload.class, "isMultipartContent", new Class[] {HttpServletRequest.class});
+
+ private boolean resolveLazily = false;
+
+
+ /**
+ * Constructor for use as bean. Determines the servlet container's
+ * temporary directory via the ServletContext passed in as through the
+ * ServletContextAware interface (typically by a WebApplicationContext).
+ * @see #setServletContext
+ * @see org.springframework.web.context.ServletContextAware
+ * @see org.springframework.web.context.WebApplicationContext
+ */
+ public CommonsMultipartResolver() {
+ super();
+ }
+
+ /**
+ * Constructor for standalone usage. Determines the servlet container's
+ * temporary directory via the given ServletContext.
+ * @param servletContext the ServletContext to use
+ */
+ public CommonsMultipartResolver(ServletContext servletContext) {
+ this();
+ setServletContext(servletContext);
+ }
+
+
+ /**
+ * Set whether to resolve the multipart request lazily at the time of
+ * file or parameter access.
+ * Default is "false", resolving the multipart elements immediately, throwing
+ * corresponding exceptions at the time of the {@link #resolveMultipart} call.
+ * Switch this to "true" for lazy multipart parsing, throwing parse exceptions
+ * once the application attempts to obtain multipart files or parameters.
+ */
+ public void setResolveLazily(boolean resolveLazily) {
+ this.resolveLazily = resolveLazily;
+ }
+
+ /**
+ * Initialize the underlying The default implementation checks the request encoding,
+ * falling back to the default encoding specified for this resolver.
+ * @param request current HTTP request
+ * @return the encoding for the request (never Looks up the MultipartResolver in Spring's root web application context.
+ * Supports a "multipartResolverBeanName" filter init-param in MultipartResolver lookup is customizable: Override this filter's
+ * Note: This filter is an alternative to using DispatcherServlet's
+ * MultipartResolver support, for example for web applications with custom
+ * web views that do not use Spring's web MVC. It should not be combined with
+ * servlet-specific multipart resolution.
+ *
+ * @author Juergen Hoeller
+ * @since 08.10.2003
+ * @see #setMultipartResolverBeanName
+ * @see #lookupMultipartResolver
+ * @see org.springframework.web.multipart.MultipartResolver
+ * @see org.springframework.web.servlet.DispatcherServlet
+ */
+public class MultipartFilter extends OncePerRequestFilter {
+
+ public static final String DEFAULT_MULTIPART_RESOLVER_BEAN_NAME = "filterMultipartResolver";
+
+ private String multipartResolverBeanName = DEFAULT_MULTIPART_RESOLVER_BEAN_NAME;
+
+
+ /**
+ * Set the bean name of the MultipartResolver to fetch from Spring's
+ * root application context. Default is "filterMultipartResolver".
+ */
+ public void setMultipartResolverBeanName(String multipartResolverBeanName) {
+ this.multipartResolverBeanName = multipartResolverBeanName;
+ }
+
+ /**
+ * Return the bean name of the MultipartResolver to fetch from Spring's
+ * root application context.
+ */
+ protected String getMultipartResolverBeanName() {
+ return multipartResolverBeanName;
+ }
+
+
+ /**
+ * Check for a multipart request via this filter's MultipartResolver,
+ * and wrap the original request with a MultipartHttpServletRequest if appropriate.
+ * All later elements in the filter chain, most importantly servlets, benefit
+ * from proper parameter extraction in the multipart case, and are able to cast to
+ * MultipartHttpServletRequest if they need to.
+ */
+ protected void doFilterInternal(
+ HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+
+ MultipartResolver multipartResolver = lookupMultipartResolver(request);
+
+ HttpServletRequest processedRequest = request;
+ if (multipartResolver.isMultipart(processedRequest)) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Resolving multipart request [" + processedRequest.getRequestURI() +
+ "] with MultipartFilter");
+ }
+ processedRequest = multipartResolver.resolveMultipart(processedRequest);
+ }
+ else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Request [" + processedRequest.getRequestURI() + "] is not a multipart request");
+ }
+ }
+
+ try {
+ filterChain.doFilter(processedRequest, response);
+ }
+ finally {
+ if (processedRequest instanceof MultipartHttpServletRequest) {
+ multipartResolver.cleanupMultipart((MultipartHttpServletRequest) processedRequest);
+ }
+ }
+ }
+
+ /**
+ * Look up the MultipartResolver that this filter should use,
+ * taking the current HTTP request as argument.
+ * Default implementation delegates to the This can be overridden to use a custom MultipartResolver instance,
+ * for example if not using a Spring web application context.
+ * @return the MultipartResolver instance, or Allows one to specify the charset to use.
+ *
+ * @author Juergen Hoeller
+ * @since 13.10.2003
+ */
+public class StringMultipartFileEditor extends PropertyEditorSupport {
+
+ private final String charsetName;
+
+
+ /**
+ * Create a new {@link StringMultipartFileEditor}, using the default charset.
+ */
+ public StringMultipartFileEditor() {
+ this.charsetName = null;
+ }
+
+ /**
+ * Create a new {@link StringMultipartFileEditor}, using the given charset.
+ * @param charsetName valid charset name
+ * @see java.lang.String#String(byte[],String)
+ */
+ public StringMultipartFileEditor(String charsetName) {
+ this.charsetName = charsetName;
+ }
+
+
+ public void setAsText(String text) {
+ setValue(text);
+ }
+
+ public void setValue(Object value) {
+ if (value instanceof MultipartFile) {
+ MultipartFile multipartFile = (MultipartFile) value;
+ try {
+ super.setValue(this.charsetName != null ?
+ new String(multipartFile.getBytes(), this.charsetName) :
+ new String(multipartFile.getBytes()));
+ }
+ catch (IOException ex) {
+ IllegalArgumentException iae = new IllegalArgumentException("Cannot read contents of multipart file");
+ iae.initCause(ex);
+ throw iae;
+ }
+ }
+ else {
+ super.setValue(value);
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/support/package.html b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/support/package.html
new file mode 100644
index 00000000000..b0b8dd82f7b
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/support/package.html
@@ -0,0 +1,9 @@
+
+ This servlet is very flexible: It can be used with just about any workflow,
+ * with the installation of the appropriate adapter classes. It offers the
+ * following functionality that distinguishes it from other request-driven
+ * web MVC frameworks:
+ *
+ * NOTE: The A web application can define any number of DispatcherServlets.
+ * Each servlet will operate in its own namespace, loading its own application
+ * context with mappings, handlers, etc. Only the root application context
+ * as loaded by {@link org.springframework.web.context.ContextLoaderListener},
+ * if any, will be shared.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @see org.springframework.web.HttpRequestHandler
+ * @see org.springframework.web.servlet.mvc.Controller
+ * @see org.springframework.web.context.ContextLoaderListener
+ */
+public class DispatcherServlet extends FrameworkServlet {
+
+ /**
+ * Well-known name for the MultipartResolver object in the bean factory for this namespace.
+ */
+ public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
+
+ /**
+ * Well-known name for the LocaleResolver object in the bean factory for this namespace.
+ */
+ public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
+
+ /**
+ * Well-known name for the ThemeResolver object in the bean factory for this namespace.
+ */
+ public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
+
+ /**
+ * Well-known name for the HandlerMapping object in the bean factory for this namespace.
+ * Only used when "detectAllHandlerMappings" is turned off.
+ * @see #setDetectAllHandlerMappings
+ */
+ public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
+
+ /**
+ * Well-known name for the HandlerAdapter object in the bean factory for this namespace.
+ * Only used when "detectAllHandlerAdapters" is turned off.
+ * @see #setDetectAllHandlerAdapters
+ */
+ public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
+
+ /**
+ * Well-known name for the HandlerExceptionResolver object in the bean factory for this
+ * namespace. Only used when "detectAllHandlerExceptionResolvers" is turned off.
+ * @see #setDetectAllHandlerExceptionResolvers
+ */
+ public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";
+
+ /**
+ * Well-known name for the RequestToViewNameTranslator object in the bean factory for
+ * this namespace.
+ */
+ public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";
+
+ /**
+ * Well-known name for the ViewResolver object in the bean factory for this namespace.
+ * Only used when "detectAllViewResolvers" is turned off.
+ * @see #setDetectAllViewResolvers
+ */
+ public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
+
+ /**
+ * Request attribute to hold the currently chosen HandlerExecutionChain.
+ * Only used for internal optimizations.
+ */
+ public static final String HANDLER_EXECUTION_CHAIN_ATTRIBUTE = DispatcherServlet.class.getName() + ".HANDLER";
+
+ /**
+ * Request attribute to hold the 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.class.getName() + ".CONTEXT";
+
+ /**
+ * Request attribute to hold the current LocaleResolver, retrievable by views.
+ * @see org.springframework.web.servlet.support.RequestContextUtils#getLocaleResolver
+ */
+ public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER";
+
+ /**
+ * Request attribute to hold the current ThemeResolver, retrievable by views.
+ * @see org.springframework.web.servlet.support.RequestContextUtils#getThemeResolver
+ */
+ public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER";
+
+ /**
+ * Request attribute to hold the current ThemeSource, retrievable by views.
+ * @see org.springframework.web.servlet.support.RequestContextUtils#getThemeSource
+ */
+ public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE";
+
+
+ /**
+ * Log category to use when no mapped handler is found for a request.
+ */
+ public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
+
+ /**
+ * Name of the class path resource (relative to the DispatcherServlet class)
+ * that defines DispatcherServlet's default strategy names.
+ */
+ private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
+
+
+ /**
+ * Additional logger to use when no mapped handler is found for a request.
+ */
+ protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
+
+ private static final Properties defaultStrategies;
+
+ static {
+ // Load default strategy implementations from properties file.
+ // This is currently strictly internal and not meant to be customized
+ // by application developers.
+ try {
+ ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
+ defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
+ }
+ catch (IOException ex) {
+ throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
+ }
+ }
+
+
+ /** Detect all HandlerMappings or just expect "handlerMapping" bean? */
+ private boolean detectAllHandlerMappings = true;
+
+ /** Detect all HandlerAdapters or just expect "handlerAdapter" bean? */
+ private boolean detectAllHandlerAdapters = true;
+
+ /** Detect all HandlerExceptionResolvers or just expect "handlerExceptionResolver" bean? */
+ private boolean detectAllHandlerExceptionResolvers = true;
+
+ /** Detect all ViewResolvers or just expect "viewResolver" bean? */
+ private boolean detectAllViewResolvers = true;
+
+ /** Perform cleanup of request attributes after include request? */
+ private boolean cleanupAfterInclude = true;
+
+ /** Expose LocaleContext and RequestAttributes as inheritable for child threads? */
+ private boolean threadContextInheritable = false;
+
+
+ /** MultipartResolver used by this servlet */
+ private MultipartResolver multipartResolver;
+
+ /** LocaleResolver used by this servlet */
+ private LocaleResolver localeResolver;
+
+ /** ThemeResolver used by this servlet */
+ private ThemeResolver themeResolver;
+
+ /** List of HandlerMappings used by this servlet */
+ private List handlerMappings;
+
+ /** List of HandlerAdapters used by this servlet */
+ private List handlerAdapters;
+
+ /** List of HandlerExceptionResolvers used by this servlet */
+ private List handlerExceptionResolvers;
+
+ /** RequestToViewNameTranslator used by this servlet */
+ private RequestToViewNameTranslator viewNameTranslator;
+
+ /** List of ViewResolvers used by this servlet */
+ private List viewResolvers;
+
+
+ /**
+ * Set whether to detect all HandlerMapping beans in this servlet's context.
+ * Else, just a single bean with name "handlerMapping" will be expected.
+ * Default is "true". Turn this off if you want this servlet to use a
+ * single HandlerMapping, despite multiple HandlerMapping beans being
+ * defined in the context.
+ */
+ public void setDetectAllHandlerMappings(boolean detectAllHandlerMappings) {
+ this.detectAllHandlerMappings = detectAllHandlerMappings;
+ }
+
+ /**
+ * Set whether to detect all HandlerAdapter beans in this servlet's context.
+ * Else, just a single bean with name "handlerAdapter" will be expected.
+ * Default is "true". Turn this off if you want this servlet to use a
+ * single HandlerAdapter, despite multiple HandlerAdapter beans being
+ * defined in the context.
+ */
+ public void setDetectAllHandlerAdapters(boolean detectAllHandlerAdapters) {
+ this.detectAllHandlerAdapters = detectAllHandlerAdapters;
+ }
+
+ /**
+ * Set whether to detect all HandlerExceptionResolver beans in this servlet's context.
+ * Else, just a single bean with name "handlerExceptionResolver" will be expected.
+ * Default is "true". Turn this off if you want this servlet to use a
+ * single HandlerExceptionResolver, despite multiple HandlerExceptionResolver
+ * beans being defined in the context.
+ */
+ public void setDetectAllHandlerExceptionResolvers(boolean detectAllHandlerExceptionResolvers) {
+ this.detectAllHandlerExceptionResolvers = detectAllHandlerExceptionResolvers;
+ }
+
+ /**
+ * Set whether to detect all ViewResolver beans in this servlet's context.
+ * Else, just a single bean with name "viewResolver" will be expected.
+ * Default is "true". Turn this off if you want this servlet to use a
+ * single ViewResolver, despite multiple ViewResolver beans being
+ * defined in the context.
+ */
+ public void setDetectAllViewResolvers(boolean detectAllViewResolvers) {
+ this.detectAllViewResolvers = detectAllViewResolvers;
+ }
+
+ /**
+ * Set whether to perform cleanup of request attributes after an include request,
+ * that is, whether to reset the original state of all request attributes after
+ * the DispatcherServlet has processed within an include request. Else, just the
+ * DispatcherServlet's own request attributes will be reset, but not model
+ * attributes for JSPs or special attributes set by views (for example, JSTL's).
+ * Default is "true", which is strongly recommended. Views should not rely on
+ * request attributes having been set by (dynamic) includes. This allows JSP views
+ * rendered by an included controller to use any model attributes, even with the
+ * same names as in the main JSP, without causing side effects. Only turn this
+ * off for special needs, for example to deliberately allow main JSPs to access
+ * attributes from JSP views rendered by an included controller.
+ */
+ public void setCleanupAfterInclude(boolean cleanupAfterInclude) {
+ this.cleanupAfterInclude = cleanupAfterInclude;
+ }
+
+ /**
+ * Set whether to expose the LocaleContext and RequestAttributes as inheritable
+ * for child threads (using an {@link java.lang.InheritableThreadLocal}).
+ * 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).
+ * WARNING: 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;
+ }
+
+
+ /**
+ * This implementation calls {@link #initStrategies}.
+ */
+ protected void onRefresh(ApplicationContext context) throws BeansException {
+ initStrategies(context);
+ }
+
+ /**
+ * Initialize the strategy objects that this servlet uses.
+ * May be overridden in subclasses in order to initialize
+ * further strategy objects.
+ */
+ protected void initStrategies(ApplicationContext context) {
+ initMultipartResolver(context);
+ initLocaleResolver(context);
+ initThemeResolver(context);
+ initHandlerMappings(context);
+ initHandlerAdapters(context);
+ initHandlerExceptionResolvers(context);
+ initRequestToViewNameTranslator(context);
+ initViewResolvers(context);
+ }
+
+ /**
+ * Initialize the MultipartResolver used by this class.
+ * If no bean is defined with the given name in the BeanFactory
+ * for this namespace, no multipart handling is provided.
+ */
+ private void initMultipartResolver(ApplicationContext context) {
+ try {
+ this.multipartResolver = (MultipartResolver)
+ context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
+ }
+ }
+ catch (NoSuchBeanDefinitionException ex) {
+ // Default is no multipart resolver.
+ this.multipartResolver = null;
+ if (logger.isDebugEnabled()) {
+ logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
+ "': no multipart request handling provided");
+ }
+ }
+ }
+
+ /**
+ * Initialize the LocaleResolver used by this class.
+ * If no bean is defined with the given name in the BeanFactory
+ * for this namespace, we default to AcceptHeaderLocaleResolver.
+ */
+ private void initLocaleResolver(ApplicationContext context) {
+ try {
+ this.localeResolver = (LocaleResolver)
+ context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
+ }
+ }
+ catch (NoSuchBeanDefinitionException ex) {
+ // We need to use the default.
+ this.localeResolver = (LocaleResolver) getDefaultStrategy(context, LocaleResolver.class);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
+ "': using default [" + this.localeResolver + "]");
+ }
+ }
+ }
+
+ /**
+ * Initialize the ThemeResolver used by this class.
+ * If no bean is defined with the given name in the BeanFactory
+ * for this namespace, we default to a FixedThemeResolver.
+ */
+ private void initThemeResolver(ApplicationContext context) {
+ try {
+ this.themeResolver = (ThemeResolver)
+ context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Using ThemeResolver [" + this.themeResolver + "]");
+ }
+ }
+ catch (NoSuchBeanDefinitionException ex) {
+ // We need to use the default.
+ this.themeResolver = (ThemeResolver) getDefaultStrategy(context, ThemeResolver.class);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Unable to locate ThemeResolver with name '" + THEME_RESOLVER_BEAN_NAME +
+ "': using default [" + this.themeResolver + "]");
+ }
+ }
+ }
+
+ /**
+ * Initialize the HandlerMappings used by this class.
+ * If no HandlerMapping beans are defined in the BeanFactory
+ * for this namespace, we default to BeanNameUrlHandlerMapping.
+ */
+ private void initHandlerMappings(ApplicationContext context) {
+ this.handlerMappings = null;
+
+ if (this.detectAllHandlerMappings) {
+ // Find all HandlerMappings in the ApplicationContext,
+ // including ancestor contexts.
+ Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
+ context, HandlerMapping.class, true, false);
+ if (!matchingBeans.isEmpty()) {
+ this.handlerMappings = new ArrayList(matchingBeans.values());
+ // We keep HandlerMappings in sorted order.
+ Collections.sort(this.handlerMappings, new OrderComparator());
+ }
+ }
+ else {
+ try {
+ Object hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
+ this.handlerMappings = Collections.singletonList(hm);
+ }
+ catch (NoSuchBeanDefinitionException ex) {
+ // Ignore, we'll add a default HandlerMapping later.
+ }
+ }
+
+ // Ensure we have at least one HandlerMapping, by registering
+ // a default HandlerMapping if no other mappings are found.
+ if (this.handlerMappings == null) {
+ this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
+ if (logger.isDebugEnabled()) {
+ logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
+ }
+ }
+ }
+
+ /**
+ * Initialize the HandlerAdapters used by this class.
+ * If no HandlerAdapter beans are defined in the BeanFactory
+ * for this namespace, we default to SimpleControllerHandlerAdapter.
+ */
+ private void initHandlerAdapters(ApplicationContext context) {
+ this.handlerAdapters = null;
+
+ if (this.detectAllHandlerAdapters) {
+ // Find all HandlerAdapters in the ApplicationContext,
+ // including ancestor contexts.
+ Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
+ context, HandlerAdapter.class, true, false);
+ if (!matchingBeans.isEmpty()) {
+ this.handlerAdapters = new ArrayList(matchingBeans.values());
+ // We keep HandlerAdapters in sorted order.
+ Collections.sort(this.handlerAdapters, new OrderComparator());
+ }
+ }
+ else {
+ try {
+ Object ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
+ this.handlerAdapters = Collections.singletonList(ha);
+ }
+ catch (NoSuchBeanDefinitionException ex) {
+ // Ignore, we'll add a default HandlerAdapter later.
+ }
+ }
+
+ // Ensure we have at least some HandlerAdapters, by registering
+ // default HandlerAdapters if no other adapters are found.
+ if (this.handlerAdapters == null) {
+ this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
+ if (logger.isDebugEnabled()) {
+ logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
+ }
+ }
+ }
+
+ /**
+ * Initialize the HandlerExceptionResolver used by this class.
+ * If no bean is defined with the given name in the BeanFactory
+ * for this namespace, we default to no exception resolver.
+ */
+ private void initHandlerExceptionResolvers(ApplicationContext context) {
+ this.handlerExceptionResolvers = null;
+
+ if (this.detectAllHandlerExceptionResolvers) {
+ // Find all HandlerExceptionResolvers in the ApplicationContext,
+ // including ancestor contexts.
+ Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
+ context, HandlerExceptionResolver.class, true, false);
+ if (!matchingBeans.isEmpty()) {
+ this.handlerExceptionResolvers = new ArrayList(matchingBeans.values());
+ // We keep HandlerExceptionResolvers in sorted order.
+ Collections.sort(this.handlerExceptionResolvers, new OrderComparator());
+ }
+ }
+ else {
+ try {
+ Object her = context.getBean(
+ HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
+ this.handlerExceptionResolvers = Collections.singletonList(her);
+ }
+ catch (NoSuchBeanDefinitionException ex) {
+ // Ignore, no HandlerExceptionResolver is fine too.
+ }
+ }
+
+ // Just for consistency, check for default HandlerExceptionResolvers...
+ // There aren't any in usual scenarios.
+ if (this.handlerExceptionResolvers == null) {
+ this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
+ if (logger.isDebugEnabled()) {
+ logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
+ }
+ }
+ }
+
+ /**
+ * Initialize the RequestToViewNameTranslator used by this servlet instance. If no
+ * implementation is configured then we default to DefaultRequestToViewNameTranslator.
+ */
+ private void initRequestToViewNameTranslator(ApplicationContext context) {
+ try {
+ this.viewNameTranslator = (RequestToViewNameTranslator) context.getBean(
+ REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Using RequestToViewNameTranslator [" + this.viewNameTranslator + "]");
+ }
+ }
+ catch (NoSuchBeanDefinitionException ex) {
+ // We need to use the default.
+ this.viewNameTranslator =
+ (RequestToViewNameTranslator) getDefaultStrategy(context, RequestToViewNameTranslator.class);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Unable to locate RequestToViewNameTranslator with name '" +
+ REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME +
+ "': using default [" + this.viewNameTranslator + "]");
+ }
+ }
+ }
+
+ /**
+ * Initialize the ViewResolvers used by this class.
+ * If no ViewResolver beans are defined in the BeanFactory
+ * for this namespace, we default to InternalResourceViewResolver.
+ */
+ private void initViewResolvers(ApplicationContext context) {
+ this.viewResolvers = null;
+
+ if (this.detectAllViewResolvers) {
+ // Find all ViewResolvers in the ApplicationContext,
+ // including ancestor contexts.
+ Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
+ context, ViewResolver.class, true, false);
+ if (!matchingBeans.isEmpty()) {
+ this.viewResolvers = new ArrayList(matchingBeans.values());
+ // We keep ViewResolvers in sorted order.
+ Collections.sort(this.viewResolvers, new OrderComparator());
+ }
+ }
+ else {
+ try {
+ Object vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
+ this.viewResolvers = Collections.singletonList(vr);
+ }
+ catch (NoSuchBeanDefinitionException ex) {
+ // Ignore, we'll add a default ViewResolver later.
+ }
+ }
+
+ // Ensure we have at least one ViewResolver, by registering
+ // a default ViewResolver if no other resolvers are found.
+ if (this.viewResolvers == null) {
+ this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
+ if (logger.isDebugEnabled()) {
+ logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");
+ }
+ }
+ }
+
+ /**
+ * Return this servlet's ThemeSource, if any; else return Default is to return the WebApplicationContext as ThemeSource,
+ * provided that it implements the ThemeSource interface.
+ * @return the ThemeSource, if any
+ * @see #getWebApplicationContext()
+ */
+ public final ThemeSource getThemeSource() {
+ if (getWebApplicationContext() instanceof ThemeSource) {
+ return (ThemeSource) getWebApplicationContext();
+ }
+ else {
+ return null;
+ }
+ }
+
+ /**
+ * Obtain this servlet's MultipartResolver, if any.
+ * @return the MultipartResolver used by this servlet, or The default implementation delegates to {@link #getDefaultStrategies},
+ * expecting a single object in the list.
+ * @param context the current WebApplicationContext
+ * @param strategyInterface the strategy interface
+ * @return the corresponding strategy object
+ * @throws BeansException if initialization failed
+ * @see #getDefaultStrategies
+ */
+ protected Object getDefaultStrategy(ApplicationContext context, Class strategyInterface) throws BeansException {
+ List strategies = getDefaultStrategies(context, strategyInterface);
+ if (strategies.size() != 1) {
+ throw new BeanInitializationException(
+ "DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
+ }
+ return strategies.get(0);
+ }
+
+ /**
+ * Create a List of default strategy objects for the given strategy interface.
+ * The default implementation uses the "DispatcherServlet.properties" file
+ * (in the same package as the DispatcherServlet class) to determine the class names.
+ * It instantiates the strategy objects through the context's BeanFactory.
+ * @param context the current WebApplicationContext
+ * @param strategyInterface the strategy interface
+ * @return the List of corresponding strategy objects
+ * @throws BeansException if initialization failed
+ */
+ protected List getDefaultStrategies(ApplicationContext context, Class strategyInterface) throws BeansException {
+ String key = strategyInterface.getName();
+ List strategies = null;
+ String value = defaultStrategies.getProperty(key);
+ if (value != null) {
+ String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
+ strategies = new ArrayList(classNames.length);
+ for (int i = 0; i < classNames.length; i++) {
+ String className = classNames[i];
+ if (JdkVersion.getMajorJavaVersion() < JdkVersion.JAVA_15 && className.indexOf("Annotation") != -1) {
+ // Skip Java 5 specific strategies when running on JDK 1.4...
+ continue;
+ }
+ try {
+ Class clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
+ Object strategy = createDefaultStrategy(context, clazz);
+ strategies.add(strategy);
+ }
+ catch (ClassNotFoundException ex) {
+ throw new BeanInitializationException(
+ "Could not find DispatcherServlet's default strategy class [" + className +
+ "] for interface [" + key + "]", ex);
+ }
+ catch (LinkageError err) {
+ throw new BeanInitializationException(
+ "Error loading DispatcherServlet's default strategy class [" + className +
+ "] for interface [" + key + "]: problem with class file or dependent class", err);
+ }
+ }
+ }
+ else {
+ strategies = Collections.EMPTY_LIST;
+ }
+ return strategies;
+ }
+
+ /**
+ * Create a default strategy.
+ * The default implementation uses
+ * {@link org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean}.
+ * @param context the current WebApplicationContext
+ * @param clazz the strategy implementation class to instantiate
+ * @throws BeansException if initialization failed
+ * @return the fully configured strategy instance
+ * @see org.springframework.context.ApplicationContext#getAutowireCapableBeanFactory()
+ * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean
+ */
+ protected Object createDefaultStrategy(ApplicationContext context, Class clazz) throws BeansException {
+ return context.getAutowireCapableBeanFactory().createBean(clazz);
+ }
+
+
+ /**
+ * Exposes the DispatcherServlet-specific request attributes and
+ * delegates to {@link #doDispatch} for the actual dispatching.
+ */
+ protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
+ if (logger.isDebugEnabled()) {
+ String requestUri = new UrlPathHelper().getRequestUri(request);
+ logger.debug("DispatcherServlet with name '" + getServletName() +
+ "' processing request for [" + requestUri + "]");
+ }
+
+ // Keep a snapshot of the request attributes in case of an include,
+ // to be able to restore the original attributes after the include.
+ Map attributesSnapshot = null;
+ if (WebUtils.isIncludeRequest(request)) {
+ logger.debug("Taking snapshot of request attributes before include");
+ attributesSnapshot = new HashMap();
+ Enumeration attrNames = request.getAttributeNames();
+ while (attrNames.hasMoreElements()) {
+ String attrName = (String) attrNames.nextElement();
+ if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
+ attributesSnapshot.put(attrName, request.getAttribute(attrName));
+ }
+ }
+ }
+
+ // Make framework objects available to handlers and view objects.
+ request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
+ request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
+ request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
+ request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
+
+ try {
+ doDispatch(request, response);
+ }
+ finally {
+ // Restore the original attribute snapshot, in case of an include.
+ if (attributesSnapshot != null) {
+ restoreAttributesAfterInclude(request, attributesSnapshot);
+ }
+ }
+ }
+
+ /**
+ * Process the actual dispatching to the handler.
+ * The handler will be obtained by applying the servlet's HandlerMappings in order.
+ * The HandlerAdapter will be obtained by querying the servlet's installed
+ * HandlerAdapters to find the first that supports the handler class.
+ * All HTTP methods are handled by this method. It's up to HandlerAdapters or
+ * handlers themselves to decide which methods are acceptable.
+ * @param request current HTTP request
+ * @param response current HTTP response
+ * @throws Exception in case of any kind of processing failure
+ */
+ protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
+ HttpServletRequest processedRequest = request;
+ HandlerExecutionChain mappedHandler = null;
+ int interceptorIndex = -1;
+
+ // 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 = new ServletRequestAttributes(request);
+ RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("Bound request context to thread: " + request);
+ }
+
+ try {
+ ModelAndView mv = null;
+ boolean errorView = false;
+
+ try {
+ processedRequest = checkMultipart(request);
+
+ // Determine handler for the current request.
+ mappedHandler = getHandler(processedRequest, false);
+ if (mappedHandler == null || mappedHandler.getHandler() == null) {
+ noHandlerFound(processedRequest, response);
+ return;
+ }
+
+ // Apply preHandle methods of registered interceptors.
+ HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
+ if (interceptors != null) {
+ for (int i = 0; i < interceptors.length; i++) {
+ HandlerInterceptor interceptor = interceptors[i];
+ if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
+ triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
+ return;
+ }
+ interceptorIndex = i;
+ }
+ }
+
+ // Actually invoke the handler.
+ HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
+ mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
+
+ // Do we need view name translation?
+ if (mv != null && !mv.hasView()) {
+ mv.setViewName(getDefaultViewName(request));
+ }
+
+ // Apply postHandle methods of registered interceptors.
+ if (interceptors != null) {
+ for (int i = interceptors.length - 1; i >= 0; i--) {
+ HandlerInterceptor interceptor = interceptors[i];
+ interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
+ }
+ }
+ }
+ catch (ModelAndViewDefiningException ex) {
+ logger.debug("ModelAndViewDefiningException encountered", ex);
+ mv = ex.getModelAndView();
+ }
+ catch (Exception ex) {
+ Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
+ mv = processHandlerException(processedRequest, response, handler, ex);
+ errorView = (mv != null);
+ }
+
+ // Did the handler return a view to render?
+ if (mv != null && !mv.wasCleared()) {
+ render(mv, processedRequest, response);
+ if (errorView) {
+ WebUtils.clearErrorRequestAttributes(request);
+ }
+ }
+ else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Null ModelAndView returned to DispatcherServlet with name '" +
+ getServletName() + "': assuming HandlerAdapter completed request handling");
+ }
+ }
+
+ // Trigger after-completion for successful outcome.
+ triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
+ }
+
+ catch (Exception ex) {
+ // Trigger after-completion for thrown exception.
+ triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
+ throw ex;
+ }
+ catch (Error err) {
+ ServletException ex = new NestedServletException("Handler processing failed", err);
+ // Trigger after-completion for thrown exception.
+ triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
+ throw ex;
+ }
+
+ finally {
+ // Clean up any resources used by a multipart request.
+ if (processedRequest != request) {
+ cleanupMultipart(processedRequest);
+ }
+
+ // Reset thread-bound context.
+ RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
+ LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
+
+ // Clear request attributes.
+ requestAttributes.requestCompleted();
+ if (logger.isTraceEnabled()) {
+ logger.trace("Cleared thread-bound request context: " + request);
+ }
+ }
+ }
+
+ /**
+ * Override HttpServlet's The default implementation uses the dispatcher's LocaleResolver
+ * to obtain the current locale, which might change during a request.
+ * @param request current HTTP request
+ * @return the corresponding LocaleContext
+ */
+ protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
+ return new LocaleContext() {
+ public Locale getLocale() {
+ return localeResolver.resolveLocale(request);
+ }
+ public String toString() {
+ return getLocale().toString();
+ }
+ };
+ }
+
+ /**
+ * Convert the request into a multipart request, and make multipart resolver available.
+ * If no multipart resolver is set, simply use the existing request.
+ * @param request current HTTP request
+ * @return the processed request (multipart wrapper if necessary)
+ * @see MultipartResolver#resolveMultipart
+ */
+ protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
+ if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
+ if (request instanceof MultipartHttpServletRequest) {
+ logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
+ "this typically results from an additional MultipartFilter in web.xml");
+ }
+ else {
+ return this.multipartResolver.resolveMultipart(request);
+ }
+ }
+ // If not returned before: return original request.
+ return request;
+ }
+
+ /**
+ * Clean up any resources used by the given multipart request (if any).
+ * @param request current HTTP request
+ * @see MultipartResolver#cleanupMultipart
+ */
+ protected void cleanupMultipart(HttpServletRequest request) {
+ if (request instanceof MultipartHttpServletRequest) {
+ this.multipartResolver.cleanupMultipart((MultipartHttpServletRequest) request);
+ }
+ }
+
+ /**
+ * Return the HandlerExecutionChain for this request.
+ * Try all handler mappings in order.
+ * @param request current HTTP request
+ * @param cache whether to cache the HandlerExecutionChain in a request attribute
+ * @return the HandlerExceutionChain, or Default implementations asks all ViewResolvers of this dispatcher.
+ * Can be overridden for custom resolution strategies, potentially based
+ * on specific model attributes or request parameters.
+ * @param viewName the name of the view to resolve
+ * @param model the model to be passed to the view
+ * @param locale the current locale
+ * @param request current HTTP servlet request
+ * @return the View object, or This class offers the following functionality:
+ * 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.
+ *
+ * Detects a "contextClass" parameter at the servlet init-param level,
+ * falling back to the default context class,
+ * {@link org.springframework.web.context.support.XmlWebApplicationContext},
+ * if not found. Note that, with the default FrameworkServlet,
+ * a custom context class needs to implement the
+ * {@link org.springframework.web.context.ConfigurableWebApplicationContext} SPI.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @see #doService
+ * @see #setContextClass
+ * @see #setContextConfigLocation
+ * @see #setNamespace
+ */
+public abstract class FrameworkServlet extends HttpServletBean implements ApplicationListener {
+
+ /**
+ * 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.";
+
+
+ /** ServletContext attribute to find the WebApplicationContext in */
+ private String contextAttribute;
+
+ /** WebApplicationContext implementation class to create */
+ private Class contextClass = DEFAULT_CONTEXT_CLASS;
+
+ /** 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;
+
+ /** 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;
+
+
+ /**
+ * 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}.
+ * 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;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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".
+ * 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 this servlet should dispatch an HTTP OPTIONS request to
+ * the {@link #doService} method.
+ * 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).
+ * 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.
+ * Note that HttpServlet's default OPTIONS processing will be applied
+ * in any case. Your controllers are simply available to override the
+ * default headers and optionally generate a response body.
+ */
+ public void setDispatchOptionsRequest(boolean dispatchOptionsRequest) {
+ this.dispatchOptionsRequest = dispatchOptionsRequest;
+ }
+
+ /**
+ * Set whether this servlet should dispatch an HTTP TRACE request to
+ * the {@link #doService} method.
+ * Default is "false", applying {@link javax.servlet.http.HttpServlet}'s
+ * default behavior (i.e. reflecting the message received back to the client).
+ * 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.
+ * Note that HttpServlet's default TRACE processing will be applied
+ * in any case. Your controllers are simply available to override the
+ * default headers and the default body, calling Delegates to {@link #createWebApplicationContext} for actual creation
+ * of the context. Can be overridden in subclasses.
+ * @return the WebApplicationContext instance
+ * @throws BeansException if the context couldn't be initialized
+ * @see #setContextClass
+ * @see #setContextConfigLocation
+ */
+ protected WebApplicationContext initWebApplicationContext() throws BeansException {
+ WebApplicationContext wac = findWebApplicationContext();
+ if (wac == null) {
+ // No fixed context defined for this servlet - create a local one.
+ WebApplicationContext parent =
+ WebApplicationContextUtils.getWebApplicationContext(getServletContext());
+ wac = createWebApplicationContext(parent);
+ }
+
+ if (!this.refreshEventReceived) {
+ // Apparently not a ConfigurableApplicationContext with refresh support:
+ // triggering 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 Subclasses may override this method to provide a different
+ * This implementation expects custom contexts to implement the
+ * {@link org.springframework.web.context.ConfigurableWebApplicationContext}
+ * interface. Can be overridden in subclasses.
+ * 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 The default implementation is empty. The default implementation returns
+ * The default implementation calls {@link #onRefresh} in case of a
+ * {@link org.springframework.context.event.ContextRefreshedEvent},
+ * triggering a refresh of this servlet's context-dependent state.
+ * @param event the incoming ApplicationContext event
+ */
+ public void onApplicationEvent(ApplicationEvent event) {
+ if (event instanceof ContextRefreshedEvent) {
+ this.refreshEventReceived = true;
+ onRefresh(((ContextRefreshedEvent) event).getApplicationContext());
+ }
+ }
+
+ /**
+ * Template method which can be overridden to add servlet-specific refresh work.
+ * Called after successful context refresh.
+ * This implementation is empty.
+ * @param context the current WebApplicationContext
+ * @throws BeansException in case of errors
+ * @see #refresh()
+ */
+ protected void onRefresh(ApplicationContext context) throws BeansException {
+ // For subclasses: do nothing by default.
+ }
+
+
+ /**
+ * Delegate GET requests to processRequest/doService.
+ * Will also be invoked by HttpServlet's default implementation of Applies HttpServlet's standard OPTIONS processing first.
+ * @see #doService
+ */
+ protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ super.doOptions(request, response);
+ if (this.dispatchOptionsRequest) {
+ processRequest(request, response);
+ }
+ }
+
+ /**
+ * Delegate TRACE requests to {@link #processRequest}, if desired.
+ * Applies HttpServlet's standard TRACE processing first.
+ * @see #doService
+ */
+ protected void doTrace(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ super.doTrace(request, response);
+ if (this.dispatchTraceRequest) {
+ processRequest(request, response);
+ }
+ }
+
+
+ /**
+ * Process this request, publishing an event regardless of the outcome.
+ * 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;
+
+ 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 {
+ 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));
+ }
+ }
+ }
+
+ /**
+ * Determine the username for the given request.
+ * 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 The contract is essentially the same as that for the commonly overridden
+ * 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()
+ */
+ public void destroy() {
+ getServletContext().log("Destroying Spring FrameworkServlet '" + getServletName() + "'");
+ if (this.webApplicationContext instanceof ConfigurableApplicationContext) {
+ ((ConfigurableApplicationContext) this.webApplicationContext).close();
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerAdapter.java
new file mode 100644
index 00000000000..fe61230af39
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerAdapter.java
@@ -0,0 +1,86 @@
+/*
+ * 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;
+
+/**
+ * MVC framework SPI interface, allowing parameterization of core MVC workflow.
+ *
+ * Interface that must be implemented for each handler type to handle a request.
+ * This interface is used to allow the 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.
+ *
+ * Note that a handler can be of type Object. This is to enable handlers from
+ * other frameworks to be integrated with this framework without custom coding.
+ *
+ * This interface is not intended for application developers. It is available
+ * to handlers who want to develop their own web workflow.
+ *
+ * Note: Implementations can implement the 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.mvc.throwaway.ThrowawayControllerHandlerAdapter
+ * @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.
+ * A typical implementation:
+ * 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.
+ * @param request current HTTP request
+ * @param response current HTTP response
+ * @param handler the executed handler, or 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.
+ *
+ * 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 <list> of <ref>).
+ *
+ * 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.
+ *
+ * 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.
+ * 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 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 Note: Will only be called if this interceptor's 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.
+ *
+ * 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
+ * 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.
+ *
+ * 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.
+ * 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";
+
+
+ /**
+ * 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.
+ * 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.
+ * Returns 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.
+ *
+ * This servlet leaves request handling to subclasses, inheriting the default
+ * behavior of HttpServlet ( 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}).
+ *
+ * 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
+ */
+public abstract class HttpServletBean extends HttpServlet {
+
+ /** 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 requiredProperties = new HashSet();
+
+
+ /**
+ * 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.
+ * 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.
+ */
+ 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));
+ 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.
+ * 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 This default implementation is empty.
+ * @throws ServletException if subclass initialization fails
+ */
+ protected void initServletBean() throws ServletException {
+ }
+
+
+ /**
+ * 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 requiredProperties)
+ throws ServletException {
+
+ Set missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?
+ new HashSet(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, ", "));
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/LocaleResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/LocaleResolver.java
new file mode 100644
index 00000000000..7a953e6e889
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/LocaleResolver.java
@@ -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.
+ *
+ * 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.
+ *
+ * Use 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;
+
+
+ /**
+ * 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 Can be used to suppress rendering of a given ModelAndView object
+ * in the 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;
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/RequestToViewNameTranslator.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/RequestToViewNameTranslator.java
new file mode 100644
index 00000000000..dc534599616
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/RequestToViewNameTranslator.java
@@ -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 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).
+ *
+ * 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.
+ *
+ * The The "resource" parameter and the The If using this servlet for direct access rather than via includes,
+ * the To apply last-modified timestamps for the target resource, set the
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ * 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.
+ * 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".
+ * 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.
+ * 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.
+ */
+ 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
+ */
+ 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.
+ * 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 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.
+ * 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
+ */
+ 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;
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ThemeResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ThemeResolver.java
new file mode 100644
index 00000000000..c1463fc9e94
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ThemeResolver.java
@@ -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.
+ *
+ * This interface allows for implementations based on session,
+ * cookies, etc. The default implementation is FixedThemeResolver,
+ * simply using a configured default theme.
+ *
+ * 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.
+ *
+ * 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);
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/View.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/View.java
new file mode 100644
index 00000000000..aa7550deeb8
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/View.java
@@ -0,0 +1,68 @@
+/*
+ * 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 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.
+ *
+ * This class and the MVC approach associated with it is discussed in Chapter 12 of
+ * Expert One-On-One J2EE Design and Development
+ * by Rod Johnson (Wrox, 2002).
+ *
+ * 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.
+ *
+ * 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
+ * @see org.springframework.web.servlet.view.AbstractView
+ * @see org.springframework.web.servlet.view.InternalResourceView
+ */
+public interface View {
+
+ /**
+ * Return the content type of the view, if predetermined.
+ * 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 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 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.
+ *
+ * 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";
+
+
+ protected final void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+
+ processRequest(request, response);
+ }
+
+ 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
+ * View state doesn't change during the running of the application,
+ * so implementations are free to cache views.
+ *
+ * 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.
+ * Note: To allow for ViewResolver chaining, a ViewResolver should
+ * return 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).
+ * 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.
+ */
+ public void initApplicationContext() throws ApplicationContextException {
+ super.initApplicationContext();
+ detectHandlers();
+ }
+
+ /**
+ * Register all handlers found in the current ApplicationContext.
+ * 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 (int i = 0; i < beanNames.length; i++) {
+ String beanName = beanNames[i];
+ 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 '" + beanNames[i] + "': 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 Note: This base class does not 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
+ * @since 07.04.2003
+ * @see #getHandlerInternal
+ * @see #setDefaultHandler
+ * @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 final List interceptors = new ArrayList();
+
+ private HandlerInterceptor[] adaptedInterceptors;
+
+
+ /**
+ * Specify the order value for this HandlerMapping bean.
+ * Default value is Default is Supported interceptor types are HandlerInterceptor and WebRequestInterceptor.
+ * @param interceptors array of handler interceptors, or Will be invoked before {@link #initInterceptors()} adapts the specified
+ * interceptors into {@link HandlerInterceptor} instances.
+ * The default implementation is empty.
+ * @param interceptors the configured interceptor List (never 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 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 The default implementation simply builds a standard HandlerExecutionChain with
+ * the given handler and this handler mapping's common interceptors. Subclasses may
+ * override this in order to extend/rearrange the list of interceptors.
+ * NOTE: 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.
+ * For simply adding an interceptor, consider calling 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.
+ *
+ * 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
+ * @since 16.04.2003
+ * @see #setAlwaysUseFullPath
+ * @see #setUrlDecode
+ * @see org.springframework.util.AntPathMatcher
+ */
+public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
+
+ private UrlPathHelper urlPathHelper = new UrlPathHelper();
+
+ private PathMatcher pathMatcher = new AntPathMatcher();
+
+ private Object rootHandler;
+
+ private boolean lazyInitHandlers = false;
+
+ private final Map handlerMap = new LinkedHashMap();
+
+
+ /**
+ * 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).
+ * 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
+ * undecoded by the Servlet API, in contrast to the servlet path.
+ * 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.
+ * Use this to override the default UrlPathHelper with a custom subclass,
+ * or to share common UrlPathHelper settings across multiple HandlerMappings
+ * and MethodNameResolvers.
+ * @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;
+ }
+
+ /**
+ * 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 root handler for this handler mapping, that is,
+ * the handler to be registered for the root path ("/").
+ * Default is 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 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.
+ * 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 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 as well as the {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE}
+ * before executing the handler.
+ * The default implementation builds a {@link HandlerExecutionChain}
+ * with a special interceptor that exposes the path attribute.
+ * @param rawHandler the raw handler to expose
+ * @param pathWithinMapping the path to expose before executing the handler
+ * @return the final handler object
+ */
+ protected Object buildPathExposingHandler(Object rawHandler, String pathWithinMapping) {
+ // Bean name or resolved handler?
+ if (rawHandler instanceof String) {
+ String handlerName = (String) rawHandler;
+ rawHandler = getApplicationContext().getBean(handlerName);
+ }
+ HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
+ chain.addInterceptor(new PathExposingHandlerInterceptor(pathWithinMapping));
+ 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 pathWithinMapping, HttpServletRequest request) {
+ request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping);
+ }
+
+
+ /**
+ * 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 (int j = 0; j < urlPaths.length; j++) {
+ registerHandler(urlPaths[j], 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 handler [" + handler + "] to URL path [" + urlPath +
+ "]: There is already handler [" + resolvedHandler + "] mapped.");
+ }
+ }
+ else {
+ if (urlPath.equals("/")) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Root mapping to handler [" + resolvedHandler + "]");
+ }
+ setRootHandler(resolvedHandler);
+ }
+ else if (urlPath.equals("/*")) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Default mapping to handler [" + resolvedHandler + "]");
+ }
+ setDefaultHandler(resolvedHandler);
+ }
+ else {
+ this.handlerMap.put(urlPath, resolvedHandler);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Mapped URL path [" + urlPath + "] onto handler [" + resolvedHandler + "]");
+ }
+ }
+ }
+ }
+
+
+ /**
+ * 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 getHandlerMap() {
+ return Collections.unmodifiableMap(this.handlerMap);
+ }
+
+
+ /**
+ * Special interceptor for exposing the
+ * {@link AbstractUrlHandlerMapping#PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE} attribute.
+ * @link AbstractUrlHandlerMapping#exposePathWithinMapping
+ */
+ private class PathExposingHandlerInterceptor extends HandlerInterceptorAdapter {
+
+ private final String pathWithinMapping;
+
+ public PathExposingHandlerInterceptor(String pathWithinMapping) {
+ this.pathWithinMapping = pathWithinMapping;
+ }
+
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+ exposePathWithinMapping(this.pathWithinMapping, request);
+ return true;
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/BeanNameUrlHandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/BeanNameUrlHandlerMapping.java
new file mode 100644
index 00000000000..388a9c54b2f
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/BeanNameUrlHandlerMapping.java
@@ -0,0 +1,70 @@
+/*
+ * 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.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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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 "/".
+ */
+ protected String[] determineUrlsForHandler(String beanName) {
+ List urls = new ArrayList();
+ if (beanName.startsWith("/")) {
+ urls.add(beanName);
+ }
+ String[] aliases = getApplicationContext().getAliases(beanName);
+ for (int i = 0; i < aliases.length; i++) {
+ if (aliases[i].startsWith("/")) {
+ urls.add(aliases[i]);
+ }
+ }
+ return StringUtils.toStringArray(urls);
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/DispatcherServletWebRequest.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/DispatcherServletWebRequest.java
new file mode 100644
index 00000000000..920fcc36260
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/DispatcherServletWebRequest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.Locale;
+
+import javax.servlet.http.HttpServletRequest;
+
+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);
+ }
+
+ public Locale getLocale() {
+ return RequestContextUtils.getLocale(getRequest());
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/HandlerInterceptorAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/HandlerInterceptorAdapter.java
new file mode 100644
index 00000000000..03b04274a2d
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/HandlerInterceptorAdapter.java
@@ -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 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
+ * @since 22.11.2003
+ * @see org.springframework.web.servlet.DispatcherServlet
+ */
+public class SimpleMappingExceptionResolver implements HandlerExceptionResolver, Ordered {
+
+ /**
+ * The default name of the exception attribute: "exception".
+ */
+ public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";
+
+
+ /** Logger available to subclasses */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ private int order = Integer.MAX_VALUE; // default: same as non-Ordered
+
+ private Set mappedHandlers;
+
+ private Class[] mappedHandlerClasses;
+
+ private Log warnLogger;
+
+ private Properties exceptionMappings;
+
+ private String defaultErrorView;
+
+ private Integer defaultStatusCode;
+
+ private String exceptionAttribute = DEFAULT_EXCEPTION_ATTRIBUTE;
+
+
+ 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.
+ * 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.
+ * 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.
+ * 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);
+ }
+
+ /**
+ * 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
+ * NB: 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.
+ * Follows the same matching algorithm as RuleBasedTransactionAttribute
+ * and RollbackRuleAttribute.
+ * @param mappings exception patterns (can also be fully qualified class names)
+ * as keys, and error view names as values
+ * @see org.springframework.transaction.interceptor.RuleBasedTransactionAttribute
+ * @see org.springframework.transaction.interceptor.RollbackRuleAttribute
+ */
+ 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.
+ * Default is none.
+ */
+ public void setDefaultErrorView(String defaultErrorView) {
+ this.defaultErrorView = defaultErrorView;
+ }
+
+ /**
+ * Set the default HTTP status code that this exception resolver will apply
+ * if it resolves an error view.
+ * 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.
+ * 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 (SC_INTERNAL_SERVER_ERROR) or 404 (SC_NOT_FOUND)
+ * @see javax.servlet.http.HttpServletResponse#SC_INTERNAL_SERVER_ERROR
+ * @see javax.servlet.http.HttpServletResponse#SC_NOT_FOUND
+ */
+ public void setDefaultStatusCode(int defaultStatusCode) {
+ this.defaultStatusCode = new Integer(defaultStatusCode);
+ }
+
+ /**
+ * Set the name of the model attribute as which the exception should
+ * be exposed. Default is "exception".
+ * This can be either set to a different attribute name or to
+ * 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 May be overridden in subclasses, in order to apply specific exception checks.
+ * Note that this template method will be invoked after 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 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";
+ }
+
+
+ /**
+ * 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 0 means ex matches exactly. Returns -1 if there's no match.
+ * Otherwise, returns depth. Lowest depth wins.
+ * Follows the same algorithm as
+ * {@link org.springframework.transaction.interceptor.RollbackRuleAttribute}.
+ */
+ 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().indexOf(exceptionMapping) != -1) {
+ // 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.
+ * The default implementation always returns the specified
+ * {@link #setDefaultStatusCode "defaultStatusCode"}, as a common
+ * status code for all error views. Override this in a custom subclass
+ * to determine a specific status code for the given view.
+ * @param request current HTTP request
+ * @param viewName the name of the error view
+ * @return the HTTP status code to use, or 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.
+ * 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;
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/SimpleServletHandlerAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/SimpleServletHandlerAdapter.java
new file mode 100644
index 00000000000..189e674d75f
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/SimpleServletHandlerAdapter.java
@@ -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 Last-modified checking is not explicitly supported: This is typically
+ * handled by the Servlet implementation itself (usually deriving from
+ * the HttpServlet base class).
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Alternatively, consider wrapping a Servlet with Spring's
+ * ServletWrappingController. 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;
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/SimpleServletPostProcessor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/SimpleServletPostProcessor.java
new file mode 100644
index 00000000000..37b7dddeb90
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/SimpleServletPostProcessor.java
@@ -0,0 +1,157 @@
+/*
+ * 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.handler;
+
+import java.util.Collections;
+import java.util.Enumeration;
+
+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.
+ *
+ * After initialization of the bean instance, the Servlet Before destruction of the bean instance, the Servlet Note that this post-processor does not support Servlet initialization
+ * parameters. 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.
+ *
+ * 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.
+ *
+ * Alternatively, consider wrapping a Servlet with Spring's
+ * {@link org.springframework.web.servlet.mvc.ServletWrappingController}.
+ * 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 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 getInitParameterNames() {
+ return Collections.enumeration(Collections.EMPTY_SET);
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/SimpleUrlHandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/SimpleUrlHandlerMapping.java
new file mode 100644
index 00000000000..e6a5c6619f1
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/SimpleUrlHandlerMapping.java
@@ -0,0 +1,134 @@
+/*
+ * 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.handler;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+
+import org.springframework.beans.BeansException;
+
+/**
+ * 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.
+ *
+ * The "urlMap" property is suitable for populating the handler map with
+ * bean references, e.g. via the map element in XML bean definitions.
+ *
+ * Mappings to bean names can be set via the "mappings" property, in a form
+ * accepted by the 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 urlMap = new HashMap();
+
+
+ /**
+ * Map URL paths to handler bean names.
+ * This is the typical way of configuring this HandlerMapping.
+ * 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) {
+ this.urlMap.putAll(mappings);
+ }
+
+ /**
+ * Set a Map with URL paths as keys and handler beans (or handler bean names)
+ * as values. Convenient for population with bean references.
+ * 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 urlMap) {
+ this.urlMap.putAll(urlMap);
+ }
+
+ /**
+ * Allow Map access to the URL path mappings, with the option to add or
+ * override specific entries.
+ * 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 getUrlMap() {
+ return this.urlMap;
+ }
+
+
+ /**
+ * Calls the {@link #registerHandlers} method in addition to the
+ * superclass's initialization.
+ */
+ 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 urlMap) throws BeansException {
+ if (urlMap.isEmpty()) {
+ logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
+ }
+ else {
+ Iterator it = urlMap.keySet().iterator();
+ while (it.hasNext()) {
+ String url = (String) it.next();
+ Object handler = urlMap.get(url);
+ // 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);
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/UserRoleAuthorizationInterceptor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/UserRoleAuthorizationInterceptor.java
new file mode 100644
index 00000000000..1144b368b1e
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/UserRoleAuthorizationInterceptor.java
@@ -0,0 +1,78 @@
+/*
+ * 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 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;
+ }
+
+
+ public final boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
+ throws ServletException, IOException {
+
+ if (this.authorizedRoles != null) {
+ for (int i = 0; i < this.authorizedRoles.length; i++) {
+ if (request.isUserInRole(this.authorizedRoles[i])) {
+ 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").
+ * 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);
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/WebRequestHandlerInterceptorAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/WebRequestHandlerInterceptorAdapter.java
new file mode 100644
index 00000000000..b1f5ea31815
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/WebRequestHandlerInterceptorAdapter.java
@@ -0,0 +1,71 @@
+/*
+ * 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.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));
+ return true;
+ }
+
+ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
+ throws Exception {
+
+ this.requestInterceptor.postHandle(new DispatcherServletWebRequest(request),
+ (modelAndView != null ? modelAndView.getModelMap() : null));
+ }
+
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
+ throws Exception {
+
+ this.requestInterceptor.afterCompletion(new DispatcherServletWebRequest(request), ex);
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/metadata/AbstractPathMapHandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/metadata/AbstractPathMapHandlerMapping.java
new file mode 100644
index 00000000000..25fed95c6f5
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/metadata/AbstractPathMapHandlerMapping.java
@@ -0,0 +1,201 @@
+/*
+ * 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.metadata;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanInitializationException;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.Constants;
+import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
+
+/**
+ * Abstract implementation of the HandlerMapping interface that recognizes
+ * metadata attributes of type PathMap on application Controllers and automatically
+ * wires them into the current servlet's WebApplicationContext.
+ *
+ * The path must be mapped to the relevant Spring DispatcherServlet in /WEB-INF/web.xml.
+ * It's possible to have multiple PathMap attributes on the one controller class.
+ *
+ * Controllers instantiated by this class may have dependencies on middle tier
+ * objects, expressed via JavaBean properties or constructor arguments. These will
+ * be resolved automatically.
+ *
+ * You will normally use this HandlerMapping with at most one DispatcherServlet in your
+ * web application. Otherwise you'll end with one instance of the mapped controller for
+ * each DispatcherServlet's context. You might want this -- for example, if
+ * one's using a .pdf mapping and a PDF view, and another a JSP view, or if
+ * using different middle tier objects, but should understand the implications. All
+ * Controllers with attributes will be picked up by each DispatcherServlet's context.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @deprecated as of Spring 2.5, in favor of annotation-based request mapping.
+ * To be removed in Spring 3.0.
+ */
+public abstract class AbstractPathMapHandlerMapping extends AbstractUrlHandlerMapping {
+
+ /** Constants instance for AutowireCapableBeanFactory */
+ private static final Constants constants = new Constants(AutowireCapableBeanFactory.class);
+
+ private int autowireMode = AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT;
+
+ private boolean dependencyCheck = true;
+
+
+ /**
+ * Set the autowire mode for handlers, by the name of the corresponding constant
+ * in the AutowireCapableBeanFactory interface, e.g. "AUTOWIRE_BY_NAME".
+ * @param constantName name of the constant
+ * @throws java.lang.IllegalArgumentException if an invalid constant was specified
+ * @see #setAutowireMode
+ * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_NAME
+ * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_TYPE
+ * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_CONSTRUCTOR
+ * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_AUTODETECT
+ */
+ public void setAutowireModeName(String constantName) throws IllegalArgumentException {
+ setAutowireMode(constants.asNumber(constantName).intValue());
+ }
+
+ /**
+ * Set the autowire mode for handlers. This determines whether any automagical
+ * detection and setting of bean references will happen.
+ * Default is AUTOWIRE_AUTODETECT, which means either constructor autowiring or
+ * autowiring by type (depending on the constructors available in the class).
+ * @param autowireMode the autowire mode to set.
+ * Must be one of the constants defined in the AutowireCapableBeanFactory interface.
+ * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_NAME
+ * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_TYPE
+ * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_CONSTRUCTOR
+ * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_AUTODETECT
+ */
+ public void setAutowireMode(int autowireMode) {
+ this.autowireMode = autowireMode;
+ }
+
+ /**
+ * Set whether to perform a dependency check for objects on autowired handlers.
+ * Not applicable to autowiring a constructor, thus ignored there.
+ * Default is "true".
+ */
+ public void setDependencyCheck(boolean dependencyCheck) {
+ this.dependencyCheck = dependencyCheck;
+ }
+
+
+ /**
+ * Calls the
+ * Controllers must have class attributes of the form:
+ * The path must be mapped to the relevant Spring DispatcherServlet in /WEB-INF/web.xml.
+ * It's possible to have multiple PathMap attributes on the one controller class.
+ *
+ * To use this feature, you must compile application classes with Commons Attributes,
+ * and run the Commons Attributes indexer tool on your application classes, which must
+ * be in a Jar rather than in WEB-INF/classes.
+ *
+ * Controllers instantiated by this class may have dependencies on middle tier
+ * objects, expressed via JavaBean properties or constructor arguments. These will
+ * be resolved automatically.
+ *
+ * You will normally use this HandlerMapping with at most one DispatcherServlet in
+ * your web application. Otherwise you'll end with one instance of the mapped controller
+ * for each DispatcherServlet's context. You might want this--for example, if
+ * one's using a .pdf mapping and a PDF view, and another a JSP view, or if using
+ * different middle tier objects, but should understand the implications. All
+ * Controllers with attributes will be picked up by each DispatcherServlet's context.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @deprecated as of Spring 2.5, in favor of annotation-based request mapping.
+ * To be removed in Spring 3.0.
+ */
+public class CommonsPathMapHandlerMapping extends AbstractPathMapHandlerMapping {
+
+ /**
+ * Use Commons Attributes AttributeIndex to get a Collection of Class
+ * objects with the required PathMap attribute. Protected so that it can
+ * be overridden during testing.
+ */
+ protected Class[] getClassesWithPathMapAttributes() throws Exception {
+ AttributeIndex ai = new AttributeIndex(getClass().getClassLoader());
+ Collection classes = ai.getClasses(PathMap.class);
+ return (Class[]) classes.toArray(new Class[classes.size()]);
+ }
+
+ /**
+ * Use Commons Attributes to find PathMap attributes for the given class.
+ * We know there's at least one, as the getClassNamesWithPathMapAttributes
+ * method return this class name.
+ */
+ protected PathMap[] getPathMapAttributes(Class handlerClass) {
+ Collection atts = Attributes.getAttributes(handlerClass, PathMap.class);
+ return (PathMap[]) atts.toArray(new PathMap[atts.size()]);
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/metadata/PathMap.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/metadata/PathMap.java
new file mode 100644
index 00000000000..f838094843c
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/metadata/PathMap.java
@@ -0,0 +1,58 @@
+/*
+ * 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.metadata;
+
+/**
+ * Attribute to be used on Controller classes to allow for automatic URL mapping
+ * without web controllers being defined as beans in an XML bean definition file.
+ *
+ * The path map should be the path in the current application, such as /foo.cgi.
+ * If there is no leading "/", one will be prepended.
+ *
+ * Application code must use the Commons Attributes indexer tool to use this option.
+ *
+ * @author Rod Johnson
+ * @deprecated as of Spring 2.5, in favor of annotation-based request mapping.
+ * To be removed in Spring 3.0.
+ * @@org.apache.commons.attributes.Indexed()
+ */
+public class PathMap {
+
+ /*
+ * NB: The Indexed attribute on this class is required. Thus the Spring jar
+ * must be built including a Commons Attributes attribute compilation step
+ * for this class.
+ */
+
+ private final String url;
+
+
+ /**
+ * Create a new PathMap attribute for the given URL.
+ */
+ public PathMap(String url) {
+ this.url = url;
+ }
+
+ /**
+ * Return the URL that this attribute indicates.
+ */
+ public String getUrl() {
+ return this.url;
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/metadata/package.html b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/metadata/package.html
new file mode 100644
index 00000000000..6ffef6dc50e
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/metadata/package.html
@@ -0,0 +1,37 @@
+
+
+To use this feature, using the Commons Attributes implementation, perform the following steps:
+
+You can also use other HandlerMappings, such as BeanNameHandlerMapping, in the
+same servlet XML file.
+
+
+
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/package.html b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/package.html
new file mode 100644
index 00000000000..56ea63f53ed
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/package.html
@@ -0,0 +1,8 @@
+
+ Note: Does not support This is particularly useful for stateless applications without user sessions.
+ *
+ * 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.
+ * 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.
+ * 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 Note: Does not support 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.
+ *
+ * Custom controllers can override the user's locale by calling
+ * 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 Autopopulates a command bean from the request. For command validation,
+ * a validator (property inherited from {@link BaseCommandController}) can be
+ * used.
+ *
+ * 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.
+ *
+ * Exposed configuration properties
+ * (and those defined by superclass): Workflow
+ * (and that defined by superclass): Call Convenient superclass for controller implementations, using the Template
+ * Method design pattern. 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). Workflow
+ * (and that defined by interface): Exposed configuration properties
+ * (and those defined by interface): More specifically, the execution of the The session mutex is guaranteed to be the same object during
+ * the entire lifetime of the session, available under the key defined
+ * by the 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 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 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. 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}. Subclasses need to override Comparing this Controller to the Struts notion of the Workflow
+ * (and that defined by superclass): 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. 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. Exposed configuration properties
+ * (and those defined by superclass): 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.
+ * "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 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.
+ * Please note that the {@link AbstractFormController} class (and all
+ * subclasses of it unless stated to the contrary) do not support
+ * the notion of a conversation. This is important in the context of this
+ * property, because it means that there is only one 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 shared 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.
+ * If you need to have per-form, per-session state management (that is,
+ * stateful web conversations), the recommendation is to use
+ * Spring WebFlow,
+ * which has full support for conversations and has a much more flexible
+ * usage model overall.
+ * @param sessionForm 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.
+ * 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.
+ * 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 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}.
+ * 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 The default implementation delegates to 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 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.
+ * 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
+ */
+ 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.
+ * 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".
+ * 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.
+ * 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.
+ * 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.
+ * A typical implementation will call
+ * For building a custom ModelAndView, call 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 In session form mode: Re-puts the form object in the session when
+ * returning to the form, as it has been removed by getCommand.
+ * 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.
+ * In session form mode: Re-puts the form object in the session when returning
+ * to the form, as it has been removed by getCommand.
+ * 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.
+ * The default implementation returns 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.
+ * For a success view, call 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.
+ * 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.
+ * Provides infrastructure for determining view names from URLs and configurable
+ * URL lookup. For information on the latter, see 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.
+ * 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}.
+ */
+ 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);
+ }
+
+ /**
+ * 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 In contrast to classic forms, wizards have more than one form view page.
+ * Therefore, there are various actions instead of one single submit action:
+ * 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").
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Note that a validator's default validate method is not executed when using
+ * this class! Rather, the {@link #validatePage} implementation should call
+ * special 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
+ */
+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.
+ * "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.
+ * 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.
+ * 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
+ * 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.
+ */
+ 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.
+ * 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)
+ */
+ protected boolean isFormSubmission(HttpServletRequest request) {
+ return super.isFormSubmission(request) || isFinishRequest(request) || isCancelRequest(request);
+ }
+
+ /**
+ * Calls page-specific referenceData method.
+ */
+ 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.
+ * 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 The default implementation returns This can be overridden in subclasses, e.g. to prepare wizard-specific
+ * error views in case of an Exception.
+ */
+ 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()}.
+ * 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.
+ * The default implementation takes the view name from the {@link #getPages()} array.
+ * 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.
+ * 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.
+ * 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.
+ * 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 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).
+ * 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 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.
+ * By default, this method returns 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.
+ * By default, this method returns 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.
+ * 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.
+ * 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)}.
+ * Implementations will typically call fine-granular 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 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.
+ * Call 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.
+ * 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.
+ * Call Controller implementation which creates an object (the command object) on
+ * receipt of a request and attempts to populate this object with request parameters. 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: Command class: Populating using request parameters and PropertyEditors: 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 Validators:
+ * 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. Workflow
+ * (and that defined by superclass): Exposed configuration properties
+ * (and those defined by superclass):getErrors method.
+ *
+ * null if not present.
+ * Throws an exception if it the parameter value isn't a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @return the Integer value, or null if not present
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static Integer getIntParameter(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return ServletRequestUtils.getIntParameter(request, name);
+ }
+
+ /**
+ * Get an int parameter, with a fallback value. Never throws an exception.
+ * Can pass a distinguished value as default to enable checks of whether it was supplied.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @param defaultVal the default value to use as fallback
+ */
+ public static int getIntParameter(HttpServletRequest request, String name, int defaultVal) {
+ return ServletRequestUtils.getIntParameter(request, name, defaultVal);
+ }
+
+ /**
+ * Get an array of int parameters, return an empty array if not found.
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ */
+ public static int[] getIntParameters(HttpServletRequest request, String name) {
+ return ServletRequestUtils.getIntParameters(request, name);
+ }
+
+ /**
+ * Get an int parameter, throwing an exception if it isn't found or isn't a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static int getRequiredIntParameter(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return ServletRequestUtils.getRequiredIntParameter(request, name);
+ }
+
+ /**
+ * Get an array of int parameters, throwing an exception if not found or one is not a number..
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static int[] getRequiredIntParameters(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return ServletRequestUtils.getRequiredIntParameters(request, name);
+ }
+
+
+ /**
+ * Get a Long parameter, or null if not present.
+ * Throws an exception if it the parameter value isn't a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @return the Long value, or null if not present
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static Long getLongParameter(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return ServletRequestUtils.getLongParameter(request, name);
+ }
+
+ /**
+ * Get a long parameter, with a fallback value. Never throws an exception.
+ * Can pass a distinguished value as default to enable checks of whether it was supplied.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @param defaultVal the default value to use as fallback
+ */
+ public static long getLongParameter(HttpServletRequest request, String name, long defaultVal) {
+ return ServletRequestUtils.getLongParameter(request, name, defaultVal);
+ }
+
+ /**
+ * Get an array of long parameters, return an empty array if not found.
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ */
+ public static long[] getLongParameters(HttpServletRequest request, String name) {
+ return ServletRequestUtils.getLongParameters(request, name);
+ }
+
+ /**
+ * Get a long parameter, throwing an exception if it isn't found or isn't a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static long getRequiredLongParameter(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return ServletRequestUtils.getRequiredLongParameter(request, name);
+ }
+
+ /**
+ * Get an array of long parameters, throwing an exception if not found or one is not a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static long[] getRequiredLongParameters(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return ServletRequestUtils.getRequiredLongParameters(request, name);
+ }
+
+
+ /**
+ * Get a Float parameter, or null if not present.
+ * Throws an exception if it the parameter value isn't a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @return the Float value, or null if not present
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static Float getFloatParameter(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return ServletRequestUtils.getFloatParameter(request, name);
+ }
+
+ /**
+ * Get a float parameter, with a fallback value. Never throws an exception.
+ * Can pass a distinguished value as default to enable checks of whether it was supplied.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @param defaultVal the default value to use as fallback
+ */
+ public static float getFloatParameter(HttpServletRequest request, String name, float defaultVal) {
+ return ServletRequestUtils.getFloatParameter(request, name, defaultVal);
+ }
+
+ /**
+ * Get an array of float parameters, return an empty array if not found.
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ */
+ public static float[] getFloatParameters(HttpServletRequest request, String name) {
+ return ServletRequestUtils.getFloatParameters(request, name);
+ }
+
+ /**
+ * Get a float parameter, throwing an exception if it isn't found or isn't a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static float getRequiredFloatParameter(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return ServletRequestUtils.getRequiredFloatParameter(request, name);
+ }
+
+ /**
+ * Get an array of float parameters, throwing an exception if not found or one is not a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static float[] getRequiredFloatParameters(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return ServletRequestUtils.getRequiredFloatParameters(request, name);
+ }
+
+
+ /**
+ * Get a Double parameter, or null if not present.
+ * Throws an exception if it the parameter value isn't a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @return the Double value, or null if not present
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static Double getDoubleParameter(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return ServletRequestUtils.getDoubleParameter(request, name);
+ }
+
+ /**
+ * Get a double parameter, with a fallback value. Never throws an exception.
+ * Can pass a distinguished value as default to enable checks of whether it was supplied.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @param defaultVal the default value to use as fallback
+ */
+ public static double getDoubleParameter(HttpServletRequest request, String name, double defaultVal) {
+ return ServletRequestUtils.getDoubleParameter(request, name, defaultVal);
+ }
+
+ /**
+ * Get an array of double parameters, return an empty array if not found.
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ */
+ public static double[] getDoubleParameters(HttpServletRequest request, String name) {
+ return ServletRequestUtils.getDoubleParameters(request, name);
+ }
+
+ /**
+ * Get a double parameter, throwing an exception if it isn't found or isn't a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static double getRequiredDoubleParameter(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return ServletRequestUtils.getRequiredDoubleParameter(request, name);
+ }
+
+ /**
+ * Get an array of double parameters, throwing an exception if not found or one is not a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static double[] getRequiredDoubleParameters(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return ServletRequestUtils.getRequiredDoubleParameters(request, name);
+ }
+
+
+ /**
+ * Get a Boolean parameter, or null if not present.
+ * Throws an exception if it the parameter value isn't a boolean.
+ * null if not present
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static Boolean getBooleanParameter(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ if (request.getParameter(name) == null) {
+ return null;
+ }
+ return (getRequiredBooleanParameter(request, name) ? Boolean.TRUE : Boolean.FALSE);
+ }
+
+ /**
+ * Get a boolean parameter, with a fallback value. Never throws an exception.
+ * Can pass a distinguished value as default to enable checks of whether it was supplied.
+ * null if not present.
+ * Throws an exception if it the parameter value is empty.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @return the String value, or null if not present
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static String getStringParameter(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ if (request.getParameter(name) == null) {
+ return null;
+ }
+ return getRequiredStringParameter(request, name);
+ }
+
+ /**
+ * Get a String parameter, with a fallback value. Never throws an exception.
+ * Can pass a distinguished value to default to enable checks of whether it was supplied.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @param defaultVal the default value to use as fallback
+ */
+ public static String getStringParameter(HttpServletRequest request, String name, String defaultVal) {
+ if (request.getParameter(name) == null) {
+ return defaultVal;
+ }
+ try {
+ return getRequiredStringParameter(request, name);
+ }
+ catch (ServletRequestBindingException ex) {
+ return defaultVal;
+ }
+ }
+
+ /**
+ * Get an array of String parameters, return an empty array if not found.
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ */
+ public static String[] getStringParameters(HttpServletRequest request, String name) {
+ try {
+ return getRequiredStringParameters(request, name);
+ }
+ catch (ServletRequestBindingException ex) {
+ return new String[0];
+ }
+ }
+
+ /**
+ * Get a String parameter, throwing an exception if it isn't found or is empty.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static String getRequiredStringParameter(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ String value = ServletRequestUtils.getRequiredStringParameter(request, name);
+ if ("".equals(value)) {
+ throw new ServletRequestBindingException(
+ "Required string parameter '" + name + "' contains no value");
+ }
+ return value;
+ }
+
+ /**
+ * Get an array of String parameters, throwing an exception if not found or one is empty.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static String[] getRequiredStringParameters(HttpServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ String[] values = ServletRequestUtils.getRequiredStringParameters(request, name);
+ for (int i = 0; i < values.length; i++) {
+ if ("".equals(values[i])) {
+ throw new ServletRequestBindingException(
+ "Required string parameter '" + name + "' contains no value");
+ }
+ }
+ return values;
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/ServletRequestBindingException.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/ServletRequestBindingException.java
new file mode 100644
index 00000000000..8165950107e
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/ServletRequestBindingException.java
@@ -0,0 +1,51 @@
+/*
+ * 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.bind;
+
+import org.springframework.web.util.NestedServletException;
+
+/**
+ * Fatal binding exception, thrown when we want to
+ * treat binding exceptions as unrecoverable.
+ *
+ * initBinder.
+ *
+ * bind with the current ServletRequest as argument:
+ *
+ *
+ * MyBean myBean = new MyBean();
+ * // apply binder to custom target object
+ * ServletRequestDataBinder binder = new ServletRequestDataBinder(myBean);
+ * // register custom editors, if desired
+ * binder.registerCustomEditor(...);
+ * // trigger actual binding of request parameters
+ * binder.bind(request);
+ * // optionally evaluate binding errors
+ * Errors errors = binder.getErrors();
+ * ...
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @see #bind(javax.servlet.ServletRequest)
+ * @see #registerCustomEditor
+ * @see #setAllowedFields
+ * @see #setRequiredFields
+ * @see #setFieldMarkerPrefix
+ * @see org.springframework.web.servlet.mvc.BaseCommandController#initBinder
+ */
+public class ServletRequestDataBinder extends WebDataBinder {
+
+ /**
+ * Create a new ServletRequestDataBinder instance, with default object name.
+ * @param target the target object to bind onto (or null
+ * if the binder is just used to convert a plain parameter value)
+ * @see #DEFAULT_OBJECT_NAME
+ */
+ public ServletRequestDataBinder(Object target) {
+ super(target);
+ }
+
+ /**
+ * Create a new ServletRequestDataBinder instance.
+ * @param target the target object to bind onto (or null
+ * if the binder is just used to convert a plain parameter value)
+ * @param objectName the name of the target object
+ */
+ public ServletRequestDataBinder(Object target, String objectName) {
+ super(target, objectName);
+ }
+
+
+ /**
+ * Bind the parameters of the given request to this binder's target,
+ * also binding multipart files in case of a multipart request.
+ * null if not present.
+ * Throws an exception if it the parameter value isn't a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @return the Integer value, or null if not present
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static Integer getIntParameter(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ if (request.getParameter(name) == null) {
+ return null;
+ }
+ return new Integer(getRequiredIntParameter(request, name));
+ }
+
+ /**
+ * Get an int parameter, with a fallback value. Never throws an exception.
+ * Can pass a distinguished value as default to enable checks of whether it was supplied.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @param defaultVal the default value to use as fallback
+ */
+ public static int getIntParameter(ServletRequest request, String name, int defaultVal) {
+ if (request.getParameter(name) == null) {
+ return defaultVal;
+ }
+ try {
+ return getRequiredIntParameter(request, name);
+ }
+ catch (ServletRequestBindingException ex) {
+ return defaultVal;
+ }
+ }
+
+ /**
+ * Get an array of int parameters, return an empty array if not found.
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ */
+ public static int[] getIntParameters(ServletRequest request, String name) {
+ try {
+ return getRequiredIntParameters(request, name);
+ }
+ catch (ServletRequestBindingException ex) {
+ return new int[0];
+ }
+ }
+
+ /**
+ * Get an int parameter, throwing an exception if it isn't found or isn't a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static int getRequiredIntParameter(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return INT_PARSER.parseInt(name, request.getParameter(name));
+ }
+
+ /**
+ * Get an array of int parameters, throwing an exception if not found or one is not a number..
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static int[] getRequiredIntParameters(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return INT_PARSER.parseInts(name, request.getParameterValues(name));
+ }
+
+
+ /**
+ * Get a Long parameter, or null if not present.
+ * Throws an exception if it the parameter value isn't a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @return the Long value, or null if not present
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static Long getLongParameter(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ if (request.getParameter(name) == null) {
+ return null;
+ }
+ return new Long(getRequiredLongParameter(request, name));
+ }
+
+ /**
+ * Get a long parameter, with a fallback value. Never throws an exception.
+ * Can pass a distinguished value as default to enable checks of whether it was supplied.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @param defaultVal the default value to use as fallback
+ */
+ public static long getLongParameter(ServletRequest request, String name, long defaultVal) {
+ if (request.getParameter(name) == null) {
+ return defaultVal;
+ }
+ try {
+ return getRequiredLongParameter(request, name);
+ }
+ catch (ServletRequestBindingException ex) {
+ return defaultVal;
+ }
+ }
+
+ /**
+ * Get an array of long parameters, return an empty array if not found.
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ */
+ public static long[] getLongParameters(ServletRequest request, String name) {
+ try {
+ return getRequiredLongParameters(request, name);
+ }
+ catch (ServletRequestBindingException ex) {
+ return new long[0];
+ }
+ }
+
+ /**
+ * Get a long parameter, throwing an exception if it isn't found or isn't a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static long getRequiredLongParameter(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return LONG_PARSER.parseLong(name, request.getParameter(name));
+ }
+
+ /**
+ * Get an array of long parameters, throwing an exception if not found or one is not a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static long[] getRequiredLongParameters(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return LONG_PARSER.parseLongs(name, request.getParameterValues(name));
+ }
+
+
+ /**
+ * Get a Float parameter, or null if not present.
+ * Throws an exception if it the parameter value isn't a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @return the Float value, or null if not present
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static Float getFloatParameter(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ if (request.getParameter(name) == null) {
+ return null;
+ }
+ return new Float(getRequiredFloatParameter(request, name));
+ }
+
+ /**
+ * Get a float parameter, with a fallback value. Never throws an exception.
+ * Can pass a distinguished value as default to enable checks of whether it was supplied.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @param defaultVal the default value to use as fallback
+ */
+ public static float getFloatParameter(ServletRequest request, String name, float defaultVal) {
+ if (request.getParameter(name) == null) {
+ return defaultVal;
+ }
+ try {
+ return getRequiredFloatParameter(request, name);
+ }
+ catch (ServletRequestBindingException ex) {
+ return defaultVal;
+ }
+ }
+
+ /**
+ * Get an array of float parameters, return an empty array if not found.
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ */
+ public static float[] getFloatParameters(ServletRequest request, String name) {
+ try {
+ return getRequiredFloatParameters(request, name);
+ }
+ catch (ServletRequestBindingException ex) {
+ return new float[0];
+ }
+ }
+
+ /**
+ * Get a float parameter, throwing an exception if it isn't found or isn't a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static float getRequiredFloatParameter(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return FLOAT_PARSER.parseFloat(name, request.getParameter(name));
+ }
+
+ /**
+ * Get an array of float parameters, throwing an exception if not found or one is not a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static float[] getRequiredFloatParameters(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return FLOAT_PARSER.parseFloats(name, request.getParameterValues(name));
+ }
+
+
+ /**
+ * Get a Double parameter, or null if not present.
+ * Throws an exception if it the parameter value isn't a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @return the Double value, or null if not present
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static Double getDoubleParameter(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ if (request.getParameter(name) == null) {
+ return null;
+ }
+ return new Double(getRequiredDoubleParameter(request, name));
+ }
+
+ /**
+ * Get a double parameter, with a fallback value. Never throws an exception.
+ * Can pass a distinguished value as default to enable checks of whether it was supplied.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @param defaultVal the default value to use as fallback
+ */
+ public static double getDoubleParameter(ServletRequest request, String name, double defaultVal) {
+ if (request.getParameter(name) == null) {
+ return defaultVal;
+ }
+ try {
+ return getRequiredDoubleParameter(request, name);
+ }
+ catch (ServletRequestBindingException ex) {
+ return defaultVal;
+ }
+ }
+
+ /**
+ * Get an array of double parameters, return an empty array if not found.
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ */
+ public static double[] getDoubleParameters(ServletRequest request, String name) {
+ try {
+ return getRequiredDoubleParameters(request, name);
+ }
+ catch (ServletRequestBindingException ex) {
+ return new double[0];
+ }
+ }
+
+ /**
+ * Get a double parameter, throwing an exception if it isn't found or isn't a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static double getRequiredDoubleParameter(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return DOUBLE_PARSER.parseDouble(name, request.getParameter(name));
+ }
+
+ /**
+ * Get an array of double parameters, throwing an exception if not found or one is not a number.
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static double[] getRequiredDoubleParameters(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return DOUBLE_PARSER.parseDoubles(name, request.getParameterValues(name));
+ }
+
+
+ /**
+ * Get a Boolean parameter, or null if not present.
+ * Throws an exception if it the parameter value isn't a boolean.
+ * null if not present
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static Boolean getBooleanParameter(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ if (request.getParameter(name) == null) {
+ return null;
+ }
+ return (getRequiredBooleanParameter(request, name) ? Boolean.TRUE : Boolean.FALSE);
+ }
+
+ /**
+ * Get a boolean parameter, with a fallback value. Never throws an exception.
+ * Can pass a distinguished value as default to enable checks of whether it was supplied.
+ * null if not present.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @return the String value, or null if not present
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static String getStringParameter(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ if (request.getParameter(name) == null) {
+ return null;
+ }
+ return getRequiredStringParameter(request, name);
+ }
+
+ /**
+ * Get a String parameter, with a fallback value. Never throws an exception.
+ * Can pass a distinguished value to default to enable checks of whether it was supplied.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @param defaultVal the default value to use as fallback
+ */
+ public static String getStringParameter(ServletRequest request, String name, String defaultVal) {
+ String val = request.getParameter(name);
+ return (val != null ? val : defaultVal);
+ }
+
+ /**
+ * Get an array of String parameters, return an empty array if not found.
+ * @param request current HTTP request
+ * @param name the name of the parameter with multiple possible values
+ */
+ public static String[] getStringParameters(ServletRequest request, String name) {
+ try {
+ return getRequiredStringParameters(request, name);
+ }
+ catch (ServletRequestBindingException ex) {
+ return new String[0];
+ }
+ }
+
+ /**
+ * Get a String parameter, throwing an exception if it isn't found.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static String getRequiredStringParameter(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return STRING_PARSER.validateRequiredString(name, request.getParameter(name));
+ }
+
+ /**
+ * Get an array of String parameters, throwing an exception if not found.
+ * @param request current HTTP request
+ * @param name the name of the parameter
+ * @throws ServletRequestBindingException a subclass of ServletException,
+ * so it doesn't need to be caught
+ */
+ public static String[] getRequiredStringParameters(ServletRequest request, String name)
+ throws ServletRequestBindingException {
+
+ return STRING_PARSER.validateRequiredStrings(name, request.getParameterValues(name));
+ }
+
+
+ private abstract static class ParameterParser {
+
+ protected final Object parse(String name, String parameter) throws ServletRequestBindingException {
+ validateRequiredParameter(name, parameter);
+ try {
+ return doParse(parameter);
+ }
+ catch (NumberFormatException ex) {
+ throw new ServletRequestBindingException(
+ "Required " + getType() + " parameter '" + name + "' with value of '" +
+ parameter + "' is not a valid number", ex);
+ }
+ }
+
+ protected final void validateRequiredParameter(String name, Object parameter)
+ throws ServletRequestBindingException {
+
+ if (parameter == null) {
+ throw new MissingServletRequestParameterException(name, getType());
+ }
+ }
+
+ protected abstract String getType();
+
+ protected abstract Object doParse(String parameter) throws NumberFormatException;
+ }
+
+
+ private static class IntParser extends ParameterParser {
+
+ protected String getType() {
+ return "int";
+ }
+
+ protected Object doParse(String s) throws NumberFormatException {
+ return Integer.valueOf(s);
+ }
+
+ public int parseInt(String name, String parameter) throws ServletRequestBindingException {
+ return ((Number) parse(name, parameter)).intValue();
+ }
+
+ public int[] parseInts(String name, String[] values) throws ServletRequestBindingException {
+ validateRequiredParameter(name, values);
+ int[] parameters = new int[values.length];
+ for (int i = 0; i < values.length; i++) {
+ parameters[i] = parseInt(name, values[i]);
+ }
+ return parameters;
+ }
+ }
+
+
+ private static class LongParser extends ParameterParser {
+
+ protected String getType() {
+ return "long";
+ }
+
+ protected Object doParse(String parameter) throws NumberFormatException {
+ return Long.valueOf(parameter);
+ }
+
+ public long parseLong(String name, String parameter) throws ServletRequestBindingException {
+ return ((Number) parse(name, parameter)).longValue();
+ }
+
+ public long[] parseLongs(String name, String[] values) throws ServletRequestBindingException {
+ validateRequiredParameter(name, values);
+ long[] parameters = new long[values.length];
+ for (int i = 0; i < values.length; i++) {
+ parameters[i] = parseLong(name, values[i]);
+ }
+ return parameters;
+ }
+ }
+
+
+ private static class FloatParser extends ParameterParser {
+
+ protected String getType() {
+ return "float";
+ }
+
+ protected Object doParse(String parameter) throws NumberFormatException {
+ return Float.valueOf(parameter);
+ }
+
+ public float parseFloat(String name, String parameter) throws ServletRequestBindingException {
+ return ((Number) parse(name, parameter)).floatValue();
+ }
+
+ public float[] parseFloats(String name, String[] values) throws ServletRequestBindingException {
+ validateRequiredParameter(name, values);
+ float[] parameters = new float[values.length];
+ for (int i = 0; i < values.length; i++) {
+ parameters[i] = parseFloat(name, values[i]);
+ }
+ return parameters;
+ }
+ }
+
+
+ private static class DoubleParser extends ParameterParser {
+
+ protected String getType() {
+ return "double";
+ }
+
+ protected Object doParse(String parameter) throws NumberFormatException {
+ return Double.valueOf(parameter);
+ }
+
+ public double parseDouble(String name, String parameter) throws ServletRequestBindingException {
+ return ((Number) parse(name, parameter)).doubleValue();
+ }
+
+ public double[] parseDoubles(String name, String[] values) throws ServletRequestBindingException {
+ validateRequiredParameter(name, values);
+ double[] parameters = new double[values.length];
+ for (int i = 0; i < values.length; i++) {
+ parameters[i] = parseDouble(name, values[i]);
+ }
+ return parameters;
+ }
+ }
+
+
+ private static class BooleanParser extends ParameterParser {
+
+ protected String getType() {
+ return "boolean";
+ }
+
+ protected Object doParse(String parameter) throws NumberFormatException {
+ return (parameter.equalsIgnoreCase("true") || parameter.equalsIgnoreCase("on") ||
+ parameter.equalsIgnoreCase("yes") || parameter.equals("1") ? Boolean.TRUE : Boolean.FALSE);
+ }
+
+ public boolean parseBoolean(String name, String parameter) throws ServletRequestBindingException {
+ return ((Boolean) parse(name, parameter)).booleanValue();
+ }
+
+ public boolean[] parseBooleans(String name, String[] values) throws ServletRequestBindingException {
+ validateRequiredParameter(name, values);
+ boolean[] parameters = new boolean[values.length];
+ for (int i = 0; i < values.length; i++) {
+ parameters[i] = parseBoolean(name, values[i]);
+ }
+ return parameters;
+ }
+ }
+
+
+ private static class StringParser extends ParameterParser {
+
+ protected String getType() {
+ return "string";
+ }
+
+ protected Object doParse(String parameter) throws NumberFormatException {
+ return parameter;
+ }
+
+ public String validateRequiredString(String name, String value) throws ServletRequestBindingException {
+ validateRequiredParameter(name, value);
+ return value;
+ }
+
+ public String[] validateRequiredStrings(String name, String[] values) throws ServletRequestBindingException {
+ validateRequiredParameter(name, values);
+ for (int i = 0; i < values.length; i++) {
+ validateRequiredParameter(name, values[i]);
+ }
+ return values;
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/WebDataBinder.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/WebDataBinder.java
new file mode 100644
index 00000000000..bacf8227b71
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/WebDataBinder.java
@@ -0,0 +1,227 @@
+/*
+ * 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.bind;
+
+import java.lang.reflect.Array;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.PropertyValue;
+import org.springframework.validation.DataBinder;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * Special {@link DataBinder} for data binding from web request parameters
+ * to JavaBean objects. Designed for web environments, but not dependent on
+ * the Servlet API; serves as base class for more specific DataBinder variants,
+ * such as {@link org.springframework.web.bind.ServletRequestDataBinder}.
+ *
+ * null
+ * if the binder is just used to convert a plain parameter value)
+ * @see #DEFAULT_OBJECT_NAME
+ */
+ public WebDataBinder(Object target) {
+ super(target);
+ }
+
+ /**
+ * Create a new WebDataBinder instance.
+ * @param target the target object to bind onto (or null
+ * if the binder is just used to convert a plain parameter value)
+ * @param objectName the name of the target object
+ */
+ public WebDataBinder(Object target, String objectName) {
+ super(target, objectName);
+ }
+
+
+ /**
+ * Specify a prefix that can be used for parameters that mark potentially
+ * empty fields, having "prefix + field" as name. Such a marker parameter is
+ * checked by existence: You can send any value for it, for example "visible".
+ * This is particularly useful for HTML checkboxes and select options.
+ * onBind implementation.
+ * Boolean.FALSE
+ * for boolean fields and an empty array of array types.
+ * Else, null is used as default.
+ * @param field the name of the field
+ * @param fieldType the type of the field
+ * @return the empty value (for most fields: null)
+ */
+ protected Object getEmptyValue(String field, Class fieldType) {
+ if (fieldType != null && boolean.class.equals(fieldType) || Boolean.class.equals(fieldType)) {
+ // Special handling of boolean property.
+ return Boolean.FALSE;
+ }
+ else if (fieldType != null && fieldType.isArray()) {
+ // Special handling of array property.
+ return Array.newInstance(fieldType.getComponentType(), 0);
+ }
+ else {
+ // Default value: try null.
+ return null;
+ }
+ }
+
+
+ /**
+ * Bind the multipart files contained in the given request, if any
+ * (in case of a multipart request).
+ * void.
+ *
+ *
+ *
+ *
+ * null.
+ * Note that session access may not be thread-safe, in particular in a
+ * Servlet environment: Consider switching the
+ * {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#setSynchronizeOnSession "synchronizeOnSession"}
+ * flag to "true" if multiple requests are allowed to access a session concurrently.
+ *
+ *
+ *
+ * ModelAndView object (Servlet MVC or Portlet MVC),
+ * with the model implicitly enriched with command objects and the results
+ * of {@link ModelAttribute} annotated reference data accessor methods.
+ * void if the method handles the response itself (by
+ * writing the response content directly, declaring an argument of type
+ * {@link javax.servlet.ServletResponse} / {@link javax.servlet.http.HttpServletResponse}
+ * / {@link javax.portlet.RenderResponse} for that purpose)
+ * or if the view name is supposed to be implicitly determined through a
+ * {@link org.springframework.web.servlet.RequestToViewNameTranslator}
+ * (not declaring a response argument in the handler method signature;
+ * only applicable in a Servlet environment).
+ * @RequestMapping will only be processed if a
+ * corresponding HandlerMapping (for type level annotations)
+ * and/or HandlerAdapter (for method level annotations) is
+ * present in the dispatcher. This is the case by default in both
+ * DispatcherServlet and DispatcherPortlet.
+ * However, if you are defining custom HandlerMappings or
+ * HandlerAdapters, then you need to make sure that a
+ * corresponding custom DefaultAnnotationHandlerMapping
+ * and/or AnnotationMethodHandlerAdapter is defined as well
+ * - provided that you intend to use @RequestMapping.
+ *
+ * @author Juergen Hoeller
+ * @author Arjen Poutsma
+ * @author Sam Brannen
+ * @since 2.5
+ * @see RequestParam
+ * @see ModelAttribute
+ * @see SessionAttributes
+ * @see InitBinder
+ * @see org.springframework.web.context.request.WebRequest
+ * @see org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
+ * @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
+ * @see org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping
+ * @see org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RequestMapping {
+
+ /**
+ * The primary mapping expressed by this annotation.
+ * true, leading to an exception thrown in case
+ * of the parameter missing in the request. Switch this to false
+ * if you prefer a null in case of the parameter missing.
+ */
+ boolean required() default true;
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/SessionAttributes.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/SessionAttributes.java
new file mode 100644
index 00000000000..076bec779be
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/SessionAttributes.java
@@ -0,0 +1,71 @@
+/*
+ * 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.bind.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation that indicates the session attributes that a specific handler
+ * uses. This will typically list the names of model attributes which should be
+ * transparently stored in the session or some conversational storage,
+ * serving as form-backing beans. Declared at the type level, applying
+ * to the model attributes that the annotated handler class operates on.
+ *
+ * session.setAttribute method instead.
+ * Alternatively, consider using the attribute management capabilities of the
+ * generic {@link org.springframework.web.context.request.WebRequest} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface SessionAttributes {
+
+ /**
+ * The names of session attributes in the model, to be stored in the
+ * session or some conversational storage.
+ * @RequestMapping, @InitBinder,
+ * @ModelAttribute and @SessionAttributes.
+ *
+ * false, using bean property access.
+ * Switch this to true for enforcing direct field access.
+ */
+ public final void setDirectFieldAccess(boolean directFieldAccess) {
+ this.directFieldAccess = directFieldAccess;
+ }
+
+ /**
+ * Set the strategy to use for resolving errors into message codes.
+ * Applies the given strategy to all data binders used by this controller.
+ * null, i.e. using the default strategy of
+ * the data binder.
+ * @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.
+ */
+ public final MessageCodesResolver getMessageCodesResolver() {
+ return this.messageCodesResolver;
+ }
+
+ /**
+ * Set the strategy to use for processing binding errors, that is,
+ * required field errors and PropertyAccessExceptions.
+ * null, that is, using the default strategy
+ * of the data binder.
+ * @see org.springframework.validation.DataBinder#setBindingErrorProcessor
+ */
+ public final void setBindingErrorProcessor(BindingErrorProcessor bindingErrorProcessor) {
+ this.bindingErrorProcessor = bindingErrorProcessor;
+ }
+
+ /**
+ * Return the strategy to use for processing binding errors.
+ */
+ public final BindingErrorProcessor getBindingErrorProcessor() {
+ return this.bindingErrorProcessor;
+ }
+
+ /**
+ * Specify a single PropertyEditorRegistrar to be applied
+ * to every DataBinder that this controller uses.
+ */
+ public final void setPropertyEditorRegistrar(PropertyEditorRegistrar propertyEditorRegistrar) {
+ this.propertyEditorRegistrars = new PropertyEditorRegistrar[] {propertyEditorRegistrar};
+ }
+
+ /**
+ * Specify multiple PropertyEditorRegistrars to be applied
+ * to every DataBinder that this controller uses.
+ */
+ public final void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) {
+ this.propertyEditorRegistrars = propertyEditorRegistrars;
+ }
+
+ /**
+ * Return the PropertyEditorRegistrars to be applied
+ * to every DataBinder that this controller uses.
+ */
+ public final PropertyEditorRegistrar[] getPropertyEditorRegistrars() {
+ return this.propertyEditorRegistrars;
+ }
+
+
+ public void initBinder(WebDataBinder binder, WebRequest request) {
+ if (this.directFieldAccess) {
+ 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);
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/DefaultSessionAttributeStore.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/DefaultSessionAttributeStore.java
new file mode 100644
index 00000000000..6f0e512a1a9
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/DefaultSessionAttributeStore.java
@@ -0,0 +1,84 @@
+/*
+ * 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.bind.support;
+
+import org.springframework.util.Assert;
+import org.springframework.web.context.request.WebRequest;
+
+/**
+ * Default implementation of the {@link SessionAttributeStore} interface,
+ * storing the attributes in the WebRequest session (i.e. HttpSession
+ * or PortletSession).
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see #setAttributeNamePrefix
+ * @see org.springframework.web.context.request.WebRequest#setAttribute
+ * @see org.springframework.web.context.request.WebRequest#getAttribute
+ * @see org.springframework.web.context.request.WebRequest#removeAttribute
+ */
+public class DefaultSessionAttributeStore implements SessionAttributeStore {
+
+ private String attributeNamePrefix = "";
+
+
+ /**
+ * Specify a prefix to use for the attribute names in the backend session.
+ * null.
+ * @param request the current request
+ * @param attributeName the name of the attribute
+ * @return the current attribute value, or null if none
+ */
+ Object retrieveAttribute(WebRequest request, String attributeName);
+
+ /**
+ * Clean up the specified attribute in the backend session.
+ * complete flag as an instance variable.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+public class SimpleSessionStatus implements SessionStatus {
+
+ private boolean complete = false;
+
+
+ public void setComplete() {
+ this.complete = true;
+ }
+
+ public boolean isComplete() {
+ return this.complete;
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/WebArgumentResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/WebArgumentResolver.java
new file mode 100644
index 00000000000..325806b8879
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/WebArgumentResolver.java
@@ -0,0 +1,63 @@
+/*
+ * 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.bind.support;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.web.context.request.NativeWebRequest;
+
+/**
+ * SPI for resolving custom arguments for a specific handler method parameter.
+ * Typically implemented to detect sppecial parameter types, resolving
+ * well-known argument values for them.
+ *
+ *
+ * public class MySpecialArgumentResolver implements ArgumentResolver {
+ *
+ * public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) {
+ * if (methodParameter.getParameterType().equals(MySpecialArg.class)) {
+ * return new MySpecialArg("myValue");
+ * }
+ * return UNRESOLVED;
+ * }
+ * }
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.2
+ * @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#setCustomArgumentResolvers
+ * @see org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter#setCustomArgumentResolvers
+ */
+public interface WebArgumentResolver {
+
+ /**
+ * Marker to be returned when the resolver does not know how to
+ * handle the given method parameter.
+ */
+ Object UNRESOLVED = new Object();
+
+
+ /**
+ * Resolve an argument for the given handler method parameter within the given web request.
+ * @param methodParameter the handler method parameter to resolve
+ * @param webRequest the current web request, allowing access to the native request as well
+ * @return the argument value, or UNRESOLVED if not resolvable
+ * @throws Exception in case of resolution failure
+ */
+ Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception;
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/WebBindingInitializer.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/WebBindingInitializer.java
new file mode 100644
index 00000000000..be7e0479dee
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/WebBindingInitializer.java
@@ -0,0 +1,38 @@
+/*
+ * 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.bind.support;
+
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.context.request.WebRequest;
+
+/**
+ * Callback interface for initializing a {@link org.springframework.web.bind.WebDataBinder}
+ * for performing data binding in the context of a specific web request.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+public interface WebBindingInitializer {
+
+ /**
+ * Initialize the given DataBinder for the given request.
+ * @param binder the DataBinder to initialize
+ * @param request the web request that the data binding happens within
+ */
+ void initBinder(WebDataBinder binder, WebRequest request);
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java
new file mode 100644
index 00000000000..5f136938174
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java
@@ -0,0 +1,125 @@
+/*
+ * 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.bind.support;
+
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.validation.BindException;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.multipart.MultipartRequest;
+
+/**
+ * Special {@link org.springframework.validation.DataBinder} to perform data binding
+ * from web request parameters to JavaBeans, including support for multipart files.
+ *
+ * bind with the current WebRequest as argument:
+ *
+ *
+ * MyBean myBean = new MyBean();
+ * // apply binder to custom target object
+ * WebRequestDataBinder binder = new WebRequestDataBinder(myBean);
+ * // register custom editors, if desired
+ * binder.registerCustomEditor(...);
+ * // trigger actual binding of request parameters
+ * binder.bind(request);
+ * // optionally evaluate binding errors
+ * Errors errors = binder.getErrors();
+ * ...
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.2
+ * @see #bind(org.springframework.web.context.request.WebRequest)
+ * @see #registerCustomEditor
+ * @see #setAllowedFields
+ * @see #setRequiredFields
+ * @see #setFieldMarkerPrefix
+ */
+public class WebRequestDataBinder extends WebDataBinder {
+
+ /**
+ * Create a new WebRequestDataBinder instance, with default object name.
+ * @param target the target object to bind onto (or null
+ * if the binder is just used to convert a plain parameter value)
+ * @see #DEFAULT_OBJECT_NAME
+ */
+ public WebRequestDataBinder(Object target) {
+ super(target);
+ }
+
+ /**
+ * Create a new WebRequestDataBinder instance.
+ * @param target the target object to bind onto (or null
+ * if the binder is just used to convert a plain parameter value)
+ * @param objectName the name of the target object
+ */
+ public WebRequestDataBinder(Object target, String objectName) {
+ super(target, objectName);
+ }
+
+
+ /**
+ * Bind the parameters of the given request to this binder's target,
+ * also binding multipart files in case of a multipart request.
+ * null or empty)
+ */
+ String getName();
+
+ /**
+ * Return the original filename in the client's filesystem.
+ * null if not defined
+ * (or no file has been chosen in the multipart form)
+ */
+ String getContentType();
+
+ /**
+ * Return whether the uploaded file is empty, that is, either no file has
+ * been chosen in the multipart form or the chosen file has no content.
+ */
+ boolean isEmpty();
+
+ /**
+ * Return the size of the file in bytes.
+ * @return the size of the file, or 0 if empty
+ */
+ long getSize();
+
+ /**
+ * Return the contents of the file as an array of bytes.
+ * @return the contents of the file as bytes, or an empty byte array if empty
+ * @throws IOException in case of access errors (if the temporary store fails)
+ */
+ byte[] getBytes() throws IOException;
+
+ /**
+ * Return an InputStream to read the contents of the file from.
+ * The user is responsible for closing the stream.
+ * @return the contents of the file as stream, or an empty stream if empty
+ * @throws IOException in case of access errors (if the temporary store fails)
+ */
+ InputStream getInputStream() throws IOException;
+
+ /**
+ * Transfer the received file to the given destination file.
+ * null if it does not exist.
+ * @param name a String specifying the parameter name of the multipart file
+ * @return the uploaded content in the form of a {@link org.springframework.web.multipart.MultipartFile} object
+ */
+ MultipartFile getFile(String name);
+
+ /**
+ * Return a {@link java.util.Map} of the multipart files contained in this request.
+ * @return a map containing the parameter names as keys, and the
+ * {@link org.springframework.web.multipart.MultipartFile} objects as values
+ * @see MultipartFile
+ */
+ Map getFileMap();
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/MultipartResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/MultipartResolver.java
new file mode 100644
index 00000000000..0e6a5baaba1
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/MultipartResolver.java
@@ -0,0 +1,127 @@
+/*
+ * 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.multipart;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * A strategy interface for multipart file upload resolution in accordance
+ * with RFC 1867.
+ * Implementations are typically usable both within an application context
+ * and standalone.
+ *
+ *
+ *
+ *
+ *
+ * public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
+ * MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
+ * MultipartFile multipartFile = multipartRequest.getFile("image");
+ * ...
+ * }
+ *
+ * Instead of direct access, command or form controllers can register a
+ * {@link org.springframework.web.multipart.support.ByteArrayMultipartFileEditor}
+ * or {@link org.springframework.web.multipart.support.StringMultipartFileEditor}
+ * with their data binder, to automatically apply multipart content to command
+ * bean properties.
+ *
+ * web.xml. It will delegate to a corresponding
+ * {@link org.springframework.web.multipart.MultipartResolver} bean in the root
+ * application context. This is mainly intended for applications that do not
+ * use Spring's own web MVC framework.
+ *
+ * org.apache.commons.fileupload.disk.DiskFileItemFactory
+ * instance. There is hardly any need to access this.
+ * @return the underlying DiskFileItemFactory instance
+ */
+ public DiskFileItemFactory getFileItemFactory() {
+ return this.fileItemFactory;
+ }
+
+ /**
+ * Return the underlying org.apache.commons.fileupload.FileUpload
+ * instance. There is hardly any need to access this.
+ * @return the underlying FileUpload instance
+ */
+ public FileUpload getFileUpload() {
+ return this.fileUpload;
+ }
+
+ /**
+ * Set the maximum allowed size (in bytes) before uploads are refused.
+ * -1 indicates no limit (the default).
+ * @param maxUploadSize the maximum upload size allowed
+ * @see org.apache.commons.fileupload.FileUploadBase#setSizeMax
+ */
+ public void setMaxUploadSize(long maxUploadSize) {
+ this.fileUpload.setSizeMax(maxUploadSize);
+ }
+
+ /**
+ * Set the maximum allowed size (in bytes) before uploads are written to disk.
+ * Uploaded files will still be received past this amount, but they will not be
+ * stored in memory. Default is 10240, according to Commons FileUpload.
+ * @param maxInMemorySize the maximum in memory size allowed
+ * @see org.apache.commons.fileupload.disk.DiskFileItemFactory#setSizeThreshold
+ */
+ public void setMaxInMemorySize(int maxInMemorySize) {
+ this.fileItemFactory.setSizeThreshold(maxInMemorySize);
+ }
+
+ /**
+ * Set the default character encoding to use for parsing requests,
+ * to be applied to headers of individual parts and to form fields.
+ * Default is ISO-8859-1, according to the Servlet spec.
+ * ServletRequest.setCharacterEncoding method.
+ * @param defaultEncoding the character encoding to use
+ * @see javax.servlet.ServletRequest#getCharacterEncoding
+ * @see javax.servlet.ServletRequest#setCharacterEncoding
+ * @see WebUtils#DEFAULT_CHARACTER_ENCODING
+ * @see org.apache.commons.fileupload.FileUploadBase#setHeaderEncoding
+ */
+ public void setDefaultEncoding(String defaultEncoding) {
+ this.fileUpload.setHeaderEncoding(defaultEncoding);
+ }
+
+ protected String getDefaultEncoding() {
+ String encoding = getFileUpload().getHeaderEncoding();
+ if (encoding == null) {
+ encoding = WebUtils.DEFAULT_CHARACTER_ENCODING;
+ }
+ return encoding;
+ }
+
+ /**
+ * Set the temporary directory where uploaded files get stored.
+ * Default is the servlet container's temporary directory for the web application.
+ * @see org.springframework.web.util.WebUtils#TEMP_DIR_CONTEXT_ATTRIBUTE
+ */
+ public void setUploadTempDir(Resource uploadTempDir) throws IOException {
+ if (!uploadTempDir.exists() && !uploadTempDir.getFile().mkdirs()) {
+ throw new IllegalArgumentException("Given uploadTempDir [" + uploadTempDir + "] could not be created");
+ }
+ this.fileItemFactory.setRepository(uploadTempDir.getFile());
+ this.uploadTempDirSpecified = true;
+ }
+
+ protected boolean isUploadTempDirSpecified() {
+ return this.uploadTempDirSpecified;
+ }
+
+
+ /**
+ * Factory method for a Commons DiskFileItemFactory instance.
+ * org.apache.commons.fileupload.FileItem
+ * instance. There is hardly any need to access this.
+ */
+ public final FileItem getFileItem() {
+ return this.fileItem;
+ }
+
+
+ public String getName() {
+ return this.fileItem.getFieldName();
+ }
+
+ public String getOriginalFilename() {
+ String filename = this.fileItem.getName();
+ if (filename == null) {
+ // Should never happen.
+ return "";
+ }
+ // check for Unix-style path
+ int pos = filename.lastIndexOf("/");
+ if (pos == -1) {
+ // check for Windows-style path
+ pos = filename.lastIndexOf("\\");
+ }
+ if (pos != -1) {
+ // any sort of path separator found
+ return filename.substring(pos + 1);
+ }
+ else {
+ // plain name
+ return filename;
+ }
+ }
+
+ public String getContentType() {
+ return this.fileItem.getContentType();
+ }
+
+ public boolean isEmpty() {
+ return (this.size == 0);
+ }
+
+ public long getSize() {
+ return this.size;
+ }
+
+ public byte[] getBytes() {
+ if (!isAvailable()) {
+ throw new IllegalStateException("File has been moved - cannot be read again");
+ }
+ byte[] bytes = this.fileItem.get();
+ return (bytes != null ? bytes : new byte[0]);
+ }
+
+ public InputStream getInputStream() throws IOException {
+ if (!isAvailable()) {
+ throw new IllegalStateException("File has been moved - cannot be read again");
+ }
+ InputStream inputStream = this.fileItem.getInputStream();
+ return (inputStream != null ? inputStream : new ByteArrayInputStream(new byte[0]));
+ }
+
+ public void transferTo(File dest) throws IOException, IllegalStateException {
+ if (!isAvailable()) {
+ throw new IllegalStateException("File has already been moved - cannot be transferred again");
+ }
+
+ if (dest.exists() && !dest.delete()) {
+ throw new IOException(
+ "Destination file [" + dest.getAbsolutePath() + "] already exists and could not be deleted");
+ }
+
+ try {
+ this.fileItem.write(dest);
+ if (logger.isDebugEnabled()) {
+ String action = "transferred";
+ if (!this.fileItem.isInMemory()) {
+ action = isAvailable() ? "copied" : "moved";
+ }
+ logger.debug("Multipart file '" + getName() + "' with original filename [" +
+ getOriginalFilename() + "], stored " + getStorageDescription() + ": " +
+ action + " to [" + dest.getAbsolutePath() + "]");
+ }
+ }
+ catch (FileUploadException ex) {
+ throw new IllegalStateException(ex.getMessage());
+ }
+ catch (IOException ex) {
+ throw ex;
+ }
+ catch (Exception ex) {
+ logger.error("Could not transfer to file", ex);
+ throw new IOException("Could not transfer to file: " + ex.getMessage());
+ }
+ }
+
+ /**
+ * Determine whether the multipart content is still available.
+ * If a temporary file has been moved, the content is no longer available.
+ */
+ protected boolean isAvailable() {
+ // If in memory, it's available.
+ if (this.fileItem.isInMemory()) {
+ return true;
+ }
+ // Check actual existence of temporary file.
+ if (this.fileItem instanceof DiskFileItem) {
+ return ((DiskFileItem) this.fileItem).getStoreLocation().exists();
+ }
+ // Check whether current file size is different than original one.
+ return (this.fileItem.getSize() == this.size);
+ }
+
+ /**
+ * Return a description for the storage location of the multipart content.
+ * Tries to be as specific as possible: mentions the file location in case
+ * of a temporary file.
+ */
+ public String getStorageDescription() {
+ if (this.fileItem.isInMemory()) {
+ return "in memory";
+ }
+ else if (this.fileItem instanceof DiskFileItem) {
+ return "at [" + ((DiskFileItem) this.fileItem).getStoreLocation().getAbsolutePath() + "]";
+ }
+ else {
+ return "on disk";
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartResolver.java
new file mode 100644
index 00000000000..918e30aece1
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartResolver.java
@@ -0,0 +1,205 @@
+/*
+ * 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.multipart.commons;
+
+import java.util.List;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.fileupload.FileItemFactory;
+import org.apache.commons.fileupload.FileUpload;
+import org.apache.commons.fileupload.FileUploadBase;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+import org.apache.commons.fileupload.servlet.ServletRequestContext;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.web.context.ServletContextAware;
+import org.springframework.web.multipart.MaxUploadSizeExceededException;
+import org.springframework.web.multipart.MultipartException;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+import org.springframework.web.multipart.MultipartResolver;
+import org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest;
+import org.springframework.web.util.WebUtils;
+
+/**
+ * Servlet-based {@link org.springframework.web.multipart.MultipartResolver} implementation
+ * for Jakarta Commons FileUpload
+ * 1.1 or above. Commons FileUpload 1.2 or above is recommended.
+ *
+ * org.apache.commons.fileupload.servlet.ServletFileUpload
+ * instance. Can be overridden to use a custom subclass, e.g. for testing purposes.
+ * @param fileItemFactory the Commons FileItemFactory to use
+ * @return the new ServletFileUpload instance
+ */
+ protected FileUpload newFileUpload(FileItemFactory fileItemFactory) {
+ return new ServletFileUpload(fileItemFactory);
+ }
+
+ public void setServletContext(ServletContext servletContext) {
+ if (!isUploadTempDirSpecified()) {
+ getFileItemFactory().setRepository(WebUtils.getTempDir(servletContext));
+ }
+ }
+
+
+ public boolean isMultipart(HttpServletRequest request) {
+ if (request == null) {
+ return false;
+ }
+ else if (commonsFileUpload12Present) {
+ return ServletFileUpload.isMultipartContent(request);
+ }
+ else {
+ return ServletFileUpload.isMultipartContent(new ServletRequestContext(request));
+ }
+ }
+
+ public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
+ Assert.notNull(request, "Request must not be null");
+ if (this.resolveLazily) {
+ return new DefaultMultipartHttpServletRequest(request) {
+ protected void initializeMultipart() {
+ MultipartParsingResult parsingResult = parseRequest(request);
+ setMultipartFiles(parsingResult.getMultipartFiles());
+ setMultipartParameters(parsingResult.getMultipartParameters());
+ }
+ };
+ }
+ else {
+ MultipartParsingResult parsingResult = parseRequest(request);
+ return new DefaultMultipartHttpServletRequest(
+ request, parsingResult.getMultipartFiles(), parsingResult.getMultipartParameters());
+ }
+ }
+
+ /**
+ * Parse the given servlet request, resolving its multipart elements.
+ * @param request the request to parse
+ * @return the parsing result
+ * @throws MultipartException if multipart resolution failed.
+ */
+ protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
+ String encoding = determineEncoding(request);
+ FileUpload fileUpload = prepareFileUpload(encoding);
+ try {
+ List fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
+ return parseFileItems(fileItems, encoding);
+ }
+ catch (FileUploadBase.SizeLimitExceededException ex) {
+ throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
+ }
+ catch (FileUploadException ex) {
+ throw new MultipartException("Could not parse multipart servlet request", ex);
+ }
+ }
+
+ /**
+ * Determine the encoding for the given request.
+ * Can be overridden in subclasses.
+ * null)
+ * @see javax.servlet.ServletRequest#getCharacterEncoding
+ * @see #setDefaultEncoding
+ */
+ protected String determineEncoding(HttpServletRequest request) {
+ String encoding = request.getCharacterEncoding();
+ if (encoding == null) {
+ encoding = getDefaultEncoding();
+ }
+ return encoding;
+ }
+
+ public void cleanupMultipart(MultipartHttpServletRequest request) {
+ if (request != null) {
+ try {
+ cleanupFileItems(request.getFileMap().values());
+ }
+ catch (Throwable ex) {
+ logger.warn("Failed to perform multipart cleanup for servlet request", ex);
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/commons/package.html b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/commons/package.html
new file mode 100644
index 00000000000..e7a033c0f7d
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/commons/package.html
@@ -0,0 +1,8 @@
+
+web.xml;
+ * the default bean name is "filterMultipartResolver". Looks up the MultipartResolver
+ * on each request, to avoid initialization order issues (when using ContextLoaderServlet,
+ * the root application context will get initialized after this filter).
+ *
+ * lookupMultipartResolver method to use a custom MultipartResolver
+ * instance, for example if not using a Spring web application context.
+ * Note that the lookup method should not create a new MultipartResolver instance
+ * for each call but rather return a reference to a pre-built instance.
+ *
+ * lookupMultipartResolver
+ * without arguments.
+ * @return the MultipartResolver to use
+ * @see #lookupMultipartResolver()
+ */
+ protected MultipartResolver lookupMultipartResolver(HttpServletRequest request) {
+ return lookupMultipartResolver();
+ }
+
+ /**
+ * Look for a MultipartResolver bean in the root web application context.
+ * Supports a "multipartResolverBeanName" filter init param; the default
+ * bean name is "filterMultipartResolver".
+ * null if none found
+ */
+ protected MultipartResolver lookupMultipartResolver() {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Using MultipartResolver '" + getMultipartResolverBeanName() + "' for MultipartFilter");
+ }
+ WebApplicationContext wac =
+ WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
+ return (MultipartResolver) wac.getBean(getMultipartResolverBeanName(), MultipartResolver.class);
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/support/StringMultipartFileEditor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/support/StringMultipartFileEditor.java
new file mode 100644
index 00000000000..ac99c7fb858
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/multipart/support/StringMultipartFileEditor.java
@@ -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.multipart.support;
+
+import java.beans.PropertyEditorSupport;
+import java.io.IOException;
+
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * Custom {@link java.beans.PropertyEditor} for converting
+ * {@link MultipartFile MultipartFiles} to Strings.
+ *
+ *
+ *
+ *
+ * @RequestMapping annotation will only be processed
+ * if a corresponding HandlerMapping (for type level annotations)
+ * and/or HandlerAdapter (for method level annotations)
+ * is present in the dispatcher. This is the case by default.
+ * However, if you are defining custom HandlerMappings or
+ * HandlerAdapters, then you need to make sure that a
+ * corresponding custom DefaultAnnotationHandlerMapping
+ * and/or AnnotationMethodHandlerAdapter is defined as well
+ * - provided that you intend to use @RequestMapping.
+ *
+ * null.
+ * null
+ * if none (indicating that no multipart support is available)
+ */
+ public final MultipartResolver getMultipartResolver() {
+ return this.multipartResolver;
+ }
+
+
+ /**
+ * Return the default strategy object for the given strategy interface.
+ * getLastModified method to evaluate
+ * the Last-Modified value of the mapped handler.
+ */
+ protected long getLastModified(HttpServletRequest request) {
+ if (logger.isDebugEnabled()) {
+ String requestUri = new UrlPathHelper().getRequestUri(request);
+ logger.debug("DispatcherServlet with name '" + getServletName() +
+ "' determining Last-Modified value for [" + requestUri + "]");
+ }
+ try {
+ HandlerExecutionChain mappedHandler = getHandler(request, true);
+ if (mappedHandler == null || mappedHandler.getHandler() == null) {
+ // Ignore -> will reappear on doService.
+ logger.debug("No handler found in getLastModified");
+ return -1;
+ }
+
+ HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
+ long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
+ if (logger.isDebugEnabled()) {
+ String requestUri = new UrlPathHelper().getRequestUri(request);
+ logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
+ }
+ return lastModified;
+ }
+ catch (Exception ex) {
+ // Ignore -> will reappear on doService.
+ logger.debug("Exception thrown in getLastModified", ex);
+ return -1;
+ }
+ }
+
+
+ /**
+ * Build a LocaleContext for the given request, exposing the request's
+ * primary locale as current locale.
+ * null if no handler could be found
+ */
+ protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
+ HandlerExecutionChain handler =
+ (HandlerExecutionChain) request.getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
+ if (handler != null) {
+ if (!cache) {
+ request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
+ }
+ return handler;
+ }
+
+ Iterator it = this.handlerMappings.iterator();
+ while (it.hasNext()) {
+ HandlerMapping hm = (HandlerMapping) it.next();
+ if (logger.isTraceEnabled()) {
+ logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" +
+ getServletName() + "'");
+ }
+ handler = hm.getHandler(request);
+ if (handler != null) {
+ if (cache) {
+ request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE, handler);
+ }
+ return handler;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * No handler found -> set appropriate HTTP response status.
+ * @param request current HTTP request
+ * @param response current HTTP response
+ * @throws Exception if preparing the response failed
+ */
+ protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
+ if (pageNotFoundLogger.isWarnEnabled()) {
+ String requestUri = new UrlPathHelper().getRequestUri(request);
+ pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" +
+ requestUri + "] in DispatcherServlet with name '" + getServletName() + "'");
+ }
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+
+ /**
+ * Return the HandlerAdapter for this handler object.
+ * @param handler the handler object to find an adapter for
+ * @throws ServletException if no HandlerAdapter can be found for the handler.
+ * This is a fatal error.
+ */
+ protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
+ Iterator it = this.handlerAdapters.iterator();
+ while (it.hasNext()) {
+ HandlerAdapter ha = (HandlerAdapter) it.next();
+ if (logger.isTraceEnabled()) {
+ logger.trace("Testing handler adapter [" + ha + "]");
+ }
+ if (ha.supports(handler)) {
+ return ha;
+ }
+ }
+ throw new ServletException("No adapter for handler [" + handler +
+ "]: Does your handler implement a supported interface like Controller?");
+ }
+
+ /**
+ * Determine an error ModelAndView via the registered HandlerExceptionResolvers.
+ * @param request current HTTP request
+ * @param response current HTTP response
+ * @param handler the executed handler, or null 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
+ * @throws Exception if no error ModelAndView found
+ */
+ protected ModelAndView processHandlerException(
+ HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
+ throws Exception {
+
+ // Check registerer HandlerExceptionResolvers...
+ ModelAndView exMv = null;
+ for (Iterator it = this.handlerExceptionResolvers.iterator(); exMv == null && it.hasNext();) {
+ HandlerExceptionResolver resolver = (HandlerExceptionResolver) it.next();
+ exMv = resolver.resolveException(request, response, handler, ex);
+ }
+ if (exMv != null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
+ }
+ WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
+ return exMv;
+ }
+
+ // Send default responses for well-known exceptions, if possible.
+ if (ex instanceof HttpRequestMethodNotSupportedException && !response.isCommitted()) {
+ String[] supportedMethods = ((HttpRequestMethodNotSupportedException) ex).getSupportedMethods();
+ if (supportedMethods != null) {
+ response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
+ }
+ response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
+ return null;
+ }
+
+ throw ex;
+ }
+
+ /**
+ * Render the given ModelAndView. This is the last stage in handling a request.
+ * It may involve resolving the view by name.
+ * @param mv the ModelAndView to render
+ * @param request current HTTP servlet request
+ * @param response current HTTP servlet response
+ * @throws Exception if there's a problem rendering the view
+ */
+ protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
+ throws Exception {
+
+ // Determine locale for request and apply it to the response.
+ Locale locale = this.localeResolver.resolveLocale(request);
+ response.setLocale(locale);
+
+ View view = null;
+
+ if (mv.isReference()) {
+ // We need to resolve the view name.
+ view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
+ if (view == null) {
+ throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
+ "' in servlet with name '" + getServletName() + "'");
+ }
+ }
+ else {
+ // No need to lookup: the ModelAndView object contains the actual View object.
+ view = mv.getView();
+ if (view == null) {
+ throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
+ "View object in servlet with name '" + getServletName() + "'");
+ }
+ }
+
+ // Delegate to the View object for rendering.
+ if (logger.isDebugEnabled()) {
+ logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
+ }
+ view.render(mv.getModelInternal(), request, response);
+ }
+
+ /**
+ * Translate the supplied request into a default view name.
+ * @param request current HTTP servlet request
+ * @return the view name (or null if no default found)
+ * @throws Exception if view name translation failed
+ */
+ protected String getDefaultViewName(HttpServletRequest request) throws Exception {
+ return this.viewNameTranslator.getViewName(request);
+ }
+
+ /**
+ * Resolve the given view name into a View object (to be rendered).
+ * null if none found
+ * @throws Exception if the view cannot be resolved
+ * (typically in case of problems creating an actual View object)
+ * @see ViewResolver#resolveViewName
+ */
+ protected View resolveViewName(String viewName, Map model, Locale locale, HttpServletRequest request)
+ throws Exception {
+
+ for (Iterator it = this.viewResolvers.iterator(); it.hasNext();) {
+ ViewResolver viewResolver = (ViewResolver) it.next();
+ View view = viewResolver.resolveViewName(viewName, locale);
+ if (view != null) {
+ return view;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
+ * Will just invoke afterCompletion for all interceptors whose preHandle
+ * invocation has successfully completed and returned true.
+ * @param mappedHandler the mapped HandlerExecutionChain
+ * @param interceptorIndex index of last interceptor that successfully completed
+ * @param ex Exception thrown on handler execution, or null if none
+ * @see HandlerInterceptor#afterCompletion
+ */
+ private void triggerAfterCompletion(
+ HandlerExecutionChain mappedHandler, int interceptorIndex,
+ HttpServletRequest request, HttpServletResponse response, Exception ex)
+ throws Exception {
+
+ // Apply afterCompletion methods of registered interceptors.
+ if (mappedHandler != null) {
+ HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
+ if (interceptors != null) {
+ for (int i = interceptorIndex; i >= 0; i--) {
+ HandlerInterceptor interceptor = interceptors[i];
+ try {
+ interceptor.afterCompletion(request, response, mappedHandler.getHandler(), ex);
+ }
+ catch (Throwable ex2) {
+ logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Restore the request attributes after an include.
+ * @param request current HTTP request
+ * @param attributesSnapshot the snapshot of the request attributes
+ * before the include
+ */
+ private void restoreAttributesAfterInclude(HttpServletRequest request, Map attributesSnapshot) {
+ logger.debug("Restoring snapshot of request attributes after include");
+
+ // Need to copy into separate Collection here, to avoid side effects
+ // on the Enumeration when removing attributes.
+ Set attrsToCheck = new HashSet();
+ Enumeration attrNames = request.getAttributeNames();
+ while (attrNames.hasMoreElements()) {
+ String attrName = (String) attrNames.nextElement();
+ if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
+ attrsToCheck.add(attrName);
+ }
+ }
+
+ // Iterate over the attributes to check, restoring the original value
+ // or removing the attribute, respectively, if appropriate.
+ for (Iterator it = attrsToCheck.iterator(); it.hasNext();) {
+ String attrName = (String) it.next();
+ Object attrValue = attributesSnapshot.get(attrName);
+ if (attrValue != null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Restoring original value of attribute [" + attrName + "] after include");
+ }
+ request.setAttribute(attrName, attrValue);
+ }
+ else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Removing attribute [" + attrName + "] after include");
+ }
+ request.removeAttribute(attrName);
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.properties b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.properties
new file mode 100644
index 00000000000..977a04a99bf
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.properties
@@ -0,0 +1,19 @@
+# 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.throwaway.ThrowawayControllerHandlerAdapter,\
+ org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
+
+org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
+
+org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FrameworkServlet.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FrameworkServlet.java
new file mode 100644
index 00000000000..dc323d87f2d
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FrameworkServlet.java
@@ -0,0 +1,647 @@
+/*
+ * 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.security.Principal;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextException;
+import org.springframework.context.ApplicationEvent;
+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.web.context.ConfigurableWebApplicationContext;
+import org.springframework.web.context.WebApplicationContext;
+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.
+ *
+ *
+ *
+ *
+ * response.reset()
+ * if necessary.
+ */
+ 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.
+ */
+ protected final void initServletBean() throws ServletException, BeansException {
+ 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 (BeansException 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.
+ * WebApplicationContext from the ServletContext
+ * attribute with the {@link #setContextAttribute configured name}. The
+ * WebApplicationContext must have already been loaded and stored in the
+ * ServletContext before this servlet gets initialized (or invoked).
+ * WebApplicationContext retrieval strategy.
+ * @return the WebApplicationContext for this servlet, or null 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.
+ * null if none
+ * @return the WebApplicationContext for this servlet
+ * @throws BeansException if the context couldn't be initialized
+ * @see org.springframework.web.context.support.XmlWebApplicationContext
+ */
+ protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent)
+ throws BeansException {
+
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Servlet with name '" + getServletName() +
+ "' will try to create custom WebApplicationContext context of class '" +
+ getContextClass().getName() + "'" + ", using parent context [" + parent + "]");
+ }
+ if (!ConfigurableWebApplicationContext.class.isAssignableFrom(getContextClass())) {
+ throw new ApplicationContextException(
+ "Fatal initialization error in servlet with name '" + getServletName() +
+ "': custom WebApplicationContext class [" + getContextClass().getName() +
+ "] is not of type ConfigurableWebApplicationContext");
+ }
+
+ ConfigurableWebApplicationContext wac =
+ (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass());
+ wac.setParent(parent);
+ wac.setServletContext(getServletContext());
+ wac.setServletConfig(getServletConfig());
+ wac.setNamespace(getNamespace());
+ wac.setConfigLocation(getContextConfigLocation());
+ wac.addApplicationListener(new SourceFilteringListener(wac, this));
+
+ postProcessWebApplicationContext(wac);
+ wac.refresh();
+
+ return wac;
+ }
+
+ /**
+ * Post-process the given WebApplicationContext before it is refreshed
+ * and activated as context for this servlet.
+ * refresh() will
+ * be called automatically after this method returns.
+ * @param wac the configured WebApplicationContext (not refreshed yet)
+ * @see #createWebApplicationContext
+ * @see ConfigurableWebApplicationContext#refresh()
+ */
+ protected void postProcessWebApplicationContext(ConfigurableWebApplicationContext wac) {
+ }
+
+ /**
+ * Return the ServletContext attribute name for this servlet's WebApplicationContext.
+ * SERVLET_CONTEXT_PREFIX + servlet name.
+ * @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
+ * @throws BeansException if thrown by ApplicationContext methods
+ */
+ protected void initFrameworkServlet() throws ServletException, BeansException {
+ }
+
+ /**
+ * Refresh this servlet's application context, as well as the
+ * dependent state of the servlet.
+ * @throws BeansException in case of errors
+ * @see #getWebApplicationContext()
+ * @see org.springframework.context.ConfigurableApplicationContext#refresh()
+ */
+ public void refresh() throws BeansException {
+ WebApplicationContext wac = getWebApplicationContext();
+ if (!(wac instanceof ConfigurableApplicationContext)) {
+ throw new IllegalStateException("WebApplicationContext does not support refresh: " + wac);
+ }
+ ((ConfigurableApplicationContext) wac).refresh();
+ }
+
+ /**
+ * ApplicationListener endpoint that receives events from this servlet's
+ * WebApplicationContext.
+ * doHead,
+ * with a NoBodyResponse that just captures the content length.
+ * @see #doService
+ * @see #doHead
+ */
+ protected final void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+
+ processRequest(request, response);
+ }
+
+ /**
+ * Delegate POST requests to {@link #processRequest}.
+ * @see #doService
+ */
+ protected final void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+
+ processRequest(request, response);
+ }
+
+ /**
+ * Delegate PUT requests to {@link #processRequest}.
+ * @see #doService
+ */
+ protected final void doPut(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+
+ processRequest(request, response);
+ }
+
+ /**
+ * Delegate DELETE requests to {@link #processRequest}.
+ * @see #doService
+ */
+ protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+
+ processRequest(request, response);
+ }
+
+ /**
+ * Delegate OPTIONS requests to {@link #processRequest}, if desired.
+ * null 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.
+ * doGet or doPost methods of HttpServlet.
+ *
+ * return (handler instanceof MyHandler);
+ *
+ * @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 supports method of this interface, which must have
+ * returned true.
+ * @throws Exception in case of errors
+ * @return ModelAndView object with the name of the view and the required
+ * model data, or null if the request has been handled directly
+ */
+ ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
+
+ /**
+ * Same contract as for HttpServlet's getLastModified 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);
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerExceptionResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerExceptionResolver.java
new file mode 100644
index 00000000000..7b843335c76
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerExceptionResolver.java
@@ -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;
+
+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.
+ *
+ * null 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 null for default processing
+ */
+ ModelAndView resolveException(
+ HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerExecutionChain.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerExecutionChain.java
new file mode 100644
index 00000000000..98f3cdbfb25
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerExecutionChain.java
@@ -0,0 +1,122 @@
+/*
+ * 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 java.util.ArrayList;
+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 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();
+ 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();
+ for (int i = 0; i < interceptors.length; i++) {
+ this.interceptorList.add(interceptors[i]);
+ }
+ }
+ }
+
+ private void initInterceptorList() {
+ if (this.interceptorList == null) {
+ this.interceptorList = new ArrayList();
+ }
+ if (this.interceptors != null) {
+ for (int i = 0; i < this.interceptors.length; i++) {
+ this.interceptorList.add(this.interceptors[i]);
+ }
+ this.interceptors = null;
+ }
+ }
+
+ /**
+ * Return the array of interceptors to apply (in the given order).
+ * @return the array of HandlerInterceptors instances (may be null)
+ */
+ public HandlerInterceptor[] getInterceptors() {
+ if (this.interceptors == null && this.interceptorList != null) {
+ this.interceptors = (HandlerInterceptor[])
+ this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]);
+ }
+ return this.interceptors;
+ }
+
+ /**
+ * Delegates to the handler's toString().
+ */
+ public String toString() {
+ return String.valueOf(handler);
+ }
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerInterceptor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerInterceptor.java
new file mode 100644
index 00000000000..1c74e9bb73a
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerInterceptor.java
@@ -0,0 +1,120 @@
+/*
+ * 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.
+ *
+ * true 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.
+ * ModelAndView that the handler returned
+ * (can also be null)
+ * @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.
+ * preHandle
+ * method has successfully completed and returned true!
+ * @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;
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerMapping.java
new file mode 100644
index 00000000000..6458beea6d0
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerMapping.java
@@ -0,0 +1,85 @@
+/*
+ * 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;
+
+/**
+ * Interface to be implemented by objects that define a mapping between
+ * requests and handler objects.
+ *
+ * preHandle method in the given order, finally invoking the handler
+ * itself if all preHandle methods have returned true.
+ *
+ * null 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 null if no mapping found
+ * @throws Exception if there is an internal error
+ */
+ HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HttpServletBean.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HttpServletBean.java
new file mode 100644
index 00000000000..3d650438023
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HttpServletBean.java
@@ -0,0 +1,213 @@
+/*
+ * 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.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.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.ServletContextResourceLoader;
+
+/**
+ * Simple extension of {@link javax.servlet.http.HttpServlet} which treats
+ * its config parameters (init-param entries within the
+ * servlet tag in web.xml) as bean properties.
+ *
+ * doGet, doPost, etc).
+ *
+ * null when no
+ * ServletConfig set yet.
+ * @see #getServletConfig()
+ */
+ public final String getServletName() {
+ return (getServletConfig() != null ? getServletConfig().getServletName() : null);
+ }
+
+ /**
+ * Overridden method that simply returns null when no
+ * ServletConfig set yet.
+ * @see #getServletConfig()
+ */
+ 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.
+ * RequestContext.getLocale() 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 null)
+ */
+ 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 null 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);
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ModelAndView.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ModelAndView.java
new file mode 100644
index 00000000000..fa0a5745bff
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ModelAndView.java
@@ -0,0 +1,303 @@
+/*
+ * 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.Map;
+
+import org.springframework.ui.ModelMap;
+
+/**
+ * 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.
+ *
+ * addObject.
+ * @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 addObject.
+ * @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 null, but the
+ * model Map may be null if there is no model data.
+ */
+ public ModelAndView(String viewName, Map model) {
+ this.view = viewName;
+ if (model != null) {
+ getModelMap().addAllAttributes(model);
+ }
+ }
+
+ /**
+ * Creates new ModelAndView given a View object and a model.
+ * null, but the
+ * model Map may be null if there is no model data.
+ */
+ public ModelAndView(View view, Map 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 null 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 null 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 ModelAndView 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. true
+ * 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 null.
+ * Called by DispatcherServlet for evaluation of the model.
+ */
+ protected Map getModelInternal() {
+ return this.model;
+ }
+
+ /**
+ * Return the underlying ModelMap instance (never null).
+ */
+ public ModelMap getModelMap() {
+ if (this.model == null) {
+ this.model = new ModelMap();
+ }
+ return this.model;
+ }
+
+ /**
+ * Return the model map. Never returns null.
+ * To be called by application code for modifying the model.
+ */
+ public Map 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 null)
+ * @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 null)
+ * @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 modelMap) {
+ getModelMap().addAllAttributes(modelMap);
+ return this;
+ }
+
+
+ /**
+ * Clear the state of this ModelAndView object.
+ * The object will be empty afterwards.
+ * postHandle 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 && this.model == null);
+ }
+
+ /**
+ * 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.
+ * Returns false if any additional state was added to the instance
+ * after the call to {@link #clear}.
+ * @see #clear()
+ */
+ public boolean wasCleared() {
+ return (this.cleared && isEmpty());
+ }
+
+
+ /**
+ * Return diagnostic information about this model and view.
+ */
+ public String toString() {
+ StringBuffer buf = new StringBuffer("ModelAndView: ");
+ if (isReference()) {
+ buf.append("reference to view with name '").append(this.view).append("'");
+ }
+ else {
+ buf.append("materialized View is [").append(this.view).append(']');
+ }
+ buf.append("; model is ").append(this.model);
+ return buf.toString();
+ }
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ModelAndViewDefiningException.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ModelAndViewDefiningException.java
new file mode 100644
index 00000000000..36ef10866fc
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ModelAndViewDefiningException.java
@@ -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.
+ *
+ * null if no default found)
+ * @throws Exception if view name translation fails
+ */
+ String getViewName(HttpServletRequest request) throws Exception;
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ResourceServlet.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ResourceServlet.java
new file mode 100644
index 00000000000..54e5a957a83
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ResourceServlet.java
@@ -0,0 +1,342 @@
+/*
+ * 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.
+ *
+ * defaultUrl 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.
+ *
+ * defaultUrl 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.
+ *
+ * allowedResources 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!
+ *
+ * contentType 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.
+ *
+ * applyLastModified 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.
+ *
+ * null 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).
+ * null if not predetermined.
+ */
+ String getContentType();
+
+ /**
+ * Render the view given the specified model.
+ * null 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 model, HttpServletRequest request, HttpServletResponse response) throws Exception;
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ViewRendererServlet.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ViewRendererServlet.java
new file mode 100644
index 00000000000..0c59f328a6d
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ViewRendererServlet.java
@@ -0,0 +1,114 @@
+/*
+ * 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.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.
+ *
+ * renderView() 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
+ */
+ 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 model = (Map) request.getAttribute(MODEL_ATTRIBUTE);
+ view.render(model, request, response);
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ViewResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ViewResolver.java
new file mode 100644
index 00000000000..d9b4ff0be73
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/ViewResolver.java
@@ -0,0 +1,55 @@
+/*
+ * 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 java.util.Locale;
+
+/**
+ * Interface to be implemented by objects that can resolve views by name.
+ *
+ * null 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 null
+ * (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 null 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;
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractDetectingUrlHandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractDetectingUrlHandlerMapping.java
new file mode 100644
index 00000000000..10b99b7edbe
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractDetectingUrlHandlerMapping.java
@@ -0,0 +1,101 @@
+/*
+ * 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 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.
+ * null or an empty array if none
+ */
+ protected abstract String[] determineUrlsForHandler(String beanName);
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
new file mode 100644
index 00000000000..f53d4da9c87
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
@@ -0,0 +1,238 @@
+/*
+ * 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.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.core.Ordered;
+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;
+
+/**
+ * Abstract base class for {@link org.springframework.web.servlet.HandlerMapping}
+ * implementations. Supports ordering, a default handler, and handler interceptors.
+ *
+ * Integer.MAX_VALUE, 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.
+ * null, indicating no default handler.
+ */
+ public void setDefaultHandler(Object defaultHandler) {
+ this.defaultHandler = defaultHandler;
+ }
+
+ /**
+ * Return the default handler for this handler mapping,
+ * or null if none.
+ */
+ public Object getDefaultHandler() {
+ return this.defaultHandler;
+ }
+
+ /**
+ * Set the interceptors to apply for all handlers mapped by this handler mapping.
+ * null 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()
+ */
+ protected void initApplicationContext() throws BeansException {
+ extendInterceptors(this.interceptors);
+ initInterceptors();
+ }
+
+ /**
+ * Extension hook that subclasses can override to register additional interceptors,
+ * given the configured interceptors (see {@link #setInterceptors}).
+ * null),
+ * allowing to add further interceptors before as well as after the existing
+ * interceptors
+ */
+ protected void extendInterceptors(List interceptors) {
+ }
+
+ /**
+ * Initialize the specified interceptors, adapting them where necessary.
+ * @see #setInterceptors
+ * @see #adaptInterceptor
+ */
+ protected void initInterceptors() {
+ if (!this.interceptors.isEmpty()) {
+ this.adaptedInterceptors = new HandlerInterceptor[this.interceptors.size()];
+ 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");
+ }
+ this.adaptedInterceptors[i] = adaptInterceptor(interceptor);
+ }
+ }
+ }
+
+ /**
+ * Adapt the given interceptor object to the HandlerInterceptor interface.
+ * null if none
+ */
+ protected final HandlerInterceptor[] getAdaptedInterceptors() {
+ return this.adaptedInterceptors;
+ }
+
+
+ /**
+ * 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 null if no
+ * specific one is found. This method is called by {@link #getHandler};
+ * a null return value will lead to the default handler, if one is set.
+ * null 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.
+ * super.getHandlerExecutionChain
+ * and invoking {@link HandlerExecutionChain#addInterceptor} on the returned chain object.
+ * @param handler the resolved handler instance (never null)
+ * @param request current HTTP request
+ * @return the HandlerExecutionChain (never null)
+ * @see #getAdaptedInterceptors()
+ */
+ protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
+ if (handler instanceof HandlerExecutionChain) {
+ HandlerExecutionChain chain = (HandlerExecutionChain) handler;
+ chain.addInterceptors(getAdaptedInterceptors());
+ return chain;
+ }
+ else {
+ return new HandlerExecutionChain(handler, getAdaptedInterceptors());
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java
new file mode 100644
index 00000000000..0c6e0fda7e8
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java
@@ -0,0 +1,362 @@
+/*
+ * 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.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.BeansException;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.Assert;
+import org.springframework.util.PathMatcher;
+import org.springframework.web.servlet.HandlerExecutionChain;
+import org.springframework.web.servlet.HandlerMapping;
+import org.springframework.web.util.UrlPathHelper;
+
+/**
+ * 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.
+ *
+ * null, indicating no root handler.
+ */
+ public void setRootHandler(Object rootHandler) {
+ this.rootHandler = rootHandler;
+ }
+
+ /**
+ * Return the root handler for this handler mapping (registered for "/"),
+ * or null 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.
+ * null if none found
+ */
+ protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
+ String lookupPath = this.urlPathHelper.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) {
+ validateHandler(rawHandler, request);
+ handler = buildPathExposingHandler(rawHandler, lookupPath);
+ }
+ }
+ if (handler != null && logger.isDebugEnabled()) {
+ logger.debug("Mapping [" + lookupPath + "] to handler '" + 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.
+ * null 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) {
+ validateHandler(handler, request);
+ return buildPathExposingHandler(handler, urlPath);
+ }
+ // Pattern match?
+ String bestPathMatch = null;
+ for (Iterator it = this.handlerMap.keySet().iterator(); it.hasNext();) {
+ String registeredPath = (String) it.next();
+ if (getPathMatcher().match(registeredPath, urlPath) &&
+ (bestPathMatch == null || bestPathMatch.length() < registeredPath.length())) {
+ bestPathMatch = registeredPath;
+ }
+ }
+ if (bestPathMatch != null) {
+ handler = this.handlerMap.get(bestPathMatch);
+ validateHandler(handler, request);
+ String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPathMatch, urlPath);
+ return buildPathExposingHandler(handler, pathWithinMapping);
+ }
+ // No handler found...
+ return null;
+ }
+
+ /**
+ * Validate the given handler against the current request.
+ * true.
+ */
+ 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 {
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java
new file mode 100644
index 00000000000..1b42941e50f
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java
@@ -0,0 +1,446 @@
+/*
+ * 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.Enumeration;
+import java.util.Properties;
+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;
+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.
+ *
+ * javax.servlet.ServletException and subclasses, for example.
+ * null for not exposing an exception attribute at all.
+ * @see #DEFAULT_EXCEPTION_ATTRIBUTE
+ */
+ public void setExceptionAttribute(String exceptionAttribute) {
+ this.exceptionAttribute = exceptionAttribute;
+ }
+
+
+ /**
+ * 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)) {
+ return doResolveException(request, response, handler, ex);
+ }
+ else {
+ return null;
+ }
+ }
+
+ /**
+ * Check whether this resolver is supposed to apply to the given handler.
+ * null 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 (int i = 0; i < this.mappedHandlerClasses.length; i++) {
+ if (this.mappedHandlerClasses[i].isInstance(handler)) {
+ return true;
+ }
+ }
+ }
+ }
+ // Else only apply if there are no explicit handler mappings.
+ return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
+ }
+
+ /**
+ * Actually resolve the given exception that got thrown during on handler execution,
+ * returning a ModelAndView that represents a specific error page if appropriate.
+ * null 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 null for default processing
+ */
+ protected ModelAndView doResolveException(
+ HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
+
+ // 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);
+
+ // 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.intValue());
+ }
+ return getModelAndView(viewName, ex, request);
+ }
+ else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Log the given exception at warn level, provided that warn logging has been
+ * activated through the {@link #setWarnLogCategory "warnLogCategory"} property.
+ * null 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 null 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.
+ * null 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) {
+ 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 javax.servlet.http.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, new Integer(statusCode));
+ }
+ }
+
+ /**
+ * Return a ModelAndView for the given request, view name and exception.
+ * service method to handle a request.
+ *
+ * init
+ * method will be called with a ServletConfig that contains the bean name
+ * of the Servlet and the ServletContext that it is running in.
+ *
+ * destroy
+ * will be called.
+ *
+ * setServletConfig, if available.
+ * java.util.Properties class, like as follows:
+ *
+ * /welcome.html=ticketController
+ * /show.html=ticketController
+ *
+ * The syntax is PATH=HANDLER_BEAN_NAME.
+ * If the path doesn't begin with a slash, one is prepended.
+ *
+ * detectAndCreateHandlers method in addition
+ * to the superclass's initialization.
+ * @see #detectAndCreateHandlers
+ */
+ public void initApplicationContext() throws BeansException {
+ super.initApplicationContext();
+
+ if (!(getApplicationContext() instanceof ConfigurableApplicationContext)) {
+ throw new IllegalStateException(
+ "[" + getClass().getName() + "] needs to run in a ConfigurableApplicationContext");
+ }
+ ConfigurableListableBeanFactory beanFactory =
+ ((ConfigurableApplicationContext) getApplicationContext()).getBeanFactory();
+ detectAndCreateHandlers(beanFactory);
+ }
+
+ /**
+ * Look for all classes with a PathMap class attribute, instantiate them in
+ * the owning ApplicationContext, and register them as MVC handlers usable
+ * by the current DispatcherServlet.
+ * @param beanFactory the ConfigurableListableBeanFactory to register the
+ * created handler instances with
+ * @throws BeansException if handler detection or creation failed
+ * @see PathMap
+ * @see #getClassesWithPathMapAttributes()
+ * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory#createBean
+ * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory#registerSingleton
+ */
+ protected void detectAndCreateHandlers(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+ try {
+ Class[] handlerClasses = getClassesWithPathMapAttributes();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Found " + handlerClasses.length + " attribute-targeted handlers");
+ }
+
+ // for each Class returned by the Commons Attribute indexer
+ for (int i = 0; i < handlerClasses.length; i++) {
+ Class handlerClass = handlerClasses[i];
+
+ // Autowire the given handler class via AutowireCapableBeanFactory.
+ // Either autowires a constructor or by type, depending on the
+ // constructors available in the given class.
+ Object handler = beanFactory.createBean(handlerClass, this.autowireMode, this.dependencyCheck);
+
+ // We now have an "autowired" handler, that may reference beans in the
+ // application context. We now add the new handler to the factory.
+ // This isn't necessary for the handler to work, but is useful if we want
+ // to enumerate controllers in the factory etc.
+ beanFactory.registerSingleton(handlerClass.getName(), handler);
+
+ // There may be multiple paths mapped to this handler.
+ PathMap[] pathMaps = getPathMapAttributes(handlerClass);
+ registerHandler(pathMaps, handler);
+ }
+ }
+ catch (BeansException ex) {
+ throw ex;
+ }
+ catch (Exception ex) {
+ throw new BeanInitializationException("Could not retrieve PathMap attributes", ex);
+ }
+ }
+
+ /**
+ * Register the given handler for the URL paths indicated by the given PathMaps.
+ * @param pathMaps the PathMap attributes for the handler class
+ * @param handler the handler instance
+ * @throws BeansException if the handler couldn't be registered
+ * @throws IllegalStateException if there is a conflicting handler registered
+ */
+ protected void registerHandler(PathMap[] pathMaps, Object handler) throws BeansException, IllegalStateException {
+ for (int j = 0; j < pathMaps.length; j++) {
+ PathMap pathMap = pathMaps[j];
+ String path = pathMap.getUrl();
+ if (!path.startsWith("/")) {
+ path = "/" + path;
+ }
+ registerHandler(path, handler);
+ }
+ }
+
+
+ /**
+ * Use an attribute index to get a Collection of Class objects
+ * with the required PathMap attribute.
+ * @return a array of Class objects
+ */
+ protected abstract Class[] getClassesWithPathMapAttributes() throws Exception;
+
+ /**
+ * Use Attributes API to find PathMap attributes for the given handler class.
+ * We know there's at least one, as the getClassNamesWithPathMapAttributes
+ * method return this class name.
+ * @param handlerClass the handler class to look for
+ * @return an array of PathMap objects
+ */
+ protected abstract PathMap[] getPathMapAttributes(Class handlerClass) throws Exception;
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/metadata/CommonsPathMapHandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/metadata/CommonsPathMapHandlerMapping.java
new file mode 100644
index 00000000000..bcefb9a56a2
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/metadata/CommonsPathMapHandlerMapping.java
@@ -0,0 +1,81 @@
+/*
+ * 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.metadata;
+
+import java.util.Collection;
+
+import org.apache.commons.attributes.AttributeIndex;
+import org.apache.commons.attributes.Attributes;
+
+/**
+ * Subclass of AbstractPathMapHandlerMapping that recognizes Commons Attributes
+ * metadata attributes of type PathMap on application Controllers and automatically
+ * wires them into the current servlet's WebApplicationContext.
+ *
+ *
+ * &64;org.springframework.web.servlet.handler.commonsattributes.PathMap("/path.cgi")
+ *
+ *
+ *
+In this model, you don't need to map URLs onto controllers in your Spring
+XML web application context bean definition files. You merely need to
+set one or more PathMap attributes on each of your Controller classes, and
+new objects of these types will automatically be added to the relevant
+web application context.
+
+Dependencies--whether expressed by via constructor arguments or JavaBean
+properties, will be resolved if possible using the middle tier definitions in
+the WebApplicationContext.
+
+
+
+
+servletName-servlet.xml
+ bean definition file in your web application. No parameters are required.
+setLocale, 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");
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java
new file mode 100644
index 00000000000..5cc25460f24
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java
@@ -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.
+ *
+ * null)
+ * @see #setDefaultLocale
+ * @see javax.servlet.http.HttpServletRequest#getLocale()
+ */
+ protected Locale determineDefaultLocale(HttpServletRequest request) {
+ Locale defaultLocale = getDefaultLocale();
+ if (defaultLocale == null) {
+ defaultLocale = request.getLocale();
+ }
+ return defaultLocale;
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/FixedLocaleResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/FixedLocaleResolver.java
new file mode 100644
index 00000000000..295a1f779f6
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/FixedLocaleResolver.java
@@ -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.
+ *
+ * setLocale, 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");
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java
new file mode 100644
index 00000000000..0e0ee586dfb
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java
@@ -0,0 +1,82 @@
+/*
+ * 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.i18n;
+
+import java.util.Locale;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.propertyeditors.LocaleEditor;
+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;
+ }
+
+
+ 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?");
+ }
+ LocaleEditor localeEditor = new LocaleEditor();
+ localeEditor.setAsText(newLocale);
+ localeResolver.setLocale(request, response, (Locale) localeEditor.getValue());
+ }
+ // Proceed in any case.
+ return true;
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/SessionLocaleResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/SessionLocaleResolver.java
new file mode 100644
index 00000000000..02127271778
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/SessionLocaleResolver.java
@@ -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.
+ *
+ * setLocale, 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 RequestContext(Utils).getLocale()
+ * 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.
+ * null)
+ * @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);
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/package.html b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/package.html
new file mode 100644
index 00000000000..ce682d462d0
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/package.html
@@ -0,0 +1,9 @@
+
+
+ * none (so only those available in superclass).
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @see #setCommandClass
+ * @see #setCommandName
+ * @see #setValidator
+ */
+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);
+ }
+
+
+ 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.
+ * errors.getModel() 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 null 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;
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/AbstractController.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/AbstractController.java
new file mode 100644
index 00000000000..676313e7698
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/AbstractController.java
@@ -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;
+
+/**
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * @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.
+ *
+ *
+ * name
+ * default
+ * description
+ *
+ *
+ * supportedMethods
+ * GET,POST
+ * comma-separated (CSV) list of methods supported by this controller,
+ * such as GET, POST and PUT
+ *
+ *
+ * requireSession
+ * false
+ * 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
+ *
+ *
+ * cacheSeconds
+ * -1
+ * 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
+ * any headers and any positive number will generate headers
+ * that state the amount indicated as seconds to cache the content
+ *
+ *
+ * synchronizeOnSession
+ * false
+ * whether the call to
+ * handleRequestInternal should be
+ * synchronized around the HttpSession, to serialize invocations
+ * from the same client. No effect if there is no HttpSession.
+ * handleRequestInternal
+ * 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.
+ * SESSION_MUTEX_ATTRIBUTE constant. It serves as a
+ * safe reference to synchronize on for locking on the current session.
+ * handleRequest.
+ * @see #handleRequest
+ */
+ protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
+ throws Exception;
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/AbstractFormController.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/AbstractFormController.java
new file mode 100644
index 00000000000..3426b181360
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/AbstractFormController.java
@@ -0,0 +1,674 @@
+/*
+ * 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;
+
+/**
+ * sessionForm property has been set to true.showForm to prepare the form view,
+ * and processFormSubmission 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.Action
+ * 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' ActionForm). 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 java.beans.PropertyEditor. For more information on that
+ * subject, see the workflow of this controller and the explanation of the
+ * {@link BaseCommandController}.
+ *
+ *
+ * bindOnNewForm is set to true, 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. Note: 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.sessionForm 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.validateOnBinding 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}
+ *
+ *
+ *
+ *
+ * name
+ * default
+ * description
+ *
+ *
+ * bindOnNewForm
+ * false
+ * Indicates whether to bind servlet request parameters when
+ * creating a new form. Otherwise, the parameters will only be
+ * bound on form submission attempts.
+ *
+ *
+ * sessionForm
+ * false
+ * 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).
+ * true 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.
+ * true if session form mode is to be activated
+ */
+ public final void setSessionForm(boolean sessionForm) {
+ this.sessionForm = sessionForm;
+ }
+
+ /**
+ * Return true 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
+ */
+ 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.
+ * null 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.
+ * bindOnNewForm is true.
+ * onBindOnNewForm(request, command).
+ * @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.
+ * bindOnNewForm is set to true.
+ * showForm(request, errors, "myView")
+ * to prepare the form view for a specific view name, returning the
+ * ModelAndView provided there.
+ * errors.getModel()
+ * 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}.
+ * null 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.
+ * null.
+ * 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 null 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.
+ * errors.getModel() 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 null
+ * @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).
+ *
+ * protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response) throws Exception {
+ * return showNewForm(request, response);
+ * }
+ * You can also show a new form but with special errors registered on it:
+ *
+ * protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response) throws Exception {
+ * BindException errors = getErrorsForNewForm(request);
+ * errors.reject("duplicateFormSubmission", "Duplicate form submission");
+ * return showForm(request, response, errors);
+ * }
+ * @param request current HTTP request
+ * @param response current HTTP response
+ * @return a prepared view, or null 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);
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/AbstractUrlViewController.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/AbstractUrlViewController.java
new file mode 100644
index 00000000000..fec8666a644
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/AbstractUrlViewController.java
@@ -0,0 +1,111 @@
+/*
+ * 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.util.UrlPathHelper;
+
+/**
+ * Abstract base class for Controllers that return a view name
+ * based on the request URL.
+ *
+ * alwaysUseFullPath
+ * and urlDecode 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 undecoded by the Servlet API,
+ * in contrast to the servlet path.
+ * null)
+ * @see #handleRequestInternal
+ * @see #setAlwaysUseFullPath
+ * @see #setUrlDecode
+ */
+ protected abstract String getViewNameForRequest(HttpServletRequest request);
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/AbstractWizardFormController.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/AbstractWizardFormController.java
new file mode 100644
index 00000000000..7f40415904d
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/AbstractWizardFormController.java
@@ -0,0 +1,743 @@
+/*
+ * 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.
+ *
+ *
+ *
+ *
+ * validateXXX methods that the validator needs to provide,
+ * validating certain pieces of the object. These can be combined to validate
+ * the elements of individual pages.
+ *
+ * getPageCount variant returns the static page count as
+ * determined by this getPageCount() 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.
+ * null 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.
+ * null.
+ * 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 null 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.
+ * null 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.
+ * null if handled directly
+ * @throws Exception in case of errors
+ * @see #showNewForm
+ * @see #setBindOnNewForm
+ */
+ protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response)
+ throws Exception {
+
+ return showNewForm(request, response);
+ }
+
+
+ /**
+ * Apply wizard workflow: finish, cancel, page change.
+ */
+ 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}.
+ * true if a parameter
+ * matching the "_finish" key is present in the request, otherwise it
+ * returns false. Subclasses may override this method
+ * to provide custom logic to detect a finish request.
+ * true if a parameter
+ * matching the "_cancel" key is present in the request, otherwise it
+ * returns false. Subclasses may override this method
+ * to provide custom logic to detect a cancel request.
+ * validateXXX
+ * methods of this instance's Validator, combining them to validation of the
+ * corresponding pages. The Validator's default validate 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.
+ * validate 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.
+ * errors.getModel() to populate the ModelAndView model
+ * with the command and the Errors instance, under the specified command name,
+ * as expected by the "spring:bind" tag.
+ * errors.getModel() 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");
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/BaseCommandController.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/BaseCommandController.java
new file mode 100644
index 00000000000..b7e7f44f4ab
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/BaseCommandController.java
@@ -0,0 +1,591 @@
+/*
+ * 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;
+
+/**
+ *
+ * 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.
+ * 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
+ * 'firstName' exists, the framework will attempt to call
+ * setFirstName([value]) passing the value of the parameter. Nested properties
+ * are of course supported. For instance a parameter named 'address.city'
+ * will result in a getAddress().setCity([value]) call on the
+ * command class.setLocale(Locale loc) is
+ * perfectly possible for a request parameter named locale having
+ * a value of en, as long as you register the appropriate
+ * PropertyEditor in the Controller (see {@link #initBinder initBinder()}
+ * for more information on that matter.
+ * 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
+ * here.
+ *
+ *
+ *
+ *
+ * name
+ * default
+ * description
+ *
+ *
+ * commandName
+ * command
+ * the name to use when binding the instantiated command class
+ * to the request
+ *
+ *
+ * commandClass
+ * null
+ * 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.
+ *
+ *
+ * validators
+ * null
+ * 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.
+ *
+ *
+ * validator
+ * null
+ * Short-form property for setting only one Validator bean (usually passed in
+ * using a <ref bean="beanId"/> property.
+ *
+ *
+ * validateOnBinding
+ * true
+ * Indicates whether or not to validate the command object after the
+ * object has been populated with request parameters.
+ *
Default is null, 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 PropertyAccessExceptions.
+ *
Default is null, 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.
+ *
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. + *
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. + *
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; + } + + + 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. + *
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. + *
This implementation uses BeanUtils.instantiateClass,
+ * 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.
+ *
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. + *
Called by {@link #bindAndValidate}. Can be overridden to plug in + * custom ServletRequestDataBinder instances. + *
The default implementation creates a standard ServletRequestDataBinder + * and invokes {@link #prepareBinder} and {@link #initBinder}. + *
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}. + *
Default is "false". Can be overridden in subclasses.
+ * @return whether to use direct field access (true)
+ * or bean property access (false)
+ * @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}.
+ *
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. + *
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. + *
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. + *
Called by the default implementation of the + * {@link #onBind(HttpServletRequest, Object, BindException)} variant + * with all parameters, after standard binding but before validation. + *
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. + *
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. + *
Called by the default implementation of the + * {@link #suppressValidation(HttpServletRequest, Object, BindException)} variant + * with all parameters. + *
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. + *
Called by the default implementation of the + * {@link #suppressValidation(HttpServletRequest, Object)} variant + * with all parameters. + *
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 + */ + 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. + *
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 { + } + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/CancellableFormController.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/CancellableFormController.java new file mode 100644 index 00000000000..b0bba252d8d --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/CancellableFormController.java @@ -0,0 +1,205 @@ +/* + * 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; + +/** + *
Extension of SimpleFormController that supports "cancellation"
+ * of form processing. By default, this controller looks for a given parameter in the
+ * request, identified by the cancelParamKey. If this parameter is present,
+ * then the controller will return the configured cancelView, otherwise
+ * processing is passed back to the superclass.
Workflow
+ * (in addition to the superclass):
+ *
true
+ * if the configured cancelParamKey exists in the request.
+ * This behavior can be overridden in subclasses.false, 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
+ * cancelView. This behavior can be overridden in subclasses.Thanks to Erwin Bolwidt for submitting the original prototype + * of such a cancellable form controller!
+ * + * @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) + */ +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". + *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) + */ + 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) + */ + 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 + */ + 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. + *
By default, this method returns true if a parameter
+ * matching the configured cancelParamKey is present in
+ * the request, otherwise it returns false. Subclasses may
+ * override this method to provide custom logic to detect a cancel request.
+ *
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 true.
+ *
Default implementation delegates to onCancel(Object) to return
+ * the configured cancelView. 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.
+ *
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 onCancel 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 null
+ * @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 onCancel version. Called by the default implementation
+ * of the onCancel version with all parameters.
+ *
Default implementation returns eturns the configured cancelView.
+ * Subclasses may override this method to build a custom {@link ModelAndView ModelAndView}
+ * that may contain model parameters used in the cancel view.
+ *
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 onCancel method.
+ * @param command form object with request parameters bound onto it
+ * @return the prepared model and view, or null
+ * @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());
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/Controller.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/Controller.java
new file mode 100644
index 00000000000..6a4eca977f1
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/Controller.java
@@ -0,0 +1,133 @@
+/*
+ * 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.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
+ * HttpServletRequest and HttpServletResponse
+ * instances just like a HttpServlet but is able to
+ * participate in an MVC workflow. Controllers are comparable to the
+ * notion of a Struts Action.
+ *
+ *
Any implementation of the Controller interface should be a + * reusable, thread-safe 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. + *
+ * + * + * + *
+ * After a
So basically any direct 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.
+ * + *Notes on design and testing
+ * + *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.
+ * + *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.
+ * + *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:
+ *org.springframework.context.ApplicationContextAwareorg.springframework.context.ResourceLoaderAwareorg.springframework.web.context.ServletContextAwareSuch 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. + * + *
Controllers can optionally implement the LastModified interface.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @see LastModified
+ * @see SimpleControllerHandlerAdapter
+ * @see AbstractController
+ * @see AbstractCommandController
+ * @see SimpleFormController
+ * @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 null 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 null if handled directly
+ * @throws Exception in case of errors
+ */
+ ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/HttpRequestHandlerAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/HttpRequestHandlerAdapter.java
new file mode 100644
index 00000000000..fd8650420ce
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/HttpRequestHandlerAdapter.java
@@ -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.
+ *
+ *
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;
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/LastModified.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/LastModified.java
new file mode 100644
index 00000000000..7d4ed6dfb83
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/LastModified.java
@@ -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 getLastModified method.
+ *
+ *
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. + * + *
Note: Alternative handler implementation approaches have different
+ * last-modified handling styles. For example, Spring 2.5's annotated controller
+ * approach (using @RequestMapping) 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 getLastModified method.
+ * Invoked before request processing.
+ *
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); + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java new file mode 100644 index 00000000000..b571b8f57a4 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java @@ -0,0 +1,104 @@ +/* + * 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; + +/** + *
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. + * + *
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. + * + *
Workflow
+ * (and that defined by superclass):
+ *
viewName. Nothing more, nothing lessExposed configuration properties
+ * (and those defined by superclass):
+ *
| name | + *default | + *description | + *
| viewName | + *null | + *the name of the view the viewResolver will use to forward to + * (if this property is not set, an exception will be thrown during + * initialization) | + *
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. + * + *
Example: web.xml, mapping all "/myservlet" requests to a Spring dispatcher. + * Also defines a custom "myServlet", but without servlet mapping. + * + *
+ * <servlet> + * <servlet-name>myServlet</servlet-name> + * <servlet-class>mypackage.TestServlet</servlet-class> + * </servlet> + * + * <servlet> + * <servlet-name>myDispatcher</servlet-name> + * <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> + * </servlet> + * + * <servlet-mapping> + * <servlet-name>myDispatcher</servlet-name> + * <url-pattern>/myservlet</url-pattern> + * </servlet-mapping>+ * + * Example: 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. + * + *
+ * <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> + * <property name="interceptors"> + * <list> + * <ref bean="openSessionInViewInterceptor"/> + * </list> + * </property> + * <property name="mappings"> + * <props> + * <prop key="/myservlet">myServletForwardingController</prop> + * </props> + * </property> + * </bean> + * + * <bean id="myServletForwardingController" class="org.springframework.web.servlet.mvc.ServletForwardingController"> + * <property name="servletName"><value>myServlet</value></property> + * </bean>+ * + * @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. + *
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;
+ }
+ }
+
+
+ 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 include or
+ * forward method.
+ *
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 true for include, false 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());
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/ServletWrappingController.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/ServletWrappingController.java
new file mode 100644
index 00000000000..7d94e9f0490
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/ServletWrappingController.java
@@ -0,0 +1,196 @@
+/*
+ * 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}).
+ *
+ *
Useful to invoke an existing servlet via Spring's dispatching infrastructure, + * for example to apply Spring HandlerInterceptors to its requests. + * + *
Note that Struts has a special requirement in that it parses web.xml
+ * 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).
+ *
+ *
Example: 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. + * + *
+ * <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> + * <property name="interceptors"> + * <list> + * <ref bean="openSessionInViewInterceptor"/> + * </list> + * </property> + * <property name="mappings"> + * <props> + * <prop key="*.do">strutsWrappingController</prop> + * </props> + * </property> + * </bean> + * + * <bean id="strutsWrappingController" class="org.springframework.web.servlet.mvc.ServletWrappingController"> + * <property name="servletClass"> + * <value>org.apache.struts.action.ActionServlet</value> + * </property> + * <property name="servletName"> + * <value>action</value> + * </property> + * <property name="initParameters"> + * <props> + * <prop key="config">/WEB-INF/struts-config.xml</prop> + * </props> + * </property> + * </bean>+ * + * @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
javax.servlet.Servlet.
+ * @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)
+ */
+ 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();
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/SimpleControllerHandlerAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/SimpleControllerHandlerAdapter.java
new file mode 100644
index 00000000000..6577e2997c9
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/SimpleControllerHandlerAdapter.java
@@ -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.
+ *
+ * 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; + } + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/SimpleFormController.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/SimpleFormController.java new file mode 100644 index 00000000000..b512b1fcb2f --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/SimpleFormController.java @@ -0,0 +1,462 @@ +/* + * 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; + +/** + *
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.
+ * + *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.
+ * + *Workflow
+ * (in addition to the superclass):
+ *
successView. Consider implementing {@link #doSubmitAction} doSubmitAction
+ * for simply performing a submit action and rendering the success view.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.
+ * + *Exposed configuration properties
+ * (and those defined by superclass):
+ *
| name | + *default | + *description | + *
| formView | + *null | + *Indicates what view to use when the user asks for a new form + * or when validation errors have occurred on form submission. | + *
| successView | + *null | + *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. | + *