Initial import of web servlet module

This commit is contained in:
Arjen Poutsma 2008-10-27 09:13:23 +00:00
parent 4df7d71c1e
commit af47a8b79b
245 changed files with 40222 additions and 0 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="org.springframework.web.servlet">
<property file="${basedir}/../build.properties"/>
<import file="${basedir}/../build-spring-framework/package-bundle.xml"/>
<import file="${basedir}/../spring-build/standard/default.xml"/>
</project>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="http://ivyrep.jayasoft.org/ivy-doc.xsl"?>
<ivy-module
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://incubator.apache.org/ivy/schemas/ivy.xsd"
version="1.3">
<info organisation="org.springframework" module="${ant.project.name}">
<license name="Apache 2.0" url="http://www.apache.org/licenses/LICENSE-2.0"/>
</info>
<configurations>
<include file="${spring.build.dir}/common/default-ivy-configurations.xml"/>
</configurations>
<publications>
<artifact name="${ant.project.name}"/>
<artifact name="${ant.project.name}-sources" type="src" ext="jar"/>
</publications>
<dependencies>
<!-- compile dependencies -->
<dependency org="org.apache.commons" name="com.springsource.org.apache.commons.logging" rev="1.1.1" conf="compile->runtime" />
<dependency org="org.springframework" name="org.springframework.context" rev="latest.integration" conf="compile->compile" />
<dependency org="org.springframework" name="org.springframework.context.support" rev="latest.integration" conf="compile->compile" />
<dependency org="org.springframework" name="org.springframework.core" rev="latest.integration" conf="compile->compile" />
<dependency org="org.springframework" name="org.springframework.web" rev="latest.integration" conf="compile->compile" />
<!-- optional dependencies -->
<dependency org="javax.servlet" name="com.springsource.javax.servlet" rev="2.5.0" conf="optional->compile" />
<dependency org="javax.servlet" name="com.springsource.javax.servlet.jsp.jstl" rev="1.1.2" />
<dependency org="org.freemarker" name="com.springsource.freemarker" rev="2.3.12" conf="optional->compile" />
<dependency org="org.apache.velocity" name="com.springsource.org.apache.velocity" rev="1.5.0" conf="optional->compile" />
<dependency org="org.apache.velocity" name="com.springsource.org.apache.velocity.tools.view" rev="1.4.0" conf="optional->compile" />
<dependency org="org.apache.tiles" name="org.apache.tiles-library" rev="2.0.5.osgi" conf="optional->compile" />
<dependency org="net.sourceforge.jasperreports" name="com.springsource.net.sf.jasperreports" rev="2.0.5" conf="optional->compile" />
<dependency org="net.sourceforge.jexcelapi" name="com.springsource.jxl" rev="2.6.6" conf="optional->compile" />
<dependency org="org.apache.poi" name="com.springsource.org.apache.poi" rev="3.0.2.FINAL" conf="optional->compile" />
<dependency org="org.apache.commons" name="com.springsource.org.apache.commons.fileupload" rev="1.2.0" conf="optional->compile" />
<!--
<dependency org="javax.servlet" name="com.springsource.javax.servlet.jsp" rev="2.1.0" conf="optional->compile" />
<dependency org="javax.el" name="com.springsource.javax.el" rev="2.1.0" conf="optional->compile" />
<dependency org="org.apache.myfaces" name="com.springsource.org.apache.myfaces.javax.faces" rev="1.2.2" conf="optional->compile" />
<dependency org="org.apache.log4j" name="com.springsource.org.apache.log4j" rev="1.2.15" conf="optional->runtime" />
<dependency org="javax.xml.rpc" name="com.springsource.javax.xml.rpc" rev="1.1.0" conf="optional->runtime" />
<dependency org="org.apache.axis" name="com.springsource.org.apache.axis" rev="1.4.0" conf="optional->runtime" />
<dependency org="com.caucho" name="com.springsource.com.caucho" rev="3.1.5" conf="optional->runtime" />
-->
<!-- test dependencies -->
<dependency org="org.junit" name="com.springsource.org.junit" rev="4.4.0" conf="test->runtime" />
</dependencies>
</ivy-module>

View File

