getErrors method.
+ *
+ * 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 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/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java b/org.springframework.web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java
new file mode 100644
index 00000000000..5bc4cd7b622
--- /dev/null
+++ b/org.springframework.web/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/src/main/java/org/springframework/web/bind/ServletRequestParameterPropertyValues.java b/org.springframework.web/src/main/java/org/springframework/web/bind/ServletRequestParameterPropertyValues.java
new file mode 100644
index 00000000000..a5e36bb89df
--- /dev/null
+++ b/org.springframework.web/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/src/main/java/org/springframework/web/bind/ServletRequestUtils.java b/org.springframework.web/src/main/java/org/springframework/web/bind/ServletRequestUtils.java
new file mode 100644
index 00000000000..28e4e34228e
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/ServletRequestUtils.java
@@ -0,0 +1,709 @@
+/*
+ * 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;
+
+/**
+ * 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. Default values, for parameters that are otherwise
+ * not present, can specify a value for the field other then empty.
+ *
+ * @author Juergen Hoeller
+ * @author Scott Andrews
+ * @since 1.2
+ * @see #registerCustomEditor
+ * @see #setAllowedFields
+ * @see #setRequiredFields
+ * @see #setFieldMarkerPrefix
+ * @see #setFieldDefaultPrefix
+ * @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 = "_";
+
+ /**
+ * Default prefix that field default parameters start with, followed by the field
+ * name: e.g. "!subscribeToNewsletter" for a field "subscribeToNewsletter".
+ * Default parameters differ from field markers in that they provide a default
+ * value instead of an empty value.
+ * @see #setFieldDefaultPrefix
+ */
+ public static final String DEFAULT_FIELD_DEFAULT_PREFIX = "!";
+
+ private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX;
+
+ private String fieldDefaultPrefix = DEFAULT_FIELD_DEFAULT_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;
+ }
+
+ /**
+ * Specify a prefix that can be used for parameters that indicate default
+ * value fields, having "prefix + field" as name. The value of the default
+ * field is used when the field is not provided.
+ * Default is "!", for "!FIELD" parameters (e.g. "!subscribeToNewsletter").
+ * Set this to null if you want to turn off the field defaults 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. A default field is especially
+ * useful when a checkbox represents a non-boolean value.
+ * The presence of a default parameter preempts the behavior of a field
+ * marker for the given field.
+ * @see #DEFAULT_FIELD_DEFAULT_PREFIX
+ * @see org.springframework.web.servlet.mvc.BaseCommandController#onBind
+ */
+ public void setFieldDefaultPrefix(String fieldDefaultPrefix) {
+ this.fieldDefaultPrefix = fieldDefaultPrefix;
+ }
+
+ /**
+ * Return the prefix for parameters that mark default fields.
+ */
+ public String getFieldDefaultPrefix() {
+ return this.fieldDefaultPrefix;
+ }
+
+ /**
+ * 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 default and marker check
+ * before delegating to the superclass binding process.
+ * @see #checkFieldDefaults
+ * @see #checkFieldMarkers
+ */
+ @Override
+ protected void doBind(MutablePropertyValues mpvs) {
+ checkFieldDefaults(mpvs);
+ checkFieldMarkers(mpvs);
+ super.doBind(mpvs);
+ }
+
+ /**
+ * Check the given property values for field defaults,
+ * i.e. for fields that start with the field default prefix.
+ * The existence of a field defaults indicates that the specified
+ * value should be used if the field is otherwise not present.
+ * @param mpvs the property values to be bound (can be modified)
+ * @see #getFieldDefaultPrefix
+ */
+ protected void checkFieldDefaults(MutablePropertyValues mpvs) {
+ if (getFieldDefaultPrefix() != null) {
+ String fieldDefaultPrefix = getFieldDefaultPrefix();
+ PropertyValue[] pvArray = mpvs.getPropertyValues();
+ for (PropertyValue pv : pvArray) {
+ if (pv.getName().startsWith(fieldDefaultPrefix)) {
+ String field = pv.getName().substring(fieldDefaultPrefix.length());
+ if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
+ mpvs.addPropertyValue(field, pv.getValue());
+ }
+ mpvs.removePropertyValue(pv);
+ }
+ }
+ }
+ }
+
+ /**
+ * 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 (PropertyValue pv : pvArray) {
+ 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 The method parameter may be declared as type {@link javax.servlet.http.Cookie}
+ * or as cookie value type (String, int, etc).
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ * @see RequestMapping
+ * @see RequestParam
+ * @see RequestHeader
+ * @see org.springframework.web.bind.annotation.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 CookieValue {
+
+ /**
+ * The name of the cookie to bind to.
+ */
+ String value() default "";
+
+ /**
+ * Whether the header is required.
+ * Default is Alternatively, provide a {@link #defaultValue() defaultValue},
+ * which implicitely sets this flag to 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:
+ * The following return types are supported for handler methods:
+ * NOTE: 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/src/main/java/org/springframework/web/bind/annotation/Mapping.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/Mapping.java
new file mode 100644
index 00000000000..afbd1f2e723
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/Mapping.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.bind.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Meta annotation that indicates a web mapping annotation.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ * @see RequestMapping
+ */
+@Target({ElementType.ANNOTATION_TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Mapping {
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/ModelAttribute.java
new file mode 100644
index 00000000000..dbd293ff867
--- /dev/null
+++ b/org.springframework.web/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/src/main/java/org/springframework/web/bind/annotation/PathVariable.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/PathVariable.java
new file mode 100644
index 00000000000..9fff5eff2e2
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/PathVariable.java
@@ -0,0 +1,26 @@
+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 URI template variable. Supported for {@link
+ * RequestMapping} annotated handler methods in Servlet environments.
+ *
+ * @author Arjen Poutsma
+ * @see RequestMapping
+ * @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
+ * @since 3.0
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface PathVariable {
+
+ /** The URI template variable to bind to. */
+ String value() default "";
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestBody.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestBody.java
new file mode 100644
index 00000000000..ebf36beab64
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestBody.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.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 the web request body. Supported for annotated
+ * handler methods in Servlet environments.
+ *
+ * @author Arjen Poutsma
+ * @see RequestHeader
+ * @see ResponseBody
+ * @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
+ * @since 3.0
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RequestBody {
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestHeader.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestHeader.java
new file mode 100644
index 00000000000..e9214a2de43
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestHeader.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.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 header.
+ * Supported for annotated handler methods in Servlet and Portlet environments.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ * @see RequestMapping
+ * @see RequestParam
+ * @see CookieValue
+ * @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 RequestHeader {
+
+ /**
+ * The name of the request header to bind to.
+ */
+ String value() default "";
+
+ /**
+ * Whether the header is required.
+ * Default is Alternatively, provide a {@link #defaultValue() defaultValue},
+ * which implicitely sets this flag to 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).
+ * Supported for Servlet environments as well as Portlet 2.0 environments.
+ */
+ 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 {};
+
+ /**
+ * The headers of the mapped request, narrowing the primary mapping.
+ * Same format for any environment: a sequence of "My-Header=myValue" style
+ * expressions, with a request only mapped if each such header is found
+ * to have the given value. "My-Header" style expressions are also supported,
+ * with such headers having to be present in the request (allowed to have
+ * any value). Finally, "!My-Header" style expressions indicate that the
+ * specified header is not supposed to be present in the request.
+ * Also supports media type wildcards (*), for headers such as Accept
+ * and Content-Type. For instance,
+ * Supported at the type level as well as at the method level!
+ * When used at the type level, all method-level mappings inherit
+ * this header restriction (i.e. the type-level restriction
+ * gets checked before the handler method is even resolved).
+ * @see org.springframework.http.MediaType
+ */
+ String[] headers() default {};
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMethod.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMethod.java
new file mode 100644
index 00000000000..3695cb34251
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMethod.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.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/src/main/java/org/springframework/web/bind/annotation/RequestParam.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestParam.java
new file mode 100644
index 00000000000..acfb1939527
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestParam.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.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 annotated handler methods in Servlet and Portlet environments.
+ *
+ * @author Arjen Poutsma
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see RequestMapping
+ * @see RequestHeader
+ * @see CookieValue
+ * @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 name of the request parameter to bind to.
+ */
+ String value() default "";
+
+ /**
+ * Whether the parameter is required.
+ * Default is Alternatively, provide a {@link #defaultValue() defaultValue},
+ * which implicitely sets this flag to If this element is not set, it will default to the standard status
+ * message for the status code.
+ *
+ * @see javax.servlet.http.HttpServletResponse#sendError(int, String)
+ */
+ String reason() default "";
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/SessionAttributes.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/SessionAttributes.java
new file mode 100644
index 00000000000..076bec779be
--- /dev/null
+++ b/org.springframework.web/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.
+ *
+ * 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/src/main/java/org/springframework/web/bind/annotation/package-info.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/package-info.java
new file mode 100644
index 00000000000..afe5c6fd6e7
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/package-info.java
@@ -0,0 +1,9 @@
+
+/**
+ *
+ * Annotations for binding requests to controllers and handler methods
+ * as well as for binding request parameters to method arguments.
+ *
+ */
+package org.springframework.web.bind.annotation;
+
diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvocationException.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvocationException.java
new file mode 100644
index 00000000000..bfbb1ac5303
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvocationException.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.support;
+
+import java.lang.reflect.Method;
+
+import org.springframework.core.NestedRuntimeException;
+
+/**
+ * Exception indicating that the execution of an annotated MVC handler method failed.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.6
+ * @see HandlerMethodInvoker#invokeHandlerMethod
+ */
+public class HandlerMethodInvocationException extends NestedRuntimeException {
+
+ /**
+ * Create a new HandlerMethodInvocationException for the given Method handle and cause.
+ * @param handlerMethod the handler method handle
+ * @param cause the cause of the invocation failure
+ */
+ public HandlerMethodInvocationException(Method handlerMethod, Throwable cause) {
+ super("Failed to invoke handler method [" + handlerMethod + "]", cause);
+ }
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java
new file mode 100644
index 00000000000..341db9029e2
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java
@@ -0,0 +1,745 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.bind.annotation.support;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.core.BridgeMethodResolver;
+import org.springframework.core.Conventions;
+import org.springframework.core.GenericTypeResolver;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.http.HttpInputMessage;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.model.ui.PresentationModelFactory;
+import org.springframework.model.ui.config.BindingLifecycle;
+import org.springframework.ui.ExtendedModelMap;
+import org.springframework.ui.Model;
+import org.springframework.ui.MvcBindingLifecycle;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.Errors;
+import org.springframework.web.HttpMediaTypeNotSupportedException;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.CookieValue;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.support.DefaultSessionAttributeStore;
+import org.springframework.web.bind.support.PresentationModelUtils;
+import org.springframework.web.bind.support.SessionAttributeStore;
+import org.springframework.web.bind.support.SessionStatus;
+import org.springframework.web.bind.support.SimpleSessionStatus;
+import org.springframework.web.bind.support.WebArgumentResolver;
+import org.springframework.web.bind.support.WebBindingInitializer;
+import org.springframework.web.bind.support.WebRequestDataBinder;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.NativeWebRequestParameterMap;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.multipart.MultipartRequest;
+
+/**
+ * Support class for invoking an annotated handler method. Operates on the introspection results of a {@link
+ * HandlerMethodResolver} for a specific handler type.
+ *
+ * Used by {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter} and {@link
+ * org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter}.
+ *
+ * @author Juergen Hoeller
+ * @author Arjen Poutsma
+ * @see #invokeHandlerMethod
+ * @since 2.5.2
+ */
+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();
+
+ private final HttpMessageConverter[] messageConverters;
+
+
+ public HandlerMethodInvoker(HandlerMethodResolver methodResolver) {
+ this(methodResolver, null);
+ }
+
+ public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer) {
+ this(methodResolver, bindingInitializer, new DefaultSessionAttributeStore(), null, new WebArgumentResolver[0],
+ new HttpMessageConverter[0]);
+ }
+
+ public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer,
+ SessionAttributeStore sessionAttributeStore, ParameterNameDiscoverer parameterNameDiscoverer,
+ WebArgumentResolver[] customArgumentResolvers, HttpMessageConverter[] messageConverters) {
+
+ this.methodResolver = methodResolver;
+ this.bindingInitializer = bindingInitializer;
+ this.sessionAttributeStore = sessionAttributeStore;
+ this.parameterNameDiscoverer = parameterNameDiscoverer;
+ this.customArgumentResolvers = customArgumentResolvers;
+ this.messageConverters = messageConverters;
+ }
+
+
+ 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;
+ String headerName = null;
+ boolean requestBodyFound = false;
+ String cookieName = null;
+ String pathVarName = null;
+ String attrName = null;
+ boolean required = false;
+ String defaultValue = null;
+ int found = 0;
+ Annotation[] paramAnns = methodParam.getParameterAnnotations();
+
+ for (Annotation paramAnn : paramAnns) {
+ if (RequestParam.class.isInstance(paramAnn)) {
+ RequestParam requestParam = (RequestParam) paramAnn;
+ paramName = requestParam.value();
+ required = requestParam.required();
+ defaultValue = requestParam.defaultValue();
+ found++;
+ }
+ else if (RequestHeader.class.isInstance(paramAnn)) {
+ RequestHeader requestHeader = (RequestHeader) paramAnn;
+ headerName = requestHeader.value();
+ required = requestHeader.required();
+ defaultValue = requestHeader.defaultValue();
+ found++;
+ }
+ else if (RequestBody.class.isInstance(paramAnn)) {
+ requestBodyFound = true;
+ found++;
+ }
+ else if (CookieValue.class.isInstance(paramAnn)) {
+ CookieValue cookieValue = (CookieValue) paramAnn;
+ cookieName = cookieValue.value();
+ required = cookieValue.required();
+ defaultValue = cookieValue.defaultValue();
+ found++;
+ }
+ else if (PathVariable.class.isInstance(paramAnn)) {
+ PathVariable pathVar = (PathVariable) paramAnn;
+ pathVarName = pathVar.value();
+ found++;
+ }
+ else if (ModelAttribute.class.isInstance(paramAnn)) {
+ ModelAttribute attr = (ModelAttribute) paramAnn;
+ attrName = attr.value();
+ found++;
+ }
+ }
+
+ if (found > 1) {
+ throw new IllegalStateException("Handler parameter annotations are exclusive choices - " +
+ "do not specify more than one such annotation on the same parameter: " + handlerMethod);
+ }
+
+ if (found == 0) {
+ Object argValue = resolveCommonArgument(methodParam, webRequest);
+ if (argValue != WebArgumentResolver.UNRESOLVED) {
+ args[i] = argValue;
+ }
+ else {
+ Class paramType = methodParam.getParameterType();
+ 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!");
+ }
+ // TODO - Code Review - NEW BINDING LIFECYCLE RESOLVABLE ARG
+ else if (BindingLifecycle.class.isAssignableFrom(paramType)) {
+ Class> modelType = resolveBindingLifecycleModelType(methodParam);
+ PresentationModelFactory factory = PresentationModelUtils.getPresentationModelFactory(webRequest);
+ Map 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/src/main/java/org/springframework/web/bind/support/PresentationModelUtils.java b/org.springframework.web/src/main/java/org/springframework/web/bind/support/PresentationModelUtils.java
new file mode 100644
index 00000000000..02febd250c7
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/support/PresentationModelUtils.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2004-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.bind.support;
+
+import javax.servlet.ServletRequest;
+
+import org.springframework.model.ui.PresentationModelFactory;
+import org.springframework.model.ui.support.DefaultPresentationModelFactory;
+import org.springframework.web.context.request.WebRequest;
+
+/**
+ * Utilities for working with the 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/src/main/java/org/springframework/web/bind/support/SessionStatus.java b/org.springframework.web/src/main/java/org/springframework/web/bind/support/SessionStatus.java
new file mode 100644
index 00000000000..cf48169e9f1
--- /dev/null
+++ b/org.springframework.web/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/src/main/java/org/springframework/web/bind/support/SimpleSessionStatus.java b/org.springframework.web/src/main/java/org/springframework/web/bind/support/SimpleSessionStatus.java
new file mode 100644
index 00000000000..1839316574c
--- /dev/null
+++ b/org.springframework.web/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/src/main/java/org/springframework/web/bind/support/package-info.java b/org.springframework.web/src/main/java/org/springframework/web/bind/support/package-info.java
new file mode 100644
index 00000000000..f610613eda7
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/support/package-info.java
@@ -0,0 +1,8 @@
+
+/**
+ *
+ * Support classes for web data binding.
+ *
+ */
+package org.springframework.web.bind.support;
+
diff --git a/org.springframework.web/src/main/java/org/springframework/web/multipart/MaxUploadSizeExceededException.java b/org.springframework.web/src/main/java/org/springframework/web/multipart/MaxUploadSizeExceededException.java
new file mode 100644
index 00000000000..fe9892dcfc5
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/multipart/MaxUploadSizeExceededException.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.multipart;
+
+/**
+ * MultipartException subclass thrown when an upload exceeds the
+ * maximum upload size allowed.
+ *
+ * @author Juergen Hoeller
+ * @since 1.0.1
+ */
+public class MaxUploadSizeExceededException extends MultipartException {
+
+ private final long maxUploadSize;
+
+
+ /**
+ * Constructor for MaxUploadSizeExceededException.
+ * @param maxUploadSize the maximum upload size allowed
+ */
+ public MaxUploadSizeExceededException(long maxUploadSize) {
+ this(maxUploadSize, null);
+ }
+
+ /**
+ * Constructor for MaxUploadSizeExceededException.
+ * @param maxUploadSize the maximum upload size allowed
+ * @param ex root cause from multipart parsing API in use
+ */
+ public MaxUploadSizeExceededException(long maxUploadSize, Throwable ex) {
+ super("Maximum upload size of " + maxUploadSize + " bytes exceeded", ex);
+ this.maxUploadSize = maxUploadSize;
+ }
+
+
+ /**
+ * Return the maximum upload size allowed.
+ */
+ public long getMaxUploadSize() {
+ return maxUploadSize;
+ }
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/multipart/MultipartException.java b/org.springframework.web/src/main/java/org/springframework/web/multipart/MultipartException.java
new file mode 100644
index 00000000000..64360f4d8d9
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/multipart/MultipartException.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.multipart;
+
+import org.springframework.core.NestedRuntimeException;
+
+/**
+ * Exception thrown when multipart resolution fails.
+ *
+ * @author Trevor D. Cook
+ * @author Juergen Hoeller
+ * @since 29.09.2003
+ * @see MultipartResolver#resolveMultipart
+ * @see org.springframework.web.multipart.support.MultipartFilter
+ */
+public class MultipartException extends NestedRuntimeException {
+
+ /**
+ * Constructor for MultipartException.
+ * @param msg the detail message
+ */
+ public MultipartException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructor for MultipartException.
+ * @param msg the detail message
+ * @param cause the root cause from the multipart parsing API in use
+ */
+ public MultipartException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/multipart/MultipartFile.java b/org.springframework.web/src/main/java/org/springframework/web/multipart/MultipartFile.java
new file mode 100644
index 00000000000..b45a5a411e1
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/multipart/MultipartFile.java
@@ -0,0 +1,103 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A representation of an uploaded file received in a multipart request.
+ *
+ * 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/src/main/java/org/springframework/web/multipart/MultipartHttpServletRequest.java b/org.springframework.web/src/main/java/org/springframework/web/multipart/MultipartHttpServletRequest.java
new file mode 100644
index 00000000000..249e508b3eb
--- /dev/null
+++ b/org.springframework.web/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/src/main/java/org/springframework/web/multipart/MultipartRequest.java b/org.springframework.web/src/main/java/org/springframework/web/multipart/MultipartRequest.java
new file mode 100644
index 00000000000..d22ce7bde75
--- /dev/null
+++ b/org.springframework.web/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 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/src/main/java/org/springframework/web/multipart/commons/CommonsFileUploadSupport.java b/org.springframework.web/src/main/java/org/springframework/web/multipart/commons/CommonsFileUploadSupport.java
new file mode 100644
index 00000000000..6ae7ca85246
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/multipart/commons/CommonsFileUploadSupport.java
@@ -0,0 +1,322 @@
+/*
+ * 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.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Collection;
+import java.util.HashMap;
+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.multipart.MultipartFile;
+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 Deletes the underlying Commons FileItem instances.
+ * @param multipartFiles Collection of MultipartFile instances
+ * @see org.apache.commons.fileupload.FileItem#delete()
+ */
+ protected void cleanupFileItems(Collection 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 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.
+ */
+ @Override
+ 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;
+ }
+
+
+ @Override
+ public void setAsText(String text) {
+ setValue(text);
+ }
+
+ @Override
+ 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) {
+ throw new IllegalArgumentException("Cannot read contents of multipart file", ex);
+ }
+ }
+ else {
+ super.setValue(value);
+ }
+ }
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/multipart/support/package-info.java b/org.springframework.web/src/main/java/org/springframework/web/multipart/support/package-info.java
new file mode 100644
index 00000000000..5b12543c781
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/multipart/support/package-info.java
@@ -0,0 +1,10 @@
+
+/**
+ *
+ * Support classes for the multipart resolution framework.
+ * Contains property editors for multipart files, and a
+ * servlet filter for multipart handling without Spring's web MVC.
+ *
+ */
+package org.springframework.web.multipart.support;
+
diff --git a/org.springframework.web/src/test/java/org/springframework/mock/web/PassThroughFilterChain.java b/org.springframework.web/src/test/java/org/springframework/mock/web/PassThroughFilterChain.java
new file mode 100644
index 00000000000..3b22b1f966a
--- /dev/null
+++ b/org.springframework.web/src/test/java/org/springframework/mock/web/PassThroughFilterChain.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.mock.web;
+
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.springframework.util.Assert;
+
+/**
+ * Implementation of the {@link javax.servlet.FilterConfig} interface which
+ * simply passes the call through to a given Filter/FilterChain combo
+ * (indicating the next Filter in the chain along with the FilterChain that it is
+ * supposed to work on) or to a given Servlet (indicating the end of the chain).
+ *
+ * @author Juergen Hoeller
+ * @since 2.0.3
+ * @see javax.servlet.Filter
+ * @see javax.servlet.Servlet
+ * @see MockFilterChain
+ */
+public class PassThroughFilterChain implements FilterChain {
+
+ private Filter filter;
+
+ private FilterChain nextFilterChain;
+
+ private Servlet servlet;
+
+
+ /**
+ * Create a new PassThroughFilterChain that delegates to the given Filter,
+ * calling it with the given FilterChain.
+ * @param filter the Filter to delegate to
+ * @param nextFilterChain the FilterChain to use for that next Filter
+ */
+ public PassThroughFilterChain(Filter filter, FilterChain nextFilterChain) {
+ Assert.notNull(filter, "Filter must not be null");
+ Assert.notNull(nextFilterChain, "'FilterChain must not be null");
+ this.filter = filter;
+ this.nextFilterChain = nextFilterChain;
+ }
+
+ /**
+ * Create a new PassThroughFilterChain that delegates to the given Servlet.
+ * @param servlet the Servlet to delegate to
+ */
+ public PassThroughFilterChain(Servlet servlet) {
+ Assert.notNull(servlet, "Servlet must not be null");
+ this.servlet = servlet;
+ }
+
+
+ /**
+ * Pass the call on to the Filter/Servlet.
+ */
+ public void doFilter(ServletRequest request, ServletResponse response) throws ServletException, IOException {
+ if (this.filter != null) {
+ this.filter.doFilter(request, response, this.nextFilterChain);
+ }
+ else {
+ this.servlet.service(request, response);
+ }
+ }
+
+}
diff --git a/org.springframework.web/src/test/java/org/springframework/web/bind/EscapedErrorsTests.java b/org.springframework.web/src/test/java/org/springframework/web/bind/EscapedErrorsTests.java
new file mode 100644
index 00000000000..a279ceab4b2
--- /dev/null
+++ b/org.springframework.web/src/test/java/org/springframework/web/bind/EscapedErrorsTests.java
@@ -0,0 +1,90 @@
+/*
+ * 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 junit.framework.TestCase;
+
+import org.springframework.beans.TestBean;
+import org.springframework.validation.BindException;
+import org.springframework.validation.Errors;
+import org.springframework.validation.FieldError;
+import org.springframework.validation.ObjectError;
+
+/**
+ * @author Juergen Hoeller
+ * @since 02.05.2003
+ */
+public class EscapedErrorsTests extends TestCase {
+
+ public void testEscapedErrors() {
+ TestBean tb = new TestBean();
+ tb.setName("empty &");
+
+ Errors errors = new EscapedErrors(new BindException(tb, "tb"));
+ errors.rejectValue("name", "NAME_EMPTY &", null, "message: &");
+ errors.rejectValue("age", "AGE_NOT_SET 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 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 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 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 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));
+ }
+
+ /**
+ * 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@RequestMapping
+ * annotation at the @Controller type level.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ * @see org.springframework.web.bind.annotation.RequestMapping#params()
+ */
+public class UnsatisfiedServletRequestParameterException extends ServletRequestBindingException {
+
+ private final String[] paramConditions;
+
+ private final Mapnull
+ * 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).
+ * true, leading to an exception thrown in case
+ * of the header missing in the request. Switch this to false
+ * if you prefer a null in case of the header missing.
+ * false.
+ */
+ boolean required() default true;
+
+ /**
+ * The default value to use as a fallback. Supplying a default value implicitely
+ * sets {@link #required()} to false.
+ */
+ String defaultValue() default "";
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/ExceptionHandler.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/ExceptionHandler.java
new file mode 100644
index 00000000000..69586992c2a
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/ExceptionHandler.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.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 handling exceptions in specific handler classes and/or
+ * handler methods. Provides consistent style between Servlet and Portlet
+ * environments, with the semantics adapting to the concrete environment.
+ *
+ *
+ *
+ *
+ * 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).
+ * 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 Arjen Poutsma
+ * @see org.springframework.web.context.request.WebRequest
+ * @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver
+ * @since 3.0
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface ExceptionHandler {
+
+ /**
+ * Exceptions handled by the annotation method. If empty, will default to any exceptions listed in the method
+ * argument list.
+ */
+ Class extends Throwable>[] value() default {};
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/InitBinder.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/InitBinder.java
new file mode 100644
index 00000000000..9b22b261c4f
--- /dev/null
+++ b/org.springframework.web/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.
+ *
+ * void.
+ *
+ * true, leading to an exception thrown in case
+ * of the header missing in the request. Switch this to false
+ * if you prefer a null in case of the header missing.
+ * false.
+ */
+ boolean required() default true;
+
+ /**
+ * The default value to use as a fallback. Supplying a default value implicitely
+ * sets {@link #required()} to false.
+ */
+ String defaultValue() default "";
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
new file mode 100644
index 00000000000..55db587c9b3
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.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.
+ *
+ *
+ *
+ *
+ * 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
+@Mapping
+public @interface RequestMapping {
+
+ /**
+ * The primary mapping expressed by this annotation.
+ *
+ * @RequestMapping(value = "/something", headers = "content-type=text/*")
+ *
+ * will match requests with a Content-Type of "text/html", "text/plain", etc.
+ * 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.
+ * false.
+ */
+ boolean required() default true;
+
+ /**
+ * The default value to use as a fallback. Supplying a default value implicitely
+ * sets {@link #required()} to false.
+ */
+ String defaultValue() default "";
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/ResponseBody.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/ResponseBody.java
new file mode 100644
index 00000000000..1f274735113
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/ResponseBody.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.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 return value should be bound to the web response body. Supported for annotated
+ * handler methods in Servlet environments.
+ *
+ * @author Arjen Poutsma
+ * @see RequestBody
+ * @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
+ * @since 3.0
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface ResponseBody {
+
+}
\ No newline at end of file
diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/ResponseStatus.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/ResponseStatus.java
new file mode 100644
index 00000000000..d07c65d8a86
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/ResponseStatus.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.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;
+
+import org.springframework.http.HttpStatus;
+
+/**
+ * Marks a method or exception class with the status code and reason that should be returned. The status code is applied
+ * to the HTTP response when the handler method is invoked, or whenever said exception is thrown.
+ *
+ * @author Arjen Poutsma
+ * @see org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver
+ * @since 3.0
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface ResponseStatus {
+
+ /**
+ * The status code to use for the response.
+ *
+ * @see javax.servlet.http.HttpServletResponse#setStatus(int)
+ */
+ HttpStatus value();
+
+ /**
+ * The reason to be used for the response. 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/src/main/java/org/springframework/web/bind/support/DefaultSessionAttributeStore.java b/org.springframework.web/src/main/java/org/springframework/web/bind/support/DefaultSessionAttributeStore.java
new file mode 100644
index 00000000000..6f0e512a1a9
--- /dev/null
+++ b/org.springframework.web/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.
+ * model.ui PresentationModel system.
+ * @author Keith Donald
+ */
+public final class PresentationModelUtils {
+
+ private static final String PRESENTATION_MODEL_FACTORY_ATTRIBUTE = "presentationModelFactory";
+
+ private PresentationModelUtils() {
+ }
+
+ /**
+ * Get the PresentationModelFactory for the current web request.
+ * Will create a new one and cache it as a request attribute if one does not exist.
+ * @param request the web request
+ * @return the presentation model factory
+ */
+ public static PresentationModelFactory getPresentationModelFactory(WebRequest request) {
+ PresentationModelFactory factory = (PresentationModelFactory) request.getAttribute(PRESENTATION_MODEL_FACTORY_ATTRIBUTE, WebRequest.SCOPE_REQUEST);
+ if (factory == null) {
+ factory = new DefaultPresentationModelFactory();
+ request.setAttribute(PRESENTATION_MODEL_FACTORY_ATTRIBUTE, factory, WebRequest.SCOPE_REQUEST);
+ }
+ return factory;
+ }
+
+ /**
+ * Get the PresentationModelFactory for the current servlet request.
+ * Will create a new one and cache it as a request attribute if one does not exist.
+ * @param request the servlet
+ * @return the presentation model factory
+ */
+ public static PresentationModelFactory getPresentationModelFactory(ServletRequest request) {
+ PresentationModelFactory factory = (PresentationModelFactory) request.getAttribute(PRESENTATION_MODEL_FACTORY_ATTRIBUTE);
+ if (factory == null) {
+ factory = new DefaultPresentationModelFactory();
+ request.setAttribute(PRESENTATION_MODEL_FACTORY_ATTRIBUTE, factory);
+ }
+ return factory;
+ }
+
+}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/support/SessionAttributeStore.java b/org.springframework.web/src/main/java/org/springframework/web/bind/support/SessionAttributeStore.java
new file mode 100644
index 00000000000..e428074dad5
--- /dev/null
+++ b/org.springframework.web/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.
+ * 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/src/main/java/org/springframework/web/bind/support/WebArgumentResolver.java b/org.springframework.web/src/main/java/org/springframework/web/bind/support/WebArgumentResolver.java
new file mode 100644
index 00000000000..d151266be93
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/bind/support/WebArgumentResolver.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.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 special parameter types, resolving
+ * well-known argument values for them.
+ *
+ *
+ * public class MySpecialArgumentResolver implements WebArgumentResolver {
+ *
+ * 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/src/main/java/org/springframework/web/bind/support/WebBindingInitializer.java b/org.springframework.web/src/main/java/org/springframework/web/bind/support/WebBindingInitializer.java
new file mode 100644
index 00000000000..be7e0479dee
--- /dev/null
+++ b/org.springframework.web/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/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java b/org.springframework.web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java
new file mode 100644
index 00000000000..5f136938174
--- /dev/null
+++ b/org.springframework.web/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
+ *
+ *
+ *
+ * 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/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartResolver.java b/org.springframework.web/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartResolver.java
new file mode 100644
index 00000000000..42a72e6b1ab
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartResolver.java
@@ -0,0 +1,195 @@
+/*
+ * 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.FileItem;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+
+import org.springframework.util.Assert;
+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.2 or above.
+ *
+ * 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
+ */
+ @Override
+ 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) {
+ return (request != null && ServletFileUpload.isMultipartContent(request));
+ }
+
+ public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
+ Assert.notNull(request, "Request must not be null");
+ if (this.resolveLazily) {
+ return new DefaultMultipartHttpServletRequest(request) {
+ @Override
+ 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.
+ */
+ @SuppressWarnings("unchecked")
+ protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
+ String encoding = determineEncoding(request);
+ FileUpload fileUpload = prepareFileUpload(encoding);
+ try {
+ Listnull)
+ * @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/src/main/java/org/springframework/web/multipart/commons/package-info.java b/org.springframework.web/src/main/java/org/springframework/web/multipart/commons/package-info.java
new file mode 100644
index 00000000000..931eaf305a4
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/multipart/commons/package-info.java
@@ -0,0 +1,9 @@
+
+/**
+ *
+ * MultipartResolver implementation for
+ * Jakarta Commons FileUpload.
+ *
+ */
+package org.springframework.web.multipart.commons;
+
diff --git a/org.springframework.web/src/main/java/org/springframework/web/multipart/package-info.java b/org.springframework.web/src/main/java/org/springframework/web/multipart/package-info.java
new file mode 100644
index 00000000000..2de262ce41d
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/multipart/package-info.java
@@ -0,0 +1,11 @@
+
+/**
+ *
+ * Multipart resolution framework for handling file uploads.
+ * Provides a MultipartResolver strategy interface,
+ * and a generic extension of the HttpServletRequest interface
+ * for accessing multipart files in web application code.
+ *
+ */
+package org.springframework.web.multipart;
+
diff --git a/org.springframework.web/src/main/java/org/springframework/web/multipart/support/AbstractMultipartHttpServletRequest.java b/org.springframework.web/src/main/java/org/springframework/web/multipart/support/AbstractMultipartHttpServletRequest.java
new file mode 100644
index 00000000000..cac07d02ba7
--- /dev/null
+++ b/org.springframework.web/src/main/java/org/springframework/web/multipart/support/AbstractMultipartHttpServletRequest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+
+/**
+ * Abstract base implementation of the MultipartHttpServletRequest interface.
+ * Provides management of pre-generated MultipartFile instances.
+ *
+ * @author Juergen Hoeller
+ * @since 06.10.2003
+ */
+public abstract class AbstractMultipartHttpServletRequest extends HttpServletRequestWrapper
+ implements MultipartHttpServletRequest {
+
+ private Mapweb.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/src/main/java/org/springframework/web/multipart/support/StringMultipartFileEditor.java b/org.springframework.web/src/main/java/org/springframework/web/multipart/support/StringMultipartFileEditor.java
new file mode 100644
index 00000000000..9c551c315f2
--- /dev/null
+++ b/org.springframework.web/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.
+ *
+ *