SPR-8215 Move HandlerMethod code into trunk
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.bind.support;
|
||||
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* A {@link WebDataBinderFactory} that creates {@link WebDataBinder} and initializes them
|
||||
* with a {@link WebBindingInitializer}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
*/
|
||||
public class DefaultDataBinderFactory implements WebDataBinderFactory {
|
||||
|
||||
private final WebBindingInitializer bindingInitializer;
|
||||
|
||||
/**
|
||||
* Create {@link DefaultDataBinderFactory} instance.
|
||||
* @param bindingInitializer a {@link WebBindingInitializer} to initialize new data binder instances with
|
||||
*/
|
||||
public DefaultDataBinderFactory(WebBindingInitializer bindingInitializer) {
|
||||
this.bindingInitializer = bindingInitializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link WebDataBinder} for the given target object and initialize it through
|
||||
* a {@link WebBindingInitializer}.
|
||||
*/
|
||||
public WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception {
|
||||
WebDataBinder dataBinder = createBinderInstance(target, objectName);
|
||||
|
||||
if (bindingInitializer != null) {
|
||||
this.bindingInitializer.initBinder(dataBinder, webRequest);
|
||||
}
|
||||
|
||||
return dataBinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link WebDataBinder} instance.
|
||||
* @param target the object to create a data binder for, or {@code null} if creating a binder for a simple type
|
||||
* @param objectName the name of the target object
|
||||
*/
|
||||
protected WebDataBinder createBinderInstance(Object target, String objectName) {
|
||||
return new WebRequestDataBinder(target, objectName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.bind.support;
|
||||
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* A factory for creating a {@link WebDataBinder} instance for a named target object.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
*/
|
||||
public interface WebDataBinderFactory {
|
||||
|
||||
/**
|
||||
* Create a {@link WebDataBinder} for the given object.
|
||||
* @param webRequest the current request
|
||||
* @param target the object to create a data binder for, or {@code null} if creating a binder for a simple type
|
||||
* @param objectName the name of the target object
|
||||
* @return the created {@link WebDataBinder} instance, never null
|
||||
* @throws Exception raised if the creation and initialization of the data binder fails
|
||||
*/
|
||||
WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Encapsulates information about a bean method consisting of a {@linkplain #getMethod() method} and a
|
||||
* {@linkplain #getBean() bean}. Provides convenient access to method parameters, the method return value,
|
||||
* method annotations.
|
||||
*
|
||||
* <p>The class may be created with a bean instance or with a bean name (e.g. lazy bean, prototype bean).
|
||||
* Use {@link #createWithResolvedBean()} to obtain an {@link HandlerMethod} instance with a bean instance
|
||||
* initialized through the bean factory.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
*/
|
||||
public class HandlerMethod {
|
||||
|
||||
/** Logger that is available to subclasses */
|
||||
protected final Log logger = LogFactory.getLog(this.getClass());
|
||||
|
||||
private final Object bean;
|
||||
|
||||
private final Method method;
|
||||
|
||||
private final BeanFactory beanFactory;
|
||||
|
||||
private MethodParameter[] parameters;
|
||||
|
||||
private final Method bridgedMethod;
|
||||
|
||||
/**
|
||||
* Constructs a new handler method with the given bean instance and method.
|
||||
* @param bean the object bean
|
||||
* @param method the method
|
||||
*/
|
||||
public HandlerMethod(Object bean, Method method) {
|
||||
Assert.notNull(bean, "bean must not be null");
|
||||
Assert.notNull(method, "method must not be null");
|
||||
this.bean = bean;
|
||||
this.beanFactory = null;
|
||||
this.method = method;
|
||||
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new handler method with the given bean instance, method name and parameters.
|
||||
* @param bean the object bean
|
||||
* @param methodName the method name
|
||||
* @param parameterTypes the method parameter types
|
||||
* @throws NoSuchMethodException when the method cannot be found
|
||||
*/
|
||||
public HandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
|
||||
Assert.notNull(bean, "bean must not be null");
|
||||
Assert.notNull(methodName, "method must not be null");
|
||||
this.bean = bean;
|
||||
this.beanFactory = null;
|
||||
this.method = bean.getClass().getMethod(methodName, parameterTypes);
|
||||
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new handler method with the given bean name and method. The bean name will be lazily
|
||||
* initialized when {@link #createWithResolvedBean()} is called.
|
||||
* @param beanName the bean name
|
||||
* @param beanFactory the bean factory to use for bean initialization
|
||||
* @param method the method for the bean
|
||||
*/
|
||||
public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
|
||||
Assert.hasText(beanName, "'beanName' must not be null");
|
||||
Assert.notNull(beanFactory, "'beanFactory' must not be null");
|
||||
Assert.notNull(method, "'method' must not be null");
|
||||
Assert.isTrue(beanFactory.containsBean(beanName),
|
||||
"Bean factory [" + beanFactory + "] does not contain bean " + "with name [" + beanName + "]");
|
||||
this.bean = beanName;
|
||||
this.beanFactory = beanFactory;
|
||||
this.method = method;
|
||||
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bean for this handler method.
|
||||
*/
|
||||
public Object getBean() {
|
||||
return this.bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method for this handler method.
|
||||
*/
|
||||
public Method getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the handler for this handler method.
|
||||
* Note that if the bean type is a CGLIB-generated class, the original, user-defined class is returned.
|
||||
*/
|
||||
public Class<?> getBeanType() {
|
||||
if (bean instanceof String) {
|
||||
String beanName = (String) bean;
|
||||
return beanFactory.getType(beanName);
|
||||
}
|
||||
else {
|
||||
return ClassUtils.getUserClass(bean.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the bean method is a bridge method, this method returns the bridged (user-defined) method.
|
||||
* Otherwise it returns the same method as {@link #getMethod()}.
|
||||
*/
|
||||
protected Method getBridgedMethod() {
|
||||
return bridgedMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method parameters for this handler method.
|
||||
*/
|
||||
public MethodParameter[] getMethodParameters() {
|
||||
if (this.parameters == null) {
|
||||
int parameterCount = this.bridgedMethod.getParameterTypes().length;
|
||||
MethodParameter[] p = new MethodParameter[parameterCount];
|
||||
for (int i = 0; i < parameterCount; i++) {
|
||||
p[i] = new HandlerMethodParameter(this.bridgedMethod, i);
|
||||
}
|
||||
this.parameters = p;
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method return type, as {@code MethodParameter}.
|
||||
*/
|
||||
public MethodParameter getReturnType() {
|
||||
return new HandlerMethodParameter(this.bridgedMethod, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the method return type is void, {@code false} otherwise.
|
||||
*/
|
||||
public boolean isVoid() {
|
||||
return Void.TYPE.equals(getReturnType().getParameterType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single annotation on the underlying method traversing its super methods if no
|
||||
* annotation can be found on the given method itself.
|
||||
* @param annotationType the type of annotation to introspect the method for.
|
||||
* @return the annotation, or {@code null} if none found
|
||||
*/
|
||||
public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
|
||||
return AnnotationUtils.findAnnotation(this.method, annotationType);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the provided instance contains a bean name rather than an object instance, the bean name is resolved
|
||||
* before a {@link HandlerMethod} is created and returned.
|
||||
*/
|
||||
public HandlerMethod createWithResolvedBean() {
|
||||
Object handler = this.bean;
|
||||
if (this.bean instanceof String) {
|
||||
String beanName = (String) this.bean;
|
||||
handler = this.beanFactory.getBean(beanName);
|
||||
}
|
||||
return new HandlerMethod(handler, method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o != null && o instanceof HandlerMethod) {
|
||||
HandlerMethod other = (HandlerMethod) o;
|
||||
return this.bean.equals(other.bean) && this.method.equals(other.method);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * this.bean.hashCode() + this.method.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return method.toGenericString();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link MethodParameter} that resolves method annotations even when the actual annotations
|
||||
* are on a bridge method rather than on the current method. Annotations on super types are
|
||||
* also returned via {@link AnnotationUtils#findAnnotation(Method, Class)}.
|
||||
*/
|
||||
private class HandlerMethodParameter extends MethodParameter {
|
||||
|
||||
public HandlerMethodParameter(Method method, int parameterIndex) {
|
||||
super(method, parameterIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@link HandlerMethod#getBeanType()} rather than the method's class, which could be
|
||||
* important for the proper discovery of generic types.
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getDeclaringClass() {
|
||||
return HandlerMethod.this.getBeanType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the method annotation via {@link HandlerMethod#getMethodAnnotation(Class)}, which will find
|
||||
* the annotation by traversing super-types and handling annotations on bridge methods correctly.
|
||||
*/
|
||||
@Override
|
||||
public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) {
|
||||
return HandlerMethod.this.getMethodAnnotation(annotationType);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodFilter;
|
||||
|
||||
/**
|
||||
* Defines the algorithm for searching handler methods exhaustively including interfaces and parent
|
||||
* classes while also dealing with parameterized methods and interface and class-based proxies.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
*/
|
||||
public abstract class HandlerMethodSelector {
|
||||
|
||||
/**
|
||||
* Selects handler methods for the given handler type. Callers of this method define handler methods
|
||||
* of interest through the {@link MethodFilter} parameter.
|
||||
*
|
||||
* @param handlerType the handler type to search handler methods on
|
||||
* @param handlerMethodFilter a {@link MethodFilter} to help recognize handler methods of interest
|
||||
*/
|
||||
public static Set<Method> selectMethods(final Class<?> handlerType, final MethodFilter handlerMethodFilter) {
|
||||
final Set<Method> handlerMethods = new LinkedHashSet<Method>();
|
||||
Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
|
||||
Class<?> specificHandlerType = null;
|
||||
if (!Proxy.isProxyClass(handlerType)) {
|
||||
handlerTypes.add(handlerType);
|
||||
specificHandlerType = handlerType;
|
||||
}
|
||||
handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces()));
|
||||
for (Class<?> currentHandlerType : handlerTypes) {
|
||||
final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
|
||||
ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
|
||||
public void doWith(Method method) {
|
||||
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
|
||||
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
|
||||
if (handlerMethodFilter.matches(specificMethod) &&
|
||||
(bridgedMethod == specificMethod || !handlerMethodFilter.matches(bridgedMethod))) {
|
||||
handlerMethods.add(specificMethod);
|
||||
}
|
||||
}
|
||||
}, ReflectionUtils.USER_DECLARED_METHODS);
|
||||
}
|
||||
return handlerMethods;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.core.ExceptionDepthComparator;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
|
||||
/**
|
||||
* Extracts and stores method-to-exception type mappings from a set of {@link ExceptionHandler}-annotated methods.
|
||||
* Subsequently {@link #getMethod(Exception)} can be used to matches an {@link Exception} to a method.
|
||||
*
|
||||
* <p>Method-to-exception type mappings are usually derived from a method's {@link ExceptionHandler} annotation value.
|
||||
* The method argument list may also be checked for {@link Throwable} types if that's empty. Exception types can be
|
||||
* mapped to one method only.
|
||||
*
|
||||
* <p>When multiple exception types match a given exception, the best matching exception type is selected by sorting
|
||||
* the list of matches with {@link ExceptionDepthComparator}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
*/
|
||||
public class ExceptionMethodMapping {
|
||||
|
||||
protected static final Method NO_METHOD_FOUND = ClassUtils.getMethodIfAvailable(System.class, "currentTimeMillis");
|
||||
|
||||
private final Map<Class<? extends Throwable>, Method> mappedExceptionTypes =
|
||||
new HashMap<Class<? extends Throwable>, Method>();
|
||||
|
||||
private final Map<Class<? extends Throwable>, Method> resolvedExceptionTypes =
|
||||
new ConcurrentHashMap<Class<? extends Throwable>, Method>();
|
||||
|
||||
/**
|
||||
* Creates an {@link ExceptionMethodMapping} instance from a set of {@link ExceptionHandler} methods.
|
||||
* <p>While any {@link ExceptionHandler} methods can be provided, it is expected that the exception types
|
||||
* handled by any one method do not overlap with the exception types handled by any other method.
|
||||
* If two methods map to the same exception type, an exception is raised.
|
||||
* @param methods the {@link ExceptionHandler}-annotated methods to add to the mappings
|
||||
*/
|
||||
public ExceptionMethodMapping(Set<Method> methods) {
|
||||
initExceptionMap(methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Examines the provided methods and populates mapped exception types.
|
||||
*/
|
||||
private void initExceptionMap(Set<Method> methods) {
|
||||
for (Method method : methods) {
|
||||
for (Class<? extends Throwable> exceptionType : getMappedExceptionTypes(method)) {
|
||||
Method prevMethod = mappedExceptionTypes.put(exceptionType, method);
|
||||
|
||||
if (prevMethod != null && !prevMethod.equals(method)) {
|
||||
throw new IllegalStateException(
|
||||
"Ambiguous exception handler mapped for [" + exceptionType + "]: {" +
|
||||
prevMethod + ", " + method + "}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the list of exception types mapped to the given method in one of the following ways:
|
||||
* <ol>
|
||||
* <li>The {@link ExceptionHandler} annotation value
|
||||
* <li>{@link Throwable} types that appear in the method parameter list
|
||||
* </ol>
|
||||
* @param method the method to derive mapped exception types for
|
||||
* @return the list of exception types the method is mapped to, or an empty list
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected List<Class<? extends Throwable>> getMappedExceptionTypes(Method method) {
|
||||
ExceptionHandler annotation = AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
|
||||
if (annotation.value().length != 0) {
|
||||
return Arrays.asList(annotation.value());
|
||||
}
|
||||
else {
|
||||
List<Class<? extends Throwable>> result = new ArrayList<Class<? extends Throwable>>();
|
||||
for (Class<?> paramType : method.getParameterTypes()) {
|
||||
if (Throwable.class.isAssignableFrom(paramType)) {
|
||||
result.add((Class<? extends Throwable>) paramType);
|
||||
}
|
||||
}
|
||||
Assert.notEmpty(result, "No exception types mapped to {" + method + "}");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ExceptionHandler} method that matches the type of the provided {@link Exception}.
|
||||
* In case of multiple matches, the best match is selected with {@link ExceptionDepthComparator}.
|
||||
* @param exception the exception to find a matching {@link ExceptionHandler} method for
|
||||
* @return the mapped method, or {@code null} if none
|
||||
*/
|
||||
public Method getMethod(Exception exception) {
|
||||
Class<? extends Exception> exceptionType = exception.getClass();
|
||||
Method method = resolvedExceptionTypes.get(exceptionType);
|
||||
if (method == null) {
|
||||
method = resolveExceptionType(exceptionType);
|
||||
resolvedExceptionTypes.put(exceptionType, method);
|
||||
}
|
||||
return (method != NO_METHOD_FOUND) ? method : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given exception type by iterating mapped exception types.
|
||||
* Uses {@link #getBestMatchingExceptionType(List, Class)} to select the best match.
|
||||
* @param exceptionType the exception type to resolve
|
||||
* @return the best matching method, or {@link ExceptionMethodMapping#NO_METHOD_FOUND}
|
||||
*/
|
||||
protected final Method resolveExceptionType(Class<? extends Exception> exceptionType) {
|
||||
List<Class<? extends Throwable>> matches = new ArrayList<Class<? extends Throwable>>();
|
||||
for(Class<? extends Throwable> mappedExceptionType : mappedExceptionTypes.keySet()) {
|
||||
if (mappedExceptionType.isAssignableFrom(exceptionType)) {
|
||||
matches.add(mappedExceptionType);
|
||||
}
|
||||
}
|
||||
if (matches.isEmpty()) {
|
||||
return NO_METHOD_FOUND;
|
||||
}
|
||||
else {
|
||||
return mappedExceptionTypes.get(getBestMatchingExceptionType(matches, exceptionType));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the best match from the given list of exception types.
|
||||
*/
|
||||
protected Class<? extends Throwable> getBestMatchingExceptionType(List<Class<? extends Throwable>> exceptionTypes,
|
||||
Class<? extends Exception> exceptionType) {
|
||||
Assert.isTrue(exceptionTypes.size() > 0, "No exception types to select from!");
|
||||
if (exceptionTypes.size() > 1) {
|
||||
Collections.sort(exceptionTypes, new ExceptionDepthComparator(exceptionType));
|
||||
}
|
||||
return exceptionTypes.get(0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.annotation;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.InitBinder;
|
||||
import org.springframework.web.bind.support.DefaultDataBinderFactory;
|
||||
import org.springframework.web.bind.support.WebBindingInitializer;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.InvocableHandlerMethod;
|
||||
|
||||
/**
|
||||
* A specialization of {@link DefaultDataBinderFactory} that further initializes {@link WebDataBinder} instances
|
||||
* through the invocation one or more {@link InitBinder} methods.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
*/
|
||||
public class InitBinderMethodDataBinderFactory extends DefaultDataBinderFactory {
|
||||
|
||||
private final List<InvocableHandlerMethod> initBinderMethods;
|
||||
|
||||
/**
|
||||
* Create an {@code InitBinderMethodDataBinderFactory} instance with the given {@link InitBinder} methods.
|
||||
* @param initBinderMethods {@link InitBinder} methods to use when initializing new data binder instances
|
||||
* @param bindingInitializer a {@link WebBindingInitializer} to initialize new data binder instances with
|
||||
*/
|
||||
public InitBinderMethodDataBinderFactory(List<InvocableHandlerMethod> initBinderMethods,
|
||||
WebBindingInitializer bindingInitializer) {
|
||||
super(bindingInitializer);
|
||||
this.initBinderMethods = initBinderMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link WebDataBinder} for the given target object and initialize it through the invocation
|
||||
* of {@link InitBinder} methods. Only {@link InitBinder} annotations that don't specify attributes names
|
||||
* and {@link InitBinder} annotations that contain the target object name are invoked.
|
||||
* @see InitBinder#value()
|
||||
*/
|
||||
@Override
|
||||
public WebDataBinder createBinder(NativeWebRequest request, Object target, String objectName) throws Exception {
|
||||
WebDataBinder dataBinder = super.createBinder(request, target, objectName);
|
||||
|
||||
for (InvocableHandlerMethod binderMethod : this.initBinderMethods) {
|
||||
InitBinder annot = binderMethod.getMethodAnnotation(InitBinder.class);
|
||||
Set<String> attributeNames = new HashSet<String>(Arrays.asList(annot.value()));
|
||||
|
||||
if (attributeNames.size() == 0 || attributeNames.contains(objectName)) {
|
||||
Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);
|
||||
|
||||
if (returnValue != null) {
|
||||
throw new IllegalStateException("InitBinder methods must not have a return value: " + binderMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dataBinder;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.Conventions;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.ui.ExtendedModelMap;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.support.BindingAwareModelMap;
|
||||
import org.springframework.web.HttpSessionRequiredException;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.support.SessionStatus;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.method.support.InvocableHandlerMethod;
|
||||
|
||||
/**
|
||||
* Provides methods to create and update the "implicit" model for a given request.
|
||||
*
|
||||
* <p>{@link #createModel(NativeWebRequest, HandlerMethod)} prepares a model for use with
|
||||
* a {@link RequestMapping} method. The model is populated with handler session attributes as well
|
||||
* as request attributes obtained by invoking model attribute methods.
|
||||
*
|
||||
* <p>{@link #updateAttributes(NativeWebRequest, SessionStatus, ModelMap, ModelMap)} updates
|
||||
* the model used for the invocation of a {@link RequestMapping} method, adding handler session
|
||||
* attributes and {@link BindingResult} structures as necessary.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
*/
|
||||
public final class ModelFactory {
|
||||
|
||||
private final List<InvocableHandlerMethod> attributeMethods;
|
||||
|
||||
private final WebDataBinderFactory binderFactory;
|
||||
|
||||
private final SessionAttributesHandler sessionHandler;
|
||||
|
||||
/**
|
||||
* Create a ModelFactory instance with the provided {@link ModelAttribute} methods.
|
||||
* @param attributeMethods {@link ModelAttribute}-annotated methods to invoke when populating a model
|
||||
* @param binderFactory the binder factory to use to add {@link BindingResult} instances to the model
|
||||
* @param sessionHandler a session attributes handler to synch attributes in the model with the session
|
||||
*/
|
||||
public ModelFactory(List<InvocableHandlerMethod> attributeMethods,
|
||||
WebDataBinderFactory binderFactory,
|
||||
SessionAttributesHandler sessionHandler) {
|
||||
this.attributeMethods = attributeMethods;
|
||||
this.binderFactory = binderFactory;
|
||||
this.sessionHandler = sessionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a model for the current request obtaining attributes in the following order:
|
||||
* <ol>
|
||||
* <li>Retrieve previously accessed handler session attributes from the session
|
||||
* <li>Invoke model attribute methods
|
||||
* <li>Find request-handling method {@link ModelAttribute}-annotated arguments that are handler session attributes
|
||||
* </ol>
|
||||
* <p>As a general rule a model attribute is added only once following the above order.
|
||||
* @param request the current request
|
||||
* @param requestMethod the request handling method for which the model is needed
|
||||
* @return the created model
|
||||
* @throws Exception if an exception occurs while invoking model attribute methods
|
||||
*/
|
||||
public ModelMap createModel(NativeWebRequest request, HandlerMethod requestMethod) throws Exception {
|
||||
ExtendedModelMap model = new BindingAwareModelMap();
|
||||
|
||||
Map<String, ?> sessionAttributes = this.sessionHandler.retrieveHandlerSessionAttributes(request);
|
||||
model.addAllAttributes(sessionAttributes);
|
||||
|
||||
invokeAttributeMethods(request, model);
|
||||
|
||||
addSessionAttributesByName(request, requestMethod, model);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the model by invoking model attribute methods. If two methods provide the same attribute,
|
||||
* the attribute produced by the first method is used.
|
||||
*/
|
||||
private void invokeAttributeMethods(NativeWebRequest request, ExtendedModelMap model) throws Exception {
|
||||
for (InvocableHandlerMethod attrMethod : this.attributeMethods) {
|
||||
String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value();
|
||||
if (StringUtils.hasText(modelName) && model.containsAttribute(modelName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object returnValue = attrMethod.invokeForRequest(request, model);
|
||||
|
||||
if (!attrMethod.isVoid()){
|
||||
String valueName = getNameForReturnValue(returnValue, attrMethod.getReturnType());
|
||||
if (!model.containsAttribute(valueName)) {
|
||||
model.addAttribute(valueName, returnValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the model name for the given method return value using one of the following:
|
||||
* <ol>
|
||||
* <li>The method {@link ModelAttribute} annotation value
|
||||
* <li>The name of the return type
|
||||
* <li>The name of the return value type if the method return type is {@code Object}
|
||||
* </ol>
|
||||
* @param returnValue the value returned from a method invocation
|
||||
* @param returnType the return type of the method
|
||||
* @return the model name, never {@code null} nor empty
|
||||
*/
|
||||
public static String getNameForReturnValue(Object returnValue, MethodParameter returnType) {
|
||||
ModelAttribute annot = returnType.getMethodAnnotation(ModelAttribute.class);
|
||||
if (annot != null && StringUtils.hasText(annot.value())) {
|
||||
return annot.value();
|
||||
}
|
||||
else {
|
||||
Method method = returnType.getMethod();
|
||||
Class<?> resolvedType = GenericTypeResolver.resolveReturnType(method, returnType.getDeclaringClass());
|
||||
return Conventions.getVariableNameForReturnType(method, resolvedType, returnValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find request-handling method, {@link ModelAttribute}-annotated arguments that are handler session attributes
|
||||
* and add them to the model if not present.
|
||||
*/
|
||||
private void addSessionAttributesByName(NativeWebRequest request, HandlerMethod requestMethod, ModelMap model) {
|
||||
for (MethodParameter parameter : requestMethod.getMethodParameters()) {
|
||||
if (!parameter.hasParameterAnnotation(ModelAttribute.class)) {
|
||||
continue;
|
||||
}
|
||||
String attrName = getNameForParameter(parameter);
|
||||
if (!model.containsKey(attrName)) {
|
||||
if (sessionHandler.isHandlerSessionAttribute(attrName, parameter.getParameterType())) {
|
||||
Object attrValue = sessionHandler.retrieveAttribute(request, attrName);
|
||||
if (attrValue == null){
|
||||
new HttpSessionRequiredException("Session attribute '" + attrName + "' not found in session");
|
||||
}
|
||||
model.addAttribute(attrName, attrValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the model name for the given method parameter using one of the following:
|
||||
* <ol>
|
||||
* <li>The parameter {@link ModelAttribute} annotation value
|
||||
* <li>The name of the parameter type
|
||||
* </ol>
|
||||
* @return the method parameter model name, never {@code null} or an empty string
|
||||
*/
|
||||
public static String getNameForParameter(MethodParameter parameter) {
|
||||
ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class);
|
||||
String attrName = (annot != null) ? annot.value() : null;
|
||||
return StringUtils.hasText(attrName) ? attrName : Conventions.getVariableNameForParameter(parameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up handler session attributes when {@link SessionStatus#isComplete()} is {@code true}.
|
||||
* Promote model attributes to the session. Add {@link BindingResult} attributes where missing.
|
||||
* @param request the current request
|
||||
* @param sessionStatus indicates whether handler session attributes are to be cleaned
|
||||
* @param actualModel the model returned from the request method, or {@code null} when the response was handled
|
||||
* @param implicitModel the model for the request
|
||||
* @throws Exception if the process of creating {@link BindingResult} attributes causes a problem
|
||||
*/
|
||||
public void updateAttributes(NativeWebRequest request,
|
||||
SessionStatus sessionStatus,
|
||||
ModelMap actualModel,
|
||||
ModelMap implicitModel) throws Exception {
|
||||
if (sessionStatus.isComplete()){
|
||||
this.sessionHandler.cleanupHandlerSessionAttributes(request);
|
||||
}
|
||||
|
||||
if (actualModel != null) {
|
||||
this.sessionHandler.storeHandlerSessionAttributes(request, actualModel);
|
||||
updateBindingResult(request, actualModel);
|
||||
}
|
||||
else {
|
||||
this.sessionHandler.storeHandlerSessionAttributes(request, implicitModel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add {@link BindingResult} structures to the model for attributes that require it.
|
||||
*/
|
||||
private void updateBindingResult(NativeWebRequest request, ModelMap model) throws Exception {
|
||||
List<String> keyNames = new ArrayList<String>(model.keySet());
|
||||
for (String name : keyNames) {
|
||||
Object value = model.get(name);
|
||||
|
||||
if (isBindingCandidate(name, value)) {
|
||||
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name;
|
||||
|
||||
if (!model.containsAttribute(bindingResultKey)) {
|
||||
WebDataBinder dataBinder = binderFactory.createBinder(request, value, name);
|
||||
model.put(bindingResultKey, dataBinder.getBindingResult());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given attribute requires a {@link BindingResult} added to the model.
|
||||
*/
|
||||
private boolean isBindingCandidate(String attributeName, Object value) {
|
||||
if (attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Class<?> attrType = (value != null) ? value.getClass() : null;
|
||||
if (this.sessionHandler.isHandlerSessionAttribute(attributeName, attrType)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (value != null && !value.getClass().isArray() && !(value instanceof Collection) &&
|
||||
!(value instanceof Map) && !BeanUtils.isSimpleValueType(value.getClass()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.annotation;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.SessionAttributes;
|
||||
import org.springframework.web.bind.support.SessionAttributeStore;
|
||||
import org.springframework.web.bind.support.SessionStatus;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
|
||||
/**
|
||||
* Provides operations for managing handler-specific session attributes as defined by the
|
||||
* {@link SessionAttributes} type-level annotation performing all operations through an
|
||||
* instance of a {@link SessionAttributeStore}.
|
||||
*
|
||||
* <p>A typical scenario involves a handler adding attributes to the {@link Model} during
|
||||
* a request. At the end of the request, model attributes that match to session attribute
|
||||
* names defined through an {@link SessionAttributes} annotation are automatically
|
||||
* "promoted" to the session. Handler session attributes are then removed when
|
||||
* {@link SessionStatus#setComplete()} is called by a handler.
|
||||
*
|
||||
* <p>Therefore "session attributes" for this class means only attributes that have been
|
||||
* previously confirmed by calls to {@link #isHandlerSessionAttribute(String, Class)}.
|
||||
* Attribute names that have never been resolved that way will be filtered out from
|
||||
* operations of this class. That means initially the actual set of resolved session
|
||||
* attribute names is empty and it grows gradually as attributes are added to
|
||||
* the {@link Model} and then considered for being added to the session.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
*/
|
||||
public class SessionAttributesHandler {
|
||||
|
||||
private final Set<String> attributeNames = new HashSet<String>();
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private final Set<Class> attributeTypes = new HashSet<Class>();
|
||||
|
||||
private final Set<String> resolvedAttributeNames = Collections.synchronizedSet(new HashSet<String>(4));
|
||||
|
||||
private final SessionAttributeStore attributeStore;
|
||||
|
||||
/**
|
||||
* Creates a {@link SessionAttributesHandler} instance for the specified handlerType.
|
||||
* <p>Inspects the given handler type for the presence of a {@link SessionAttributes} annotation and
|
||||
* stores that information for use in subsequent calls to {@link #isHandlerSessionAttribute(String, Class)}.
|
||||
* If the handler type does not contain such an annotation,
|
||||
* {@link #isHandlerSessionAttribute(String, Class)} always returns {@code false} and all other operations
|
||||
* on handler session attributes have no effect on the backend session.
|
||||
* <p>Use {@link #hasSessionAttributes()} to check if the handler type has defined any session attribute names
|
||||
* of interest through a {@link SessionAttributes} annotation.
|
||||
* @param handlerType the handler type to inspect for a {@link SessionAttributes} annotation
|
||||
* @param attributeStore the {@link SessionAttributeStore} to delegate to for the actual backend session access
|
||||
*/
|
||||
public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore attributeStore) {
|
||||
Assert.notNull(attributeStore, "SessionAttributeStore may not be null.");
|
||||
this.attributeStore = attributeStore;
|
||||
|
||||
SessionAttributes annotation = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class);
|
||||
if (annotation != null) {
|
||||
this.attributeNames.addAll(Arrays.asList(annotation.value()));
|
||||
this.attributeTypes.addAll(Arrays.asList(annotation.types()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the handler type has specified any session attribute names of interest through a
|
||||
* {@link SessionAttributes} annotation.
|
||||
*/
|
||||
public boolean hasSessionAttributes() {
|
||||
return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether or not an attribute is a handler session attribute of interest as defined
|
||||
* in a {@link SessionAttributes} annotation. Attributes names successfully resolved through
|
||||
* this method are remembered and in other operations.
|
||||
* @param attributeName the attribute name to check, must not be null
|
||||
* @param attributeType the type for the attribute, not required but should be provided when
|
||||
* available as session attributes of interest can be matched by type
|
||||
*/
|
||||
public boolean isHandlerSessionAttribute(String attributeName, Class<?> attributeType) {
|
||||
Assert.notNull(attributeName, "Attribute name must not be null");
|
||||
if (this.attributeNames.contains(attributeName) || this.attributeTypes.contains(attributeType)) {
|
||||
this.resolvedAttributeNames.add(attributeName);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the specified attribute through the underlying {@link SessionAttributeStore}.
|
||||
* Although not required use of this method implies a prior call to
|
||||
* {@link #isHandlerSessionAttribute(String, Class)} has been made to see if the attribute
|
||||
* name is a handler-specific session attribute of interest.
|
||||
* @param request the request for the session operation
|
||||
* @param attributeName the name of the attribute
|
||||
* @return the attribute value or {@code null} if none
|
||||
*/
|
||||
public Object retrieveAttribute(WebRequest request, String attributeName) {
|
||||
return this.attributeStore.retrieveAttribute(request, attributeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve attributes for the underlying handler type from the backend session.
|
||||
* <p>Only attributes that have previously been successfully resolved via calls to
|
||||
* {@link #isHandlerSessionAttribute(String, Class)} are considered.
|
||||
* @param request the current request
|
||||
* @return a map with attributes or an empty map
|
||||
*/
|
||||
public Map<String, ?> retrieveHandlerSessionAttributes(WebRequest request) {
|
||||
Map<String, Object> attributes = new HashMap<String, Object>();
|
||||
for (String name : this.resolvedAttributeNames) {
|
||||
Object value = this.attributeStore.retrieveAttribute(request, name);
|
||||
if (value != null) {
|
||||
attributes.put(name, value);
|
||||
}
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up attributes for the underlying handler type from the backend session.
|
||||
* <p>Only attributes that have previously been successfully resolved via calls to
|
||||
* {@link #isHandlerSessionAttribute(String, Class)} are removed.
|
||||
* @param request the current request
|
||||
*/
|
||||
public void cleanupHandlerSessionAttributes(WebRequest request) {
|
||||
for (String attributeName : this.resolvedAttributeNames) {
|
||||
this.attributeStore.cleanupAttribute(request, attributeName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store attributes in the backend session.
|
||||
* <p>Only attributes that have previously been successfully resolved via calls to
|
||||
* {@link #isHandlerSessionAttribute(String, Class)} are stored. All other attributes
|
||||
* from the input map are ignored.
|
||||
* @param request the current request
|
||||
* @param attributes the attribute pairs to consider for storing
|
||||
*/
|
||||
public void storeHandlerSessionAttributes(WebRequest request, Map<String, Object> attributes) {
|
||||
for (String name : attributes.keySet()) {
|
||||
Object value = attributes.get(name);
|
||||
Class<?> attrType = (value != null) ? value.getClass() : null;
|
||||
|
||||
if (isHandlerSessionAttribute(name, attrType)) {
|
||||
this.attributeStore.storeAttribute(request, name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.annotation.support;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanExpressionContext;
|
||||
import org.springframework.beans.factory.config.BeanExpressionResolver;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.ValueConstants;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.RequestScope;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
|
||||
/**
|
||||
* Abstract base class for argument resolvers that resolve named values.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
*/
|
||||
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final ConfigurableBeanFactory beanFactory;
|
||||
|
||||
private final BeanExpressionContext expressionContext;
|
||||
|
||||
private Map<MethodParameter, NamedValueInfo> namedValueInfoCache =
|
||||
new ConcurrentHashMap<MethodParameter, NamedValueInfo>();
|
||||
|
||||
public AbstractNamedValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
this.expressionContext = (beanFactory != null) ? new BeanExpressionContext(beanFactory, new RequestScope()) : null;
|
||||
}
|
||||
|
||||
public boolean usesResponseArgument(MethodParameter parameter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public final Object resolveArgument(MethodParameter parameter,
|
||||
ModelMap model,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) throws Exception {
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
|
||||
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
|
||||
|
||||
Object arg = resolveNamedValueArgument(webRequest, parameter, namedValueInfo.name);
|
||||
|
||||
if (arg == null) {
|
||||
if (namedValueInfo.defaultValue != null) {
|
||||
arg = resolveDefaultValue(namedValueInfo.defaultValue);
|
||||
}
|
||||
else if (namedValueInfo.required) {
|
||||
handleMissingValue(namedValueInfo.name, parameter);
|
||||
}
|
||||
arg = checkForNull(namedValueInfo.name, arg, paramType);
|
||||
}
|
||||
|
||||
if (binderFactory != null) {
|
||||
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
|
||||
return binder.convertIfNecessary(arg, paramType, parameter);
|
||||
}
|
||||
else {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
|
||||
private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
|
||||
NamedValueInfo result = namedValueInfoCache.get(parameter);
|
||||
if (result == null) {
|
||||
NamedValueInfo info = createNamedValueInfo(parameter);
|
||||
String name = info.name;
|
||||
if (name.length() == 0) {
|
||||
name = parameter.getParameterName();
|
||||
if (name == null) {
|
||||
throw new IllegalStateException("No parameter name specified for argument of type [" +
|
||||
parameter.getParameterType().getName() +
|
||||
"], and no parameter name information found in class file either.");
|
||||
}
|
||||
}
|
||||
boolean required = info.required;
|
||||
String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
|
||||
|
||||
result = new NamedValueInfo(name, required, defaultValue);
|
||||
namedValueInfoCache.put(parameter, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link NamedValueInfo} object for the given method parameter.
|
||||
*
|
||||
* <p>Implementations typically retrieve the method annotation by means of {@link
|
||||
* MethodParameter#getParameterAnnotation(Class)}.
|
||||
*
|
||||
* @param parameter the method parameter
|
||||
* @return the named value information
|
||||
*/
|
||||
protected abstract NamedValueInfo createNamedValueInfo(MethodParameter parameter);
|
||||
|
||||
/**
|
||||
* Resolves the given parameter into a method argument.
|
||||
*
|
||||
* @param webRequest the current web request, allowing access to the native request as well
|
||||
* @param parameter the parameter to resolve to an argument. This parameter must have previously been passed to the
|
||||
* {@link #supportsParameter(org.springframework.core.MethodParameter)} method of this interface, which must have
|
||||
* returned {@code true}.
|
||||
* @param name the name
|
||||
* @return the resolved argument. May be {@code null}.
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
protected abstract Object resolveNamedValueArgument(NativeWebRequest webRequest,
|
||||
MethodParameter parameter,
|
||||
String name) throws Exception;
|
||||
|
||||
private Object resolveDefaultValue(String value) {
|
||||
if (beanFactory == null) {
|
||||
return value;
|
||||
}
|
||||
String placeholdersResolved = beanFactory.resolveEmbeddedValue(value);
|
||||
BeanExpressionResolver exprResolver = beanFactory.getBeanExpressionResolver();
|
||||
if (exprResolver == null) {
|
||||
return value;
|
||||
}
|
||||
return exprResolver.evaluate(placeholdersResolved, expressionContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a named value is required, but
|
||||
* {@link #resolveNamedValueArgument(NativeWebRequest, MethodParameter, String)} returned {@code null}
|
||||
* and there is no default value set.
|
||||
*
|
||||
* <p>Concrete subclasses typically throw an exception in this scenario.
|
||||
*
|
||||
* @param name the name
|
||||
* @param parameter the method parameter
|
||||
*/
|
||||
protected abstract void handleMissingValue(String name, MethodParameter parameter) throws ServletException;
|
||||
|
||||
private Object checkForNull(String name, Object value, Class<?> paramType) {
|
||||
if (value == null) {
|
||||
if (Boolean.TYPE.equals(paramType)) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
else if (paramType.isPrimitive()) {
|
||||
throw new IllegalStateException("Optional " + paramType + " parameter '" + name +
|
||||
"' is 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.");
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the information about a named value, including name, whether it's required and a default value.
|
||||
*/
|
||||
protected static class NamedValueInfo {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final boolean required;
|
||||
|
||||
private final String defaultValue;
|
||||
|
||||
protected NamedValueInfo(String name, boolean required, String defaultValue) {
|
||||
this.name = name;
|
||||
this.required = required;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.annotation.support;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.web.bind.annotation.CookieValue;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
|
||||
/**
|
||||
* Implementation of {@link HandlerMethodArgumentResolver} that supports arguments annotated with
|
||||
* {@link CookieValue @CookieValue}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class CookieValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
|
||||
|
||||
private UrlPathHelper urlPathHelper = new UrlPathHelper();
|
||||
|
||||
public CookieValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory) {
|
||||
super(beanFactory);
|
||||
}
|
||||
|
||||
public UrlPathHelper getUrlPathHelper() {
|
||||
return urlPathHelper;
|
||||
}
|
||||
|
||||
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
|
||||
this.urlPathHelper = urlPathHelper;
|
||||
}
|
||||
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return parameter.hasParameterAnnotation(CookieValue.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
|
||||
CookieValue annotation = parameter.getParameterAnnotation(CookieValue.class);
|
||||
return new CookieValueNamedValueInfo(annotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object resolveNamedValueArgument(NativeWebRequest webRequest,
|
||||
MethodParameter parameter,
|
||||
String cookieName) throws Exception {
|
||||
|
||||
throw new UnsupportedOperationException("@CookieValue not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleMissingValue(String cookieName, MethodParameter parameter) {
|
||||
throw new IllegalStateException(
|
||||
"Missing cookie value '" + cookieName + "' of type [" + parameter.getParameterType().getName() + "]");
|
||||
}
|
||||
|
||||
private static class CookieValueNamedValueInfo extends NamedValueInfo {
|
||||
|
||||
private CookieValueNamedValueInfo(CookieValue annotation) {
|
||||
super(annotation.value(), annotation.required(), annotation.defaultValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.annotation.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
|
||||
/**
|
||||
* An implementation of {@link HandlerMethodArgumentResolver} that resolves {@link Errors} method parameters.
|
||||
* Such parameters must be preceded by {@link ModelAttribute} parameters as described in {@link RequestMapping}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class ErrorsMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
return Errors.class.isAssignableFrom(paramType);
|
||||
}
|
||||
|
||||
public boolean usesResponseArgument(MethodParameter parameter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Object resolveArgument(MethodParameter parameter,
|
||||
ModelMap model,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) throws Exception {
|
||||
if (model.size() > 0) {
|
||||
List<String> keys = new ArrayList<String>(model.keySet());
|
||||
String lastKey = keys.get(model.size()-1);
|
||||
if (isBindingResultKey(lastKey)) {
|
||||
return model.get(lastKey);
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Errors/BindingResult argument declared "
|
||||
+ "without preceding model attribute. Check your handler method signature!");
|
||||
}
|
||||
|
||||
private boolean isBindingResultKey(String key) {
|
||||
return key.startsWith(BindingResult.MODEL_KEY_PREFIX);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.annotation.support;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
|
||||
/**
|
||||
* Implementation of {@link HandlerMethodArgumentResolver} that supports arguments annotated
|
||||
* with {@link Value @Value}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
*/
|
||||
public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
|
||||
|
||||
public ExpressionValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory) {
|
||||
super(beanFactory);
|
||||
}
|
||||
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return parameter.hasParameterAnnotation(Value.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
|
||||
Value annotation = parameter.getParameterAnnotation(Value.class);
|
||||
return new ExpressionValueNamedValueInfo(annotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object resolveNamedValueArgument(NativeWebRequest webRequest, MethodParameter parameter, String name)
|
||||
throws Exception {
|
||||
// Only interested in default value resolution
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
|
||||
// Should not happen
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private static class ExpressionValueNamedValueInfo extends NamedValueInfo {
|
||||
|
||||
private ExpressionValueNamedValueInfo(Value annotation) {
|
||||
super("@Value", false, annotation.value());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.annotation.support;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.DataBinder;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.support.SessionAttributeStore;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.bind.support.WebRequestDataBinder;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.annotation.ModelFactory;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* Resolves model attribute method parameters.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
*/
|
||||
public class ModelAttributeMethodProcessor
|
||||
implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
|
||||
|
||||
private final boolean resolveArgumentsWithoutAnnotations;
|
||||
|
||||
/**
|
||||
* Creates a {@link ModelAttributeMethodProcessor} instance.
|
||||
* @param resolveArgumentsWithoutAnnotations enable default resolution mode in which arguments without
|
||||
* annotations that aren't simple types (see {@link BeanUtils#isSimpleProperty(Class)})
|
||||
* are also treated as model attributes with a default name based on the model attribute type.
|
||||
*/
|
||||
public ModelAttributeMethodProcessor(boolean resolveArgumentsWithoutAnnotations) {
|
||||
this.resolveArgumentsWithoutAnnotations = resolveArgumentsWithoutAnnotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the parameter is annotated with {@link ModelAttribute} or if it is a
|
||||
* simple type without any annotations.
|
||||
*/
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
|
||||
return true;
|
||||
}
|
||||
else if (this.resolveArgumentsWithoutAnnotations && !parameter.hasParameterAnnotations()) {
|
||||
return !BeanUtils.isSimpleProperty(parameter.getParameterType());
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean usesResponseArgument(MethodParameter parameter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link WebDataBinder} for the target model attribute and applies data binding to it.
|
||||
* The model attribute may be obtained from the "implicit" model, from the session via, or by
|
||||
* direct instantiation.
|
||||
*
|
||||
* @throws Exception if invoking an data binder initialization fails or if data binding and/or
|
||||
* validation results in errors and the next method parameter is not of type {@link Errors}.
|
||||
*/
|
||||
public final Object resolveArgument(MethodParameter parameter,
|
||||
ModelMap model,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) throws Exception {
|
||||
WebDataBinder binder = createDataBinder(parameter, model, webRequest, binderFactory);
|
||||
|
||||
if (binder.getTarget() != null) {
|
||||
doBind(binder, webRequest);
|
||||
|
||||
if (shouldValidate(parameter)) {
|
||||
binder.validate();
|
||||
}
|
||||
|
||||
if (failOnError(parameter) && binder.getBindingResult().hasErrors()) {
|
||||
throw new BindException(binder.getBindingResult());
|
||||
}
|
||||
}
|
||||
|
||||
model.putAll(binder.getBindingResult().getModel());
|
||||
|
||||
return binder.getTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link WebDataBinder} for a target object which may be obtained from the "implicit" model,
|
||||
* the session via {@link SessionAttributeStore}, or by direct instantiation.
|
||||
*/
|
||||
private WebDataBinder createDataBinder(MethodParameter parameter,
|
||||
ModelMap model,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) throws Exception {
|
||||
String attrName = ModelFactory.getNameForParameter(parameter);
|
||||
|
||||
Object target;
|
||||
if (model.containsKey(attrName)) {
|
||||
target = model.get(attrName);
|
||||
}
|
||||
else {
|
||||
target = BeanUtils.instantiateClass(parameter.getParameterType());
|
||||
}
|
||||
|
||||
return binderFactory.createBinder(webRequest, target, attrName);
|
||||
}
|
||||
|
||||
protected void doBind(WebDataBinder binder, NativeWebRequest request) {
|
||||
((WebRequestDataBinder) binder).bind(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if {@link DataBinder#validate()} should be invoked, false otherwise.
|
||||
*/
|
||||
protected boolean shouldValidate(MethodParameter parameter) {
|
||||
Annotation[] annotations = parameter.getParameterAnnotations();
|
||||
for (Annotation annot : annotations) {
|
||||
if ("Valid".equals(annot.annotationType().getSimpleName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the binding or validation errors should result in a {@link BindException}, false otherwise.
|
||||
*/
|
||||
protected boolean failOnError(MethodParameter parameter) {
|
||||
int i = parameter.getParameterIndex();
|
||||
Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();
|
||||
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
|
||||
|
||||
return !hasBindingResult;
|
||||
}
|
||||
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
return returnType.getMethodAnnotation(ModelAttribute.class) != null;
|
||||
}
|
||||
|
||||
public <V> void handleReturnValue(Object returnValue,
|
||||
MethodParameter returnType,
|
||||
ModelAndViewContainer<V> mavContainer,
|
||||
NativeWebRequest webRequest) throws Exception {
|
||||
String name = ModelFactory.getNameForReturnValue(returnValue, returnType);
|
||||
mavContainer.addModelAttribute(name, returnValue);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.annotation.support;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* Resolves {@link Model} and {@link Map} method parameters.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
*/
|
||||
public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
|
||||
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
return Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType);
|
||||
}
|
||||
|
||||
public boolean usesResponseArgument(MethodParameter parameter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Object resolveArgument(MethodParameter parameter,
|
||||
ModelMap model,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) throws Exception {
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
if (Model.class.isAssignableFrom(paramType)) {
|
||||
return model;
|
||||
}
|
||||
else if (Map.class.isAssignableFrom(paramType)) {
|
||||
return model;
|
||||
}
|
||||
|
||||
// should not happen
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
Class<?> paramType = returnType.getParameterType();
|
||||
boolean hasModelAttr = returnType.getMethodAnnotation(ModelAttribute.class) != null;
|
||||
|
||||
return (Model.class.isAssignableFrom(paramType)
|
||||
|| (Map.class.isAssignableFrom(paramType) && !hasModelAttr));
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public <V> void handleReturnValue(Object returnValue,
|
||||
MethodParameter returnType,
|
||||
ModelAndViewContainer<V> mavContainer,
|
||||
NativeWebRequest webRequest) throws Exception {
|
||||
if (returnValue instanceof Model) {
|
||||
mavContainer.addModelAttributes((Model) returnValue);
|
||||
}
|
||||
else {
|
||||
mavContainer.addModelAttributes((Map) returnValue);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.annotation.support;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
|
||||
/**
|
||||
* Implementation of {@link HandlerMethodArgumentResolver} that supports {@link Map} arguments annotated with
|
||||
* {@link RequestHeader @RequestHeader}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class RequestHeaderMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return parameter.hasParameterAnnotation(RequestHeader.class)
|
||||
&& Map.class.isAssignableFrom(parameter.getParameterType());
|
||||
}
|
||||
|
||||
public boolean usesResponseArgument(MethodParameter parameter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Object resolveArgument(MethodParameter parameter,
|
||||
ModelMap model,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) throws Exception {
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
|
||||
if (MultiValueMap.class.isAssignableFrom(paramType)) {
|
||||
MultiValueMap<String, String> result;
|
||||
if (HttpHeaders.class.isAssignableFrom(paramType)) {
|
||||
result = new HttpHeaders();
|
||||
}
|
||||
else {
|
||||
result = new LinkedMultiValueMap<String, String>();
|
||||
}
|
||||
for (Iterator<String> iterator = webRequest.getHeaderNames(); iterator.hasNext();) {
|
||||
String headerName = iterator.next();
|
||||
for (String headerValue : webRequest.getHeaderValues(headerName)) {
|
||||
result.add(headerName, headerValue);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
Map<String, String> result = new LinkedHashMap<String, String>();
|
||||
for (Iterator<String> iterator = webRequest.getHeaderNames(); iterator.hasNext();) {
|
||||
String headerName = iterator.next();
|
||||
String headerValue = webRequest.getHeader(headerName);
|
||||
result.put(headerName, headerValue);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.annotation.support;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
|
||||
/**
|
||||
* Implementation of {@link HandlerMethodArgumentResolver} that supports arguments annotated with
|
||||
* {@link RequestHeader @RequestHeader}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
|
||||
|
||||
public RequestHeaderMethodArgumentResolver(ConfigurableBeanFactory beanFactory) {
|
||||
super(beanFactory);
|
||||
}
|
||||
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return parameter.hasParameterAnnotation(RequestHeader.class)
|
||||
&& !Map.class.isAssignableFrom(parameter.getParameterType());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
|
||||
RequestHeader annotation = parameter.getParameterAnnotation(RequestHeader.class);
|
||||
return new RequestHeaderNamedValueInfo(annotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object resolveNamedValueArgument(NativeWebRequest webRequest,
|
||||
MethodParameter parameter,
|
||||
String headerName) throws Exception {
|
||||
String[] headerValues = webRequest.getHeaderValues(headerName);
|
||||
if (headerValues != null) {
|
||||
return (headerValues.length == 1 ? headerValues[0] : headerValues);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleMissingValue(String headerName, MethodParameter parameter) {
|
||||
throw new IllegalStateException(
|
||||
"Missing header '" + headerName + "' of type [" + parameter.getParameterType().getName() + "]");
|
||||
}
|
||||
|
||||
private static class RequestHeaderNamedValueInfo extends NamedValueInfo {
|
||||
|
||||
private RequestHeaderNamedValueInfo(RequestHeader annotation) {
|
||||
super(annotation.value(), annotation.required(), annotation.defaultValue());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.annotation.support;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
|
||||
/**
|
||||
* Implementation of {@link HandlerMethodArgumentResolver} that supports {@link Map} arguments annotated with
|
||||
* {@link RequestParam @RequestParam}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
RequestParam requestParamAnnot = parameter.getParameterAnnotation(RequestParam.class);
|
||||
if (requestParamAnnot != null) {
|
||||
if (Map.class.isAssignableFrom(parameter.getParameterType())) {
|
||||
return !StringUtils.hasText(requestParamAnnot.value());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean usesResponseArgument(MethodParameter parameter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Object resolveArgument(MethodParameter parameter,
|
||||
ModelMap model,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) throws Exception {
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
|
||||
Map<String, String[]> parameterMap = webRequest.getParameterMap();
|
||||
if (MultiValueMap.class.isAssignableFrom(paramType)) {
|
||||
MultiValueMap<String, String> result = new LinkedMultiValueMap<String, String>(parameterMap.size());
|
||||
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
|
||||
for (String value : entry.getValue()) {
|
||||
result.add(entry.getKey(), value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
Map<String, String> result = new LinkedHashMap<String, String>(parameterMap.size());
|
||||
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
|
||||
if (entry.getValue().length > 0) {
|
||||
result.put(entry.getKey(), entry.getValue()[0]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.annotation.support;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ValueConstants;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartRequest;
|
||||
|
||||
/**
|
||||
* Implementation of {@link HandlerMethodArgumentResolver} that supports arguments annotated with
|
||||
* {@link RequestParam @RequestParam}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
|
||||
|
||||
private final boolean resolveParamsWithoutAnnotations;
|
||||
|
||||
/**
|
||||
* Creates a {@link RequestParamMethodArgumentResolver} instance.
|
||||
*
|
||||
* @param beanFactory the bean factory to use for resolving default value expressions
|
||||
* @param resolveParamsWithoutAnnotations enable default resolution mode in which parameters without
|
||||
* annotations that are simple types (see {@link BeanUtils#isSimpleProperty(Class)})
|
||||
* are also treated as model attributes with a default name based on the method argument name.
|
||||
*/
|
||||
public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory,
|
||||
boolean resolveParamsWithoutAnnotations) {
|
||||
super(beanFactory);
|
||||
this.resolveParamsWithoutAnnotations = resolveParamsWithoutAnnotations;
|
||||
}
|
||||
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
RequestParam requestParamAnnot = parameter.getParameterAnnotation(RequestParam.class);
|
||||
if (requestParamAnnot != null) {
|
||||
if (Map.class.isAssignableFrom(paramType)) {
|
||||
return StringUtils.hasText(requestParamAnnot.value());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (this.resolveParamsWithoutAnnotations && !parameter.hasParameterAnnotations()) {
|
||||
return BeanUtils.isSimpleProperty(paramType);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
|
||||
RequestParam annotation = parameter.getParameterAnnotation(RequestParam.class);
|
||||
return (annotation != null) ?
|
||||
new RequestParamNamedValueInfo(annotation) :
|
||||
new RequestParamNamedValueInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object resolveNamedValueArgument(NativeWebRequest webRequest,
|
||||
MethodParameter parameter,
|
||||
String paramName) throws Exception {
|
||||
MultipartRequest multipartRequest = webRequest.getNativeRequest(MultipartRequest.class);
|
||||
if (multipartRequest != null) {
|
||||
List<MultipartFile> files = multipartRequest.getFiles(paramName);
|
||||
if (!files.isEmpty()) {
|
||||
return (files.size() == 1 ? files.get(0) : files);
|
||||
}
|
||||
}
|
||||
|
||||
String[] paramValues = webRequest.getParameterValues(paramName);
|
||||
if (paramValues != null) {
|
||||
return paramValues.length == 1 ? paramValues[0] : paramValues;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleMissingValue(String paramName, MethodParameter parameter) throws ServletException {
|
||||
throw new MissingServletRequestParameterException(paramName, parameter.getParameterType().getSimpleName());
|
||||
}
|
||||
|
||||
private class RequestParamNamedValueInfo extends NamedValueInfo {
|
||||
|
||||
private RequestParamNamedValueInfo() {
|
||||
super("", true, ValueConstants.DEFAULT_NONE);
|
||||
}
|
||||
|
||||
private RequestParamNamedValueInfo(RequestParam annotation) {
|
||||
super(annotation.value(), annotation.required(), annotation.defaultValue());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.annotation.support;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.bind.support.WebArgumentResolver;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
|
||||
/**
|
||||
* Adapts a {@link WebArgumentResolver} into the {@link HandlerMethodArgumentResolver} contract.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class WebArgumentResolverAdapter implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final WebArgumentResolver adaptee;
|
||||
|
||||
public WebArgumentResolverAdapter(WebArgumentResolver adaptee) {
|
||||
Assert.notNull(adaptee, "'adaptee' must not be null");
|
||||
this.adaptee = adaptee;
|
||||
}
|
||||
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
try {
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
Object result ;
|
||||
if (requestAttributes instanceof NativeWebRequest) {
|
||||
result = adaptee.resolveArgument(parameter, (NativeWebRequest) requestAttributes);
|
||||
}
|
||||
else {
|
||||
result = adaptee.resolveArgument(parameter, null);
|
||||
}
|
||||
if (result == WebArgumentResolver.UNRESOLVED) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return ClassUtils.isAssignableValue(parameter.getParameterType(), result);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// ignore
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean usesResponseArgument(MethodParameter parameter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Object resolveArgument(MethodParameter parameter,
|
||||
ModelMap model,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) throws Exception {
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
Object result = adaptee.resolveArgument(parameter, webRequest);
|
||||
if (result == WebArgumentResolver.UNRESOLVED || !ClassUtils.isAssignableValue(paramType, result)) {
|
||||
throw new IllegalStateException(
|
||||
"Standard argument type [" + paramType.getName() + "] resolved to incompatible value of type [" +
|
||||
(result != null ? result.getClass() : null) +
|
||||
"]. Consider declaring the argument type in a less specific fashion.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.support;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* Strategy interface for resolving method parameters into argument values from a given request.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
*/
|
||||
public interface HandlerMethodArgumentResolver extends HandlerMethodProcessor {
|
||||
|
||||
/**
|
||||
* Indicates whether the given {@linkplain MethodParameter method parameter} is supported by this resolver.
|
||||
*
|
||||
* @param parameter the method parameter to check
|
||||
* @return {@code true} if this resolver supports the supplied parameter; {@code false} otherwise
|
||||
*/
|
||||
boolean supportsParameter(MethodParameter parameter);
|
||||
|
||||
/**
|
||||
* Resolves a method parameter into an argument value from a given request and a {@link ModelMap} providing
|
||||
* the ability to both access and add new model attributes. A {@link WebDataBinderFactory} is also provided
|
||||
* for creating a {@link WebDataBinder} instance to use for data binding and type conversion.
|
||||
*
|
||||
* @param parameter the parameter to resolve to an argument. This parameter must have previously been passed to
|
||||
* {@link #supportsParameter(org.springframework.core.MethodParameter)} and it must have returned {@code true}
|
||||
* @param model the model for the current request
|
||||
* @param webRequest the current request.
|
||||
* @param binderFactory a factory in case the resolver needs to create a {@link WebDataBinder} instance
|
||||
* @return the resolved argument value, or {@code null}.
|
||||
* @throws Exception in case of errors with the preparation of argument values
|
||||
*/
|
||||
Object resolveArgument(MethodParameter parameter,
|
||||
ModelMap model,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* Implementation of {@link HandlerMethodArgumentResolver} that resolves handler method arguments by delegating
|
||||
* to a list of registered {@link HandlerMethodArgumentResolver}s.
|
||||
*
|
||||
* <p>Previously resolved method argument types are cached internally for faster lookups.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
*/
|
||||
public class HandlerMethodArgumentResolverContainer implements HandlerMethodArgumentResolver {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(HandlerMethodArgumentResolverContainer.class);
|
||||
|
||||
private List<HandlerMethodArgumentResolver> argumentResolvers =
|
||||
new ArrayList<HandlerMethodArgumentResolver>();
|
||||
|
||||
private Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
|
||||
new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>();
|
||||
|
||||
/**
|
||||
* Indicates whether the given {@linkplain MethodParameter method parameter} is supported by any of the
|
||||
* registered {@link HandlerMethodArgumentResolver}s.
|
||||
*/
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return getArgumentResolver(parameter) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a method parameter into an argument value for the given request by iterating over registered
|
||||
* {@link HandlerMethodArgumentResolver}s to find one that supports the given method parameter.
|
||||
*/
|
||||
public Object resolveArgument(MethodParameter parameter,
|
||||
ModelMap model,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) throws Exception {
|
||||
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
|
||||
if (resolver != null) {
|
||||
return resolver.resolveArgument(parameter, model, webRequest, binderFactory);
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
"No suitable HandlerMethodArgumentResolver found. " +
|
||||
"supportsParameter(MethodParameter) should have been called previously.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a registered {@link HandlerMethodArgumentResolver} that supports the given method parameter.
|
||||
* @return a {@link HandlerMethodArgumentResolver} instance, or {@code null} if none
|
||||
*/
|
||||
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
|
||||
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
|
||||
if (result == null) {
|
||||
for (HandlerMethodArgumentResolver methodArgumentResolver : argumentResolvers) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
|
||||
parameter.getGenericParameterType() + "]");
|
||||
}
|
||||
if (methodArgumentResolver.supportsParameter(parameter)) {
|
||||
result = methodArgumentResolver;
|
||||
this.argumentResolverCache.put(parameter, result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the argument resolver that supports the given method parameter uses the response argument.
|
||||
* @see HandlerMethodProcessor#usesResponseArgument(MethodParameter)
|
||||
*/
|
||||
public boolean usesResponseArgument(MethodParameter parameter) {
|
||||
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
|
||||
return (resolver != null && resolver.usesResponseArgument(parameter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given {@link HandlerMethodArgumentResolver}.
|
||||
*/
|
||||
public void registerArgumentResolver(HandlerMethodArgumentResolver argumentResolver) {
|
||||
this.argumentResolvers.add(argumentResolver);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.support;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
|
||||
/**
|
||||
* A base interface for {@link HandlerMethodArgumentResolver}s and {@link HandlerMethodReturnValueHandler}s.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
*/
|
||||
public interface HandlerMethodProcessor {
|
||||
|
||||
/**
|
||||
* Indicates whether the given {@linkplain org.springframework.core.MethodParameter method parameter},
|
||||
* uses the response argument with the implication that the response will be handled directly by invoking
|
||||
* the method and will not require view name resolution followed by rendering.
|
||||
*
|
||||
* @param parameter the method parameter, either a method argument or a return type
|
||||
* @return {@code true} if the supplied parameter uses the response argument; {@code false} otherwise
|
||||
*/
|
||||
boolean usesResponseArgument(MethodParameter parameter);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.support;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* Strategy interface to process the value returned from a handler method invocation.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
*/
|
||||
public interface HandlerMethodReturnValueHandler extends HandlerMethodProcessor {
|
||||
|
||||
/**
|
||||
* Indicates whether the given {@linkplain MethodParameter method return type} is supported by this handler.
|
||||
*
|
||||
* @param returnType the method return type to check
|
||||
* @return {@code true} if this handler supports the supplied return type; {@code false} otherwise
|
||||
*/
|
||||
boolean supportsReturnType(MethodParameter returnType);
|
||||
|
||||
/**
|
||||
* Handles the given value returned by a handler method invocation by writing directly to the response
|
||||
* or by using the {@code mavContainer} argument to add model attributes and/or set the view.
|
||||
*
|
||||
* @param returnValue the return value to handle
|
||||
* @param returnType the return type to handle. This type must have previously been passed to
|
||||
* {@link #supportsReturnType(org.springframework.core.MethodParameter)} and it must have returned {@code true}
|
||||
* @param mavContainer records model and view choices
|
||||
* @param webRequest the current request
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
<V> void handleReturnValue(Object returnValue,
|
||||
MethodParameter returnType,
|
||||
ModelAndViewContainer<V> mavContainer,
|
||||
NativeWebRequest webRequest) throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* Implementation of {@link HandlerMethodReturnValueHandler} that handles method return values by
|
||||
* delegating to a list of registered {@link HandlerMethodReturnValueHandler}s.
|
||||
*
|
||||
* <p>Previously resolved return types are cached internally for faster lookups.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
*/
|
||||
public class HandlerMethodReturnValueHandlerContainer implements HandlerMethodReturnValueHandler {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(HandlerMethodArgumentResolverContainer.class);
|
||||
|
||||
private List<HandlerMethodReturnValueHandler> returnValueHandlers =
|
||||
new ArrayList<HandlerMethodReturnValueHandler>();
|
||||
|
||||
private Map<MethodParameter, HandlerMethodReturnValueHandler> returnValueHandlerCache =
|
||||
new ConcurrentHashMap<MethodParameter, HandlerMethodReturnValueHandler>();
|
||||
|
||||
/**
|
||||
* Indicates whether the given {@linkplain MethodParameter method return type} is supported by any of the
|
||||
* registered {@link HandlerMethodReturnValueHandler}s.
|
||||
*/
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
return getReturnValueHandler(returnType) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the given method return value by iterating over registered {@link HandlerMethodReturnValueHandler}s
|
||||
* to find one that supports it.
|
||||
*/
|
||||
public <V> void handleReturnValue(Object returnValue,
|
||||
MethodParameter returnType,
|
||||
ModelAndViewContainer<V> mavContainer,
|
||||
NativeWebRequest webRequest) throws Exception {
|
||||
HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType);
|
||||
if (handler != null) {
|
||||
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
"No suitable HandlerMethodReturnValueHandler found. " +
|
||||
"supportsReturnType(MethodParameter) should have been called previously");
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a registered {@link HandlerMethodReturnValueHandler} that supports the given method return type.
|
||||
*/
|
||||
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
|
||||
if (this.returnValueHandlers == null) {
|
||||
return null;
|
||||
}
|
||||
HandlerMethodReturnValueHandler result = this.returnValueHandlerCache.get(returnType);
|
||||
if (result == null) {
|
||||
for (HandlerMethodReturnValueHandler methodReturnValueHandler : returnValueHandlers) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Testing if return value handler [" + methodReturnValueHandler + "] supports [" +
|
||||
returnType.getGenericParameterType() + "]");
|
||||
}
|
||||
if (methodReturnValueHandler.supportsReturnType(returnType)) {
|
||||
result = methodReturnValueHandler;
|
||||
this.returnValueHandlerCache.put(returnType, methodReturnValueHandler);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the return value handler that supports the given method parameter uses the response.
|
||||
* @see HandlerMethodProcessor#usesResponseArgument(MethodParameter)
|
||||
*/
|
||||
public boolean usesResponseArgument(MethodParameter parameter) {
|
||||
HandlerMethodReturnValueHandler handler = getReturnValueHandler(parameter);
|
||||
return (handler != null && handler.usesResponseArgument(parameter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given {@link HandlerMethodReturnValueHandler}.
|
||||
*/
|
||||
public void registerReturnValueHandler(HandlerMethodReturnValueHandler returnValuehandler) {
|
||||
returnValueHandlers.add(returnValuehandler);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.support;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.support.SessionStatus;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
/**
|
||||
* Provides a way to invoke a handler method after resolving its method argument values through
|
||||
* {@link HandlerMethodArgumentResolver}s in the context of the current request.
|
||||
*
|
||||
* <p>Resolving argument values often requires a {@link WebDataBinder} for data binding and type conversion.
|
||||
* Use {@link #setDataBinderFactory(WebDataBinderFactory)} to provide a factory for that. The list of argument
|
||||
* resolvers can be set via {@link #setArgumentResolverContainer(HandlerMethodArgumentResolverContainer)}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
*/
|
||||
public class InvocableHandlerMethod extends HandlerMethod {
|
||||
|
||||
private HandlerMethodArgumentResolverContainer argumentResolvers = new HandlerMethodArgumentResolverContainer();
|
||||
|
||||
private WebDataBinderFactory dataBinderFactory;
|
||||
|
||||
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
|
||||
|
||||
/**
|
||||
* Constructs a new invocable handler method with the given bean instance and method.
|
||||
* @param bean the bean instance
|
||||
* @param method the method
|
||||
*/
|
||||
public InvocableHandlerMethod(Object bean, Method method) {
|
||||
super(bean, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new invocable handler method with the given bean instance, method name and parameters.
|
||||
* @param bean the object bean
|
||||
* @param methodName the method name
|
||||
* @param parameterTypes the method parameter types
|
||||
* @throws NoSuchMethodException when the method cannot be found
|
||||
*/
|
||||
public InvocableHandlerMethod(
|
||||
Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
|
||||
super(bean, methodName, parameterTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link WebDataBinderFactory} to be passed to argument resolvers that require a
|
||||
* {@link WebDataBinder} to do type conversion or data binding on the method argument value.
|
||||
*
|
||||
* @param dataBinderFactory the data binder factory.
|
||||
*/
|
||||
public void setDataBinderFactory(WebDataBinderFactory dataBinderFactory) {
|
||||
this.dataBinderFactory = dataBinderFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set {@link HandlerMethodArgumentResolver}s to use to use for resolving method argument values.
|
||||
*/
|
||||
public void setArgumentResolverContainer(HandlerMethodArgumentResolverContainer argumentResolvers) {
|
||||
this.argumentResolvers = argumentResolvers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ParameterNameDiscoverer to use for resolving method parameter names if needed
|
||||
* (e.g. for default attribute names).
|
||||
* <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
|
||||
*/
|
||||
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
|
||||
this.parameterNameDiscoverer = parameterNameDiscoverer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the method after resolving its argument values based on the given request.
|
||||
* <p>Most argument values are resolved with the help of {@link HandlerMethodArgumentResolver}s
|
||||
* configured via {@link #setArgumentResolverContainer(HandlerMethodArgumentResolverContainer)}.
|
||||
* However, the {@code provideArgs} parameter can be used to supply argument values for use
|
||||
* directly rather than relying on argument resolution - e.g. {@link WebDataBinder},
|
||||
* {@link SessionStatus}, or the thrown exception in a HandlerExceptionResolver.
|
||||
* @param request the current request
|
||||
* @param model the model used throughout the current request
|
||||
* @param providedArgs argument values to try to use, thus bypassing argument resolution
|
||||
* @return the raw value returned by the invoked method
|
||||
*/
|
||||
public final Object invokeForRequest(NativeWebRequest request, ModelMap model, Object... providedArgs)
|
||||
throws Exception {
|
||||
|
||||
Object[] args = getMethodArguments(request, model, providedArgs);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
StringBuilder builder = new StringBuilder("Invoking [");
|
||||
builder.append(this.getMethod().getName()).append("] method with arguments ");
|
||||
builder.append(Arrays.asList(args));
|
||||
logger.trace(builder.toString());
|
||||
}
|
||||
|
||||
Object returnValue = invoke(args);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Method [" + this.getMethod().getName() + "] returned [" + returnValue + "]");
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
private Object[] getMethodArguments(NativeWebRequest request, ModelMap model, Object... providedArgs)
|
||||
throws Exception {
|
||||
MethodParameter[] parameters = getMethodParameters();
|
||||
Object[] args = new Object[parameters.length];
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
MethodParameter parameter = parameters[i];
|
||||
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
|
||||
GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
|
||||
|
||||
args[i] = resolveProvidedArgument(parameter, providedArgs);
|
||||
|
||||
if (args[i] != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.argumentResolvers.supportsParameter(parameter)) {
|
||||
args[i] = this.argumentResolvers.resolveArgument(parameter, model, request, dataBinderFactory);
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("Cannot resolve argument index=" + parameter.getParameterIndex() + ""
|
||||
+ ", name=" + parameter.getParameterName() + ", type=" + parameter.getParameterType()
|
||||
+ " in method " + toString());
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
private Object resolveProvidedArgument(MethodParameter parameter, Object... providedArgs) {
|
||||
if (providedArgs == null) {
|
||||
return null;
|
||||
}
|
||||
for (Object providedArg : providedArgs) {
|
||||
if (parameter.getParameterType().isInstance(providedArg)) {
|
||||
return providedArg;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes this handler method with the given argument values.
|
||||
* @param args the argument values
|
||||
* @return the result of the invocation
|
||||
* @throws Exception when the method invocation results in an exception
|
||||
*/
|
||||
private Object invoke(Object... args) throws Exception {
|
||||
ReflectionUtils.makeAccessible(this.getBridgedMethod());
|
||||
try {
|
||||
return getBridgedMethod().invoke(getBean(), args);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
handleIllegalArgumentException(ex, args);
|
||||
throw ex;
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
handleInvocationTargetException(ex);
|
||||
throw new IllegalStateException(
|
||||
"Unexpected exception thrown by method - " + ex.getTargetException().getClass().getName() + ": " +
|
||||
ex.getTargetException().getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleIllegalArgumentException(IllegalArgumentException ex, Object... args) {
|
||||
StringBuilder builder = new StringBuilder(ex.getMessage());
|
||||
builder.append(" :: method=").append(getBridgedMethod().toGenericString());
|
||||
builder.append(" :: invoked with handler type=").append(getBeanType().getName());
|
||||
|
||||
if (args != null && args.length > 0) {
|
||||
builder.append(" and argument types ");
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
builder.append(" : arg[").append(i).append("] ").append(args[i].getClass());
|
||||
}
|
||||
}
|
||||
else {
|
||||
builder.append(" and 0 arguments");
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(builder.toString(), ex);
|
||||
}
|
||||
|
||||
private void handleInvocationTargetException(InvocationTargetException ex) throws Exception {
|
||||
Throwable targetException = ex.getTargetException();
|
||||
if (targetException instanceof RuntimeException) {
|
||||
throw (RuntimeException) targetException;
|
||||
}
|
||||
if (targetException instanceof Error) {
|
||||
throw (Error) targetException;
|
||||
}
|
||||
if (targetException instanceof Exception) {
|
||||
throw (Exception) targetException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether any of the registered {@link HandlerMethodArgumentResolver}s use the response argument.
|
||||
* @see HandlerMethodProcessor#usesResponseArgument(MethodParameter)
|
||||
*/
|
||||
protected boolean usesResponseArgument() {
|
||||
MethodParameter[] methodParameters = getMethodParameters();
|
||||
for (MethodParameter methodParameter : methodParameters) {
|
||||
if (this.argumentResolvers.usesResponseArgument(methodParameter)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.method.support;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.ui.ExtendedModelMap;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.ui.ModelMap;
|
||||
|
||||
/**
|
||||
* Contains model and view choices made by {@link HandlerMethodReturnValueHandler}s.
|
||||
*
|
||||
* <p>Allows return value handlers to set only the bits that are relevant to them - i.e. model, view,
|
||||
* or none, while also taking care of merging attributes added by the {@link HandlerMethodReturnValueHandler}
|
||||
* with attributes from the implicit model.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
*
|
||||
* @param <V> Servlet or Portlet specific View type.
|
||||
*/
|
||||
public class ModelAndViewContainer<V> {
|
||||
|
||||
private String viewName;
|
||||
|
||||
private V view;
|
||||
|
||||
private final ModelMap actualModel = new ExtendedModelMap();
|
||||
|
||||
private final ModelMap implicitModel;
|
||||
|
||||
public ModelAndViewContainer(ModelMap implicitModel) {
|
||||
this.implicitModel = (implicitModel != null) ? implicitModel : new ExtendedModelMap();
|
||||
}
|
||||
|
||||
public ModelMap getModel() {
|
||||
return new ExtendedModelMap().addAllAttributes(actualModel).mergeAttributes(implicitModel);
|
||||
}
|
||||
|
||||
public String getViewName() {
|
||||
return this.viewName;
|
||||
}
|
||||
|
||||
public void setViewName(String viewName) {
|
||||
this.viewName = viewName;
|
||||
}
|
||||
|
||||
public V getView() {
|
||||
return this.view;
|
||||
}
|
||||
|
||||
public void setView(V view) {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
public void addModelAttributes(Model attributes) {
|
||||
actualModel.addAllAttributes(attributes.asMap());
|
||||
}
|
||||
|
||||
public void addModelAttributes(Map<String, Object> attributes) {
|
||||
actualModel.addAllAttributes(attributes);
|
||||
}
|
||||
|
||||
public void addModelAttribute(String name, Object value) {
|
||||
actualModel.addAttribute(name, value);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user