@ -0,0 +1,86 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.parent</artifactId>
<version>3.0-M1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>org.springframework.web</artifactId>
<packaging>jar</packaging>
<name>Spring Framework: Web</name>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.context</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>com.springsource.javax.servlet</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>com.springsource.javax.el</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>com.springsource.javax.servlet.jsp</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.xml.rpc</groupId>
<artifactId>com.springsource.javax.xml.rpc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.aopalliance</groupId>
<artifactId>com.springsource.org.aopalliance</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.myfaces</groupId>
<artifactId>com.springsource.org.apache.myfaces.javax.faces</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>com.springsource.com.caucho</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.log4j</groupId>
<artifactId>com.springsource.org.apache.log4j</artifactId>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>com.springsource.org.apache.commons.httpclient</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.axis</groupId>
<artifactId>com.springsource.org.apache.axis</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>com.springsource.org.apache.taglibs.standard</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,210 @@
/*
* 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 java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.util.HtmlUtils;
/**
* Errors wrapper that adds automatic HTML escaping to the wrapped instance,
* for convenient usage in HTML views. Can be retrieved easily via
* RequestContext's <code>getErrors</code> method.
*
* <p>Note that BindTag does <i>not</i> use this class to avoid unnecessary
* creation of ObjectError instances. It just escapes the messages and values
* that get copied into the respective BindStatus instance.
*
* @author Juergen Hoeller
* @since 01.03.2003
* @see org.springframework.web.servlet.support.RequestContext#getErrors
* @see org.springframework.web.servlet.tags.BindTag
*/
public class EscapedErrors implements Errors {
private final Errors source;
/**
* Create a new EscapedErrors instance for the given source instance.
*/
public EscapedErrors(Errors source) {
if (source == null) {
throw new IllegalArgumentException("Cannot wrap a null instance");
}
this.source = source;
}
public Errors getSource() {
return this.source;
}
public String getObjectName() {
return this.source.getObjectName();
}
public void setNestedPath(String nestedPath) {
this.source.setNestedPath(nestedPath);
}
public String getNestedPath() {
return this.source.getNestedPath();
}
public void pushNestedPath(String subPath) {
this.source.pushNestedPath(subPath);
}
public void popNestedPath() throws IllegalStateException {
this.source.popNestedPath();
}
public void reject(String errorCode) {
this.source.reject(errorCode);
}
public void reject(String errorCode, String defaultMessage) {
this.source.reject(errorCode, defaultMessage);
}
public void reject(String errorCode, Object[] errorArgs, String defaultMessage) {
this.source.reject(errorCode, errorArgs, defaultMessage);
}
public void rejectValue(String field, String errorCode) {
this.source.rejectValue(field, errorCode);
}
public void rejectValue(String field, String errorCode, String defaultMessage) {
this.source.rejectValue(field, errorCode, defaultMessage);
}
public void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) {
this.source.rejectValue(field, errorCode, errorArgs, defaultMessage);
}
public void addAllErrors(Errors errors) {
this.source.addAllErrors(errors);
}
public boolean hasErrors() {
return this.source.hasErrors();
}
public int getErrorCount() {
return this.source.getErrorCount();
}
public List getAllErrors() {
return escapeObjectErrors(this.source.getAllErrors());
}
public boolean hasGlobalErrors() {
return this.source.hasGlobalErrors();
}
public int getGlobalErrorCount() {
return this.source.getGlobalErrorCount();
}
public List getGlobalErrors() {
return escapeObjectErrors(this.source.getGlobalErrors());
}
public ObjectError getGlobalError() {
return escapeObjectError(this.source.getGlobalError());
}
public boolean hasFieldErrors() {
return this.source.hasFieldErrors();
}
public int getFieldErrorCount() {
return this.source.getFieldErrorCount();
}
public List getFieldErrors() {
return this.source.getFieldErrors();
}
public FieldError getFieldError() {
return this.source.getFieldError();
}
public boolean hasFieldErrors(String field) {
return this.source.hasFieldErrors(field);
}
public int getFieldErrorCount(String field) {
return this.source.getFieldErrorCount(field);
}
public List getFieldErrors(String field) {
return escapeObjectErrors(this.source.getFieldErrors(field));
}
public FieldError getFieldError(String field) {
return (FieldError) escapeObjectError(this.source.getFieldError(field));
}
public Object getFieldValue(String field) {
Object value = this.source.getFieldValue(field);
return (value instanceof String ? HtmlUtils.htmlEscape((String) value) : value);
}
public Class getFieldType(String field) {
return this.source.getFieldType(field);
}
private ObjectError escapeObjectError(ObjectError source) {
if (source == null) {
return null;
}
if (source instanceof FieldError) {
FieldError fieldError = (FieldError) source;
Object value = fieldError.getRejectedValue();
if (value instanceof String) {
value = HtmlUtils.htmlEscape((String) value);
}
return new FieldError(
fieldError.getObjectName(), fieldError.getField(), value,
fieldError.isBindingFailure(), fieldError.getCodes(),
fieldError.getArguments(), HtmlUtils.htmlEscape(fieldError.getDefaultMessage()));
}
return new ObjectError(
source.getObjectName(), source.getCodes(), source.getArguments(),
HtmlUtils.htmlEscape(source.getDefaultMessage()));
}
private List escapeObjectErrors(List source) {
List escaped = new ArrayList(source.size());
for (Iterator it = source.iterator(); it.hasNext();) {
ObjectError objectError = (ObjectError)it.next();
escaped.add(escapeObjectError(objectError));
}
return escaped;
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.bind;
/**
* {@link ServletRequestBindingException} subclass that indicates a missing parameter.
*
* @author Juergen Hoeller
* @since 2.0.2
*/
public class MissingServletRequestParameterException extends ServletRequestBindingException {
private String parameterName;
private String parameterType;
/**
* Constructor for MissingServletRequestParameterException.
* @param parameterName the name of the missing parameter
* @param parameterType the expected type of the missing parameter
*/
public MissingServletRequestParameterException(String parameterName, String parameterType) {
super("");
this.parameterName = parameterName;
this.parameterType = parameterType;
}
public String getMessage() {
return "Required " + this.parameterType + " parameter '" + this.parameterName + "' is not present";
}
/**
* Return the name of the offending parameter.
*/
public String getParameterName() {
return this.parameterName;
}
/**
* Return the expected type of the offending parameter.
*/
public String getParameterType() {
return this.parameterType;
}
}

View File

@ -0,0 +1,491 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.bind;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.HttpRequestMethodNotSupportedException;
/**
* Parameter extraction methods, for an approach distinct from data binding,
* in which parameters of specific types are required.
*
* <p>This approach is very useful for simple submissions, where binding
* request parameters to a command object would be overkill.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Keith Donald
* @deprecated as of Spring 2.0: use ServletRequestUtils instead
* @see ServletRequestUtils
*/
public abstract class RequestUtils {
/**
* Throw a ServletException if the given HTTP request method should be rejected.
* @param request request to check
* @param method method (such as "GET") which should be rejected
* @throws ServletException if the given HTTP request is rejected
*/
public static void rejectRequestMethod(HttpServletRequest request, String method) throws ServletException {
if (request.getMethod().equals(method)) {
throw new HttpRequestMethodNotSupportedException(method);
}
}
/**
* Get an Integer parameter, or <code>null</code> 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 <code>null</code> if not present
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static Integer getIntParameter(HttpServletRequest request, String name)
throws ServletRequestBindingException {
return ServletRequestUtils.getIntParameter(request, name);
}
/**
* Get an int parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value as default to enable checks of whether it was supplied.
* @param request current HTTP request
* @param name the name of the parameter
* @param defaultVal the default value to use as fallback
*/
public static int getIntParameter(HttpServletRequest request, String name, int defaultVal) {
return ServletRequestUtils.getIntParameter(request, name, defaultVal);
}
/**
* Get an array of int parameters, return an empty array if not found.
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
*/
public static int[] getIntParameters(HttpServletRequest request, String name) {
return ServletRequestUtils.getIntParameters(request, name);
}
/**
* Get an int parameter, throwing an exception if it isn't found or isn't a number.
* @param request current HTTP request
* @param name the name of the parameter
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static int getRequiredIntParameter(HttpServletRequest request, String name)
throws ServletRequestBindingException {
return ServletRequestUtils.getRequiredIntParameter(request, name);
}
/**
* Get an array of int parameters, throwing an exception if not found or one is not a number..
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static int[] getRequiredIntParameters(HttpServletRequest request, String name)
throws ServletRequestBindingException {
return ServletRequestUtils.getRequiredIntParameters(request, name);
}
/**
* Get a Long parameter, or <code>null</code> 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 <code>null</code> if not present
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static Long getLongParameter(HttpServletRequest request, String name)
throws ServletRequestBindingException {
return ServletRequestUtils.getLongParameter(request, name);
}
/**
* Get a long parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value as default to enable checks of whether it was supplied.
* @param request current HTTP request
* @param name the name of the parameter
* @param defaultVal the default value to use as fallback
*/
public static long getLongParameter(HttpServletRequest request, String name, long defaultVal) {
return ServletRequestUtils.getLongParameter(request, name, defaultVal);
}
/**
* Get an array of long parameters, return an empty array if not found.
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
*/
public static long[] getLongParameters(HttpServletRequest request, String name) {
return ServletRequestUtils.getLongParameters(request, name);
}
/**
* Get a long parameter, throwing an exception if it isn't found or isn't a number.
* @param request current HTTP request
* @param name the name of the parameter
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static long getRequiredLongParameter(HttpServletRequest request, String name)
throws ServletRequestBindingException {
return ServletRequestUtils.getRequiredLongParameter(request, name);
}
/**
* Get an array of long parameters, throwing an exception if not found or one is not a number.
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static long[] getRequiredLongParameters(HttpServletRequest request, String name)
throws ServletRequestBindingException {
return ServletRequestUtils.getRequiredLongParameters(request, name);
}
/**
* Get a Float parameter, or <code>null</code> 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 <code>null</code> if not present
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static Float getFloatParameter(HttpServletRequest request, String name)
throws ServletRequestBindingException {
return ServletRequestUtils.getFloatParameter(request, name);
}
/**
* Get a float parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value as default to enable checks of whether it was supplied.
* @param request current HTTP request
* @param name the name of the parameter
* @param defaultVal the default value to use as fallback
*/
public static float getFloatParameter(HttpServletRequest request, String name, float defaultVal) {
return ServletRequestUtils.getFloatParameter(request, name, defaultVal);
}
/**
* Get an array of float parameters, return an empty array if not found.
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
*/
public static float[] getFloatParameters(HttpServletRequest request, String name) {
return ServletRequestUtils.getFloatParameters(request, name);
}
/**
* Get a float parameter, throwing an exception if it isn't found or isn't a number.
* @param request current HTTP request
* @param name the name of the parameter
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static float getRequiredFloatParameter(HttpServletRequest request, String name)
throws ServletRequestBindingException {
return ServletRequestUtils.getRequiredFloatParameter(request, name);
}
/**
* Get an array of float parameters, throwing an exception if not found or one is not a number.
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static float[] getRequiredFloatParameters(HttpServletRequest request, String name)
throws ServletRequestBindingException {
return ServletRequestUtils.getRequiredFloatParameters(request, name);
}
/**
* Get a Double parameter, or <code>null</code> 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 <code>null</code> if not present
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static Double getDoubleParameter(HttpServletRequest request, String name)
throws ServletRequestBindingException {
return ServletRequestUtils.getDoubleParameter(request, name);
}
/**
* Get a double parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value as default to enable checks of whether it was supplied.
* @param request current HTTP request
* @param name the name of the parameter
* @param defaultVal the default value to use as fallback
*/
public static double getDoubleParameter(HttpServletRequest request, String name, double defaultVal) {
return ServletRequestUtils.getDoubleParameter(request, name, defaultVal);
}
/**
* Get an array of double parameters, return an empty array if not found.
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
*/
public static double[] getDoubleParameters(HttpServletRequest request, String name) {
return ServletRequestUtils.getDoubleParameters(request, name);
}
/**
* Get a double parameter, throwing an exception if it isn't found or isn't a number.
* @param request current HTTP request
* @param name the name of the parameter
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static double getRequiredDoubleParameter(HttpServletRequest request, String name)
throws ServletRequestBindingException {
return ServletRequestUtils.getRequiredDoubleParameter(request, name);
}
/**
* Get an array of double parameters, throwing an exception if not found or one is not a number.
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static double[] getRequiredDoubleParameters(HttpServletRequest request, String name)
throws ServletRequestBindingException {
return ServletRequestUtils.getRequiredDoubleParameters(request, name);
}
/**
* Get a Boolean parameter, or <code>null</code> if not present.
* Throws an exception if it the parameter value isn't a boolean.
* <p>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 <code>null</code> if not present
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static Boolean getBooleanParameter(HttpServletRequest request, String name)
throws ServletRequestBindingException {
if (request.getParameter(name) == null) {
return null;
}
return (getRequiredBooleanParameter(request, name) ? Boolean.TRUE : Boolean.FALSE);
}
/**
* Get a boolean parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value as default to enable checks of whether it was supplied.
* <p>Accepts "true", "on", "yes" (any case) and "1" as values for true;
* treats every other non-empty value as false (i.e. parses leniently).
* @param request current HTTP request
* @param name the name of the parameter
* @param defaultVal the default value to use as fallback
*/
public static boolean getBooleanParameter(HttpServletRequest request, String name, boolean defaultVal) {
if (request.getParameter(name) == null) {
return defaultVal;
}
try {
return getRequiredBooleanParameter(request, name);
}
catch (ServletRequestBindingException ex) {
return defaultVal;
}
}
/**
* Get an array of boolean parameters, return an empty array if not found.
* <p>Accepts "true", "on", "yes" (any case) and "1" as values for true;
* treats every other non-empty value as false (i.e. parses leniently).
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
*/
public static boolean[] getBooleanParameters(HttpServletRequest request, String name) {
try {
return getRequiredBooleanParameters(request, name);
}
catch (ServletRequestBindingException ex) {
return new boolean[0];
}
}
/**
* Get a boolean parameter, throwing an exception if it isn't found
* or isn't a boolean.
* <p>Accepts "true", "on", "yes" (any case) and "1" as values for true;
* treats every other non-empty value as false (i.e. parses leniently).
* @param request current HTTP request
* @param name the name of the parameter
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static boolean getRequiredBooleanParameter(HttpServletRequest request, String name)
throws ServletRequestBindingException {
boolean value = ServletRequestUtils.getRequiredBooleanParameter(request, name);
if (!value && "".equals(request.getParameter(name))) {
throw new ServletRequestBindingException(
"Required boolean parameter '" + name + "' contains no value");
}
return value;
}
/**
* Get an array of boolean parameters, throwing an exception if not found
* or one isn't a boolean.
* <p>Accepts "true", "on", "yes" (any case) and "1" as values for true;
* treats every other non-empty value as false (i.e. parses leniently).
* @param request current HTTP request
* @param name the name of the parameter
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static boolean[] getRequiredBooleanParameters(HttpServletRequest request, String name)
throws ServletRequestBindingException {
boolean[] values = ServletRequestUtils.getRequiredBooleanParameters(request, name);
for (int i = 0; i < values.length; i++) {
if (!values[i] && "".equals(request.getParameterValues(name)[i])) {
throw new ServletRequestBindingException(
"Required boolean parameter '" + name + "' contains no value");
}
}
return values;
}
/**
* Get a String parameter, or <code>null</code> if not present.
* Throws an exception if it the parameter value is empty.
* @param request current HTTP request
* @param name the name of the parameter
* @return the String value, or <code>null</code> if not present
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static String getStringParameter(HttpServletRequest request, String name)
throws ServletRequestBindingException {
if (request.getParameter(name) == null) {
return null;
}
return getRequiredStringParameter(request, name);
}
/**
* Get a String parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value to default to enable checks of whether it was supplied.
* @param request current HTTP request
* @param name the name of the parameter
* @param defaultVal the default value to use as fallback
*/
public static String getStringParameter(HttpServletRequest request, String name, String defaultVal) {
if (request.getParameter(name) == null) {
return defaultVal;
}
try {
return getRequiredStringParameter(request, name);
}
catch (ServletRequestBindingException ex) {
return defaultVal;
}
}
/**
* Get an array of String parameters, return an empty array if not found.
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
*/
public static String[] getStringParameters(HttpServletRequest request, String name) {
try {
return getRequiredStringParameters(request, name);
}
catch (ServletRequestBindingException ex) {
return new String[0];
}
}
/**
* Get a String parameter, throwing an exception if it isn't found or is empty.
* @param request current HTTP request
* @param name the name of the parameter
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static String getRequiredStringParameter(HttpServletRequest request, String name)
throws ServletRequestBindingException {
String value = ServletRequestUtils.getRequiredStringParameter(request, name);
if ("".equals(value)) {
throw new ServletRequestBindingException(
"Required string parameter '" + name + "' contains no value");
}
return value;
}
/**
* Get an array of String parameters, throwing an exception if not found or one is empty.
* @param request current HTTP request
* @param name the name of the parameter
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static String[] getRequiredStringParameters(HttpServletRequest request, String name)
throws ServletRequestBindingException {
String[] values = ServletRequestUtils.getRequiredStringParameters(request, name);
for (int i = 0; i < values.length; i++) {
if ("".equals(values[i])) {
throw new ServletRequestBindingException(
"Required string parameter '" + name + "' contains no value");
}
}
return values;
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.bind;
import org.springframework.web.util.NestedServletException;
/**
* Fatal binding exception, thrown when we want to
* treat binding exceptions as unrecoverable.
*
* <p>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);
}
}

View File

@ -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.
*
* <p>See the DataBinder/WebDataBinder superclasses for customization options,
* which include specifying allowed/required fields, and registering custom
* property editors.
*
* <p>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 <code>initBinder</code>.
*
* <p>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 <code>bind</code> with the current ServletRequest as argument:
*
* <pre class="code">
* 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();
* ...</pre>
*
* @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 <code>null</code>
* 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 <code>null</code>
* 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.
* <p>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").
* <p>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.
* <p>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.
* <p>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()));
}
}
}

View File

@ -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 "_").
*
* <p>For example, with a prefix of "spring", "spring_param1" and
* "spring_param2" result in a Map with "param1" and "param2" as keys.
*
* <p>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));
}
}

View File

@ -0,0 +1,697 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.bind;
import javax.servlet.ServletRequest;
/**
* Parameter extraction methods, for an approach distinct from data binding,
* in which parameters of specific types are required.
*
* <p>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 <code>null</code> 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 <code>null</code> if not present
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static Integer getIntParameter(ServletRequest request, String name)
throws ServletRequestBindingException {
if (request.getParameter(name) == null) {
return null;
}
return new Integer(getRequiredIntParameter(request, name));
}
/**
* Get an int parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value as default to enable checks of whether it was supplied.
* @param request current HTTP request
* @param name the name of the parameter
* @param defaultVal the default value to use as fallback
*/
public static int getIntParameter(ServletRequest request, String name, int defaultVal) {
if (request.getParameter(name) == null) {
return defaultVal;
}
try {
return getRequiredIntParameter(request, name);
}
catch (ServletRequestBindingException ex) {
return defaultVal;
}
}
/**
* Get an array of int parameters, return an empty array if not found.
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
*/
public static int[] getIntParameters(ServletRequest request, String name) {
try {
return getRequiredIntParameters(request, name);
}
catch (ServletRequestBindingException ex) {
return new int[0];
}
}
/**
* Get an int parameter, throwing an exception if it isn't found or isn't a number.
* @param request current HTTP request
* @param name the name of the parameter
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static int getRequiredIntParameter(ServletRequest request, String name)
throws ServletRequestBindingException {
return INT_PARSER.parseInt(name, request.getParameter(name));
}
/**
* Get an array of int parameters, throwing an exception if not found or one is not a number..
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static int[] getRequiredIntParameters(ServletRequest request, String name)
throws ServletRequestBindingException {
return INT_PARSER.parseInts(name, request.getParameterValues(name));
}
/**
* Get a Long parameter, or <code>null</code> 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 <code>null</code> if not present
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static Long getLongParameter(ServletRequest request, String name)
throws ServletRequestBindingException {
if (request.getParameter(name) == null) {
return null;
}
return new Long(getRequiredLongParameter(request, name));
}
/**
* Get a long parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value as default to enable checks of whether it was supplied.
* @param request current HTTP request
* @param name the name of the parameter
* @param defaultVal the default value to use as fallback
*/
public static long getLongParameter(ServletRequest request, String name, long defaultVal) {
if (request.getParameter(name) == null) {
return defaultVal;
}
try {
return getRequiredLongParameter(request, name);
}
catch (ServletRequestBindingException ex) {
return defaultVal;
}
}
/**
* Get an array of long parameters, return an empty array if not found.
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
*/
public static long[] getLongParameters(ServletRequest request, String name) {
try {
return getRequiredLongParameters(request, name);
}
catch (ServletRequestBindingException ex) {
return new long[0];
}
}
/**
* Get a long parameter, throwing an exception if it isn't found or isn't a number.
* @param request current HTTP request
* @param name the name of the parameter
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static long getRequiredLongParameter(ServletRequest request, String name)
throws ServletRequestBindingException {
return LONG_PARSER.parseLong(name, request.getParameter(name));
}
/**
* Get an array of long parameters, throwing an exception if not found or one is not a number.
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static long[] getRequiredLongParameters(ServletRequest request, String name)
throws ServletRequestBindingException {
return LONG_PARSER.parseLongs(name, request.getParameterValues(name));
}
/**
* Get a Float parameter, or <code>null</code> 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 <code>null</code> if not present
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static Float getFloatParameter(ServletRequest request, String name)
throws ServletRequestBindingException {
if (request.getParameter(name) == null) {
return null;
}
return new Float(getRequiredFloatParameter(request, name));
}
/**
* Get a float parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value as default to enable checks of whether it was supplied.
* @param request current HTTP request
* @param name the name of the parameter
* @param defaultVal the default value to use as fallback
*/
public static float getFloatParameter(ServletRequest request, String name, float defaultVal) {
if (request.getParameter(name) == null) {
return defaultVal;
}
try {
return getRequiredFloatParameter(request, name);
}
catch (ServletRequestBindingException ex) {
return defaultVal;
}
}
/**
* Get an array of float parameters, return an empty array if not found.
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
*/
public static float[] getFloatParameters(ServletRequest request, String name) {
try {
return getRequiredFloatParameters(request, name);
}
catch (ServletRequestBindingException ex) {
return new float[0];
}
}
/**
* Get a float parameter, throwing an exception if it isn't found or isn't a number.
* @param request current HTTP request
* @param name the name of the parameter
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static float getRequiredFloatParameter(ServletRequest request, String name)
throws ServletRequestBindingException {
return FLOAT_PARSER.parseFloat(name, request.getParameter(name));
}
/**
* Get an array of float parameters, throwing an exception if not found or one is not a number.
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static float[] getRequiredFloatParameters(ServletRequest request, String name)
throws ServletRequestBindingException {
return FLOAT_PARSER.parseFloats(name, request.getParameterValues(name));
}
/**
* Get a Double parameter, or <code>null</code> 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 <code>null</code> if not present
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static Double getDoubleParameter(ServletRequest request, String name)
throws ServletRequestBindingException {
if (request.getParameter(name) == null) {
return null;
}
return new Double(getRequiredDoubleParameter(request, name));
}
/**
* Get a double parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value as default to enable checks of whether it was supplied.
* @param request current HTTP request
* @param name the name of the parameter
* @param defaultVal the default value to use as fallback
*/
public static double getDoubleParameter(ServletRequest request, String name, double defaultVal) {
if (request.getParameter(name) == null) {
return defaultVal;
}
try {
return getRequiredDoubleParameter(request, name);
}
catch (ServletRequestBindingException ex) {
return defaultVal;
}
}
/**
* Get an array of double parameters, return an empty array if not found.
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
*/
public static double[] getDoubleParameters(ServletRequest request, String name) {
try {
return getRequiredDoubleParameters(request, name);
}
catch (ServletRequestBindingException ex) {
return new double[0];
}
}
/**
* Get a double parameter, throwing an exception if it isn't found or isn't a number.
* @param request current HTTP request
* @param name the name of the parameter
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static double getRequiredDoubleParameter(ServletRequest request, String name)
throws ServletRequestBindingException {
return DOUBLE_PARSER.parseDouble(name, request.getParameter(name));
}
/**
* Get an array of double parameters, throwing an exception if not found or one is not a number.
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static double[] getRequiredDoubleParameters(ServletRequest request, String name)
throws ServletRequestBindingException {
return DOUBLE_PARSER.parseDoubles(name, request.getParameterValues(name));
}
/**
* Get a Boolean parameter, or <code>null</code> if not present.
* Throws an exception if it the parameter value isn't a boolean.
* <p>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 <code>null</code> if not present
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static Boolean getBooleanParameter(ServletRequest request, String name)
throws ServletRequestBindingException {
if (request.getParameter(name) == null) {
return null;
}
return (getRequiredBooleanParameter(request, name) ? Boolean.TRUE : Boolean.FALSE);
}
/**
* Get a boolean parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value as default to enable checks of whether it was supplied.
* <p>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.
* <p>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.
* <p>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.
* <p>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 <code>null</code> if not present.
* @param request current HTTP request
* @param name the name of the parameter
* @return the String value, or <code>null</code> if not present
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static String getStringParameter(ServletRequest request, String name)
throws ServletRequestBindingException {
if (request.getParameter(name) == null) {
return null;
}
return getRequiredStringParameter(request, name);
}
/**
* Get a String parameter, with a fallback value. Never throws an exception.
* Can pass a distinguished value to default to enable checks of whether it was supplied.
* @param request current HTTP request
* @param name the name of the parameter
* @param defaultVal the default value to use as fallback
*/
public static String getStringParameter(ServletRequest request, String name, String defaultVal) {
String val = request.getParameter(name);
return (val != null ? val : defaultVal);
}
/**
* Get an array of String parameters, return an empty array if not found.
* @param request current HTTP request
* @param name the name of the parameter with multiple possible values
*/
public static String[] getStringParameters(ServletRequest request, String name) {
try {
return getRequiredStringParameters(request, name);
}
catch (ServletRequestBindingException ex) {
return new String[0];
}
}
/**
* Get a String parameter, throwing an exception if it isn't found.
* @param request current HTTP request
* @param name the name of the parameter
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static String getRequiredStringParameter(ServletRequest request, String name)
throws ServletRequestBindingException {
return STRING_PARSER.validateRequiredString(name, request.getParameter(name));
}
/**
* Get an array of String parameters, throwing an exception if not found.
* @param request current HTTP request
* @param name the name of the parameter
* @throws ServletRequestBindingException a subclass of ServletException,
* so it doesn't need to be caught
*/
public static String[] getRequiredStringParameters(ServletRequest request, String name)
throws ServletRequestBindingException {
return STRING_PARSER.validateRequiredStrings(name, request.getParameterValues(name));
}
private abstract static class ParameterParser {
protected final Object parse(String name, String parameter) throws ServletRequestBindingException {
validateRequiredParameter(name, parameter);
try {
return doParse(parameter);
}
catch (NumberFormatException ex) {
throw new ServletRequestBindingException(
"Required " + getType() + " parameter '" + name + "' with value of '" +
parameter + "' is not a valid number", ex);
}
}
protected final void validateRequiredParameter(String name, Object parameter)
throws ServletRequestBindingException {
if (parameter == null) {
throw new MissingServletRequestParameterException(name, getType());
}
}
protected abstract String getType();
protected abstract Object doParse(String parameter) throws NumberFormatException;
}
private static class IntParser extends ParameterParser {
protected String getType() {
return "int";
}
protected Object doParse(String s) throws NumberFormatException {
return Integer.valueOf(s);
}
public int parseInt(String name, String parameter) throws ServletRequestBindingException {
return ((Number) parse(name, parameter)).intValue();
}
public int[] parseInts(String name, String[] values) throws ServletRequestBindingException {
validateRequiredParameter(name, values);
int[] parameters = new int[values.length];
for (int i = 0; i < values.length; i++) {
parameters[i] = parseInt(name, values[i]);
}
return parameters;
}
}
private static class LongParser extends ParameterParser {
protected String getType() {
return "long";
}
protected Object doParse(String parameter) throws NumberFormatException {
return Long.valueOf(parameter);
}
public long parseLong(String name, String parameter) throws ServletRequestBindingException {
return ((Number) parse(name, parameter)).longValue();
}
public long[] parseLongs(String name, String[] values) throws ServletRequestBindingException {
validateRequiredParameter(name, values);
long[] parameters = new long[values.length];
for (int i = 0; i < values.length; i++) {
parameters[i] = parseLong(name, values[i]);
}
return parameters;
}
}
private static class FloatParser extends ParameterParser {
protected String getType() {
return "float";
}
protected Object doParse(String parameter) throws NumberFormatException {
return Float.valueOf(parameter);
}
public float parseFloat(String name, String parameter) throws ServletRequestBindingException {
return ((Number) parse(name, parameter)).floatValue();
}
public float[] parseFloats(String name, String[] values) throws ServletRequestBindingException {
validateRequiredParameter(name, values);
float[] parameters = new float[values.length];
for (int i = 0; i < values.length; i++) {
parameters[i] = parseFloat(name, values[i]);
}
return parameters;
}
}
private static class DoubleParser extends ParameterParser {
protected String getType() {
return "double";
}
protected Object doParse(String parameter) throws NumberFormatException {
return Double.valueOf(parameter);
}
public double parseDouble(String name, String parameter) throws ServletRequestBindingException {
return ((Number) parse(name, parameter)).doubleValue();
}
public double[] parseDoubles(String name, String[] values) throws ServletRequestBindingException {
validateRequiredParameter(name, values);
double[] parameters = new double[values.length];
for (int i = 0; i < values.length; i++) {
parameters[i] = parseDouble(name, values[i]);
}
return parameters;
}
}
private static class BooleanParser extends ParameterParser {
protected String getType() {
return "boolean";
}
protected Object doParse(String parameter) throws NumberFormatException {
return (parameter.equalsIgnoreCase("true") || parameter.equalsIgnoreCase("on") ||
parameter.equalsIgnoreCase("yes") || parameter.equals("1") ? Boolean.TRUE : Boolean.FALSE);
}
public boolean parseBoolean(String name, String parameter) throws ServletRequestBindingException {
return ((Boolean) parse(name, parameter)).booleanValue();
}
public boolean[] parseBooleans(String name, String[] values) throws ServletRequestBindingException {
validateRequiredParameter(name, values);
boolean[] parameters = new boolean[values.length];
for (int i = 0; i < values.length; i++) {
parameters[i] = parseBoolean(name, values[i]);
}
return parameters;
}
}
private static class StringParser extends ParameterParser {
protected String getType() {
return "string";
}
protected Object doParse(String parameter) throws NumberFormatException {
return parameter;
}
public String validateRequiredString(String name, String value) throws ServletRequestBindingException {
validateRequiredParameter(name, value);
return value;
}
public String[] validateRequiredStrings(String name, String[] values) throws ServletRequestBindingException {
validateRequiredParameter(name, values);
for (int i = 0; i < values.length; i++) {
validateRequiredParameter(name, values[i]);
}
return values;
}
}
}

View File

@ -0,0 +1,227 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.bind;
import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.Map;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.validation.DataBinder;
import org.springframework.web.multipart.MultipartFile;
/**
* Special {@link DataBinder} for data binding from web request parameters
* to JavaBean objects. Designed for web environments, but not dependent on
* the Servlet API; serves as base class for more specific DataBinder variants,
* such as {@link org.springframework.web.bind.ServletRequestDataBinder}.
*
* <p>Includes support for field markers which address a common problem with
* HTML checkboxes and select options: detecting that a field was part of
* the form, but did not generate a request parameter because it was empty.
* A field marker allows to detect that state and reset the corresponding
* bean property accordingly.
*
* @author Juergen Hoeller
* @since 1.2
* @see #registerCustomEditor
* @see #setAllowedFields
* @see #setRequiredFields
* @see #setFieldMarkerPrefix
* @see ServletRequestDataBinder
*/
public class WebDataBinder extends DataBinder {
/**
* Default prefix that field marker parameters start with, followed by the field
* name: e.g. "_subscribeToNewsletter" for a field "subscribeToNewsletter".
* <p>Such a marker parameter indicates that the field was visible, that is,
* existed in the form that caused the submission. If no corresponding field
* value parameter was found, the field will be reset. The value of the field
* marker parameter does not matter in this case; an arbitrary value can be used.
* This is particularly useful for HTML checkboxes and select options.
* @see #setFieldMarkerPrefix
*/
public static final String DEFAULT_FIELD_MARKER_PREFIX = "_";
private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX;
private boolean bindEmptyMultipartFiles = true;
/**
* Create a new WebDataBinder instance, with default object name.
* @param target the target object to bind onto (or <code>null</code>
* 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 <code>null</code>
* 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.
* <p>Default is "_", for "_FIELD" parameters (e.g. "_subscribeToNewsletter").
* Set this to null if you want to turn off the empty field check completely.
* <p>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.
* <p>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 <code>onBind</code> implementation.
* <p>This auto-reset mechanism addresses this deficiency, provided
* that a marker parameter is sent for each checkbox field, like
* "_subscribeToNewsletter" for a "subscribeToNewsletter" field.
* As the marker parameter is sent in any case, the data binder can
* detect an empty field and automatically reset its value.
* @see #DEFAULT_FIELD_MARKER_PREFIX
* @see org.springframework.web.servlet.mvc.BaseCommandController#onBind
*/
public void setFieldMarkerPrefix(String fieldMarkerPrefix) {
this.fieldMarkerPrefix = fieldMarkerPrefix;
}
/**
* Return the prefix for parameters that mark potentially empty fields.
*/
public String getFieldMarkerPrefix() {
return this.fieldMarkerPrefix;
}
/**
* Set whether to bind empty MultipartFile parameters. Default is "true".
* <p>Turn this off if you want to keep an already bound MultipartFile
* when the user resubmits the form without choosing a different file.
* Else, the already bound MultipartFile will be replaced by an empty
* MultipartFile holder.
* @see org.springframework.web.multipart.MultipartFile
*/
public void setBindEmptyMultipartFiles(boolean bindEmptyMultipartFiles) {
this.bindEmptyMultipartFiles = bindEmptyMultipartFiles;
}
/**
* Return whether to bind empty MultipartFile parameters.
*/
public boolean isBindEmptyMultipartFiles() {
return this.bindEmptyMultipartFiles;
}
/**
* This implementation performs a field marker check
* before delegating to the superclass binding process.
* @see #checkFieldMarkers
*/
protected void doBind(MutablePropertyValues mpvs) {
checkFieldMarkers(mpvs);
super.doBind(mpvs);
}
/**
* Check the given property values for field markers,
* i.e. for fields that start with the field marker prefix.
* <p>The existence of a field marker indicates that the specified
* field existed in the form. If the property values do not contain
* a corresponding field value, the field will be considered as empty
* and will be reset appropriately.
* @param mpvs the property values to be bound (can be modified)
* @see #getFieldMarkerPrefix
* @see #getEmptyValue(String, Class)
*/
protected void checkFieldMarkers(MutablePropertyValues mpvs) {
if (getFieldMarkerPrefix() != null) {
String fieldMarkerPrefix = getFieldMarkerPrefix();
PropertyValue[] pvArray = mpvs.getPropertyValues();
for (int i = 0; i < pvArray.length; i++) {
PropertyValue pv = pvArray[i];
if (pv.getName().startsWith(fieldMarkerPrefix)) {
String field = pv.getName().substring(fieldMarkerPrefix.length());
if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
Class fieldType = getPropertyAccessor().getPropertyType(field);
mpvs.addPropertyValue(field, getEmptyValue(field, fieldType));
}
mpvs.removePropertyValue(pv);
}
}
}
}
/**
* Determine an empty value for the specified field.
* <p>Default implementation returns <code>Boolean.FALSE</code>
* for boolean fields and an empty array of array types.
* Else, <code>null</code> 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).
* <p>Multipart files will only be added to the property values if they
* are not empty or if we're configured to bind empty multipart files too.
* @param multipartFiles Map of field name String to MultipartFile object
* @param mpvs the property values to be bound (can be modified)
* @see org.springframework.web.multipart.MultipartFile
* @see #setBindEmptyMultipartFiles
*/
protected void bindMultipartFiles(Map multipartFiles, MutablePropertyValues mpvs) {
for (Iterator it = multipartFiles.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
String key = (String) entry.getKey();
MultipartFile value = (MultipartFile) entry.getValue();
if (isBindEmptyMultipartFiles() || !value.isEmpty()) {
mpvs.addPropertyValue(key, value);
}
}
}
}

View File

@ -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.
*
* <p>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 <code>void</code>.
*
* <p>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.
* <p>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 {};
}

View File

@ -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.
*
* <p>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).
*
* <p>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.
* <p>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&lt;mypackage.OrderAddress&gt;".
*/
String value() default "";
}

View File

@ -0,0 +1,230 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.bind.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation for mapping web requests onto specific handler classes and/or
* handler methods. Provides consistent style between Servlet and Portlet
* environments, with the semantics adapting to the concrete environment.
*
* <p><b>NOTE:</b> 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.
*
* <p>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):
* <ul>
* <li>Request and/or response objects (Servlet API or Portlet API).
* You may choose any specific request/response type, e.g.
* {@link javax.servlet.ServletRequest} / {@link javax.servlet.http.HttpServletRequest}
* or {@link javax.portlet.PortletRequest} / {@link javax.portlet.ActionRequest} /
* {@link javax.portlet.RenderRequest}. Note that in the Portlet case,
* an explicitly declared action/render argument is also used for mapping
* specific request types onto a handler method (in case of no other
* information given that differentiates between action and render requests).
* <li>Session object (Servlet API or Portlet API): either
* {@link javax.servlet.http.HttpSession} or {@link javax.portlet.PortletSession}.
* An argument of this type will enforce the presence of a corresponding session.
* As a consequence, such an argument will never be <code>null</code>.
* <i>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.</i>
* <li>{@link org.springframework.web.context.request.WebRequest} or
* {@link org.springframework.web.context.request.NativeWebRequest}.
* Allows for generic request parameter access as well as request/session
* attribute access, without ties to the native Servlet/Portlet API.
* <li>{@link java.util.Locale} for the current request locale
* (determined by the most specific locale resolver available,
* i.e. the configured {@link org.springframework.web.servlet.LocaleResolver}
* in a Servlet environment and the portal locale in a Portlet environment).
* <li>{@link java.io.InputStream} / {@link java.io.Reader} for access
* to the request's content. This will be the raw InputStream/Reader as
* exposed by the Servlet/Portlet API.
* <li>{@link java.io.OutputStream} / {@link java.io.Writer} for generating
* the response's content. This will be the raw OutputStream/Writer as
* exposed by the Servlet/Portlet API.
* <li>{@link RequestParam @RequestParam} annotated parameters for access to
* specific Servlet/Portlet request parameters. Parameter values will be
* converted to the declared method argument type.
* <li>{@link java.util.Map} / {@link org.springframework.ui.Model} /
* {@link org.springframework.ui.ModelMap} for enriching the implicit model
* that will be exposed to the web view.
* <li>Command/form objects to bind parameters to: as bean properties or fields,
* with customizable type conversion, depending on {@link InitBinder} methods
* and/or the HandlerAdapter configuration - see the "webBindingInitializer"
* property on AnnotationMethodHandlerAdapter.
* Such command objects along with their validation results will be exposed
* as model attributes, by default using the non-qualified command class name
* in property notation (e.g. "orderAddress" for type "mypackage.OrderAddress").
* Specify a parameter-level {@link ModelAttribute} annotation for declaring
* a specific model attribute name.
* <li>{@link org.springframework.validation.Errors} /
* {@link org.springframework.validation.BindingResult} validation results
* for a preceding command/form object (the immediate preceding argument).
* <li>{@link org.springframework.web.bind.support.SessionStatus} status handle
* for marking form processing as complete (triggering the cleanup of session
* attributes that have been indicated by the {@link SessionAttributes} annotation
* at the handler type level).
* </ul>
*
* <p>The following return types are supported for handler methods:
* <ul>
* <li>A <code>ModelAndView</code> 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.
* <li>A {@link org.springframework.ui.Model Model} object, with the view name
* implicitly determined through a {@link org.springframework.web.servlet.RequestToViewNameTranslator}
* and the model implicitly enriched with command objects and the results
* of {@link ModelAttribute} annotated reference data accessor methods.
* <li>A {@link java.util.Map} object for exposing a model,
* with the view name implicitly determined through a
* {@link org.springframework.web.servlet.RequestToViewNameTranslator}
* and the model implicitly enriched with command objects and the results
* of {@link ModelAttribute} annotated reference data accessor methods.
* <li>A {@link org.springframework.web.servlet.View} object, with the
* model implicitly determined through command objects and
* {@link ModelAttribute} annotated reference data accessor methods.
* The handler method may also programmatically enrich the model by
* declaring a {@link org.springframework.ui.Model} argument (see above).
* <li>A {@link java.lang.String} value which is interpreted as view name,
* with the model implicitly determined through command objects and
* {@link ModelAttribute} annotated reference data accessor methods.
* The handler method may also programmatically enrich the model by
* declaring a {@link org.springframework.ui.ModelMap} argument
* (see above).
* <li><code>void</code> 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).
* <li>Any other return type will be considered as single model attribute
* to be exposed to the view, using the attribute name specified through
* {@link ModelAttribute} at the method level (or the default attribute name
* based on the return type's class name otherwise). The model will be
* implicitly enriched with command objects and the results of
* {@link ModelAttribute} annotated reference data accessor methods.
* </ul>
*
* <p><b>NOTE: <code>@RequestMapping</code> will only be processed if a
* corresponding <code>HandlerMapping</code> (for type level annotations)
* and/or <code>HandlerAdapter</code> (for method level annotations) is
* present in the dispatcher.</b> This is the case by default in both
* <code>DispatcherServlet</code> and <code>DispatcherPortlet</code>.
* However, if you are defining custom <code>HandlerMappings</code> or
* <code>HandlerAdapters</code>, then you need to make sure that a
* corresponding custom <code>DefaultAnnotationHandlerMapping</code>
* and/or <code>AnnotationMethodHandlerAdapter</code> is defined as well
* - provided that you intend to use <code>@RequestMapping</code>.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
* @author Sam Brannen
* @since 2.5
* @see RequestParam
* @see ModelAttribute
* @see SessionAttributes
* @see InitBinder
* @see org.springframework.web.context.request.WebRequest
* @see org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
* @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
* @see org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping
* @see org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
/**
* The primary mapping expressed by this annotation.
* <p>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.
* <p>In a Portlet environment: the mapped portlet modes
* (i.e. "EDIT", "VIEW", "HELP" or any custom modes).
* <p><b>Supported at the type level as well as at the method level!</b>
* When used at the type level, all method-level mappings inherit
* this primary mapping, narrowing it for a specific handler method.
* <p>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.
* <p>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.
* <p><b>Supported at the type level as well as at the method level!</b>
* 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).
* <p><b>Currently only supported in Servlet environments!</b>
* To be supported for Portlet 2.0 resource requests in Spring 3.0 as well.
*/
RequestMethod[] method() default {};
/**
* The parameters of the mapped request, narrowing the primary mapping.
* <p>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 <i>not</i> supposed to be present in the request.
* <p><b>Supported at the type level as well as at the method level!</b>
* 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).
* <p>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.
* <p>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 {};
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.bind.annotation;
/**
* Java 5 enumeration of HTTP request methods. Intended for use
* with the {@link RequestMapping#method()} attribute of the
* {@link RequestMapping} annotation.
*
* <p>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;
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.bind.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation which indicates that a method parameter should be bound
* to a web request parameter. Supported for {@link RequestMapping}
* annotated handler methods in Servlet and Portlet environments.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 2.5
* @see RequestMapping
* @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
* @see org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
/**
* The request parameter to bind to.
*/
String value() default "";
/**
* Whether the parameter is required.
* <p>Default is <code>true</code>, leading to an exception thrown in case
* of the parameter missing in the request. Switch this to <code>false</code>
* if you prefer a <code>null</value> in case of the parameter missing.
*/
boolean required() default true;
}

View File

@ -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. <b>Declared at the type level,</b> applying
* to the model attributes that the annotated handler class operates on.
*
* <p><b>NOTE:</b> 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 <i>temporarily</i> during the course of a
* specific handler's conversation.
*
* <p>For permanent session attributes, e.g. a user authentication object,
* use the traditional <code>session.setAttribute</code> 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.
* <p>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 {};
}

View File

@ -0,0 +1,8 @@
<html>
<body>
Annotations for binding requests to controllers and handler methods
as well as for binding request parameters to method arguments.
</body>
</html>

View File

@ -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);
}
}

View File

@ -0,0 +1,487 @@
/*
* 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.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
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.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.support.DefaultSessionAttributeStore;
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.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.
*
* <p>Used by {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter}
* and {@link org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter}.
*
* @author Juergen Hoeller
* @since 2.5.2
* @see #invokeHandlerMethod
*/
public class HandlerMethodInvoker {
/**
* We'll create a lot of these objects, so we don't want a new logger every time.
*/
private static final Log logger = LogFactory.getLog(HandlerMethodInvoker.class);
private final HandlerMethodResolver methodResolver;
private final WebBindingInitializer bindingInitializer;
private final SessionAttributeStore sessionAttributeStore;
private final ParameterNameDiscoverer parameterNameDiscoverer;
private final WebArgumentResolver[] customArgumentResolvers;
private final SimpleSessionStatus sessionStatus = new SimpleSessionStatus();
public HandlerMethodInvoker(HandlerMethodResolver methodResolver) {
this(methodResolver, null);
}
public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer) {
this(methodResolver, bindingInitializer, new DefaultSessionAttributeStore(), null);
}
public HandlerMethodInvoker(
HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer,
SessionAttributeStore sessionAttributeStore, ParameterNameDiscoverer parameterNameDiscoverer,
WebArgumentResolver... customArgumentResolvers) {
this.methodResolver = methodResolver;
this.bindingInitializer = bindingInitializer;
this.sessionAttributeStore = sessionAttributeStore;
this.parameterNameDiscoverer = parameterNameDiscoverer;
this.customArgumentResolvers = customArgumentResolvers;
}
public final Object invokeHandlerMethod(
Method handlerMethod, Object handler, NativeWebRequest webRequest, ExtendedModelMap implicitModel)
throws Exception {
Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);
try {
boolean debug = logger.isDebugEnabled();
for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) {
Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod);
Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);
if (debug) {
logger.debug("Invoking model attribute method: " + attributeMethodToInvoke);
}
Object attrValue = doInvokeMethod(attributeMethodToInvoke, handler, args);
String attrName = AnnotationUtils.findAnnotation(attributeMethodToInvoke, ModelAttribute.class).value();
if ("".equals(attrName)) {
Class resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass());
attrName = Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue);
}
implicitModel.addAttribute(attrName, attrValue);
}
Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);
if (debug) {
logger.debug("Invoking request handler method: " + handlerMethodToInvoke);
}
return doInvokeMethod(handlerMethodToInvoke, handler, args);
}
catch (IllegalStateException ex) {
// Throw exception with full handler method context...
throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex);
}
}
@SuppressWarnings("unchecked")
private Object[] resolveHandlerArguments(
Method handlerMethod, Object handler, NativeWebRequest webRequest, ExtendedModelMap implicitModel)
throws Exception {
Class[] paramTypes = handlerMethod.getParameterTypes();
Object[] args = new Object[paramTypes.length];
for (int i = 0; i < args.length; i++) {
MethodParameter methodParam = new MethodParameter(handlerMethod, i);
methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);
GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());
String paramName = null;
boolean paramRequired = false;
String attrName = null;
Object[] paramAnns = methodParam.getParameterAnnotations();
for (int j = 0; j < paramAnns.length; j++) {
Object paramAnn = paramAnns[j];
if (RequestParam.class.isInstance(paramAnn)) {
RequestParam requestParam = (RequestParam) paramAnn;
paramName = requestParam.value();
paramRequired = requestParam.required();
break;
}
else if (ModelAttribute.class.isInstance(paramAnn)) {
ModelAttribute attr = (ModelAttribute) paramAnn;
attrName = attr.value();
}
}
if (paramName != null && attrName != null) {
throw new IllegalStateException("@RequestParam and @ModelAttribute are an exclusive choice -" +
"do not specify both on the same parameter: " + handlerMethod);
}
Class paramType = methodParam.getParameterType();
if (paramName == null && attrName == null) {
Object argValue = resolveCommonArgument(methodParam, webRequest);
if (argValue != WebArgumentResolver.UNRESOLVED) {
args[i] = argValue;
}
else {
if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {
args[i] = implicitModel;
}
else if (SessionStatus.class.isAssignableFrom(paramType)) {
args[i] = this.sessionStatus;
}
else if (Errors.class.isAssignableFrom(paramType)) {
throw new IllegalStateException("Errors/BindingResult argument declared " +
"without preceding model attribute. Check your handler method signature!");
}
else if (BeanUtils.isSimpleProperty(paramType)) {
paramName = "";
}
else {
attrName = "";
}
}
}
if (paramName != null) {
args[i] = resolveRequestParam(paramName, paramRequired, methodParam, webRequest, handler);
}
else if (attrName != null) {
WebDataBinder binder = resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);
boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
if (binder.getTarget() != null) {
doBind(webRequest, binder, !assignBindingResult);
}
args[i] = binder.getTarget();
if (assignBindingResult) {
args[i + 1] = binder.getBindingResult();
i++;
}
implicitModel.putAll(binder.getBindingResult().getModel());
}
}
return args;
}
private void initBinder(Object handler, String attrName, WebDataBinder binder, NativeWebRequest webRequest)
throws Exception {
if (this.bindingInitializer != null) {
this.bindingInitializer.initBinder(binder, webRequest);
}
if (handler != null) {
Set<Method> initBinderMethods = this.methodResolver.getInitBinderMethods();
if (!initBinderMethods.isEmpty()) {
boolean debug = logger.isDebugEnabled();
for (Method initBinderMethod : initBinderMethods) {
Method methodToInvoke = BridgeMethodResolver.findBridgedMethod(initBinderMethod);
String[] targetNames = AnnotationUtils.findAnnotation(methodToInvoke, InitBinder.class).value();
if (targetNames.length == 0 || Arrays.asList(targetNames).contains(attrName)) {
Object[] initBinderArgs = resolveInitBinderArguments(handler, methodToInvoke, binder, webRequest);
if (debug) {
logger.debug("Invoking init-binder method: " + methodToInvoke);
}
Object returnValue = doInvokeMethod(methodToInvoke, handler, initBinderArgs);
if (returnValue != null) {
throw new IllegalStateException(
"InitBinder methods must not have a return value: " + methodToInvoke);
}
}
}
}
}
}
private Object[] resolveInitBinderArguments(Object handler, Method initBinderMethod,
WebDataBinder binder, NativeWebRequest webRequest) throws Exception {
Class[] initBinderParams = initBinderMethod.getParameterTypes();
Object[] initBinderArgs = new Object[initBinderParams.length];
for (int i = 0; i < initBinderArgs.length; i++) {
MethodParameter methodParam = new MethodParameter(initBinderMethod, i);
methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);
GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());
String paramName = null;
boolean paramRequired = false;
Object[] paramAnns = methodParam.getParameterAnnotations();
for (int j = 0; j < paramAnns.length; j++) {
Object paramAnn = paramAnns[j];
if (RequestParam.class.isInstance(paramAnn)) {
RequestParam requestParam = (RequestParam) paramAnn;
paramName = requestParam.value();
paramRequired = requestParam.required();
break;
}
else if (ModelAttribute.class.isInstance(paramAnn)) {
throw new IllegalStateException(
"@ModelAttribute is not supported on @InitBinder methods: " + initBinderMethod);
}
}
if (paramName == null) {
Object argValue = resolveCommonArgument(methodParam, webRequest);
if (argValue != WebArgumentResolver.UNRESOLVED) {
initBinderArgs[i] = argValue;
}
else {
Class paramType = initBinderParams[i];
if (paramType.isInstance(binder)) {
initBinderArgs[i] = binder;
}
else if (BeanUtils.isSimpleProperty(paramType)) {
paramName = "";
}
else {
throw new IllegalStateException("Unsupported argument [" + paramType.getName() +
"] for @InitBinder method: " + initBinderMethod);
}
}
}
if (paramName != null) {
initBinderArgs[i] = resolveRequestParam(paramName, paramRequired, methodParam, webRequest, null);
}
}
return initBinderArgs;
}
private Object resolveRequestParam(String paramName, boolean paramRequired,
MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall)
throws Exception {
Class paramType = methodParam.getParameterType();
if ("".equals(paramName)) {
paramName = methodParam.getParameterName();
if (paramName == null) {
throw new IllegalStateException("No parameter specified for @RequestParam argument of type [" +
paramType.getName() + "], and no parameter name information found in class file either.");
}
}
Object paramValue = null;
if (webRequest.getNativeRequest() instanceof MultipartRequest) {
paramValue = ((MultipartRequest) webRequest.getNativeRequest()).getFile(paramName);
}
if (paramValue == null) {
String[] paramValues = webRequest.getParameterValues(paramName);
if (paramValues != null) {
paramValue = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}
if (paramValue == null) {
if (paramRequired) {
raiseMissingParameterException(paramName, paramType);
}
if (paramType.isPrimitive()) {
throw new IllegalStateException("Optional " + paramType + " parameter '" + paramName +
"' is not present but cannot be translated into a null value due to being declared as a " +
"primitive type. Consider declaring it as object wrapper for the corresponding primitive type.");
}
}
WebDataBinder binder = createBinder(webRequest, null, paramName);
initBinder(handlerForInitBinderCall, paramName, binder, webRequest);
return binder.convertIfNecessary(paramValue, paramType, methodParam);
}
private WebDataBinder resolveModelAttribute(String attrName, MethodParameter methodParam,
ExtendedModelMap implicitModel, NativeWebRequest webRequest, Object handler) throws Exception {
// Bind request parameter onto object...
String name = attrName;
if ("".equals(name)) {
name = Conventions.getVariableNameForParameter(methodParam);
}
Class paramType = methodParam.getParameterType();
Object bindObject = null;
if (implicitModel.containsKey(name)) {
bindObject = implicitModel.get(name);
}
else if (this.methodResolver.isSessionAttribute(name, paramType)) {
bindObject = this.sessionAttributeStore.retrieveAttribute(webRequest, name);
if (bindObject == null) {
raiseSessionRequiredException("Session attribute '" + name + "' required - not found in session");
}
}
else {
bindObject = BeanUtils.instantiateClass(paramType);
}
WebDataBinder binder = createBinder(webRequest, bindObject, name);
initBinder(handler, name, binder, webRequest);
return binder;
}
@SuppressWarnings("unchecked")
public final void updateModelAttributes(
Object handler, Map mavModel, ExtendedModelMap implicitModel, NativeWebRequest webRequest)
throws Exception {
if (this.methodResolver.hasSessionAttributes() && this.sessionStatus.isComplete()) {
for (String attrName : this.methodResolver.getActualSessionAttributeNames()) {
this.sessionAttributeStore.cleanupAttribute(webRequest, attrName);
}
}
// Expose model attributes as session attributes, if required.
// Expose BindingResults for all attributes, making custom editors available.
Map<String, Object> model = (mavModel != null ? mavModel : implicitModel);
for (String attrName : new HashSet<String>(model.keySet())) {
Object attrValue = model.get(attrName);
boolean isSessionAttr =
this.methodResolver.isSessionAttribute(attrName, (attrValue != null ? attrValue.getClass() : null));
if (isSessionAttr && !this.sessionStatus.isComplete()) {
this.sessionAttributeStore.storeAttribute(webRequest, attrName, attrValue);
}
if (!attrName.startsWith(BindingResult.MODEL_KEY_PREFIX) &&
(isSessionAttr || isBindingCandidate(attrValue))) {
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + attrName;
if (mavModel != null && !model.containsKey(bindingResultKey)) {
WebDataBinder binder = createBinder(webRequest, attrValue, attrName);
initBinder(handler, attrName, binder, webRequest);
mavModel.put(bindingResultKey, binder.getBindingResult());
}
}
}
}
/**
* Determine whether the given value qualifies as a "binding candidate",
* i.e. might potentially be subject to bean-style data binding later on.
*/
protected boolean isBindingCandidate(Object value) {
return (value != null && !value.getClass().isArray() && !(value instanceof Collection) &&
!(value instanceof Map) && !BeanUtils.isSimpleValueType(value.getClass()));
}
private Object doInvokeMethod(Method method, Object target, Object[] args) throws Exception {
ReflectionUtils.makeAccessible(method);
try {
return method.invoke(target, args);
}
catch (InvocationTargetException ex) {
ReflectionUtils.rethrowException(ex.getTargetException());
}
throw new IllegalStateException("Should never get here");
}
protected void raiseMissingParameterException(String paramName, Class paramType) throws Exception {
throw new IllegalStateException("Missing parameter '" + paramName + "' of type [" + paramType.getName() + "]");
}
protected void raiseSessionRequiredException(String message) throws Exception {
throw new IllegalStateException(message);
}
protected WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName)
throws Exception {
return new WebRequestDataBinder(target, objectName);
}
protected void doBind(NativeWebRequest webRequest, WebDataBinder binder, boolean failOnErrors)
throws Exception {
WebRequestDataBinder requestBinder = (WebRequestDataBinder) binder;
requestBinder.bind(webRequest);
if (failOnErrors) {
requestBinder.closeNoCatch();
}
}
protected Object resolveCommonArgument(MethodParameter methodParameter, NativeWebRequest webRequest)
throws Exception {
// Invoke custom argument resolvers if present...
if (this.customArgumentResolvers != null) {
for (WebArgumentResolver argumentResolver : this.customArgumentResolvers) {
Object value = argumentResolver.resolveArgument(methodParameter, webRequest);
if (value != WebArgumentResolver.UNRESOLVED) {
return value;
}
}
}
// Resolution of standard parameter types...
Class paramType = methodParameter.getParameterType();
Object value = resolveStandardArgument(paramType, webRequest);
if (value != WebArgumentResolver.UNRESOLVED && !ClassUtils.isAssignableValue(paramType, value)) {
throw new IllegalStateException("Standard argument type [" + paramType.getName() +
"] resolved to incompatible value of type [" + (value != null ? value.getClass() : null) +
"]. Consider declaring the argument type in a less specific fashion.");
}
return value;
}
protected Object resolveStandardArgument(Class parameterType, NativeWebRequest webRequest)
throws Exception {
if (WebRequest.class.isAssignableFrom(parameterType)) {
return webRequest;
}
return WebArgumentResolver.UNRESOLVED;
}
}

View File

@ -0,0 +1,137 @@
/*
* 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 java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
/**
* Support class for resolving web method annotations in a handler type.
* Processes <code>@RequestMapping</code>, <code>@InitBinder</code>,
* <code>@ModelAttribute</code> and <code>@SessionAttributes</code>.
*
* <p>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<Method> handlerMethods = new LinkedHashSet<Method>();
private final Set<Method> initBinderMethods = new LinkedHashSet<Method>();
private final Set<Method> modelAttributeMethods = new LinkedHashSet<Method>();
private final RequestMapping typeLevelMapping;
private final boolean sessionAttributesFound;
private final Set<String> sessionAttributeNames = new HashSet<String>();
private final Set<Class> sessionAttributeTypes = new HashSet<Class>();
private final Set<String> actualSessionAttributeNames = Collections.synchronizedSet(new HashSet<String>(4));
/**
* Create a new HandlerMethodResolver for the specified handler type.
* @param handlerType the handler class to introspect
*/
public HandlerMethodResolver(final Class<?> handlerType) {
ReflectionUtils.doWithMethods(handlerType, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) {
if (method.isAnnotationPresent(RequestMapping.class)) {
handlerMethods.add(ClassUtils.getMostSpecificMethod(method, handlerType));
}
else if (method.isAnnotationPresent(InitBinder.class)) {
initBinderMethods.add(ClassUtils.getMostSpecificMethod(method, handlerType));
}
else if (method.isAnnotationPresent(ModelAttribute.class)) {
modelAttributeMethods.add(ClassUtils.getMostSpecificMethod(method, handlerType));
}
}
});
this.typeLevelMapping = handlerType.getAnnotation(RequestMapping.class);
SessionAttributes sessionAttributes = handlerType.getAnnotation(SessionAttributes.class);
this.sessionAttributesFound = (sessionAttributes != null);
if (this.sessionAttributesFound) {
this.sessionAttributeNames.addAll(Arrays.asList(sessionAttributes.value()));
this.sessionAttributeTypes.addAll(Arrays.asList(sessionAttributes.types()));
}
}
public final boolean hasHandlerMethods() {
return !this.handlerMethods.isEmpty();
}
public final Set<Method> getHandlerMethods() {
return this.handlerMethods;
}
public final Set<Method> getInitBinderMethods() {
return this.initBinderMethods;
}
public final Set<Method> getModelAttributeMethods() {
return this.modelAttributeMethods;
}
public boolean hasTypeLevelMapping() {
return (this.typeLevelMapping != null);
}
public RequestMapping getTypeLevelMapping() {
return this.typeLevelMapping;
}
public boolean hasSessionAttributes() {
return this.sessionAttributesFound;
}
public boolean isSessionAttribute(String attrName, Class attrType) {
if (this.sessionAttributeNames.contains(attrName) || this.sessionAttributeTypes.contains(attrType)) {
this.actualSessionAttributeNames.add(attrName);
return true;
}
else {
return false;
}
}
public Set<String> getActualSessionAttributeNames() {
return this.actualSessionAttributeNames;
}
}

View File

@ -0,0 +1,7 @@
<html>
<body>
Support classes for web annotation processing.
</body>
</html>

View File

@ -0,0 +1,7 @@
<html>
<body>
Provides web-specific data binding functionality.
</body>
</html>

View File

@ -0,0 +1,135 @@
/*
* 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.beans.PropertyEditorRegistrar;
import org.springframework.validation.BindingErrorProcessor;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.context.request.WebRequest;
/**
* Convenient {@link WebBindingInitializer} for declarative configuration
* in a Spring application context. Allows for reusing pre-configured
* initializers with multiple controller/handlers.
*
* @author Juergen Hoeller
* @since 2.5
* @see #setDirectFieldAccess
* @see #setMessageCodesResolver
* @see #setBindingErrorProcessor
* @see #setPropertyEditorRegistrar
*/
public class ConfigurableWebBindingInitializer implements WebBindingInitializer {
private boolean directFieldAccess = false;
private MessageCodesResolver messageCodesResolver;
private BindingErrorProcessor bindingErrorProcessor;
private PropertyEditorRegistrar[] propertyEditorRegistrars;
/**
* Set whether to use direct field access instead of bean property access.
* <p>Default is <code>false</code>, using bean property access.
* Switch this to <code>true</code> 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.
* <p>Default is <code>null</code>, 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 <code>PropertyAccessException</code>s.
* <p>Default is <code>null</code>, 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);
}
}
}
}

View File

@ -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.
* <p>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.
* <p>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;
}
}

View File

@ -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.
* <p>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.
* <p>This will typically be called with the expectation that the
* attribute is already present, with an exception to be thrown
* if this method returns <code>null</code>.
* @param request the current request
* @param attributeName the name of the attribute
* @return the current attribute value, or <code>null</code> if none
*/
Object retrieveAttribute(WebRequest request, String attributeName);
/**
* Clean up the specified attribute in the backend session.
* <p>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);
}

View File

@ -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();
}

View File

@ -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 <code>complete</code> 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;
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.bind.support;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
/**
* SPI for resolving custom arguments for a specific handler method parameter.
* Typically implemented to detect sppecial parameter types, resolving
* well-known argument values for them.
*
* <p>A typical implementation could look like as follows:
*
* <pre class="code">
* public class MySpecialArgumentResolver implements ArgumentResolver {
*
* public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) {
* if (methodParameter.getParameterType().equals(MySpecialArg.class)) {
* return new MySpecialArg("myValue");
* }
* return UNRESOLVED;
* }
* }</pre>
*
* @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 <code>UNRESOLVED</code> if not resolvable
* @throws Exception in case of resolution failure
*/
Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception;
}

View File

@ -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);
}

View File

@ -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.
*
* <p>See the DataBinder/WebDataBinder superclasses for customization options,
* which include specifying allowed/required fields, and registering custom
* property editors.
*
* <p>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 <code>bind</code> with the current WebRequest as argument:
*
* <pre class="code">
* 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();
* ...</pre>
*
* @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 <code>null</code>
* 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 <code>null</code>
* 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.
* <p>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").
* <p>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.
* <p>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.
* <p>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());
}
}
}

View File

@ -0,0 +1,7 @@
<html>
<body>
Support classes for web data binding.
</body>
</html>

View File

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

View File

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

View File

@ -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.
*
* <p>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 <code>null</code> or empty)
*/
String getName();
/**
* Return the original filename in the client's filesystem.
* <p>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 <code>null</code> 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.
* <p>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.
* <p>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;
}

View File

@ -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.
*
* <p>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 {
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.multipart;
import java.util.Iterator;
import java.util.Map;
/**
* This interface defines the multipart request access operations
* that are exposed for actual multipart requests. It is extended
* by {@link MultipartHttpServletRequest} and the Portlet
* {@link org.springframework.web.portlet.multipart.MultipartActionRequest}.
*
* @author Juergen Hoeller
* @since 2.5.2
*/
public interface MultipartRequest {
/**
* Return an {@link java.util.Iterator} of String objects containing the
* parameter names of the multipart files contained in this request. These
* are the field names of the form (like with normal parameters), not the
* original file names.
* @return the names of the files
*/
Iterator getFileNames();
/**
* Return the contents plus description of an uploaded file in this request,
* or <code>null</code> if it does not exist.
* @param name a String specifying the parameter name of the multipart file
* @return the uploaded content in the form of a {@link org.springframework.web.multipart.MultipartFile} object
*/
MultipartFile getFile(String name);
/**
* Return a {@link java.util.Map} of the multipart files contained in this request.
* @return a map containing the parameter names as keys, and the
* {@link org.springframework.web.multipart.MultipartFile} objects as values
* @see MultipartFile
*/
Map getFileMap();
}

View File

@ -0,0 +1,127 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.multipart;
import javax.servlet.http.HttpServletRequest;
/**
* A strategy interface for multipart file upload resolution in accordance
* with <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.
* Implementations are typically usable both within an application context
* and standalone.
*
* <p>There is only one concrete implementation included in Spring,
* as of Spring 2.5:
* <ul>
* <li>{@link org.springframework.web.multipart.commons.CommonsMultipartResolver} for Jakarta Commons FileUpload
* </ul>
*
* <p>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}.
*
* <p>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.
*
* <pre class="code">
* public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
* MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
* MultipartFile multipartFile = multipartRequest.getFile("image");
* ...
* }</pre>
*
* 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.
*
* <p>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 <code>web.xml</code>. 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.
*
* <p>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.
* <p>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);
}

View File

@ -0,0 +1,321 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.multipart.commons;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.util.WebUtils;
/**
* Base class for multipart resolvers that use Jakarta Commons FileUpload
* 1.1 or higher.
*
* <p>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.
*
* <p>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 <code>org.apache.commons.fileupload.disk.DiskFileItemFactory</code>
* instance. There is hardly any need to access this.
* @return the underlying DiskFileItemFactory instance
*/
public DiskFileItemFactory getFileItemFactory() {
return this.fileItemFactory;
}
/**
* Return the underlying <code>org.apache.commons.fileupload.FileUpload</code>
* 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.
* <p>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
* <code>ServletRequest.setCharacterEncoding</code> 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.
* <p>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.
* <p><b>To be implemented by subclasses.</b>
* @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.
* <p>Default implementation returns the shared FileUpload instance
* if the encoding matches, else creates a new FileUpload instance
* with the same configuration other than the desired encoding.
* @param encoding the character encoding to use
* @return an appropriate FileUpload instance.
*/
protected FileUpload prepareFileUpload(String encoding) {
FileUpload fileUpload = getFileUpload();
FileUpload actualFileUpload = fileUpload;
// Use new temporary FileUpload instance if the request specifies
// its own encoding that does not match the default encoding.
if (encoding != null && !encoding.equals(fileUpload.getHeaderEncoding())) {
actualFileUpload = newFileUpload(getFileItemFactory());
actualFileUpload.setSizeMax(fileUpload.getSizeMax());
actualFileUpload.setHeaderEncoding(encoding);
}
return actualFileUpload;
}
/**
* Parse the given List of Commons FileItems into a Spring MultipartParsingResult,
* containing Spring MultipartFile instances and a Map of multipart parameter.
* @param fileItems the Commons FileIterms to parse
* @param encoding the encoding to use for form fields
* @return the Spring MultipartParsingResult
* @see CommonsMultipartFile#CommonsMultipartFile(org.apache.commons.fileupload.FileItem)
*/
protected MultipartParsingResult parseFileItems(List fileItems, String encoding) {
Map multipartFiles = new HashMap();
Map multipartParameters = new HashMap();
// Extract multipart files and multipart parameters.
for (Iterator it = fileItems.iterator(); it.hasNext();) {
FileItem fileItem = (FileItem) it.next();
if (fileItem.isFormField()) {
String value = null;
if (encoding != null) {
try {
value = fileItem.getString(encoding);
}
catch (UnsupportedEncodingException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Could not decode multipart item '" + fileItem.getFieldName() +
"' with encoding '" + encoding + "': using platform default");
}
value = fileItem.getString();
}
}
else {
value = fileItem.getString();
}
String[] curParam = (String[]) multipartParameters.get(fileItem.getFieldName());
if (curParam == null) {
// simple form field
multipartParameters.put(fileItem.getFieldName(), new String[] { value });
}
else {
// array of simple form fields
String[] newParam = StringUtils.addStringToArray(curParam, value);
multipartParameters.put(fileItem.getFieldName(), newParam);
}
}
else {
// multipart file field
CommonsMultipartFile file = new CommonsMultipartFile(fileItem);
if (multipartFiles.put(file.getName(), file) != null) {
throw new MultipartException(
"Multiple files for field name [" + file.getName() + "] found - not supported by MultipartResolver");
}
if (logger.isDebugEnabled()) {
logger.debug("Found multipart file [" + file.getName() + "] of size " + file.getSize() +
" bytes with original filename [" + file.getOriginalFilename() + "], stored " +
file.getStorageDescription());
}
}
}
return new MultipartParsingResult(multipartFiles, multipartParameters);
}
/**
* Cleanup the Spring MultipartFiles created during multipart parsing,
* potentially holding temporary data on disk.
* <p>Deletes the underlying Commons FileItem instances.
* @param multipartFiles Collection of MultipartFile instances
* @see org.apache.commons.fileupload.FileItem#delete()
*/
protected void cleanupFileItems(Collection multipartFiles) {
for (Iterator it = multipartFiles.iterator(); it.hasNext();) {
CommonsMultipartFile file = (CommonsMultipartFile) it.next();
if (logger.isDebugEnabled()) {
logger.debug("Cleaning up multipart file [" + file.getName() + "] with original filename [" +
file.getOriginalFilename() + "], stored " + file.getStorageDescription());
}
file.getFileItem().delete();
}
}
/**
* Holder for a Map of Spring MultipartFiles and a Map of
* multipart parameters.
*/
protected static class MultipartParsingResult {
private final Map multipartFiles;
private final Map multipartParameters;
/**
* Create a new MultipartParsingResult.
* @param multipartFiles Map of field name to MultipartFile instance
* @param multipartParameters Map of field name to form field String value
*/
public MultipartParsingResult(Map multipartFiles, Map multipartParameters) {
this.multipartFiles = multipartFiles;
this.multipartParameters = multipartParameters;
}
/**
* Return the multipart files as Map of field name to MultipartFile instance.
*/
public Map getMultipartFiles() {
return this.multipartFiles;
}
/**
* Return the multipart parameters as Map of field name to form field String value.
*/
public Map getMultipartParameters() {
return this.multipartParameters;
}
}
}

View File

@ -0,0 +1,194 @@
/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.multipart.commons;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.multipart.MultipartFile;
/**
* MultipartFile implementation for Jakarta Commons FileUpload.
*
* <p><b>NOTE:</b> 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 <code>org.apache.commons.fileupload.FileItem</code>
* 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";
}
}
}

View File

@ -0,0 +1,205 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.multipart.commons;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.servlet.ServletRequestContext;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest;
import org.springframework.web.util.WebUtils;
/**
* Servlet-based {@link org.springframework.web.multipart.MultipartResolver} implementation
* for <a href="http://jakarta.apache.org/commons/fileupload">Jakarta Commons FileUpload</a>
* 1.1 or above. Commons FileUpload 1.2 or above is recommended.
*
* <p>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.
*
* <p>Saves temporary files to the servlet container's temporary directory.
* Needs to be initialized <i>either</i> by an application context <i>or</i>
* via the constructor that takes a ServletContext (for standalone usage).
*
* @author Trevor D. Cook
* @author Juergen Hoeller
* @since 29.09.2003
* @see #CommonsMultipartResolver(ServletContext)
* @see #setResolveLazily
* @see org.springframework.web.portlet.multipart.CommonsPortletMultipartResolver
* @see org.apache.commons.fileupload.servlet.ServletFileUpload
* @see org.apache.commons.fileupload.disk.DiskFileItemFactory
*/
public class CommonsMultipartResolver extends CommonsFileUploadSupport
implements MultipartResolver, ServletContextAware {
private final boolean commonsFileUpload12Present =
ClassUtils.hasMethod(ServletFileUpload.class, "isMultipartContent", new Class[] {HttpServletRequest.class});
private boolean resolveLazily = false;
/**
* Constructor for use as bean. Determines the servlet container's
* temporary directory via the ServletContext passed in as through the
* ServletContextAware interface (typically by a WebApplicationContext).
* @see #setServletContext
* @see org.springframework.web.context.ServletContextAware
* @see org.springframework.web.context.WebApplicationContext
*/
public CommonsMultipartResolver() {
super();
}
/**
* Constructor for standalone usage. Determines the servlet container's
* temporary directory via the given ServletContext.
* @param servletContext the ServletContext to use
*/
public CommonsMultipartResolver(ServletContext servletContext) {
this();
setServletContext(servletContext);
}
/**
* Set whether to resolve the multipart request lazily at the time of
* file or parameter access.
* <p>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 <code>org.apache.commons.fileupload.servlet.ServletFileUpload</code>
* instance. Can be overridden to use a custom subclass, e.g. for testing purposes.
* @param fileItemFactory the Commons FileItemFactory to use
* @return the new ServletFileUpload instance
*/
protected FileUpload newFileUpload(FileItemFactory fileItemFactory) {
return new ServletFileUpload(fileItemFactory);
}
public void setServletContext(ServletContext servletContext) {
if (!isUploadTempDirSpecified()) {
getFileItemFactory().setRepository(WebUtils.getTempDir(servletContext));
}
}
public boolean isMultipart(HttpServletRequest request) {
if (request == null) {
return false;
}
else if (commonsFileUpload12Present) {
return ServletFileUpload.isMultipartContent(request);
}
else {
return ServletFileUpload.isMultipartContent(new ServletRequestContext(request));
}
}
public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
Assert.notNull(request, "Request must not be null");
if (this.resolveLazily) {
return new DefaultMultipartHttpServletRequest(request) {
protected void initializeMultipart() {
MultipartParsingResult parsingResult = parseRequest(request);
setMultipartFiles(parsingResult.getMultipartFiles());
setMultipartParameters(parsingResult.getMultipartParameters());
}
};
}
else {
MultipartParsingResult parsingResult = parseRequest(request);
return new DefaultMultipartHttpServletRequest(
request, parsingResult.getMultipartFiles(), parsingResult.getMultipartParameters());
}
}
/**
* Parse the given servlet request, resolving its multipart elements.
* @param request the request to parse
* @return the parsing result
* @throws MultipartException if multipart resolution failed.
*/
protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
String encoding = determineEncoding(request);
FileUpload fileUpload = prepareFileUpload(encoding);
try {
List fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
return parseFileItems(fileItems, encoding);
}
catch (FileUploadBase.SizeLimitExceededException ex) {
throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
}
catch (FileUploadException ex) {
throw new MultipartException("Could not parse multipart servlet request", ex);
}
}
/**
* Determine the encoding for the given request.
* Can be overridden in subclasses.
* <p>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 <code>null</code>)
* @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);
}
}
}
}

View File

@ -0,0 +1,8 @@
<html>
<body>
MultipartResolver implementation for
<a href="http://jakarta.apache.org/commons/fileupload">Jakarta Commons FileUpload</a>.
</body>
</html>

View File

@ -0,0 +1,10 @@
<html>
<body>
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.
</body>
</html>

View File

@ -0,0 +1,92 @@
/*
* 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.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 Map multipartFiles;
/**
* Wrap the given HttpServletRequest in a MultipartHttpServletRequest.
* @param request the request to wrap
*/
protected AbstractMultipartHttpServletRequest(HttpServletRequest request) {
super(request);
}
public Iterator getFileNames() {
return getMultipartFiles().keySet().iterator();
}
public MultipartFile getFile(String name) {
return (MultipartFile) getMultipartFiles().get(name);
}
public Map getFileMap() {
return getMultipartFiles();
}
/**
* Set a Map with parameter names as keys and MultipartFile objects as values.
* To be invoked by subclasses on initialization.
*/
protected final void setMultipartFiles(Map multipartFiles) {
this.multipartFiles = Collections.unmodifiableMap(multipartFiles);
}
/**
* Obtain the MultipartFile Map for retrieval,
* lazily initializing it if necessary.
* @see #initializeMultipart()
*/
protected Map getMultipartFiles() {
if (this.multipartFiles == null) {
initializeMultipart();
}
return this.multipartFiles;
}
/**
* Lazily initialize the multipart request, if possible.
* Only called if not already eagerly initialized.
*/
protected void initializeMultipart() {
throw new IllegalStateException("Multipart request not initialized");
}
}

View File

@ -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.support;
import java.io.IOException;
import org.springframework.beans.propertyeditors.ByteArrayPropertyEditor;
import org.springframework.web.multipart.MultipartFile;
/**
* Custom {@link java.beans.PropertyEditor} for converting
* {@link MultipartFile MultipartFiles} to byte arrays.
*
* @author Juergen Hoeller
* @since 13.10.2003
*/
public class ByteArrayMultipartFileEditor extends ByteArrayPropertyEditor {
public void setValue(Object value) {
if (value instanceof MultipartFile) {
MultipartFile multipartFile = (MultipartFile) value;
try {
super.setValue(multipartFile.getBytes());
}
catch (IOException ex) {
IllegalArgumentException iae = new IllegalArgumentException("Cannot read contents of multipart file");
iae.initCause(ex);
throw iae;
}
}
else if (value instanceof byte[]) {
super.setValue(value);
}
else {
super.setValue(value != null ? value.toString().getBytes() : null);
}
}
public String getAsText() {
byte[] value = (byte[]) getValue();
return (value != null ? new String(value) : "");
}
}

View File

@ -0,0 +1,121 @@
/*
* 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.support;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
/**
* Default implementation of the
* {@link org.springframework.web.multipart.MultipartHttpServletRequest}
* interface. Provides management of pre-generated parameter values.
*
* @author Trevor D. Cook
* @author Juergen Hoeller
* @since 29.09.2003
* @see org.springframework.web.multipart.MultipartResolver
*/
public class DefaultMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest {
private Map multipartParameters;
/**
* Wrap the given HttpServletRequest in a MultipartHttpServletRequest.
* @param request the servlet request to wrap
* @param multipartFiles a map of the multipart files
* @param multipartParameters a map of the parameters to expose,
* with Strings as keys and String arrays as values
*/
public DefaultMultipartHttpServletRequest(
HttpServletRequest request, Map multipartFiles, Map multipartParameters) {
super(request);
setMultipartFiles(multipartFiles);
setMultipartParameters(multipartParameters);
}
/**
* Wrap the given HttpServletRequest in a MultipartHttpServletRequest.
* @param request the servlet request to wrap
*/
public DefaultMultipartHttpServletRequest(HttpServletRequest request) {
super(request);
}
public Enumeration getParameterNames() {
Set paramNames = new HashSet();
Enumeration paramEnum = super.getParameterNames();
while (paramEnum.hasMoreElements()) {
paramNames.add(paramEnum.nextElement());
}
paramNames.addAll(getMultipartParameters().keySet());
return Collections.enumeration(paramNames);
}
public String getParameter(String name) {
String[] values = (String[]) getMultipartParameters().get(name);
if (values != null) {
return (values.length > 0 ? values[0] : null);
}
return super.getParameter(name);
}
public String[] getParameterValues(String name) {
String[] values = (String[]) getMultipartParameters().get(name);
if (values != null) {
return values;
}
return super.getParameterValues(name);
}
public Map getParameterMap() {
Map paramMap = new HashMap();
paramMap.putAll(super.getParameterMap());
paramMap.putAll(getMultipartParameters());
return paramMap;
}
/**
* Set a Map with parameter names as keys and String array objects as values.
* To be invoked by subclasses on initialization.
*/
protected final void setMultipartParameters(Map multipartParameters) {
this.multipartParameters = multipartParameters;
}
/**
* Obtain the multipart parameter Map for retrieval,
* lazily initializing it if necessary.
* @see #initializeMultipart()
*/
protected Map getMultipartParameters() {
if (this.multipartParameters == null) {
initializeMultipart();
}
return this.multipartParameters;
}
}

View File

@ -0,0 +1,150 @@
/*
* 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.support;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
/**
* Servlet 2.3 Filter that resolves multipart requests via a MultipartResolver.
* in the root web application context.
*
* <p>Looks up the MultipartResolver in Spring's root web application context.
* Supports a "multipartResolverBeanName" filter init-param in <code>web.xml</code>;
* 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 <i>after</i> this filter).
*
* <p>MultipartResolver lookup is customizable: Override this filter's
* <code>lookupMultipartResolver</code> 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.
*
* <p>Note: This filter is an <b>alternative</b> 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.
* <p>All later elements in the filter chain, most importantly servlets, benefit
* from proper parameter extraction in the multipart case, and are able to cast to
* MultipartHttpServletRequest if they need to.
*/
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
MultipartResolver multipartResolver = lookupMultipartResolver(request);
HttpServletRequest processedRequest = request;
if (multipartResolver.isMultipart(processedRequest)) {
if (logger.isDebugEnabled()) {
logger.debug("Resolving multipart request [" + processedRequest.getRequestURI() +
"] with MultipartFilter");
}
processedRequest = multipartResolver.resolveMultipart(processedRequest);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Request [" + processedRequest.getRequestURI() + "] is not a multipart request");
}
}
try {
filterChain.doFilter(processedRequest, response);
}
finally {
if (processedRequest instanceof MultipartHttpServletRequest) {
multipartResolver.cleanupMultipart((MultipartHttpServletRequest) processedRequest);
}
}
}
/**
* Look up the MultipartResolver that this filter should use,
* taking the current HTTP request as argument.
* <p>Default implementation delegates to the <code>lookupMultipartResolver</code>
* 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".
* <p>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 <code>null</code> 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);
}
}

View File

@ -0,0 +1,78 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.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.
*
* <p>Allows one to specify the charset to use.
*
* @author Juergen Hoeller
* @since 13.10.2003
*/
public class StringMultipartFileEditor extends PropertyEditorSupport {
private final String charsetName;
/**
* Create a new {@link StringMultipartFileEditor}, using the default charset.
*/
public StringMultipartFileEditor() {
this.charsetName = null;
}
/**
* Create a new {@link StringMultipartFileEditor}, using the given charset.
* @param charsetName valid charset name
* @see java.lang.String#String(byte[],String)
*/
public StringMultipartFileEditor(String charsetName) {
this.charsetName = charsetName;
}
public void setAsText(String text) {
setValue(text);
}
public void setValue(Object value) {
if (value instanceof MultipartFile) {
MultipartFile multipartFile = (MultipartFile) value;
try {
super.setValue(this.charsetName != null ?
new String(multipartFile.getBytes(), this.charsetName) :
new String(multipartFile.getBytes()));
}
catch (IOException ex) {
IllegalArgumentException iae = new IllegalArgumentException("Cannot read contents of multipart file");
iae.initCause(ex);
throw iae;
}
}
else {
super.setValue(value);
}
}
}

View File

@ -0,0 +1,9 @@
<html>
<body>
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.
</body>
</html>

View File

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

View File

@ -0,0 +1,647 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet;
import java.io.IOException;
import java.security.Principal;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SourceFilteringListener;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.ServletRequestHandledEvent;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.util.NestedServletException;
import org.springframework.web.util.WebUtils;
/**
* Base servlet for Spring's web framework. Provides integration with
* a Spring application context, in a JavaBean-based overall solution.
*
* <p>This class offers the following functionality:
* <ul>
* <li>Manages a {@link org.springframework.web.context.WebApplicationContext}
* instance per servlet. The servlet's configuration is determined by beans
* in the servlet's namespace.
* <li>Publishes events on request processing, whether or not a request is
* successfully handled.
* </ul>
*
* <p>Subclasses must implement {@link #doService} to handle requests. Because this extends
* {@link HttpServletBean} rather than HttpServlet directly, bean properties are
* automatically mapped onto it. Subclasses can override {@link #initFrameworkServlet()}
* for custom initialization.
*
* <p>Detects a "contextClass" parameter at the servlet init-param level,
* falling back to the default context class,
* {@link org.springframework.web.context.support.XmlWebApplicationContext},
* if not found. Note that, with the default FrameworkServlet,
* a custom context class needs to implement the
* {@link org.springframework.web.context.ConfigurableWebApplicationContext} SPI.
*
* <p>Passes a "contextConfigLocation" servlet init-param to the context instance,
* parsing it into potentially multiple file paths which can be separated by any
* number of commas and spaces, like "test-servlet.xml, myServlet.xml".
* If not explicitly specified, the context implementation is supposed to build a
* default location from the namespace of the servlet.
*
* <p>Note: In case of multiple config locations, later bean definitions will
* override ones defined in earlier loaded files, at least when using Spring's
* default ApplicationContext implementation. This can be leveraged to
* deliberately override certain bean definitions via an extra XML file.
*
* <p>The default namespace is "'servlet-name'-servlet", e.g. "test-servlet" for a
* servlet-name "test" (leading to a "/WEB-INF/test-servlet.xml" default location
* with XmlWebApplicationContext). The namespace can also be set explicitly via
* the "namespace" servlet init-param.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Sam Brannen
* @see #doService
* @see #setContextClass
* @see #setContextConfigLocation
* @see #setNamespace
*/
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationListener {
/**
* Suffix for WebApplicationContext namespaces. If a servlet of this class is
* given the name "test" in a context, the namespace used by the servlet will
* resolve to "test-servlet".
*/
public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";
/**
* Default context class for FrameworkServlet.
* @see org.springframework.web.context.support.XmlWebApplicationContext
*/
public static final Class DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
/**
* Prefix for the ServletContext attribute for the WebApplicationContext.
* The completion is the servlet name.
*/
public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
/** ServletContext attribute to find the WebApplicationContext in */
private String contextAttribute;
/** WebApplicationContext implementation class to create */
private Class contextClass = DEFAULT_CONTEXT_CLASS;
/** Namespace for this servlet */
private String namespace;
/** Explicit context config location */
private String contextConfigLocation;
/** Should we publish the context as a ServletContext attribute? */
private boolean publishContext = true;
/** Should we publish a ServletRequestHandledEvent at the end of each request? */
private boolean publishEvents = true;
/** Should we dispatch an HTTP OPTIONS request to {@link #doService}? */
private boolean dispatchOptionsRequest = false;
/** Should we dispatch an HTTP TRACE request to {@link #doService}? */
private boolean dispatchTraceRequest = false;
/** WebApplicationContext for this servlet */
private WebApplicationContext webApplicationContext;
/** Flag used to detect whether onRefresh has already been called */
private boolean refreshEventReceived = false;
/**
* Set the name of the ServletContext attribute which should be used to retrieve the
* {@link WebApplicationContext} that this servlet is supposed to use.
*/
public void setContextAttribute(String contextAttribute) {
this.contextAttribute = contextAttribute;
}
/**
* Return the name of the ServletContext attribute which should be used to retrieve the
* {@link WebApplicationContext} that this servlet is supposed to use.
*/
public String getContextAttribute() {
return this.contextAttribute;
}
/**
* Set a custom context class. This class must be of type
* {@link org.springframework.web.context.WebApplicationContext}.
* <p>When using the default FrameworkServlet implementation,
* the context class must also implement the
* {@link org.springframework.web.context.ConfigurableWebApplicationContext}
* interface.
* @see #createWebApplicationContext
*/
public void setContextClass(Class contextClass) {
this.contextClass = contextClass;
}
/**
* Return the custom context class.
*/
public Class getContextClass() {
return this.contextClass;
}
/**
* Set a custom namespace for this servlet,
* to be used for building a default context config location.
*/
public void setNamespace(String namespace) {
this.namespace = namespace;
}
/**
* Return the namespace for this servlet, falling back to default scheme if
* no custom namespace was set: e.g. "test-servlet" for a servlet named "test".
*/
public String getNamespace() {
return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX);
}
/**
* Set the context config location explicitly, instead of relying on the default
* location built from the namespace. This location string can consist of
* multiple locations separated by any number of commas and spaces.
*/
public void setContextConfigLocation(String contextConfigLocation) {
this.contextConfigLocation = contextConfigLocation;
}
/**
* Return the explicit context config location, if any.
*/
public String getContextConfigLocation() {
return this.contextConfigLocation;
}
/**
* Set whether to publish this servlet's context as a ServletContext attribute,
* available to all objects in the web container. Default is "true".
* <p>This is especially handy during testing, although it is debatable whether
* it's good practice to let other application objects access the context this way.
*/
public void setPublishContext(boolean publishContext) {
this.publishContext = publishContext;
}
/**
* Set whether this servlet should publish a ServletRequestHandledEvent at the end
* of each request. Default is "true"; can be turned off for a slight performance
* improvement, provided that no ApplicationListeners rely on such events.
* @see org.springframework.web.context.support.ServletRequestHandledEvent
*/
public void setPublishEvents(boolean publishEvents) {
this.publishEvents = publishEvents;
}
/**
* Set whether this servlet should dispatch an HTTP OPTIONS request to
* the {@link #doService} method.
* <p>Default is "false", applying {@link javax.servlet.http.HttpServlet}'s
* default behavior (i.e. enumerating all standard HTTP request methods
* as a response to the OPTIONS request).
* <p>Turn this flag on if you prefer OPTIONS requests to go through the
* regular dispatching chain, just like other HTTP requests. This usually
* means that your controllers will receive those requests; make sure
* that those endpoints are actually able to handle an OPTIONS request.
* <p>Note that HttpServlet's default OPTIONS processing will be applied
* in any case. Your controllers are simply available to override the
* default headers and optionally generate a response body.
*/
public void setDispatchOptionsRequest(boolean dispatchOptionsRequest) {
this.dispatchOptionsRequest = dispatchOptionsRequest;
}
/**
* Set whether this servlet should dispatch an HTTP TRACE request to
* the {@link #doService} method.
* <p>Default is "false", applying {@link javax.servlet.http.HttpServlet}'s
* default behavior (i.e. reflecting the message received back to the client).
* <p>Turn this flag on if you prefer TRACE requests to go through the
* regular dispatching chain, just like other HTTP requests. This usually
* means that your controllers will receive those requests; make sure
* that those endpoints are actually able to handle a TRACE request.
* <p>Note that HttpServlet's default TRACE processing will be applied
* in any case. Your controllers are simply available to override the
* default headers and the default body, calling <code>response.reset()</code>
* if necessary.
*/
public void setDispatchTraceRequest(boolean dispatchTraceRequest) {
this.dispatchTraceRequest = dispatchTraceRequest;
}
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
protected final void initServletBean() throws ServletException, BeansException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (BeansException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
/**
* Initialize and publish the WebApplicationContext for this servlet.
* <p>Delegates to {@link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @throws BeansException if the context couldn't be initialized
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() throws BeansException {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
// No fixed context defined for this servlet - create a local one.
WebApplicationContext parent =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
wac = createWebApplicationContext(parent);
}
if (!this.refreshEventReceived) {
// Apparently not a ConfigurableApplicationContext with refresh support:
// triggering initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
/**
* Retrieve a <code>WebApplicationContext</code> from the <code>ServletContext</code>
* attribute with the {@link #setContextAttribute configured name}. The
* <code>WebApplicationContext</code> must have already been loaded and stored in the
* <code>ServletContext</code> before this servlet gets initialized (or invoked).
* <p>Subclasses may override this method to provide a different
* <code>WebApplicationContext</code> retrieval strategy.
* @return the WebApplicationContext for this servlet, or <code>null</code> if not found
* @see #getContextAttribute()
*/
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
if (attrName == null) {
return null;
}
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
/**
* Instantiate the WebApplicationContext for this servlet, either a default
* {@link org.springframework.web.context.support.XmlWebApplicationContext}
* or a {@link #setContextClass custom context class}, if set.
* <p>This implementation expects custom contexts to implement the
* {@link org.springframework.web.context.ConfigurableWebApplicationContext}
* interface. Can be overridden in subclasses.
* <p>Do not forget to register this servlet instance as application listener on the
* created context (for triggering its {@link #onRefresh callback}, and to call
* {@link org.springframework.context.ConfigurableApplicationContext#refresh()}
* before returning the context instance.
* @param parent the parent ApplicationContext to use, or <code>null</code> if none
* @return the WebApplicationContext for this servlet
* @throws BeansException if the context couldn't be initialized
* @see org.springframework.web.context.support.XmlWebApplicationContext
*/
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent)
throws BeansException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
getContextClass().getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(getContextClass())) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + getContextClass().getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass());
wac.setParent(parent);
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.setConfigLocation(getContextConfigLocation());
wac.addApplicationListener(new SourceFilteringListener(wac, this));
postProcessWebApplicationContext(wac);
wac.refresh();
return wac;
}
/**
* Post-process the given WebApplicationContext before it is refreshed
* and activated as context for this servlet.
* <p>The default implementation is empty. <code>refresh()</code> will
* be called automatically after this method returns.
* @param wac the configured WebApplicationContext (not refreshed yet)
* @see #createWebApplicationContext
* @see ConfigurableWebApplicationContext#refresh()
*/
protected void postProcessWebApplicationContext(ConfigurableWebApplicationContext wac) {
}
/**
* Return the ServletContext attribute name for this servlet's WebApplicationContext.
* <p>The default implementation returns
* <code>SERVLET_CONTEXT_PREFIX + servlet name</code>.
* @see #SERVLET_CONTEXT_PREFIX
* @see #getServletName
*/
public String getServletContextAttributeName() {
return SERVLET_CONTEXT_PREFIX + getServletName();
}
/**
* Return this servlet's WebApplicationContext.
*/
public final WebApplicationContext getWebApplicationContext() {
return this.webApplicationContext;
}
/**
* This method will be invoked after any bean properties have been set and
* the WebApplicationContext has been loaded. The default implementation is empty;
* subclasses may override this method to perform any initialization they require.
* @throws ServletException in case of an initialization exception
* @throws BeansException if thrown by ApplicationContext methods
*/
protected void initFrameworkServlet() throws ServletException, BeansException {
}
/**
* Refresh this servlet's application context, as well as the
* dependent state of the servlet.
* @throws BeansException in case of errors
* @see #getWebApplicationContext()
* @see org.springframework.context.ConfigurableApplicationContext#refresh()
*/
public void refresh() throws BeansException {
WebApplicationContext wac = getWebApplicationContext();
if (!(wac instanceof ConfigurableApplicationContext)) {
throw new IllegalStateException("WebApplicationContext does not support refresh: " + wac);
}
((ConfigurableApplicationContext) wac).refresh();
}
/**
* ApplicationListener endpoint that receives events from this servlet's
* WebApplicationContext.
* <p>The default implementation calls {@link #onRefresh} in case of a
* {@link org.springframework.context.event.ContextRefreshedEvent},
* triggering a refresh of this servlet's context-dependent state.
* @param event the incoming ApplicationContext event
*/
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
this.refreshEventReceived = true;
onRefresh(((ContextRefreshedEvent) event).getApplicationContext());
}
}
/**
* Template method which can be overridden to add servlet-specific refresh work.
* Called after successful context refresh.
* <p>This implementation is empty.
* @param context the current WebApplicationContext
* @throws BeansException in case of errors
* @see #refresh()
*/
protected void onRefresh(ApplicationContext context) throws BeansException {
// For subclasses: do nothing by default.
}
/**
* Delegate GET requests to processRequest/doService.
* <p>Will also be invoked by HttpServlet's default implementation of <code>doHead</code>,
* with a <code>NoBodyResponse</code> that just captures the content length.
* @see #doService
* @see #doHead
*/
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Delegate POST requests to {@link #processRequest}.
* @see #doService
*/
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Delegate PUT requests to {@link #processRequest}.
* @see #doService
*/
protected final void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Delegate DELETE requests to {@link #processRequest}.
* @see #doService
*/
protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Delegate OPTIONS requests to {@link #processRequest}, if desired.
* <p>Applies HttpServlet's standard OPTIONS processing first.
* @see #doService
*/
protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
super.doOptions(request, response);
if (this.dispatchOptionsRequest) {
processRequest(request, response);
}
}
/**
* Delegate TRACE requests to {@link #processRequest}, if desired.
* <p>Applies HttpServlet's standard TRACE processing first.
* @see #doService
*/
protected void doTrace(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
super.doTrace(request, response);
if (this.dispatchTraceRequest) {
processRequest(request, response);
}
}
/**
* Process this request, publishing an event regardless of the outcome.
* <p>The actual event handling is performed by the abstract
* {@link #doService} template method.
*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
try {
doService(request, response);
}
catch (ServletException ex) {
failureCause = ex;
throw ex;
}
catch (IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
this.logger.debug("Successfully completed request");
}
if (this.publishEvents) {
// Whether or not we succeeded, publish an event.
long processingTime = System.currentTimeMillis() - startTime;
this.webApplicationContext.publishEvent(
new ServletRequestHandledEvent(this,
request.getRequestURI(), request.getRemoteAddr(),
request.getMethod(), getServletConfig().getServletName(),
WebUtils.getSessionId(request), getUsernameForRequest(request),
processingTime, failureCause));
}
}
}
/**
* Determine the username for the given request.
* <p>The default implementation takes the name of the UserPrincipal, if any.
* Can be overridden in subclasses.
* @param request current HTTP request
* @return the username, or <code>null</code> if none found
* @see javax.servlet.http.HttpServletRequest#getUserPrincipal()
*/
protected String getUsernameForRequest(HttpServletRequest request) {
Principal userPrincipal = request.getUserPrincipal();
return (userPrincipal != null ? userPrincipal.getName() : null);
}
/**
* Subclasses must implement this method to do the work of request handling,
* receiving a centralized callback for GET, POST, PUT and DELETE.
* <p>The contract is essentially the same as that for the commonly overridden
* <code>doGet</code> or <code>doPost</code> methods of HttpServlet.
* <p>This class intercepts calls to ensure that exception handling and
* event publication takes place.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
* @see javax.servlet.http.HttpServlet#doGet
* @see javax.servlet.http.HttpServlet#doPost
*/
protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
throws Exception;
/**
* Close the WebApplicationContext of this servlet.
* @see org.springframework.context.ConfigurableApplicationContext#close()
*/
public void destroy() {
getServletContext().log("Destroying Spring FrameworkServlet '" + getServletName() + "'");
if (this.webApplicationContext instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) this.webApplicationContext).close();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,238 @@
/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.BeansException;
import org.springframework.core.Ordered;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.context.support.WebApplicationObjectSupport;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
/**
* Abstract base class for {@link org.springframework.web.servlet.HandlerMapping}
* implementations. Supports ordering, a default handler, and handler interceptors.
*
* <p>Note: This base class does <i>not</i> support exposure of the
* {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE}. Support for this attribute
* is up to concrete subclasses, typically based on request URL mappings.
*
* @author Juergen Hoeller
* @since 07.04.2003
* @see #getHandlerInternal
* @see #setDefaultHandler
* @see #setInterceptors
* @see org.springframework.web.servlet.HandlerInterceptor
*/
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
implements HandlerMapping, Ordered {
private int order = Integer.MAX_VALUE; // default: same as non-Ordered
private Object defaultHandler;
private final List interceptors = new ArrayList();
private HandlerInterceptor[] adaptedInterceptors;
/**
* Specify the order value for this HandlerMapping bean.
* <p>Default value is <code>Integer.MAX_VALUE</code>, meaning that it's non-ordered.
* @see org.springframework.core.Ordered#getOrder()
*/
public final void setOrder(int order) {
this.order = order;
}
public final int getOrder() {
return this.order;
}
/**
* Set the default handler for this handler mapping.
* This handler will be returned if no specific mapping was found.
* <p>Default is <code>null</code>, indicating no default handler.
*/
public void setDefaultHandler(Object defaultHandler) {
this.defaultHandler = defaultHandler;
}
/**
* Return the default handler for this handler mapping,
* or <code>null</code> if none.
*/
public Object getDefaultHandler() {
return this.defaultHandler;
}
/**
* Set the interceptors to apply for all handlers mapped by this handler mapping.
* <p>Supported interceptor types are HandlerInterceptor and WebRequestInterceptor.
* @param interceptors array of handler interceptors, or <code>null</code> if none
* @see #adaptInterceptor
* @see org.springframework.web.servlet.HandlerInterceptor
* @see org.springframework.web.context.request.WebRequestInterceptor
*/
public void setInterceptors(Object[] interceptors) {
this.interceptors.addAll(Arrays.asList(interceptors));
}
/**
* Initializes the interceptors.
* @see #extendInterceptors(java.util.List)
* @see #initInterceptors()
*/
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
initInterceptors();
}
/**
* Extension hook that subclasses can override to register additional interceptors,
* given the configured interceptors (see {@link #setInterceptors}).
* <p>Will be invoked before {@link #initInterceptors()} adapts the specified
* interceptors into {@link HandlerInterceptor} instances.
* <p>The default implementation is empty.
* @param interceptors the configured interceptor List (never <code>null</code>),
* allowing to add further interceptors before as well as after the existing
* interceptors
*/
protected void extendInterceptors(List interceptors) {
}
/**
* Initialize the specified interceptors, adapting them where necessary.
* @see #setInterceptors
* @see #adaptInterceptor
*/
protected void initInterceptors() {
if (!this.interceptors.isEmpty()) {
this.adaptedInterceptors = new HandlerInterceptor[this.interceptors.size()];
for (int i = 0; i < this.interceptors.size(); i++) {
Object interceptor = this.interceptors.get(i);
if (interceptor == null) {
throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
}
this.adaptedInterceptors[i] = adaptInterceptor(interceptor);
}
}
}
/**
* Adapt the given interceptor object to the HandlerInterceptor interface.
* <p>Supported interceptor types are HandlerInterceptor and WebRequestInterceptor.
* Each given WebRequestInterceptor will be wrapped in a WebRequestHandlerInterceptorAdapter.
* Can be overridden in subclasses.
* @param interceptor the specified interceptor object
* @return the interceptor wrapped as HandlerInterceptor
* @see org.springframework.web.servlet.HandlerInterceptor
* @see org.springframework.web.context.request.WebRequestInterceptor
* @see WebRequestHandlerInterceptorAdapter
*/
protected HandlerInterceptor adaptInterceptor(Object interceptor) {
if (interceptor instanceof HandlerInterceptor) {
return (HandlerInterceptor) interceptor;
}
else if (interceptor instanceof WebRequestInterceptor) {
return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
}
else {
throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
}
}
/**
* Return the adapted interceptors as HandlerInterceptor array.
* @return the array of HandlerInterceptors, or <code>null</code> if none
*/
protected final HandlerInterceptor[] getAdaptedInterceptors() {
return this.adaptedInterceptors;
}
/**
* Look up a handler for the given request, falling back to the default
* handler if no specific one is found.
* @param request current HTTP request
* @return the corresponding handler instance, or the default handler
* @see #getHandlerInternal
*/
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
return getHandlerExecutionChain(handler, request);
}
/**
* Look up a handler for the given request, returning <code>null</code> if no
* specific one is found. This method is called by {@link #getHandler};
* a <code>null</code> return value will lead to the default handler, if one is set.
* <p>Note: This method may also return a pre-built {@link HandlerExecutionChain},
* combining a handler object with dynamically determined interceptors.
* Statically specified interceptors will get merged into such an existing chain.
* @param request current HTTP request
* @return the corresponding handler instance, or <code>null</code> if none found
* @throws Exception if there is an internal error
*/
protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
/**
* Build a HandlerExecutionChain for the given handler, including applicable interceptors.
* <p>The default implementation simply builds a standard HandlerExecutionChain with
* the given handler and this handler mapping's common interceptors. Subclasses may
* override this in order to extend/rearrange the list of interceptors.
* <p><b>NOTE:</b> The passed-in handler object may be a raw handler or a pre-built
* HandlerExecutionChain. This method should handle those two cases explicitly,
* either building a new HandlerExecutionChain or extending the existing chain.
* <p>For simply adding an interceptor, consider calling <code>super.getHandlerExecutionChain</code>
* and invoking {@link HandlerExecutionChain#addInterceptor} on the returned chain object.
* @param handler the resolved handler instance (never <code>null</code>)
* @param request current HTTP request
* @return the HandlerExecutionChain (never <code>null</code>)
* @see #getAdaptedInterceptors()
*/
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
if (handler instanceof HandlerExecutionChain) {
HandlerExecutionChain chain = (HandlerExecutionChain) handler;
chain.addInterceptors(getAdaptedInterceptors());
return chain;
}
else {
return new HandlerExecutionChain(handler, getAdaptedInterceptors());
}
}
}

View File

@ -0,0 +1,362 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeansException;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.UrlPathHelper;
/**
* Abstract base class for URL-mapped {@link org.springframework.web.servlet.HandlerMapping}
* implementations. Provides infrastructure for mapping handlers to URLs and configurable
* URL lookup. For information on the latter, see "alwaysUseFullPath" property.
*
* <p>Supports direct matches, e.g. a registered "/test" matches "/test", and
* various Ant-style pattern matches, e.g. a registered "/t*" pattern matches
* both "/test" and "/team", "/test/*" matches all paths in the "/test" directory,
* "/test/**" matches all paths below "/test". For details, see the
* {@link org.springframework.util.AntPathMatcher AntPathMatcher} javadoc.
*
* <p>Will search all path patterns to find the most exact match for the
* current request path. The most exact match is defined as the longest
* path pattern that matches the current request path.
*
* @author Juergen Hoeller
* @since 16.04.2003
* @see #setAlwaysUseFullPath
* @see #setUrlDecode
* @see org.springframework.util.AntPathMatcher
*/
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
private UrlPathHelper urlPathHelper = new UrlPathHelper();
private PathMatcher pathMatcher = new AntPathMatcher();
private Object rootHandler;
private boolean lazyInitHandlers = false;
private final Map handlerMap = new LinkedHashMap();
/**
* Set if URL lookup should always use the full path within the current servlet
* context. Else, the path within the current servlet mapping is used if applicable
* (that is, in the case of a ".../*" servlet mapping in web.xml).
* <p>Default is "false".
* @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
*/
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
}
/**
* Set if context path and request URI should be URL-decoded. Both are returned
* <i>undecoded</i> by the Servlet API, in contrast to the servlet path.
* <p>Uses either the request encoding or the default encoding according
* to the Servlet spec (ISO-8859-1).
* @see org.springframework.web.util.UrlPathHelper#setUrlDecode
*/
public void setUrlDecode(boolean urlDecode) {
this.urlPathHelper.setUrlDecode(urlDecode);
}
/**
* Set the UrlPathHelper to use for resolution of lookup paths.
* <p>Use this to override the default UrlPathHelper with a custom subclass,
* or to share common UrlPathHelper settings across multiple HandlerMappings
* and MethodNameResolvers.
* @see org.springframework.web.servlet.mvc.multiaction.AbstractUrlMethodNameResolver#setUrlPathHelper
*/
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
this.urlPathHelper = urlPathHelper;
}
/**
* Set the PathMatcher implementation to use for matching URL paths
* against registered URL patterns. Default is AntPathMatcher.
* @see org.springframework.util.AntPathMatcher
*/
public void setPathMatcher(PathMatcher pathMatcher) {
Assert.notNull(pathMatcher, "PathMatcher must not be null");
this.pathMatcher = pathMatcher;
}
/**
* Return the PathMatcher implementation to use for matching URL paths
* against registered URL patterns.
*/
public PathMatcher getPathMatcher() {
return this.pathMatcher;
}
/**
* Set the root handler for this handler mapping, that is,
* the handler to be registered for the root path ("/").
* <p>Default is <code>null</code>, indicating no root handler.
*/
public void setRootHandler(Object rootHandler) {
this.rootHandler = rootHandler;
}
/**
* Return the root handler for this handler mapping (registered for "/"),
* or <code>null</code> if none.
*/
public Object getRootHandler() {
return this.rootHandler;
}
/**
* Set whether to lazily initialize handlers. Only applicable to
* singleton handlers, as prototypes are always lazily initialized.
* Default is "false", as eager initialization allows for more efficiency
* through referencing the controller objects directly.
* <p>If you want to allow your controllers to be lazily initialized,
* make them "lazy-init" and set this flag to true. Just making them
* "lazy-init" will not work, as they are initialized through the
* references from the handler mapping in this case.
*/
public void setLazyInitHandlers(boolean lazyInitHandlers) {
this.lazyInitHandlers = lazyInitHandlers;
}
/**
* Look up a handler for the URL path of the given request.
* @param request current HTTP request
* @return the handler instance, or <code>null</code> if none found
*/
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
Object handler = lookupHandler(lookupPath, request);
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
if ("/".equals(lookupPath)) {
rawHandler = getRootHandler();
}
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
validateHandler(rawHandler, request);
handler = buildPathExposingHandler(rawHandler, lookupPath);
}
}
if (handler != null && logger.isDebugEnabled()) {
logger.debug("Mapping [" + lookupPath + "] to handler '" + handler + "'");
}
else if (handler == null && logger.isTraceEnabled()) {
logger.trace("No handler mapping found for [" + lookupPath + "]");
}
return handler;
}
/**
* Look up a handler instance for the given URL path.
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
* and various Ant-style pattern matches, e.g. a registered "/t*" matches
* both "/test" and "/team". For details, see the AntPathMatcher class.
* <p>Looks for the most exact pattern, where most exact is defined as
* the longest path pattern.
* @param urlPath URL the bean is mapped to
* @param request current HTTP request (to expose the path within the mapping to)
* @return the associated handler instance, or <code>null</code> if not found
* @see #exposePathWithinMapping
* @see org.springframework.util.AntPathMatcher
*/
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// Direct match?
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath);
}
// Pattern match?
String bestPathMatch = null;
for (Iterator it = this.handlerMap.keySet().iterator(); it.hasNext();) {
String registeredPath = (String) it.next();
if (getPathMatcher().match(registeredPath, urlPath) &&
(bestPathMatch == null || bestPathMatch.length() < registeredPath.length())) {
bestPathMatch = registeredPath;
}
}
if (bestPathMatch != null) {
handler = this.handlerMap.get(bestPathMatch);
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPathMatch, urlPath);
return buildPathExposingHandler(handler, pathWithinMapping);
}
// No handler found...
return null;
}
/**
* Validate the given handler against the current request.
* <p>The default implementation is empty. Can be overridden in subclasses,
* for example to enforce specific preconditions expressed in URL mappings.
* @param handler the handler object to validate
* @param request current HTTP request
* @throws Exception if validation failed
*/
protected void validateHandler(Object handler, HttpServletRequest request) throws Exception {
}
/**
* Build a handler object for the given raw handler, exposing the actual
* handler as well as the {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE}
* before executing the handler.
* <p>The default implementation builds a {@link HandlerExecutionChain}
* with a special interceptor that exposes the path attribute.
* @param rawHandler the raw handler to expose
* @param pathWithinMapping the path to expose before executing the handler
* @return the final handler object
*/
protected Object buildPathExposingHandler(Object rawHandler, String pathWithinMapping) {
// Bean name or resolved handler?
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = getApplicationContext().getBean(handlerName);
}
HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
chain.addInterceptor(new PathExposingHandlerInterceptor(pathWithinMapping));
return chain;
}
/**
* Expose the path within the current mapping as request attribute.
* @param pathWithinMapping the path within the current mapping
* @param request the request to expose the path to
* @see #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
*/
protected void exposePathWithinMapping(String pathWithinMapping, HttpServletRequest request) {
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping);
}
/**
* Register the specified handler for the given URL paths.
* @param urlPaths the URLs that the bean should be mapped to
* @param beanName the name of the handler bean
* @throws BeansException if the handler couldn't be registered
* @throws IllegalStateException if there is a conflicting handler registered
*/
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
Assert.notNull(urlPaths, "URL path array must not be null");
for (int j = 0; j < urlPaths.length; j++) {
registerHandler(urlPaths[j], beanName);
}
}
/**
* Register the specified handler for the given URL path.
* @param urlPath the URL the bean should be mapped to
* @param handler the handler instance or handler bean name String
* (a bean name will automatically be resolved into the corresponding handler bean)
* @throws BeansException if the handler couldn't be registered
* @throws IllegalStateException if there is a conflicting handler registered
*/
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
// Eagerly resolve handler if referencing singleton via name.
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
if (getApplicationContext().isSingleton(handlerName)) {
resolvedHandler = getApplicationContext().getBean(handlerName);
}
}
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map handler [" + handler + "] to URL path [" + urlPath +
"]: There is already handler [" + resolvedHandler + "] mapped.");
}
}
else {
if (urlPath.equals("/")) {
if (logger.isDebugEnabled()) {
logger.debug("Root mapping to handler [" + resolvedHandler + "]");
}
setRootHandler(resolvedHandler);
}
else if (urlPath.equals("/*")) {
if (logger.isDebugEnabled()) {
logger.debug("Default mapping to handler [" + resolvedHandler + "]");
}
setDefaultHandler(resolvedHandler);
}
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isDebugEnabled()) {
logger.debug("Mapped URL path [" + urlPath + "] onto handler [" + resolvedHandler + "]");
}
}
}
}
/**
* Return the registered handlers as an unmodifiable Map, with the registered path
* as key and the handler object (or handler bean name in case of a lazy-init handler)
* as value.
* @see #getDefaultHandler()
*/
public final Map getHandlerMap() {
return Collections.unmodifiableMap(this.handlerMap);
}
/**
* Special interceptor for exposing the
* {@link AbstractUrlHandlerMapping#PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE} attribute.
* @link AbstractUrlHandlerMapping#exposePathWithinMapping
*/
private class PathExposingHandlerInterceptor extends HandlerInterceptorAdapter {
private final String pathWithinMapping;
public PathExposingHandlerInterceptor(String pathWithinMapping) {
this.pathWithinMapping = pathWithinMapping;
}
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
exposePathWithinMapping(this.pathWithinMapping, request);
return true;
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,446 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.WebUtils;
/**
* {@link org.springframework.web.servlet.HandlerExceptionResolver} implementation
* that allows for mapping exception class names to view names, either for a
* set of given handlers or for all handlers in the DispatcherServlet.
*
* <p>Error views are analogous to error page JSPs, but can be used with any
* kind of exception including any checked one, with fine-granular mappings for
* specific handlers.
*
* @author Juergen Hoeller
* @since 22.11.2003
* @see org.springframework.web.servlet.DispatcherServlet
*/
public class SimpleMappingExceptionResolver implements HandlerExceptionResolver, Ordered {
/**
* The default name of the exception attribute: "exception".
*/
public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private int order = Integer.MAX_VALUE; // default: same as non-Ordered
private Set mappedHandlers;
private Class[] mappedHandlerClasses;
private Log warnLogger;
private Properties exceptionMappings;
private String defaultErrorView;
private Integer defaultStatusCode;
private String exceptionAttribute = DEFAULT_EXCEPTION_ATTRIBUTE;
public void setOrder(int order) {
this.order = order;
}
public int getOrder() {
return this.order;
}
/**
* Specify the set of handlers that this exception resolver should apply to.
* The exception mappings and the default error view will only apply
* to the specified handlers.
* <p>If no handlers and handler classes are set, the exception mappings
* and the default error view will apply to all handlers. This means that
* a specified default error view will be used as fallback for all exceptions;
* any further HandlerExceptionResolvers in the chain will be ignored in
* this case.
*/
public void setMappedHandlers(Set mappedHandlers) {
this.mappedHandlers = mappedHandlers;
}
/**
* Specify the set of classes that this exception resolver should apply to.
* The exception mappings and the default error view will only apply
* to handlers of the specified type; the specified types may be interfaces
* and superclasses of handlers as well.
* <p>If no handlers and handler classes are set, the exception mappings
* and the default error view will apply to all handlers. This means that
* a specified default error view will be used as fallback for all exceptions;
* any further HandlerExceptionResolvers in the chain will be ignored in
* this case.
*/
public void setMappedHandlerClasses(Class[] mappedHandlerClasses) {
this.mappedHandlerClasses = mappedHandlerClasses;
}
/**
* Set the log category for warn logging. The name will be passed to the
* underlying logger implementation through Commons Logging, getting
* interpreted as log category according to the logger's configuration.
* <p>Default is no warn logging. Specify this setting to activate
* warn logging into a specific category. Alternatively, override
* the {@link #logException} method for custom logging.
* @see org.apache.commons.logging.LogFactory#getLog(String)
* @see org.apache.log4j.Logger#getLogger(String)
* @see java.util.logging.Logger#getLogger(String)
*/
public void setWarnLogCategory(String loggerName) {
this.warnLogger = LogFactory.getLog(loggerName);
}
/**
* Set the mappings between exception class names and error view names.
* The exception class name can be a substring, with no wildcard support
* at present. A value of "ServletException" would match
* <code>javax.servlet.ServletException</code> and subclasses, for example.
* <p><b>NB:</b> Consider carefully how specific the pattern is, and whether
* to include package information (which isn't mandatory). For example,
* "Exception" will match nearly anything, and will probably hide other rules.
* "java.lang.Exception" would be correct if "Exception" was meant to define
* a rule for all checked exceptions. With more unusual exception names such
* as "BaseBusinessException" there's no need to use a FQN.
* <p>Follows the same matching algorithm as RuleBasedTransactionAttribute
* and RollbackRuleAttribute.
* @param mappings exception patterns (can also be fully qualified class names)
* as keys, and error view names as values
* @see org.springframework.transaction.interceptor.RuleBasedTransactionAttribute
* @see org.springframework.transaction.interceptor.RollbackRuleAttribute
*/
public void setExceptionMappings(Properties mappings) {
this.exceptionMappings = mappings;
}
/**
* Set the name of the default error view.
* This view will be returned if no specific mapping was found.
* <p>Default is none.
*/
public void setDefaultErrorView(String defaultErrorView) {
this.defaultErrorView = defaultErrorView;
}
/**
* Set the default HTTP status code that this exception resolver will apply
* if it resolves an error view.
* <p>Note that this error code will only get applied in case of a top-level
* request. It will not be set for an include request, since the HTTP status
* cannot be modified from within an include.
* <p>If not specified, no status code will be applied, either leaving this to
* the controller or view, or keeping the servlet engine's default of 200 (OK).
* @param defaultStatusCode HTTP status code value, for example
* 500 (SC_INTERNAL_SERVER_ERROR) or 404 (SC_NOT_FOUND)
* @see javax.servlet.http.HttpServletResponse#SC_INTERNAL_SERVER_ERROR
* @see javax.servlet.http.HttpServletResponse#SC_NOT_FOUND
*/
public void setDefaultStatusCode(int defaultStatusCode) {
this.defaultStatusCode = new Integer(defaultStatusCode);
}
/**
* Set the name of the model attribute as which the exception should
* be exposed. Default is "exception".
* <p>This can be either set to a different attribute name or to
* <code>null</code> for not exposing an exception attribute at all.
* @see #DEFAULT_EXCEPTION_ATTRIBUTE
*/
public void setExceptionAttribute(String exceptionAttribute) {
this.exceptionAttribute = exceptionAttribute;
}
/**
* Checks whether this resolver is supposed to apply (i.e. the handler
* matches in case of "mappedHandlers" having been specified), then
* delegates to the {@link #doResolveException} template method.
*/
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
return doResolveException(request, response, handler, ex);
}
else {
return null;
}
}
/**
* Check whether this resolver is supposed to apply to the given handler.
* <p>The default implementation checks against the specified mapped handlers
* and handler classes, if any.
* @param request current HTTP request
* @param handler the executed handler, or <code>null</code> if none chosen at the
* time of the exception (for example, if multipart resolution failed)
* @return whether this resolved should proceed with resolving the exception
* for the given request and handler
* @see #setMappedHandlers
* @see #setMappedHandlerClasses
*/
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
if (handler != null) {
if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
return true;
}
if (this.mappedHandlerClasses != null) {
for (int i = 0; i < this.mappedHandlerClasses.length; i++) {
if (this.mappedHandlerClasses[i].isInstance(handler)) {
return true;
}
}
}
}
// Else only apply if there are no explicit handler mappings.
return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
}
/**
* Actually resolve the given exception that got thrown during on handler execution,
* returning a ModelAndView that represents a specific error page if appropriate.
* <p>May be overridden in subclasses, in order to apply specific exception checks.
* Note that this template method will be invoked <i>after</i> checking whether
* this resolved applies ("mappedHandlers" etc), so an implementation may simply
* proceed with its actual exception handling.
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or <code>null</code> if none chosen at the
* time of the exception (for example, if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to, or <code>null</code> for default processing
*/
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// Log exception, both at debug log level and at warn level, if desired.
if (logger.isDebugEnabled()) {
logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
}
logException(ex, request);
// Expose ModelAndView for chosen error view.
String viewName = determineViewName(ex, request);
if (viewName != null) {
// Apply HTTP status code for error views, if specified.
// Only apply it if we're processing a top-level request.
Integer statusCode = determineStatusCode(request, viewName);
if (statusCode != null) {
applyStatusCodeIfPossible(request, response, statusCode.intValue());
}
return getModelAndView(viewName, ex, request);
}
else {
return null;
}
}
/**
* Log the given exception at warn level, provided that warn logging has been
* activated through the {@link #setWarnLogCategory "warnLogCategory"} property.
* <p>Calls {@link #buildLogMessage} in order to determine the concrete message
* to log. Always passes the full exception to the logger.
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @see #setWarnLogCategory
* @see #buildLogMessage
* @see org.apache.commons.logging.Log#warn(Object, Throwable)
*/
protected void logException(Exception ex, HttpServletRequest request) {
if (this.warnLogger != null && this.warnLogger.isWarnEnabled()) {
this.warnLogger.warn(buildLogMessage(ex, request), ex);
}
}
/**
* Build a log message for the given exception, occured during processing
* the given request.
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @return the log message to use
*/
protected String buildLogMessage(Exception ex, HttpServletRequest request) {
return "Handler execution resulted in exception";
}
/**
* Determine the view name for the given exception, searching the
* {@link #setExceptionMappings "exceptionMappings"}, using the
* {@link #setDefaultErrorView "defaultErrorView"} as fallback.
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @return the resolved view name, or <code>null</code> if none found
*/
protected String determineViewName(Exception ex, HttpServletRequest request) {
String viewName = null;
// Check for specific exception mappings.
if (this.exceptionMappings != null) {
viewName = findMatchingViewName(this.exceptionMappings, ex);
}
// Return default error view else, if defined.
if (viewName == null && this.defaultErrorView != null) {
if (logger.isDebugEnabled()) {
logger.debug("Resolving to default view '" + this.defaultErrorView +
"' for exception of type [" + ex.getClass().getName() + "]");
}
viewName = this.defaultErrorView;
}
return viewName;
}
/**
* Find a matching view name in the given exception mappings.
* @param exceptionMappings mappings between exception class names and error view names
* @param ex the exception that got thrown during handler execution
* @return the view name, or <code>null</code> if none found
* @see #setExceptionMappings
*/
protected String findMatchingViewName(Properties exceptionMappings, Exception ex) {
String viewName = null;
String dominantMapping = null;
int deepest = Integer.MAX_VALUE;
for (Enumeration names = exceptionMappings.propertyNames(); names.hasMoreElements();) {
String exceptionMapping = (String) names.nextElement();
int depth = getDepth(exceptionMapping, ex);
if (depth >= 0 && depth < deepest) {
deepest = depth;
dominantMapping = exceptionMapping;
viewName = exceptionMappings.getProperty(exceptionMapping);
}
}
if (viewName != null && logger.isDebugEnabled()) {
logger.debug("Resolving to view '" + viewName + "' for exception of type [" + ex.getClass().getName() +
"], based on exception mapping [" + dominantMapping + "]");
}
return viewName;
}
/**
* Return the depth to the superclass matching.
* <p>0 means ex matches exactly. Returns -1 if there's no match.
* Otherwise, returns depth. Lowest depth wins.
* <p>Follows the same algorithm as
* {@link org.springframework.transaction.interceptor.RollbackRuleAttribute}.
*/
protected int getDepth(String exceptionMapping, Exception ex) {
return getDepth(exceptionMapping, ex.getClass(), 0);
}
private int getDepth(String exceptionMapping, Class exceptionClass, int depth) {
if (exceptionClass.getName().indexOf(exceptionMapping) != -1) {
// Found it!
return depth;
}
// If we've gone as far as we can go and haven't found it...
if (exceptionClass.equals(Throwable.class)) {
return -1;
}
return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
}
/**
* Determine the HTTP status code to apply for the given error view.
* <p>The default implementation always returns the specified
* {@link #setDefaultStatusCode "defaultStatusCode"}, as a common
* status code for all error views. Override this in a custom subclass
* to determine a specific status code for the given view.
* @param request current HTTP request
* @param viewName the name of the error view
* @return the HTTP status code to use, or <code>null</code> for the
* servlet container's default (200 in case of a standard error view)
* @see #setDefaultStatusCode
* @see #applyStatusCodeIfPossible
*/
protected Integer determineStatusCode(HttpServletRequest request, String viewName) {
return this.defaultStatusCode;
}
/**
* Apply the specified HTTP status code to the given response, if possible
* (that is, if not executing within an include request).
* @param request current HTTP request
* @param response current HTTP response
* @param statusCode the status code to apply
* @see #determineStatusCode
* @see #setDefaultStatusCode
* @see javax.servlet.http.HttpServletResponse#setStatus
*/
protected void applyStatusCodeIfPossible(HttpServletRequest request, HttpServletResponse response, int statusCode) {
if (!WebUtils.isIncludeRequest(request)) {
if (logger.isDebugEnabled()) {
logger.debug("Applying HTTP status code " + statusCode);
}
response.setStatus(statusCode);
request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, new Integer(statusCode));
}
}
/**
* Return a ModelAndView for the given request, view name and exception.
* <p>The default implementation delegates to {@link #getModelAndView(String, Exception)}.
* @param viewName the name of the error view
* @param ex the exception that got thrown during handler execution
* @param request current HTTP request (useful for obtaining metadata)
* @return the ModelAndView instance
*/
protected ModelAndView getModelAndView(String viewName, Exception ex, HttpServletRequest request) {
return getModelAndView(viewName, ex);
}
/**
* Return a ModelAndView for the given view name and exception.
* <p>The default implementation adds the specified exception attribute.
* Can be overridden in subclasses.
* @param viewName the name of the error view
* @param ex the exception that got thrown during handler execution
* @return the ModelAndView instance
* @see #setExceptionAttribute
*/
protected ModelAndView getModelAndView(String viewName, Exception ex) {
ModelAndView mv = new ModelAndView(viewName);
if (this.exceptionAttribute != null) {
if (logger.isDebugEnabled()) {
logger.debug("Exposing Exception as model attribute '" + this.exceptionAttribute + "'");
}
mv.addObject(this.exceptionAttribute, ex);
}
return mv;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,201 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler.metadata;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Constants;
import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
/**
* Abstract implementation of the HandlerMapping interface that recognizes
* metadata attributes of type PathMap on application Controllers and automatically
* wires them into the current servlet's WebApplicationContext.
*
* <p>The path must be mapped to the relevant Spring DispatcherServlet in /WEB-INF/web.xml.
* It's possible to have multiple PathMap attributes on the one controller class.
*
* <p>Controllers instantiated by this class may have dependencies on middle tier
* objects, expressed via JavaBean properties or constructor arguments. These will
* be resolved automatically.
*
* <p>You will normally use this HandlerMapping with at most one DispatcherServlet in your
* web application. Otherwise you'll end with one instance of the mapped controller for
* each DispatcherServlet's context. You <i>might</i> want this -- for example, if
* one's using a .pdf mapping and a PDF view, and another a JSP view, or if
* using different middle tier objects, but should understand the implications. All
* Controllers with attributes will be picked up by each DispatcherServlet's context.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @deprecated as of Spring 2.5, in favor of annotation-based request mapping.
* To be removed in Spring 3.0.
*/
public abstract class AbstractPathMapHandlerMapping extends AbstractUrlHandlerMapping {
/** Constants instance for AutowireCapableBeanFactory */
private static final Constants constants = new Constants(AutowireCapableBeanFactory.class);
private int autowireMode = AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT;
private boolean dependencyCheck = true;
/**
* Set the autowire mode for handlers, by the name of the corresponding constant
* in the AutowireCapableBeanFactory interface, e.g. "AUTOWIRE_BY_NAME".
* @param constantName name of the constant
* @throws java.lang.IllegalArgumentException if an invalid constant was specified
* @see #setAutowireMode
* @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_NAME
* @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_TYPE
* @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_CONSTRUCTOR
* @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_AUTODETECT
*/
public void setAutowireModeName(String constantName) throws IllegalArgumentException {
setAutowireMode(constants.asNumber(constantName).intValue());
}
/**
* Set the autowire mode for handlers. This determines whether any automagical
* detection and setting of bean references will happen.
* <p>Default is AUTOWIRE_AUTODETECT, which means either constructor autowiring or
* autowiring by type (depending on the constructors available in the class).
* @param autowireMode the autowire mode to set.
* Must be one of the constants defined in the AutowireCapableBeanFactory interface.
* @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_NAME
* @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_TYPE
* @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_CONSTRUCTOR
* @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_AUTODETECT
*/
public void setAutowireMode(int autowireMode) {
this.autowireMode = autowireMode;
}
/**
* Set whether to perform a dependency check for objects on autowired handlers.
* Not applicable to autowiring a constructor, thus ignored there.
* <p>Default is "true".
*/
public void setDependencyCheck(boolean dependencyCheck) {
this.dependencyCheck = dependencyCheck;
}
/**
* Calls the <code>detectAndCreateHandlers</code> method in addition
* to the superclass's initialization.
* @see #detectAndCreateHandlers
*/
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
if (!(getApplicationContext() instanceof ConfigurableApplicationContext)) {
throw new IllegalStateException(
"[" + getClass().getName() + "] needs to run in a ConfigurableApplicationContext");
}
ConfigurableListableBeanFactory beanFactory =
((ConfigurableApplicationContext) getApplicationContext()).getBeanFactory();
detectAndCreateHandlers(beanFactory);
}
/**
* Look for all classes with a PathMap class attribute, instantiate them in
* the owning ApplicationContext, and register them as MVC handlers usable
* by the current DispatcherServlet.
* @param beanFactory the ConfigurableListableBeanFactory to register the
* created handler instances with
* @throws BeansException if handler detection or creation failed
* @see PathMap
* @see #getClassesWithPathMapAttributes()
* @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory#createBean
* @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory#registerSingleton
*/
protected void detectAndCreateHandlers(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
Class[] handlerClasses = getClassesWithPathMapAttributes();
if (logger.isDebugEnabled()) {
logger.debug("Found " + handlerClasses.length + " attribute-targeted handlers");
}
// for each Class returned by the Commons Attribute indexer
for (int i = 0; i < handlerClasses.length; i++) {
Class handlerClass = handlerClasses[i];
// Autowire the given handler class via AutowireCapableBeanFactory.
// Either autowires a constructor or by type, depending on the
// constructors available in the given class.
Object handler = beanFactory.createBean(handlerClass, this.autowireMode, this.dependencyCheck);
// We now have an "autowired" handler, that may reference beans in the
// application context. We now add the new handler to the factory.
// This isn't necessary for the handler to work, but is useful if we want
// to enumerate controllers in the factory etc.
beanFactory.registerSingleton(handlerClass.getName(), handler);
// There may be multiple paths mapped to this handler.
PathMap[] pathMaps = getPathMapAttributes(handlerClass);
registerHandler(pathMaps, handler);
}
}
catch (BeansException ex) {
throw ex;
}
catch (Exception ex) {
throw new BeanInitializationException("Could not retrieve PathMap attributes", ex);
}
}
/**
* Register the given handler for the URL paths indicated by the given PathMaps.
* @param pathMaps the PathMap attributes for the handler class
* @param handler the handler instance
* @throws BeansException if the handler couldn't be registered
* @throws IllegalStateException if there is a conflicting handler registered
*/
protected void registerHandler(PathMap[] pathMaps, Object handler) throws BeansException, IllegalStateException {
for (int j = 0; j < pathMaps.length; j++) {
PathMap pathMap = pathMaps[j];
String path = pathMap.getUrl();
if (!path.startsWith("/")) {
path = "/" + path;
}
registerHandler(path, handler);
}
}
/**
* Use an attribute index to get a Collection of Class objects
* with the required PathMap attribute.
* @return a array of Class objects
*/
protected abstract Class[] getClassesWithPathMapAttributes() throws Exception;
/**
* Use Attributes API to find PathMap attributes for the given handler class.
* We know there's at least one, as the getClassNamesWithPathMapAttributes
* method return this class name.
* @param handlerClass the handler class to look for
* @return an array of PathMap objects
*/
protected abstract PathMap[] getPathMapAttributes(Class handlerClass) throws Exception;
}

View File

@ -0,0 +1,81 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler.metadata;
import java.util.Collection;
import org.apache.commons.attributes.AttributeIndex;
import org.apache.commons.attributes.Attributes;
/**
* Subclass of AbstractPathMapHandlerMapping that recognizes Commons Attributes
* metadata attributes of type PathMap on application Controllers and automatically
* wires them into the current servlet's WebApplicationContext.
*
* <p>
* Controllers must have class attributes of the form:
* <code>
* &64;org.springframework.web.servlet.handler.commonsattributes.PathMap("/path.cgi")
* </code>
*
* <p>The path must be mapped to the relevant Spring DispatcherServlet in /WEB-INF/web.xml.
* It's possible to have multiple PathMap attributes on the one controller class.
*
* <p>To use this feature, you must compile application classes with Commons Attributes,
* and run the Commons Attributes indexer tool on your application classes, which must
* be in a Jar rather than in WEB-INF/classes.
*
* <p>Controllers instantiated by this class may have dependencies on middle tier
* objects, expressed via JavaBean properties or constructor arguments. These will
* be resolved automatically.
*
* <p>You will normally use this HandlerMapping with at most one DispatcherServlet in
* your web application. Otherwise you'll end with one instance of the mapped controller
* for each DispatcherServlet's context. You <i>might</i> want this--for example, if
* one's using a .pdf mapping and a PDF view, and another a JSP view, or if using
* different middle tier objects, but should understand the implications. All
* Controllers with attributes will be picked up by each DispatcherServlet's context.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @deprecated as of Spring 2.5, in favor of annotation-based request mapping.
* To be removed in Spring 3.0.
*/
public class CommonsPathMapHandlerMapping extends AbstractPathMapHandlerMapping {
/**
* Use Commons Attributes AttributeIndex to get a Collection of Class
* objects with the required PathMap attribute. Protected so that it can
* be overridden during testing.
*/
protected Class[] getClassesWithPathMapAttributes() throws Exception {
AttributeIndex ai = new AttributeIndex(getClass().getClassLoader());
Collection classes = ai.getClasses(PathMap.class);
return (Class[]) classes.toArray(new Class[classes.size()]);
}
/**
* Use Commons Attributes to find PathMap attributes for the given class.
* We know there's at least one, as the getClassNamesWithPathMapAttributes
* method return this class name.
*/
protected PathMap[] getPathMapAttributes(Class handlerClass) {
Collection atts = Attributes.getAttributes(handlerClass, PathMap.class);
return (PathMap[]) atts.toArray(new PathMap[atts.size()]);
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.handler.metadata;
/**
* Attribute to be used on Controller classes to allow for automatic URL mapping
* without web controllers being defined as beans in an XML bean definition file.
*
* <p>The path map should be the path in the current application, such as /foo.cgi.
* If there is no leading "/", one will be prepended.
*
* <p>Application code must use the Commons Attributes indexer tool to use this option.
*
* @author Rod Johnson
* @deprecated as of Spring 2.5, in favor of annotation-based request mapping.
* To be removed in Spring 3.0.
* @@org.apache.commons.attributes.Indexed()
*/
public class PathMap {
/*
* NB: The Indexed attribute on this class is required. Thus the Spring jar
* must be built including a Commons Attributes attribute compilation step
* for this class.
*/
private final String url;
/**
* Create a new PathMap attribute for the given URL.
*/
public PathMap(String url) {
this.url = url;
}
/**
* Return the URL that this attribute indicates.
*/
public String getUrl() {
return this.url;
}
}

View File

@ -0,0 +1,37 @@
<html>
<body>
This package enables automatic web controller targeting.
The only implementation at present is CommonsPathMapHandlerMapping, based on Commons
Attributes source-level attributes, but metadata implementation is pluggable.
<br>
In this model, you don't need to map URLs onto controllers in your Spring
XML web application context bean definition files. You merely need to
set one or more PathMap attributes on each of your Controller classes, and
new objects of these types will automatically be added to the relevant
web application context.
<br>
Dependencies--whether expressed by via constructor arguments or JavaBean
properties, will be resolved if possible using the middle tier definitions in
the WebApplicationContext.
<p>
To use this feature, using the Commons Attributes implementation, perform the following steps:
<ol>
<li>Invoke the Commons Attributes compiler on your application classes. The
classes must be compiled into a Jar rather than the WEB-INF/classes directory.
<li>Run the Commons Attributes attribute indexer tool on the Jar containing
your controllers.
<li>Use Commons Attributes syntax to define a PathMap attribute for each
Controller you want mapped.
<li>Define a bean of type UrlHandlerMapping in your <code>servletName-servlet.xml</code>
bean definition file in your web application. No parameters are required.
</ol>
<p>
You can also use other HandlerMappings, such as BeanNameHandlerMapping, in the
same servlet XML file.
</body>
</html>

View File

@ -0,0 +1,8 @@
<html>
<body>
Provides standard HandlerMapping implementations,
including abstract base classes for custom implementations.
</body>
</html>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,9 @@
<html>
<body>
Locale support classes for Spring's web MVC framework.
Provides standard LocaleResolver implementations,
and a HandlerInterceptor for locale changes.
</body>
</html>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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