diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodExceptionResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodExceptionResolver.java
new file mode 100644
index 0000000000..a561a6992d
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodExceptionResolver.java
@@ -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.servlet.handler;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.ModelAndView;
+
+/**
+ * Abstract base class for {@link org.springframework.web.servlet.HandlerExceptionResolver HandlerExceptionResolver}
+ * implementations that support {@link HandlerMethod HandlerMethod}s.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ */
+public class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExceptionResolver {
+
+ /**
+ * Checks if the handler is a {@link HandlerMethod} instance and performs the check against the bean
+ * instance it contains. If the provided handler is not an instance of {@link HandlerMethod},
+ * {@code false} is returned instead.
+ */
+ @Override
+ protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
+ if (handler == null) {
+ return super.shouldApplyTo(request, handler);
+ }
+ else if (handler instanceof HandlerMethod) {
+ HandlerMethod handlerMethod = (HandlerMethod) handler;
+ handler = handlerMethod.getBean();
+ return super.shouldApplyTo(request, handler);
+ }
+ else {
+ return false;
+ }
+ }
+
+ @Override
+ protected final ModelAndView doResolveException(HttpServletRequest request,
+ HttpServletResponse response,
+ Object handler,
+ Exception ex) {
+ return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
+ }
+
+ /**
+ * Actually resolve the given exception that got thrown during on handler execution,
+ * returning a ModelAndView that represents a specific error page if appropriate.
+ *
May be overridden in subclasses, in order to apply specific exception checks.
+ * Note that this template method will be invoked after checking whether this
+ * resolved applies ("mappedHandlers" etc), so an implementation may simply proceed
+ * with its actual exception handling.
+ * @param request current HTTP request
+ * @param response current HTTP response
+ * @param handlerMethod the executed handler method, or null if none chosen at the time
+ * of the exception (for example, if multipart resolution failed)
+ * @param ex the exception that got thrown during handler execution
+ * @return a corresponding ModelAndView to forward to, or null for default processing
+ */
+ protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
+ HttpServletResponse response,
+ HandlerMethod handlerMethod,
+ Exception ex) {
+ return null;
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java
new file mode 100644
index 0000000000..ac0ad59445
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.handler;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.context.ApplicationContextException;
+import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils.MethodFilter;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.method.HandlerMethodSelector;
+
+/**
+ * Abstract base class for {@link org.springframework.web.servlet.HandlerMapping HandlerMapping} implementations that
+ * support {@link HandlerMethod}s.
+ *
+ * @author Arjen Poutsma
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ */
+public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMapping {
+
+ private final Map handlerMethods = new LinkedHashMap();
+
+ /**
+ * Calls the initialization of the superclass and detects handlers.
+ */
+ @Override
+ public void initApplicationContext() throws ApplicationContextException {
+ super.initApplicationContext();
+ initHandlerMethods();
+ }
+
+ /**
+ * Register handler methods found in beans of the current ApplicationContext.
+ *
The actual key determination for a handler is up to the concrete
+ * {@link #getKeyForMethod(Method)} implementation. A method in a bean for which no key
+ * could be determined is simply not considered a handler method.
+ * @see #getKeyForMethod(Method)
+ */
+ protected void initHandlerMethods() {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
+ }
+ for (String beanName : getApplicationContext().getBeanNamesForType(Object.class)) {
+ if (isHandler(beanName)){
+ detectHandlerMethods(beanName);
+ }
+ }
+ }
+
+ /**
+ * Determines if the given bean is a handler that should be introspected for handler methods.
+ * @param beanName the name of the bean to check
+ * @return true if the bean is a handler and may contain handler methods, false otherwise.
+ */
+ protected abstract boolean isHandler(String beanName);
+
+ private void detectHandlerMethods(final String handlerName) {
+ Class> handlerType = getApplicationContext().getType(handlerName);
+
+ Set methods = HandlerMethodSelector.selectMethods(handlerType, new MethodFilter() {
+ public boolean matches(Method method) {
+ return getKeyForMethod(method) != null;
+ }
+ });
+ for (Method method : methods) {
+ T key = getKeyForMethod(method);
+ HandlerMethod handlerMethod = new HandlerMethod(handlerName, getApplicationContext(), method);
+ registerHandlerMethod(key, handlerMethod);
+ }
+ }
+
+ /**
+ * Provides a lookup key for the given method. A method for which no key can be determined is
+ * not considered a handler method.
+ *
+ * @param method the method to create a key for
+ * @return the lookup key, or {@code null} if the method has none
+ */
+ protected abstract T getKeyForMethod(Method method);
+
+ /**
+ * Registers a {@link HandlerMethod} under the given key.
+ *
+ * @param key the key to register the method under
+ * @param handlerMethod the handler method to register
+ * @throws IllegalStateException if another method was already register under the key
+ */
+ protected void registerHandlerMethod(T key, HandlerMethod handlerMethod) {
+ Assert.notNull(key, "'key' must not be null");
+ Assert.notNull(handlerMethod, "'handlerMethod' must not be null");
+ HandlerMethod mappedHandlerMethod = handlerMethods.get(key);
+ if (mappedHandlerMethod != null && !mappedHandlerMethod.equals(handlerMethod)) {
+ throw new IllegalStateException("Cannot map " + handlerMethod + " to \"" + key + "\": There is already "
+ + mappedHandlerMethod + " mapped.");
+ }
+ handlerMethods.put(key, handlerMethod);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Mapped \"" + key + "\" onto " + handlerMethod);
+ }
+ }
+
+ @Override
+ protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
+ T key = getKeyForRequest(request);
+ if (key == null) {
+ return null;
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Looking up handler method for [" + key + "]");
+ }
+
+ HandlerMethod handlerMethod = lookupHandlerMethod(key, request);
+
+ if (logger.isDebugEnabled()) {
+ if (handlerMethod != null) {
+ logger.debug("Returning [" + handlerMethod + "] as best match for [" + key + "]");
+ }
+ else {
+ logger.debug("Did not find handler method for [" + key + "]");
+ }
+ }
+
+ return (handlerMethod != null) ? handlerMethod.createWithResolvedBean() : null;
+ }
+
+ /**
+ * Abstract template method that returns the lookup key for the given HTTP servlet request.
+ *
+ * @param request the request to look up the key for
+ * @return the key, or {@code null} if the request does not have one
+ * @throws Exception in case of errors
+ */
+ protected abstract T getKeyForRequest(HttpServletRequest request) throws Exception;
+
+ /**
+ * Looks up the best-matching {@link HandlerMethod} for the given request.
+ *
+ *
This implementation iterators through all handler methods, calls {@link #getMatchingKey(Object,
+ * HttpServletRequest)} for each of them, {@linkplain #getKeyComparator(HttpServletRequest) sorts} all matches, and
+ * returns the 1st entry, if any. If no matches are found, {@link #handleNoMatch(Set, HttpServletRequest)} is
+ * invoked.
+ *
+ * @param lookupKey current lookup key
+ * @param request the current HTTP servlet request
+ * @return the best-matching handler method, or {@code null} if there is no match
+ */
+ protected HandlerMethod lookupHandlerMethod(T lookupKey, HttpServletRequest request) throws Exception {
+ if (handlerMethods.containsKey(lookupKey)) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Found direct match for [" + lookupKey + "]");
+ }
+
+ handleMatch(lookupKey, request);
+ return handlerMethods.get(lookupKey);
+ }
+ else {
+ List matches = new ArrayList();
+
+ for (Map.Entry entry : handlerMethods.entrySet()) {
+ T match = getMatchingKey(entry.getKey(), request);
+ if (match != null) {
+ matches.add(new Match(match, entry.getValue()));
+ }
+ }
+
+ if (!matches.isEmpty()) {
+ Comparator comparator = getMatchComparator(request);
+ Collections.sort(matches, comparator);
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("Found " + matches.size() + " matching key(s) for [" + lookupKey + "] : " + matches);
+ }
+
+ Match bestMatch = matches.get(0);
+ if (matches.size() > 1) {
+ Match secondBestMatch = matches.get(1);
+ if (comparator.compare(bestMatch, secondBestMatch) == 0) {
+ Method m1 = bestMatch.handlerMethod.getMethod();
+ Method m2 = secondBestMatch.handlerMethod.getMethod();
+ throw new IllegalStateException(
+ "Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +
+ m1 + ", " + m2 + "}");
+ }
+ }
+
+ handleMatch(bestMatch.key, request);
+ return bestMatch.handlerMethod;
+ }
+ else {
+ return handleNoMatch(handlerMethods.keySet(), request);
+ }
+ }
+ }
+
+ /**
+ * Invoked when a key matching to a request has been identified.
+ *
+ * @param key the key selected for the request returned by {@link #getMatchingKey(Object, HttpServletRequest)}.
+ * @param request the current request
+ */
+ protected void handleMatch(T key, HttpServletRequest request) {
+ }
+
+ /**
+ * Returns the matching variant of the given key, given the current HTTP servlet request.
+ *
+ * @param key the key to get the matches for
+ * @param request the current HTTP servlet request
+ * @return the matching key, or {@code null} if the given key does not match against the servlet request
+ */
+ protected abstract T getMatchingKey(T key, HttpServletRequest request);
+
+ private Comparator getMatchComparator(HttpServletRequest request) {
+ final Comparator keyComparator = getKeyComparator(request);
+ return new Comparator() {
+ public int compare(Match m1, Match m2) {
+ return keyComparator.compare(m1.key, m2.key);
+ }
+ };
+ }
+
+ /**
+ * Returns a comparator to sort the keys with. The returned comparator should sort 'better' matches higher.
+ *
+ * @param request the current HTTP servlet request
+ * @return the comparator
+ */
+ protected abstract Comparator getKeyComparator(HttpServletRequest request);
+
+ /**
+ * Invoked when no match was found. Default implementation returns {@code null}.
+ *
+ * @param requestKeys the registered request keys
+ * @param request the current HTTP request
+ * @throws ServletException in case of errors
+ */
+ protected HandlerMethod handleNoMatch(Set requestKeys, HttpServletRequest request) throws Exception {
+ return null;
+ }
+
+ private class Match {
+
+ private final T key;
+
+ private final HandlerMethod handlerMethod;
+
+ private Match(T key, HandlerMethod handlerMethod) {
+ this.key = key;
+ this.handlerMethod = handlerMethod;
+ }
+
+ @Override
+ public String toString() {
+ return key.toString();
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java
index f9623c76d4..1ddfbf190b 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java
@@ -22,7 +22,7 @@ import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
+
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -204,9 +204,9 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
}
}
if (handler != null && this.mappedInterceptors != null) {
- Set mappedInterceptors =
+ HandlerInterceptor[] mappedInterceptors =
this.mappedInterceptors.getInterceptors(lookupPath, this.pathMatcher);
- if (!mappedInterceptors.isEmpty()) {
+ if (mappedInterceptors.length != 0) {
HandlerExecutionChain chain;
if (handler instanceof HandlerExecutionChain) {
chain = (HandlerExecutionChain) handler;
@@ -214,7 +214,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
else {
chain = new HandlerExecutionChain(handler);
}
- chain.addInterceptors(mappedInterceptors.toArray(new HandlerInterceptor[mappedInterceptors.size()]));
+ chain.addInterceptors(mappedInterceptors);
}
}
if (handler != null && logger.isDebugEnabled()) {
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/MappedInterceptors.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/MappedInterceptors.java
index b9ae883975..766cd30625 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/MappedInterceptors.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/MappedInterceptors.java
@@ -1,29 +1,60 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package org.springframework.web.servlet.handler;
import java.util.LinkedHashSet;
+import java.util.Map;
import java.util.Set;
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;
-class MappedInterceptors {
-
+public final class MappedInterceptors {
+
private MappedInterceptor[] mappedInterceptors;
public MappedInterceptors(MappedInterceptor[] mappedInterceptors) {
this.mappedInterceptors = mappedInterceptors;
}
-
- public Set getInterceptors(String lookupPath, PathMatcher pathMatcher) {
+
+ public static MappedInterceptors createFromDeclaredBeans(ListableBeanFactory beanFactory) {
+ Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory,
+ MappedInterceptor.class, true, false);
+
+ if (!beans.isEmpty()) {
+ return new MappedInterceptors(beans.values().toArray(new MappedInterceptor[beans.size()]));
+ }
+ else {
+ return null;
+ }
+ }
+
+ public HandlerInterceptor[] getInterceptors(String lookupPath, PathMatcher pathMatcher) {
Set interceptors = new LinkedHashSet();
for (MappedInterceptor interceptor : this.mappedInterceptors) {
if (matches(interceptor, lookupPath, pathMatcher)) {
- interceptors.add(interceptor.getInterceptor());
+ interceptors.add(interceptor.getInterceptor());
}
}
- return interceptors;
+ return interceptors.toArray(new HandlerInterceptor[interceptors.size()]);
}
-
+
private boolean matches(MappedInterceptor interceptor, String lookupPath, PathMatcher pathMatcher) {
String[] pathPatterns = interceptor.getPathPatterns();
if (pathPatterns != null) {
@@ -33,9 +64,10 @@ class MappedInterceptors {
}
}
return false;
- } else {
+ }
+ else {
return true;
}
}
-
+
}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/AbstractHandlerMethodAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/AbstractHandlerMethodAdapter.java
new file mode 100644
index 0000000000..f4f64d01e9
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/AbstractHandlerMethodAdapter.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.core.Ordered;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerAdapter;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.support.WebContentGenerator;
+
+/**
+ * Abstract base class for {@link HandlerAdapter} implementations that support {@link HandlerMethod}s.
+ * Contains template methods for handling these handler method.
+ *
+ * @author Arjen Poutsma
+ */
+public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
+
+ private int order = Ordered.LOWEST_PRECEDENCE;
+
+ public AbstractHandlerMethodAdapter() {
+ // no restriction of HTTP methods by default
+ super(false);
+ }
+
+ /**
+ * Specify the order value for this HandlerAdapter bean.
+ *
Default value is Integer.MAX_VALUE, meaning that it's non-ordered.
+ * @see org.springframework.core.Ordered#getOrder()
+ */
+ public void setOrder(int order) {
+ this.order = order;
+ }
+
+ public int getOrder() {
+ return this.order;
+ }
+
+ /**
+ * {@inheritDoc}
This implementation expects the handler to be an {@link HandlerMethod}.
+ *
+ * @param handler the handler instance to check
+ * @return whether or not this adapter can adapt the given handler
+ */
+ public final boolean supports(Object handler) {
+ return handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler);
+ }
+
+ /**
+ * Given a handler method, return whether or not this adapter can support it.
+ *
+ * @param handlerMethod the handler method to check
+ * @return whether or not this adapter can adapt the given method
+ */
+ protected abstract boolean supportsInternal(HandlerMethod handlerMethod);
+
+ /**
+ * {@inheritDoc}
This implementation expects the handler to be an {@link HandlerMethod}.
+ */
+ public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
+ throws Exception {
+ return handleInternal(request, response, (HandlerMethod) handler);
+ }
+
+ /**
+ * Use the given handler method to handle the request.
+ *
+ * @param request current HTTP request
+ * @param response current HTTP response
+ * @param handlerMethod handler method to use. This object must have previously been passed to the
+ * {@link #supportsInternal(HandlerMethod)} this interface, which must have returned {@code true}.
+ * @return ModelAndView object with the name of the view and the required model data, or {@code null} if
+ * the request has been handled directly
+ * @throws Exception in case of errors
+ */
+ protected abstract ModelAndView handleInternal(HttpServletRequest request,
+ HttpServletResponse response,
+ HandlerMethod handlerMethod) throws Exception;
+
+ /**
+ * {@inheritDoc}
This implementation expects the handler to be an {@link HandlerMethod}.
+ */
+ public final long getLastModified(HttpServletRequest request, Object handler) {
+ return getLastModifiedInternal(request, (HandlerMethod) handler);
+ }
+
+ /**
+ * Same contract as for {@link javax.servlet.http.HttpServlet#getLastModified(HttpServletRequest)}.
+ *
+ * @param request current HTTP request
+ * @param handlerMethod handler method to use
+ * @return the lastModified value for the given handler
+ */
+ protected abstract long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod);
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestCondition.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestCondition.java
new file mode 100644
index 0000000000..335066939a
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestCondition.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * A condition that can be matched to a ServletRequest.
+ *
+ * @author Rossen Stoyanchev
+ */
+interface RequestCondition {
+
+ boolean match(HttpServletRequest request);
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestConditionFactory.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestConditionFactory.java
new file mode 100644
index 0000000000..ef226b3d11
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestConditionFactory.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.http.MediaType;
+import org.springframework.web.util.WebUtils;
+
+/**
+ * Factory for request condition objects.
+ *
+ * @author Arjen Poutsma
+ * @author Rossen Stoyanchev
+ */
+public abstract class RequestConditionFactory {
+
+ /**
+ * Parses the given parameters, and returns them as a set of request conditions.
+ *
+ * @param params the parameters
+ * @return the request conditions
+ * @see org.springframework.web.bind.annotation.RequestMapping#params()
+ */
+ public static Set parseParams(String... params) {
+ if (params == null) {
+ return Collections.emptySet();
+ }
+ Set result = new LinkedHashSet(params.length);
+ for (String expression : params) {
+ result.add(new ParamNameValueCondition(expression));
+ }
+ return result;
+ }
+
+ /**
+ * Parses the given headers, and returns them as a set of request conditions.
+ *
+ * @param headers the headers
+ * @return the request conditions
+ * @see org.springframework.web.bind.annotation.RequestMapping#headers()
+ */
+ public static Set parseHeaders(String... headers) {
+ if (headers == null) {
+ return Collections.emptySet();
+ }
+ Set result = new LinkedHashSet(headers.length);
+ for (String expression : headers) {
+ HeaderNameValueCondition header = new HeaderNameValueCondition(expression);
+ if (isMediaTypeHeader(header.name)) {
+ result.add(new MediaTypeHeaderNameValueCondition(expression));
+ }
+ else {
+ result.add(header);
+ }
+ }
+ return result;
+ }
+
+ private static boolean isMediaTypeHeader(String name) {
+ return "Accept".equalsIgnoreCase(name) || "Content-Type".equalsIgnoreCase(name);
+ }
+
+ /**
+ * A condition that supports simple "name=value" style expressions as documented in
+ * @RequestMapping.params() and @RequestMapping.headers().
+ */
+ private static abstract class AbstractNameValueCondition implements RequestCondition {
+
+ protected final String name;
+
+ protected final T value;
+
+ protected final boolean isNegated;
+
+ protected AbstractNameValueCondition(String expression) {
+ int separator = expression.indexOf('=');
+ if (separator == -1) {
+ this.isNegated = expression.startsWith("!");
+ this.name = isNegated ? expression.substring(1) : expression;
+ this.value = null;
+ }
+ else {
+ this.isNegated = (separator > 0) && (expression.charAt(separator - 1) == '!');
+ this.name = isNegated ? expression.substring(0, separator - 1) : expression.substring(0, separator);
+ this.value = parseValue(expression.substring(separator + 1));
+ }
+ }
+
+ protected abstract T parseValue(String valueExpression);
+
+ public final boolean match(HttpServletRequest request) {
+ boolean isMatch;
+ if (this.value != null) {
+ isMatch = matchValue(request);
+ }
+ else {
+ isMatch = matchName(request);
+ }
+ return isNegated ? !isMatch : isMatch;
+ }
+
+ protected abstract boolean matchName(HttpServletRequest request);
+
+ protected abstract boolean matchValue(HttpServletRequest request);
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ if (value != null) {
+ builder.append(name);
+ if (isNegated) {
+ builder.append('!');
+ }
+ builder.append('=');
+ builder.append(value);
+ }
+ else {
+ if (isNegated) {
+ builder.append('!');
+ }
+ builder.append(name);
+ }
+ return builder.toString();
+ }
+
+
+ @Override
+ public int hashCode() {
+ int result = name.hashCode();
+ result = 31 * result + (value != null ? value.hashCode() : 0);
+ result = 31 * result + (isNegated ? 1 : 0);
+ return result;
+ }
+ }
+
+ /**
+ * Request parameter name-value condition.
+ */
+ private static class ParamNameValueCondition extends AbstractNameValueCondition {
+
+ private ParamNameValueCondition(String expression) {
+ super(expression);
+ }
+
+ @Override
+ protected String parseValue(String valueExpression) {
+ return valueExpression;
+ }
+
+ @Override
+ protected boolean matchName(HttpServletRequest request) {
+ return WebUtils.hasSubmitParameter(request, name);
+ }
+
+ @Override
+ protected boolean matchValue(HttpServletRequest request) {
+ return value.equals(request.getParameter(name));
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj != null && obj instanceof ParamNameValueCondition) {
+ ParamNameValueCondition other = (ParamNameValueCondition) obj;
+ return ((this.name.equals(other.name)) &&
+ (this.value != null ? this.value.equals(other.value) : other.value == null) &&
+ this.isNegated == other.isNegated);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Request header name-value condition.
+ */
+ static class HeaderNameValueCondition extends AbstractNameValueCondition {
+
+ public HeaderNameValueCondition(String expression) {
+ super(expression);
+ }
+
+ @Override
+ protected String parseValue(String valueExpression) {
+ return valueExpression;
+ }
+
+ @Override
+ protected boolean matchName(HttpServletRequest request) {
+ return request.getHeader(name) != null;
+ }
+
+ @Override
+ final protected boolean matchValue(HttpServletRequest request) {
+ return value.equals(request.getHeader(name));
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj != null && obj instanceof HeaderNameValueCondition) {
+ HeaderNameValueCondition other = (HeaderNameValueCondition) obj;
+ return ((this.name.equalsIgnoreCase(other.name)) &&
+ (this.value != null ? this.value.equals(other.value) : other.value == null) &&
+ this.isNegated == other.isNegated);
+ }
+ return false;
+ }
+
+
+ }
+
+ /**
+ * A RequestCondition that for headers that contain {@link org.springframework.http.MediaType MediaTypes}.
+ */
+ private static class MediaTypeHeaderNameValueCondition extends AbstractNameValueCondition> {
+
+ public MediaTypeHeaderNameValueCondition(String expression) {
+ super(expression);
+ }
+
+ @Override
+ protected List parseValue(String valueExpression) {
+ return Collections.unmodifiableList(MediaType.parseMediaTypes(valueExpression));
+ }
+
+ @Override
+ protected boolean matchName(HttpServletRequest request) {
+ return request.getHeader(name) != null;
+ }
+
+ @Override
+ protected boolean matchValue(HttpServletRequest request) {
+ List requestMediaTypes = MediaType.parseMediaTypes(request.getHeader(name));
+
+ for (MediaType mediaType : this.value) {
+ for (MediaType requestMediaType : requestMediaTypes) {
+ if (mediaType.includes(requestMediaType)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj != null && obj instanceof MediaTypeHeaderNameValueCondition) {
+ MediaTypeHeaderNameValueCondition other = (MediaTypeHeaderNameValueCondition) obj;
+ return ((this.name.equalsIgnoreCase(other.name)) &&
+ (this.value != null ? this.value.equals(other.value) : other.value == null) &&
+ this.isNegated == other.isNegated);
+ }
+ return false;
+ }
+
+
+ }
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestKey.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestKey.java
new file mode 100644
index 0000000000..988c58ddff
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestKey.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.util.PathMatcher;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.util.UrlPathHelper;
+
+/**
+ * TODO
+ *
+ * @author Arjen Poutsma
+ * @author Rossen Stoyanchev
+ */
+public final class RequestKey {
+
+ private final Set patterns;
+
+ private final Set methods;
+
+ private final Set paramConditions;
+
+ private final Set headerConditions;
+
+ private int hash;
+
+ /**
+ * Creates a new {@code RequestKey} instance with the given parameters.
+ *
+ * Package protected for testing purposes.
+ */
+ RequestKey(Collection patterns,
+ Collection methods,
+ Collection paramConditions,
+ Collection headerConditions) {
+ this.patterns = asUnmodifiableSet(prependLeadingSlash(patterns));
+ this.methods = asUnmodifiableSet(methods);
+ this.paramConditions = asUnmodifiableSet(paramConditions);
+ this.headerConditions = asUnmodifiableSet(headerConditions);
+ }
+
+ private static Set prependLeadingSlash(Collection patterns) {
+ if (patterns == null) {
+ return Collections.emptySet();
+ }
+ Set result = new LinkedHashSet(patterns.size());
+ for (String pattern : patterns) {
+ if (!pattern.startsWith("/")) {
+ pattern = "/" + pattern;
+ }
+ result.add(pattern);
+ }
+ return result;
+ }
+
+ private static Set asUnmodifiableSet(Collection collection) {
+ if (collection == null) {
+ return Collections.emptySet();
+ }
+ Set result = new LinkedHashSet(collection);
+ return Collections.unmodifiableSet(result);
+ }
+
+ /**
+ * Creates a new {@code RequestKey} from a {@link RequestMapping @RequestMapping} annotation.
+ *
+ * @param annotation the annotation
+ * @return the request key created from the annotation
+ */
+ public static RequestKey createFromRequestMapping(RequestMapping annotation) {
+ return new RequestKey(Arrays.asList(annotation.value()), Arrays.asList(annotation.method()),
+ RequestConditionFactory.parseParams(annotation.params()),
+ RequestConditionFactory.parseHeaders(annotation.headers()));
+ }
+
+ /**
+ * Creates a new {@code RequestKey} from a {@link HttpServletRequest}.
+ *
+ * @param request the servlet request
+ * @param urlPathHelper to create the {@linkplain UrlPathHelper#getLookupPathForRequest(HttpServletRequest) lookup
+ * path}
+ * @return the request key created from the servlet request
+ */
+ public static RequestKey createFromServletRequest(HttpServletRequest request, UrlPathHelper urlPathHelper) {
+ String lookupPath = urlPathHelper.getLookupPathForRequest(request);
+ RequestMethod method = RequestMethod.valueOf(request.getMethod());
+ return new RequestKey(Arrays.asList(lookupPath), Arrays.asList(method), null, null);
+ }
+
+ /**
+ * Returns the patterns of this request key.
+ */
+ public Set getPatterns() {
+ return patterns;
+ }
+
+ /**
+ * Returns the request methods of this request key.
+ */
+ public Set getMethods() {
+ return methods;
+ }
+
+ /**
+ * Returns the request parameters of this request key.
+ */
+ public Set getParams() {
+ return paramConditions;
+ }
+
+ /**
+ * Returns the request headers of this request key.
+ */
+ public Set getHeaders() {
+ return headerConditions;
+ }
+
+ /**
+ * Creates a new {@code RequestKey} by combining it with another. The typical use case for this is combining type
+ * and method-level {@link RequestMapping @RequestMapping} annotations.
+ *
+ * @param methodKey the method-level RequestKey
+ * @param pathMatcher to {@linkplain PathMatcher#combine(String, String) combine} the patterns
+ * @return the combined request key
+ */
+ public RequestKey combine(RequestKey methodKey, PathMatcher pathMatcher) {
+ Set patterns = combinePatterns(this.patterns, methodKey.patterns, pathMatcher);
+ Set methods = union(this.methods, methodKey.methods);
+ Set params = union(this.paramConditions, methodKey.paramConditions);
+ Set headers = union(this.headerConditions, methodKey.headerConditions);
+
+ return new RequestKey(patterns, methods, params, headers);
+ }
+
+ private static Set combinePatterns(Collection typePatterns,
+ Collection methodPatterns,
+ PathMatcher pathMatcher) {
+ Set result = new LinkedHashSet();
+ if (!typePatterns.isEmpty() && !methodPatterns.isEmpty()) {
+ for (String pattern1 : typePatterns) {
+ for (String p2 : methodPatterns) {
+ result.add(pathMatcher.combine(pattern1, p2));
+ }
+ }
+ }
+ else if (!typePatterns.isEmpty()) {
+ result.addAll(typePatterns);
+ }
+ else if (!methodPatterns.isEmpty()) {
+ result.addAll(methodPatterns);
+ }
+ else {
+ result.add("");
+ }
+ return result;
+ }
+
+ private static Set union(Collection s1, Collection s2) {
+ Set union = new LinkedHashSet(s1);
+ union.addAll(s2);
+ return union;
+ }
+
+ /**
+ * Returns a new {@code RequestKey} that contains all matching attributes of this key, given the {@link
+ * HttpServletRequest}. Matching patterns in the returned RequestKey are sorted according to {@link
+ * PathMatcher#getPatternComparator(String)} with the best matching pattern at the top.
+ *
+ * @param request the servlet request
+ * @param pathMatcher to {@linkplain PathMatcher#match(String, String) match} patterns
+ * @param urlPathHelper to create the {@linkplain UrlPathHelper#getLookupPathForRequest(HttpServletRequest) lookup
+ * path}
+ * @return a new request key that contains all matching attributes
+ */
+ public RequestKey getMatchingKey(HttpServletRequest request, PathMatcher pathMatcher, UrlPathHelper urlPathHelper) {
+ if (!checkMethod(request) || !checkParams(request) || !checkHeaders(request)) {
+ return null;
+ }
+ else {
+ List matchingPatterns = getMatchingPatterns(request, pathMatcher, urlPathHelper);
+ if (!matchingPatterns.isEmpty()) {
+ Set matchingMethods = getMatchingMethods(request);
+ return new RequestKey(matchingPatterns, matchingMethods, this.paramConditions, this.headerConditions);
+ }
+ else {
+ return null;
+ }
+ }
+ }
+
+ private List getMatchingPatterns(HttpServletRequest request,
+ PathMatcher pathMatcher,
+ UrlPathHelper urlPathHelper) {
+ String lookupPath = urlPathHelper.getLookupPathForRequest(request);
+
+ List matchingPatterns = new ArrayList();
+ for (String pattern : this.patterns) {
+ String matchingPattern = getMatchingPattern(pattern, lookupPath, pathMatcher);
+ if (matchingPattern != null) {
+ matchingPatterns.add(matchingPattern);
+ }
+ }
+
+ Collections.sort(matchingPatterns, pathMatcher.getPatternComparator(lookupPath));
+
+ return matchingPatterns;
+ }
+
+ private Set getMatchingMethods(HttpServletRequest request) {
+ if (this.methods.isEmpty()) {
+ return this.methods;
+ }
+ else {
+ return Collections.singleton(RequestMethod.valueOf(request.getMethod()));
+ }
+ }
+
+ private boolean checkMethod(HttpServletRequest request) {
+ return methods.isEmpty() || methods.contains(RequestMethod.valueOf(request.getMethod()));
+ }
+
+ private boolean checkParams(HttpServletRequest request) {
+ return checkConditions(paramConditions, request);
+ }
+
+ private boolean checkHeaders(HttpServletRequest request) {
+ return checkConditions(headerConditions, request);
+ }
+
+ private String getMatchingPattern(String pattern, String lookupPath, PathMatcher pathMatcher) {
+ if (pattern.equals(lookupPath) || pathMatcher.match(pattern, lookupPath)) {
+ return pattern;
+ }
+ boolean hasSuffix = pattern.indexOf('.') != -1;
+ if (!hasSuffix && pathMatcher.match(pattern + ".*", lookupPath)) {
+ return pattern + ".*";
+ }
+ boolean endsWithSlash = pattern.endsWith("/");
+ if (!endsWithSlash && pathMatcher.match(pattern + "/", lookupPath)) {
+ return pattern +"/";
+ }
+ return null;
+ }
+
+ private static boolean checkConditions(Set conditions, HttpServletRequest request) {
+ for (RequestCondition condition : conditions) {
+ if (!condition.match(request)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj != null && obj instanceof RequestKey) {
+ RequestKey other = (RequestKey) obj;
+ return (this.patterns.equals(other.patterns) && this.methods.equals(other.methods) &&
+ this.paramConditions.equals(other.paramConditions) &&
+ this.headerConditions.equals(other.headerConditions));
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = hash;
+ if (result == 0) {
+ result = patterns.hashCode();
+ result = 31 * result + methods.hashCode();
+ result = 31 * result + paramConditions.hashCode();
+ result = 31 * result + headerConditions.hashCode();
+ hash = result;
+ }
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("{");
+ builder.append(patterns);
+ if (!methods.isEmpty()) {
+ builder.append(',');
+ builder.append(methods);
+ }
+ if (!headerConditions.isEmpty()) {
+ builder.append(',');
+ builder.append(headerConditions);
+ }
+ if (!paramConditions.isEmpty()) {
+ builder.append(',');
+ builder.append(paramConditions);
+ }
+ builder.append('}');
+ return builder.toString();
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
new file mode 100644
index 0000000000..efb199ce81
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.xml.transform.Source;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.http.converter.ByteArrayHttpMessageConverter;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.http.converter.xml.SourceHttpMessageConverter;
+import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
+import org.springframework.ui.ModelMap;
+import org.springframework.util.ReflectionUtils.MethodFilter;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.support.DefaultDataBinderFactory;
+import org.springframework.web.bind.support.DefaultSessionAttributeStore;
+import org.springframework.web.bind.support.SessionAttributeStore;
+import org.springframework.web.bind.support.SessionStatus;
+import org.springframework.web.bind.support.SimpleSessionStatus;
+import org.springframework.web.bind.support.WebArgumentResolver;
+import org.springframework.web.bind.support.WebBindingInitializer;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.ServletWebRequest;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.method.HandlerMethodSelector;
+import org.springframework.web.method.annotation.ModelFactory;
+import org.springframework.web.method.annotation.SessionAttributesHandler;
+import org.springframework.web.method.annotation.support.ErrorsMethodArgumentResolver;
+import org.springframework.web.method.annotation.support.ExpressionValueMethodArgumentResolver;
+import org.springframework.web.method.annotation.support.ModelAttributeMethodProcessor;
+import org.springframework.web.method.annotation.support.ModelMethodProcessor;
+import org.springframework.web.method.annotation.support.RequestHeaderMapMethodArgumentResolver;
+import org.springframework.web.method.annotation.support.RequestHeaderMethodArgumentResolver;
+import org.springframework.web.method.annotation.support.RequestParamMapMethodArgumentResolver;
+import org.springframework.web.method.annotation.support.RequestParamMethodArgumentResolver;
+import org.springframework.web.method.annotation.support.WebArgumentResolverAdapter;
+import org.springframework.web.method.support.HandlerMethodArgumentResolverContainer;
+import org.springframework.web.method.support.HandlerMethodReturnValueHandlerContainer;
+import org.springframework.web.method.support.InvocableHandlerMethod;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver;
+import org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter;
+import org.springframework.web.servlet.mvc.method.annotation.support.DefaultMethodReturnValueHandler;
+import org.springframework.web.servlet.mvc.method.annotation.support.HttpEntityMethodProcessor;
+import org.springframework.web.servlet.mvc.method.annotation.support.ModelAndViewMethodReturnValueHandler;
+import org.springframework.web.servlet.mvc.method.annotation.support.PathVariableMethodArgumentResolver;
+import org.springframework.web.servlet.mvc.method.annotation.support.RequestResponseBodyMethodProcessor;
+import org.springframework.web.servlet.mvc.method.annotation.support.ServletCookieValueMethodArgumentResolver;
+import org.springframework.web.servlet.mvc.method.annotation.support.ServletModelAttributeMethodProcessor;
+import org.springframework.web.servlet.mvc.method.annotation.support.ServletRequestMethodArgumentResolver;
+import org.springframework.web.servlet.mvc.method.annotation.support.ServletResponseMethodArgumentResolver;
+import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodReturnValueHandler;
+import org.springframework.web.util.WebUtils;
+
+/**
+ * An extension of {@link AbstractHandlerMethodAdapter} with support for {@link RequestMapping} handler methods.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ */
+public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware,
+ InitializingBean {
+
+ private WebArgumentResolver[] customArgumentResolvers;
+
+ private ModelAndViewResolver[] customModelAndViewResolvers;
+
+ private HttpMessageConverter>[] messageConverters;
+
+ private WebBindingInitializer webBindingInitializer;
+
+ private int cacheSecondsForSessionAttributeHandlers = 0;
+
+ private boolean synchronizeOnSession = false;
+
+ private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
+
+ private ConfigurableBeanFactory beanFactory;
+
+ private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();
+
+ private final Map, SessionAttributesHandler> sessionAttributesHandlerCache =
+ new ConcurrentHashMap, SessionAttributesHandler>();
+
+ private final Map, Set> modelAttributeMethodCache = new ConcurrentHashMap, Set>();
+
+ private final Map, Set> initBinderMethodCache = new ConcurrentHashMap, Set>();
+
+ private final HandlerMethodReturnValueHandlerContainer returnValueHandlers = new HandlerMethodReturnValueHandlerContainer();
+
+ private final HandlerMethodArgumentResolverContainer requestMethodArgResolvers = new HandlerMethodArgumentResolverContainer();
+
+ private final HandlerMethodArgumentResolverContainer binderMethodArgResolvers = new HandlerMethodArgumentResolverContainer();
+
+ /**
+ * Create a {@link RequestMappingHandlerAdapter} instance.
+ */
+ public RequestMappingHandlerAdapter() {
+
+ StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
+ stringHttpMessageConverter.setWriteAcceptCharset(false); // See SPR-7316
+
+ this.messageConverters = new HttpMessageConverter[] { new ByteArrayHttpMessageConverter(),
+ stringHttpMessageConverter, new SourceHttpMessageConverter(),
+ new XmlAwareFormHttpMessageConverter() };
+ }
+
+ /**
+ * Set a custom WebArgumentResolvers to use for special method parameter types.
+ *
Such a custom WebArgumentResolver will kick in first, having a chance to resolve
+ * an argument value before the standard argument handling kicks in.
+ */
+ public void setCustomArgumentResolver(WebArgumentResolver argumentResolver) {
+ this.customArgumentResolvers = new WebArgumentResolver[] {argumentResolver};
+ }
+
+ /**
+ * Set one or more custom WebArgumentResolvers to use for special method parameter types.
+ *
Any such custom WebArgumentResolver will kick in first, having a chance to resolve
+ * an argument value before the standard argument handling kicks in.
+ */
+ public void setCustomArgumentResolvers(WebArgumentResolver[] argumentResolvers) {
+ this.customArgumentResolvers = argumentResolvers;
+ }
+
+ /**
+ * Set a custom ModelAndViewResolvers to use for special method return types.
+ *
Such a custom ModelAndViewResolver will kick in first, having a chance to resolve
+ * a return value before the standard ModelAndView handling kicks in.
+ */
+ public void setCustomModelAndViewResolver(ModelAndViewResolver customModelAndViewResolver) {
+ this.customModelAndViewResolvers = new ModelAndViewResolver[] {customModelAndViewResolver};
+ }
+
+ /**
+ * Set one or more custom ModelAndViewResolvers to use for special method return types.
+ *
Any such custom ModelAndViewResolver will kick in first, having a chance to resolve
+ * a return value before the standard ModelAndView handling kicks in.
+ */
+ public void setCustomModelAndViewResolvers(ModelAndViewResolver[] customModelAndViewResolvers) {
+ this.customModelAndViewResolvers = customModelAndViewResolvers;
+ }
+
+ /**
+ * Set the message body converters to use.
+ *
These converters are used to convert from and to HTTP requests and responses.
+ */
+ public void setMessageConverters(HttpMessageConverter>[] messageConverters) {
+ this.messageConverters = messageConverters;
+ }
+
+ /**
+ * Specify a WebBindingInitializer which will apply pre-configured
+ * configuration to every DataBinder that this controller uses.
+ */
+ public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
+ this.webBindingInitializer = webBindingInitializer;
+ }
+
+ /**
+ * Specify the strategy to store session attributes with.
+ *
Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
+ * storing session attributes in the HttpSession, using the same attribute name as in the model.
+ */
+ public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
+ this.sessionAttributeStore = sessionAttributeStore;
+ }
+
+ /**
+ * Cache content produced by @SessionAttributes annotated handlers
+ * for the given number of seconds. Default is 0, preventing caching completely.
+ *
In contrast to the "cacheSeconds" property which will apply to all general handlers
+ * (but not to @SessionAttributes annotated handlers), this setting will
+ * apply to @SessionAttributes annotated handlers only.
+ * @see #setCacheSeconds
+ * @see org.springframework.web.bind.annotation.SessionAttributes
+ */
+ public void setCacheSecondsForSessionAttributeHandlers(int cacheSecondsForSessionAttributeHandlers) {
+ this.cacheSecondsForSessionAttributeHandlers = cacheSecondsForSessionAttributeHandlers;
+ }
+
+ /**
+ * Set if controller execution should be synchronized on the session,
+ * to serialize parallel invocations from the same client.
+ *
More specifically, the execution of the handleRequestInternal
+ * method will get synchronized if this flag is "true". The best available
+ * session mutex will be used for the synchronization; ideally, this will
+ * be a mutex exposed by HttpSessionMutexListener.
+ *
The session mutex is guaranteed to be the same object during
+ * the entire lifetime of the session, available under the key defined
+ * by the SESSION_MUTEX_ATTRIBUTE constant. It serves as a
+ * safe reference to synchronize on for locking on the current session.
+ *
In many cases, the HttpSession reference itself is a safe mutex
+ * as well, since it will always be the same object reference for the
+ * same active logical session. However, this is not guaranteed across
+ * different servlet containers; the only 100% safe way is a session mutex.
+ * @see org.springframework.web.util.HttpSessionMutexListener
+ * @see org.springframework.web.util.WebUtils#getSessionMutex(javax.servlet.http.HttpSession)
+ */
+ public void setSynchronizeOnSession(boolean synchronizeOnSession) {
+ this.synchronizeOnSession = synchronizeOnSession;
+ }
+
+ /**
+ * Set the ParameterNameDiscoverer to use for resolving method parameter names if needed
+ * (e.g. for default attribute names).
+ *
Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
+ */
+ public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
+ this.parameterNameDiscoverer = parameterNameDiscoverer;
+ }
+
+ public void setBeanFactory(BeanFactory beanFactory) {
+ if (beanFactory instanceof ConfigurableBeanFactory) {
+ this.beanFactory = (ConfigurableBeanFactory) beanFactory;
+ }
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ initRequestMethodArgResolvers();
+ initBinderMethodArgResolvers();
+ initReturnValueHandlers();
+ }
+
+ private void initRequestMethodArgResolvers() {
+ requestMethodArgResolvers.registerArgumentResolver(new RequestParamMethodArgumentResolver(beanFactory, false));
+ requestMethodArgResolvers.registerArgumentResolver(new RequestParamMapMethodArgumentResolver());
+ requestMethodArgResolvers.registerArgumentResolver(new PathVariableMethodArgumentResolver(beanFactory));
+ requestMethodArgResolvers.registerArgumentResolver(new ServletModelAttributeMethodProcessor(false));
+ requestMethodArgResolvers.registerArgumentResolver(new RequestResponseBodyMethodProcessor(messageConverters));
+ requestMethodArgResolvers.registerArgumentResolver(new RequestHeaderMethodArgumentResolver(beanFactory));
+ requestMethodArgResolvers.registerArgumentResolver(new RequestHeaderMapMethodArgumentResolver());
+ requestMethodArgResolvers.registerArgumentResolver(new ServletCookieValueMethodArgumentResolver(beanFactory));
+ requestMethodArgResolvers.registerArgumentResolver(new ExpressionValueMethodArgumentResolver(beanFactory));
+
+ if (customArgumentResolvers != null) {
+ for (WebArgumentResolver customResolver : customArgumentResolvers) {
+ requestMethodArgResolvers.registerArgumentResolver(new WebArgumentResolverAdapter(customResolver));
+ }
+ }
+
+ requestMethodArgResolvers.registerArgumentResolver(new ServletRequestMethodArgumentResolver());
+ requestMethodArgResolvers.registerArgumentResolver(new ServletResponseMethodArgumentResolver());
+ requestMethodArgResolvers.registerArgumentResolver(new HttpEntityMethodProcessor(messageConverters));
+ requestMethodArgResolvers.registerArgumentResolver(new ModelMethodProcessor());
+ requestMethodArgResolvers.registerArgumentResolver(new ErrorsMethodArgumentResolver());
+ requestMethodArgResolvers.registerArgumentResolver(new RequestParamMethodArgumentResolver(beanFactory, true));
+ requestMethodArgResolvers.registerArgumentResolver(new ServletModelAttributeMethodProcessor(true));
+ }
+
+ private void initBinderMethodArgResolvers() {
+ binderMethodArgResolvers.registerArgumentResolver(new RequestParamMethodArgumentResolver(beanFactory, false));
+ binderMethodArgResolvers.registerArgumentResolver(new RequestParamMapMethodArgumentResolver());
+ binderMethodArgResolvers.registerArgumentResolver(new PathVariableMethodArgumentResolver(beanFactory));
+ binderMethodArgResolvers.registerArgumentResolver(new ExpressionValueMethodArgumentResolver(beanFactory));
+
+ if (customArgumentResolvers != null) {
+ for (WebArgumentResolver customResolver : customArgumentResolvers) {
+ binderMethodArgResolvers.registerArgumentResolver(new WebArgumentResolverAdapter(customResolver));
+ }
+ }
+
+ binderMethodArgResolvers.registerArgumentResolver(new ServletRequestMethodArgumentResolver());
+ binderMethodArgResolvers.registerArgumentResolver(new ServletResponseMethodArgumentResolver());
+ binderMethodArgResolvers.registerArgumentResolver(new RequestParamMethodArgumentResolver(beanFactory, true));
+ }
+
+ private void initReturnValueHandlers() {
+ returnValueHandlers.registerReturnValueHandler(new RequestResponseBodyMethodProcessor(messageConverters));
+ returnValueHandlers.registerReturnValueHandler(new ModelAttributeMethodProcessor(false));
+ returnValueHandlers.registerReturnValueHandler(new ModelAndViewMethodReturnValueHandler());
+ returnValueHandlers.registerReturnValueHandler(new ModelMethodProcessor());
+ returnValueHandlers.registerReturnValueHandler(new ViewMethodReturnValueHandler());
+ returnValueHandlers.registerReturnValueHandler(new HttpEntityMethodProcessor(messageConverters));
+ returnValueHandlers.registerReturnValueHandler(new DefaultMethodReturnValueHandler(customModelAndViewResolvers));
+ }
+
+ @Override
+ protected boolean supportsInternal(HandlerMethod handlerMethod) {
+ return supportsMethodParameters(handlerMethod.getMethodParameters()) &&
+ supportsReturnType(handlerMethod.getReturnType());
+ }
+
+ private boolean supportsMethodParameters(MethodParameter[] methodParameters) {
+ for (MethodParameter methodParameter : methodParameters) {
+ if (! this.requestMethodArgResolvers.supportsParameter(methodParameter)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean supportsReturnType(MethodParameter methodReturnType) {
+ return (this.returnValueHandlers.supportsReturnType(methodReturnType) ||
+ Void.TYPE.equals(methodReturnType.getParameterType()));
+ }
+
+ @Override
+ protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) {
+ return -1;
+ }
+
+ @Override
+ protected final ModelAndView handleInternal(HttpServletRequest request,
+ HttpServletResponse response,
+ HandlerMethod handlerMethod) throws Exception {
+
+ if (hasSessionAttributes(handlerMethod.getBeanType())) {
+ // Always prevent caching in case of session attribute management.
+ checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
+ }
+ else {
+ // Uses configured default cacheSeconds setting.
+ checkAndPrepare(request, response, true);
+ }
+
+ // Execute invokeHandlerMethod in synchronized block if required.
+ if (this.synchronizeOnSession) {
+ HttpSession session = request.getSession(false);
+ if (session != null) {
+ Object mutex = WebUtils.getSessionMutex(session);
+ synchronized (mutex) {
+ return invokeHandlerMethod(request, response, handlerMethod);
+ }
+ }
+ }
+
+ return invokeHandlerMethod(request, response, handlerMethod);
+ }
+
+ private boolean hasSessionAttributes(Class> handlerType) {
+ SessionAttributesHandler handler = null;
+ synchronized(this.sessionAttributesHandlerCache) {
+ handler = this.sessionAttributesHandlerCache.get(handlerType);
+ if (handler == null) {
+ handler = new SessionAttributesHandler(handlerType, sessionAttributeStore);
+ this.sessionAttributesHandlerCache.put(handlerType, handler);
+ }
+ }
+ return handler.hasSessionAttributes();
+ }
+
+ private ModelAndView invokeHandlerMethod(HttpServletRequest request,
+ HttpServletResponse response,
+ HandlerMethod handlerMethod) throws Exception {
+
+ WebDataBinderFactory binderFactory = createDataBinderFactory(handlerMethod);
+ ModelFactory modelFactory = createModelFactory(handlerMethod, binderFactory);
+ ServletInvocableHandlerMethod requestMethod = createRequestMappingMethod(handlerMethod, binderFactory);
+
+ ServletWebRequest webRequest = new ServletWebRequest(request, response);
+ SessionStatus sessionStatus = new SimpleSessionStatus();
+
+ ModelMap implicitModel = modelFactory.createModel(webRequest, requestMethod);
+ ModelAndView mav = requestMethod.invokeAndHandle(webRequest, implicitModel, sessionStatus);
+
+ ModelMap actualModel = (mav != null) ? mav.getModelMap() : null;
+ modelFactory.updateAttributes(webRequest, sessionStatus, actualModel, implicitModel);
+
+ return mav;
+ }
+
+ private WebDataBinderFactory createDataBinderFactory(HandlerMethod handlerMethod) {
+ List initBinderMethods = new ArrayList();
+
+ Class> handlerType = handlerMethod.getBeanType();
+ Set binderMethods = initBinderMethodCache.get(handlerType);
+ if (binderMethods == null) {
+ binderMethods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS);
+ initBinderMethodCache.put(handlerType, binderMethods);
+ }
+
+ for (Method method : binderMethods) {
+ Object bean = handlerMethod.getBean();
+ InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(bean, method);
+ binderMethod.setArgumentResolverContainer(this.binderMethodArgResolvers);
+ binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer));
+ binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
+
+ initBinderMethods.add(binderMethod);
+ }
+
+ return new ServletInitBinderMethodDataBinderFactory(initBinderMethods, this.webBindingInitializer);
+ }
+
+ private ModelFactory createModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
+ List modelAttrMethods = new ArrayList();
+
+ Class> handlerType = handlerMethod.getBeanType();
+ Set attributeMethods = modelAttributeMethodCache.get(handlerType);
+ if (attributeMethods == null) {
+ attributeMethods = HandlerMethodSelector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
+ modelAttributeMethodCache.put(handlerType, attributeMethods);
+ }
+
+ for (Method method : attributeMethods) {
+ InvocableHandlerMethod attrMethod = new InvocableHandlerMethod(handlerMethod.getBean(), method);
+ attrMethod.setArgumentResolverContainer(this.requestMethodArgResolvers);
+ attrMethod.setDataBinderFactory(binderFactory);
+ attrMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
+ modelAttrMethods.add(attrMethod);
+ }
+
+ return new ModelFactory(modelAttrMethods, binderFactory, sessionAttributesHandlerCache.get(handlerType));
+ }
+
+ private ServletInvocableHandlerMethod createRequestMappingMethod(HandlerMethod handlerMethod,
+ WebDataBinderFactory binderFactory) {
+ Method method = handlerMethod.getMethod();
+ ServletInvocableHandlerMethod requestMethod = new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
+ requestMethod.setArgumentResolverContainer(this.requestMethodArgResolvers);
+ requestMethod.setReturnValueHandlers(this.returnValueHandlers);
+ requestMethod.setDataBinderFactory(binderFactory);
+ requestMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
+ return requestMethod;
+ }
+
+ /**
+ * MethodFilter that matches {@link InitBinder @InitBinder} methods.
+ */
+ public static MethodFilter INIT_BINDER_METHODS = new MethodFilter() {
+
+ public boolean matches(Method method) {
+ return AnnotationUtils.findAnnotation(method, InitBinder.class) != null;
+ }
+ };
+
+ /**
+ * MethodFilter that matches {@link ModelAttribute @ModelAttribute} methods.
+ */
+ public static MethodFilter MODEL_ATTRIBUTE_METHODS = new MethodFilter() {
+
+ public boolean matches(Method method) {
+ return ((AnnotationUtils.findAnnotation(method, RequestMapping.class) == null) &&
+ (AnnotationUtils.findAnnotation(method, ModelAttribute.class) != null));
+ }
+ };
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodExceptionResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodExceptionResolver.java
new file mode 100644
index 0000000000..d77eb37a54
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodExceptionResolver.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.transform.Source;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.http.converter.ByteArrayHttpMessageConverter;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.http.converter.xml.SourceHttpMessageConverter;
+import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
+import org.springframework.ui.ExtendedModelMap;
+import org.springframework.ui.ModelMap;
+import org.springframework.util.ReflectionUtils.MethodFilter;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.support.WebArgumentResolver;
+import org.springframework.web.context.request.ServletWebRequest;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.method.HandlerMethodSelector;
+import org.springframework.web.method.annotation.ExceptionMethodMapping;
+import org.springframework.web.method.annotation.support.ModelAttributeMethodProcessor;
+import org.springframework.web.method.annotation.support.ModelMethodProcessor;
+import org.springframework.web.method.annotation.support.WebArgumentResolverAdapter;
+import org.springframework.web.method.support.HandlerMethodArgumentResolverContainer;
+import org.springframework.web.method.support.HandlerMethodReturnValueHandlerContainer;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver;
+import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver;
+import org.springframework.web.servlet.mvc.method.annotation.support.DefaultMethodReturnValueHandler;
+import org.springframework.web.servlet.mvc.method.annotation.support.HttpEntityMethodProcessor;
+import org.springframework.web.servlet.mvc.method.annotation.support.ModelAndViewMethodReturnValueHandler;
+import org.springframework.web.servlet.mvc.method.annotation.support.RequestResponseBodyMethodProcessor;
+import org.springframework.web.servlet.mvc.method.annotation.support.ServletRequestMethodArgumentResolver;
+import org.springframework.web.servlet.mvc.method.annotation.support.ServletResponseMethodArgumentResolver;
+import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodReturnValueHandler;
+
+/**
+ * An extension of {@link AbstractHandlerMethodExceptionResolver} that matches thrown exceptions to
+ * {@link ExceptionHandler @ExceptionHandler} methods in the handler. If a match is found the
+ * exception-handling method is invoked to process the request.
+ *
+ *
See {@link ExceptionHandler} for information on supported method arguments and return values
+ * for exception-handling methods. You can customize method argument resolution and return value
+ * processing through the various bean properties in this class.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ * @see #setCustomArgumentResolvers(WebArgumentResolver[])
+ * @see #setCustomModelAndViewResolvers(ModelAndViewResolver[])
+ * @see #setMessageConverters(HttpMessageConverter[])
+ */
+public class RequestMappingHandlerMethodExceptionResolver extends AbstractHandlerMethodExceptionResolver implements
+ InitializingBean {
+
+ private WebArgumentResolver[] customArgumentResolvers;
+
+ private HttpMessageConverter>[] messageConverters;
+
+ private ModelAndViewResolver[] customModelAndViewResolvers;
+
+ private final Map, ExceptionMethodMapping> exceptionMethodMappingCache =
+ new ConcurrentHashMap, ExceptionMethodMapping>();
+
+ private final HandlerMethodArgumentResolverContainer argumentResolvers = new HandlerMethodArgumentResolverContainer();
+
+ private final HandlerMethodReturnValueHandlerContainer returnValueHandlers = new HandlerMethodReturnValueHandlerContainer();
+
+ /**
+ * Creates an instance of {@link RequestMappingHandlerMethodExceptionResolver}.
+ */
+ public RequestMappingHandlerMethodExceptionResolver() {
+
+ StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
+ stringHttpMessageConverter.setWriteAcceptCharset(false); // See SPR-7316
+
+ this.messageConverters = new HttpMessageConverter[] { new ByteArrayHttpMessageConverter(),
+ stringHttpMessageConverter, new SourceHttpMessageConverter(),
+ new XmlAwareFormHttpMessageConverter() };
+ }
+
+ /**
+ * Set a custom ArgumentResolvers to use for special method parameter types.
+ *
Such a custom ArgumentResolver will kick in first, having a chance to resolve
+ * an argument value before the standard argument handling kicks in.
+ */
+ public void setCustomArgumentResolver(WebArgumentResolver argumentResolver) {
+ this.customArgumentResolvers = new WebArgumentResolver[]{argumentResolver};
+ }
+
+ /**
+ * Set one or more custom ArgumentResolvers to use for special method parameter types.
+ *
Any such custom ArgumentResolver will kick in first, having a chance to resolve
+ * an argument value before the standard argument handling kicks in.
+ */
+ public void setCustomArgumentResolvers(WebArgumentResolver[] argumentResolvers) {
+ this.customArgumentResolvers = argumentResolvers;
+ }
+
+ /**
+ * Set the message body converters to use.
+ *
These converters are used to convert from and to HTTP requests and responses.
+ */
+ public void setMessageConverters(HttpMessageConverter>[] messageConverters) {
+ this.messageConverters = messageConverters;
+ }
+
+ /**
+ * Set a custom ModelAndViewResolvers to use for special method return types.
+ *
Such a custom ModelAndViewResolver will kick in first, having a chance to resolve
+ * a return value before the standard ModelAndView handling kicks in.
+ */
+ public void setCustomModelAndViewResolver(ModelAndViewResolver customModelAndViewResolver) {
+ this.customModelAndViewResolvers = new ModelAndViewResolver[] {customModelAndViewResolver};
+ }
+
+ /**
+ * Set one or more custom ModelAndViewResolvers to use for special method return types.
+ *
Any such custom ModelAndViewResolver will kick in first, having a chance to resolve
+ * a return value before the standard ModelAndView handling kicks in.
+ */
+ public void setCustomModelAndViewResolvers(ModelAndViewResolver[] customModelAndViewResolvers) {
+ this.customModelAndViewResolvers = customModelAndViewResolvers;
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ if (customArgumentResolvers != null) {
+ for (WebArgumentResolver customResolver : customArgumentResolvers) {
+ argumentResolvers.registerArgumentResolver(new WebArgumentResolverAdapter(customResolver));
+ }
+ }
+
+ argumentResolvers.registerArgumentResolver(new ServletRequestMethodArgumentResolver());
+ argumentResolvers.registerArgumentResolver(new ServletResponseMethodArgumentResolver());
+
+ returnValueHandlers.registerReturnValueHandler(new RequestResponseBodyMethodProcessor(messageConverters));
+ returnValueHandlers.registerReturnValueHandler(new ModelAttributeMethodProcessor(false));
+ returnValueHandlers.registerReturnValueHandler(new ModelAndViewMethodReturnValueHandler());
+ returnValueHandlers.registerReturnValueHandler(new ModelMethodProcessor());
+ returnValueHandlers.registerReturnValueHandler(new ViewMethodReturnValueHandler());
+ returnValueHandlers.registerReturnValueHandler(new HttpEntityMethodProcessor(messageConverters));
+ returnValueHandlers.registerReturnValueHandler(new DefaultMethodReturnValueHandler(customModelAndViewResolvers));
+ }
+
+ @Override
+ protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
+ HttpServletResponse response,
+ HandlerMethod handlerMethod,
+ Exception ex) {
+ if (handlerMethod != null) {
+ ExceptionMethodMapping mapping = getExceptionMethodMapping(handlerMethod);
+ Method method = mapping.getMethod(ex);
+
+ if (method != null) {
+ Object handler = handlerMethod.getBean();
+ ServletInvocableHandlerMethod exceptionHandler = new ServletInvocableHandlerMethod(handler, method);
+ exceptionHandler.setArgumentResolverContainer(argumentResolvers);
+ exceptionHandler.setReturnValueHandlers(returnValueHandlers);
+
+ ServletWebRequest webRequest = new ServletWebRequest(request, response);
+ ModelMap model = new ExtendedModelMap();
+ try {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Invoking exception-handling method: " + exceptionHandler);
+ }
+ ModelAndView mav = exceptionHandler.invokeAndHandle(webRequest , model , ex);
+ return (mav != null) ? mav : new ModelAndView();
+ }
+ catch (Exception invocationEx) {
+ logger.error("Invoking exception-handling method resulted in exception : " +
+ exceptionHandler, invocationEx);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private ExceptionMethodMapping getExceptionMethodMapping(HandlerMethod handlerMethod) {
+ Class> handlerType = handlerMethod.getBeanType();
+ ExceptionMethodMapping mapping = exceptionMethodMappingCache.get(handlerType);
+ if (mapping == null) {
+ Set methods = HandlerMethodSelector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS);
+ mapping = new ExceptionMethodMapping(methods);
+ exceptionMethodMappingCache.put(handlerType, mapping);
+ }
+ return mapping;
+ }
+
+ /**
+ * Pre-built MethodFilter that matches {@link ExceptionHandler @ExceptionHandler} methods.
+ */
+ public static MethodFilter EXCEPTION_HANDLER_METHODS = new MethodFilter() {
+
+ public boolean matches(Method method) {
+ return AnnotationUtils.findAnnotation(method, ExceptionHandler.class) != null;
+ }
+ };
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMapping.java
new file mode 100644
index 0000000000..8e6a5d89e4
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodMapping.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation;
+
+import java.lang.reflect.Method;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Controller;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.Assert;
+import org.springframework.util.PathMatcher;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerExecutionChain;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.HandlerMapping;
+import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
+import org.springframework.web.servlet.handler.MappedInterceptor;
+import org.springframework.web.servlet.handler.MappedInterceptors;
+import org.springframework.web.util.UrlPathHelper;
+
+/**
+ * TODO
+ *
+ * @author Arjen Poutsma
+ * @author Rossen Stoyanchev
+ * @since 3.1.0
+ */
+public class RequestMappingHandlerMethodMapping extends AbstractHandlerMethodMapping {
+
+ private UrlPathHelper urlPathHelper = new UrlPathHelper();
+
+ private PathMatcher pathMatcher = new AntPathMatcher();
+
+ private MappedInterceptors mappedInterceptors;
+
+ /**
+ * Set if URL lookup should always use the full path within the current servlet context. Else, the path within the
+ * current servlet mapping is used if applicable (that is, in the case of a ".../*" servlet mapping in web.xml).
+ *
Default is "false".
+ *
+ * @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
+ */
+ public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
+ this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
+ }
+
+ /**
+ * Set if context path and request URI should be URL-decoded. Both are returned undecoded by the Servlet API, in
+ * contrast to the servlet path.
Uses either the request encoding or the default encoding according to the Servlet
+ * spec (ISO-8859-1).
+ *
+ * @see org.springframework.web.util.UrlPathHelper#setUrlDecode
+ */
+ public void setUrlDecode(boolean urlDecode) {
+ this.urlPathHelper.setUrlDecode(urlDecode);
+ }
+
+ /**
+ * Set the UrlPathHelper to use for resolution of lookup paths.
Use this to override the default UrlPathHelper
+ * with a custom subclass, or to share common UrlPathHelper settings across multiple HandlerMappings and
+ * MethodNameResolvers.
+ *
+ */
+ public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
+ Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
+ this.urlPathHelper = urlPathHelper;
+ }
+
+ /**
+ * Set the PathMatcher implementation to use for matching URL paths against registered URL patterns. Default is
+ * AntPathMatcher.
+ *
+ * @see org.springframework.util.AntPathMatcher
+ */
+ public void setPathMatcher(PathMatcher pathMatcher) {
+ Assert.notNull(pathMatcher, "PathMatcher must not be null");
+ this.pathMatcher = pathMatcher;
+ }
+
+ /**
+ * Set the {@link MappedInterceptor} instances to use to intercept handler method invocations.
+ */
+ public void setMappedInterceptors(MappedInterceptor[] mappedInterceptors) {
+ this.mappedInterceptors = new MappedInterceptors(mappedInterceptors);
+ }
+
+ @Override
+ protected void initInterceptors() {
+ super.initInterceptors();
+ if (this.mappedInterceptors == null) {
+ this.mappedInterceptors = MappedInterceptors.createFromDeclaredBeans(getApplicationContext());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * The handler determination is made based on the presence of a type-level {@link Controller} or
+ * a type-level {@link RequestMapping} annotation.
+ */
+ @Override
+ protected boolean isHandler(String beanName) {
+ return ((getApplicationContext().findAnnotationOnBean(beanName, RequestMapping.class) != null) ||
+ (getApplicationContext().findAnnotationOnBean(beanName, Controller.class) != null));
+ }
+
+ /**
+ * Returns a {@link RequestKey} instances that represents the given HTTP servlet request.
+ *
+ * @param request the request to look up the key for
+ * @return the key, never null
+ */
+ @Override
+ protected RequestKey getKeyForRequest(HttpServletRequest request) {
+ return RequestKey.createFromServletRequest(request, urlPathHelper);
+ }
+
+ /**
+ * Provides a {@link RequestKey} for the given method.
+ *
Only {@link RequestMapping @RequestMapping}-annotated methods are considered.
+ * Type-level {@link RequestMapping @RequestMapping} annotations are also detected and their
+ * attributes combined with method-level {@link RequestMapping @RequestMapping} attributes.
+ *
+ * @param method the method to create a key for
+ * @return the key, or {@code null}
+ * @see RequestKey#combine(RequestKey, PathMatcher)
+ */
+ @Override
+ protected RequestKey getKeyForMethod(Method method) {
+ RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
+ if (annotation != null) {
+ RequestKey methodKey = RequestKey.createFromRequestMapping(annotation);
+ RequestMapping typeAnnot = AnnotationUtils.findAnnotation(method.getDeclaringClass(), RequestMapping.class);
+ if (typeAnnot != null) {
+ RequestKey typeKey = RequestKey.createFromRequestMapping(typeAnnot);
+ return typeKey.combine(methodKey, pathMatcher);
+ }
+ else {
+ return methodKey;
+ }
+ }
+ else {
+ return null;
+ }
+ }
+
+ @Override
+ protected Comparator getKeyComparator(HttpServletRequest request) {
+ return new RequestKeyComparator(request);
+ }
+
+ @Override
+ protected void handleMatch(RequestKey key, HttpServletRequest request) {
+ String pattern = key.getPatterns().iterator().next();
+ String lookupPath = urlPathHelper.getLookupPathForRequest(request);
+ Map uriTemplateVariables = pathMatcher.extractUriTemplateVariables(pattern, lookupPath);
+ request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
+ }
+
+ @Override
+ protected RequestKey getMatchingKey(RequestKey key, HttpServletRequest request) {
+ return key.getMatchingKey(request, pathMatcher, urlPathHelper);
+ }
+
+ @Override
+ protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
+ HandlerExecutionChain chain = super.getHandlerExecutionChain(handler, request);
+ if (this.mappedInterceptors != null) {
+ String lookupPath = urlPathHelper.getLookupPathForRequest(request);
+ HandlerInterceptor[] handlerInterceptors = mappedInterceptors.getInterceptors(lookupPath, pathMatcher);
+ if (handlerInterceptors.length > 0) {
+ chain.addInterceptors(handlerInterceptors);
+ }
+ }
+ return chain;
+ }
+
+ @Override
+ protected HandlerMethod handleNoMatch(Set requestKeys, HttpServletRequest request)
+ throws HttpRequestMethodNotSupportedException {
+ String lookupPath = urlPathHelper.getLookupPathForRequest(request);
+ Set allowedMethods = new HashSet(6);
+ for (RequestKey requestKey : requestKeys) {
+ for (String pattern : requestKey.getPatterns()) {
+ if (pathMatcher.match(pattern, lookupPath)) {
+ for (RequestMethod method : requestKey.getMethods()) {
+ allowedMethods.add(method.name());
+ }
+ }
+ }
+ }
+ if (!allowedMethods.isEmpty()) {
+ throw new HttpRequestMethodNotSupportedException(request.getMethod(),
+ allowedMethods.toArray(new String[allowedMethods.size()]));
+
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * A comparator for RequestKey types. Effective comparison can only be done in the context of a specific request. For
+ * example not all configured patterns may apply to the current request. Therefore an HttpServletRequest is required as
+ * input.
+ *
+ * Furthermore, the following assumptions are made about the input RequestKeys:
Each RequestKey has been fully
+ * matched to the request
The RequestKey contains matched patterns only
Patterns are ordered with the best
+ * matching pattern at the top
+ *
+ * @see RequestMappingHandlerMethodMapping#getMatchingKey(RequestKey, HttpServletRequest)
+ */
+ private class RequestKeyComparator implements Comparator {
+
+ private Comparator patternComparator;
+
+ private List requestAcceptHeader;
+
+ public RequestKeyComparator(HttpServletRequest request) {
+ String lookupPath = urlPathHelper.getLookupPathForRequest(request);
+ this.patternComparator = pathMatcher.getPatternComparator(lookupPath);
+ String acceptHeader = request.getHeader("Accept");
+ this.requestAcceptHeader = MediaType.parseMediaTypes(acceptHeader);
+ MediaType.sortByQualityValue(this.requestAcceptHeader);
+ }
+
+ public int compare(RequestKey key, RequestKey otherKey) {
+ int result = comparePatterns(key.getPatterns(), otherKey.getPatterns());
+ if (result != 0) {
+ return result;
+ }
+ result = otherKey.getParams().size() - key.getParams().size();
+ if (result != 0) {
+ return result;
+ }
+ result = otherKey.getHeaders().size() - key.getHeaders().size();
+ if (result != 0) {
+ return result;
+ }
+/*
+ TODO: fix
+ result = compareAcceptHeaders(key.getAcceptHeaderMediaTypes(), otherKey.getAcceptHeaderMediaTypes());
+ if (result != 0) {
+ return result;
+ }
+*/
+ result = otherKey.getMethods().size() - key.getMethods().size();
+ if (result != 0) {
+ return result;
+ }
+ return 0;
+ }
+
+ private int comparePatterns(Set patterns, Set otherPatterns) {
+ Iterator iterator = patterns.iterator();
+ Iterator iteratorOther = otherPatterns.iterator();
+ while (iterator.hasNext() && iteratorOther.hasNext()) {
+ int result = patternComparator.compare(iterator.next(), iteratorOther.next());
+ if (result != 0) {
+ return result;
+ }
+ }
+ if (iterator.hasNext()) {
+ return -1;
+ }
+ else if (iteratorOther.hasNext()) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ private int compareAcceptHeaders(List accept, List otherAccept) {
+ for (MediaType requestAccept : this.requestAcceptHeader) {
+ int pos1 = indexOfIncluded(requestAccept, accept);
+ int pos2 = indexOfIncluded(requestAccept, otherAccept);
+ if (pos1 != pos2) {
+ return pos2 - pos1;
+ }
+ }
+ return 0;
+ }
+
+ private int indexOfIncluded(MediaType requestAccept, List accept) {
+ for (int i = 0; i < accept.size(); i++) {
+ if (requestAccept.includes(accept.get(i))) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInitBinderMethodDataBinderFactory.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInitBinderMethodDataBinderFactory.java
new file mode 100644
index 0000000000..ef1728f619
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInitBinderMethodDataBinderFactory.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation;
+
+import java.util.List;
+
+import org.springframework.web.bind.ServletRequestDataBinder;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.support.WebBindingInitializer;
+import org.springframework.web.method.annotation.InitBinderMethodDataBinderFactory;
+import org.springframework.web.method.support.InvocableHandlerMethod;
+
+/**
+ * An {@link InitBinderMethodDataBinderFactory} for Servlet environments.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ */
+public class ServletInitBinderMethodDataBinderFactory extends InitBinderMethodDataBinderFactory {
+
+ /**
+ * Create an {@link ServletInitBinderMethodDataBinderFactory} instance.
+ * @param initBinderMethods init binder methods to use to initialize new data binders.
+ * @param bindingInitializer a WebBindingInitializer to use to initialize created data binder instances.
+ */
+ public ServletInitBinderMethodDataBinderFactory(List initBinderMethods,
+ WebBindingInitializer bindingInitializer) {
+ super(initBinderMethods, bindingInitializer);
+ }
+
+ /**
+ * {@inheritDoc} creates a Servlet data binder.
+ */
+ @Override
+ protected WebDataBinder createBinderInstance(Object target, String objectName) {
+ return new ServletRequestDataBinder(target, objectName);
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java
new file mode 100644
index 0000000000..522cdec899
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.http.HttpStatus;
+import org.springframework.ui.ModelMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.support.SessionStatus;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.ServletWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.HandlerMethodProcessor;
+import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
+import org.springframework.web.method.support.InvocableHandlerMethod;
+import org.springframework.web.method.support.HandlerMethodReturnValueHandlerContainer;
+import org.springframework.web.method.support.ModelAndViewContainer;
+import org.springframework.web.servlet.HandlerAdapter;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.View;
+
+/**
+ * Extends {@link InvocableHandlerMethod} with the ability to handle the return value of the invocation
+ * resulting in a {@link ModelAndView} according to the {@link HandlerAdapter} contract.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ */
+public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
+
+ private HttpStatus responseStatus;
+
+ private String responseReason;
+
+ private HandlerMethodReturnValueHandlerContainer returnValueHandlers;
+
+ public void setReturnValueHandlers(HandlerMethodReturnValueHandlerContainer returnValueHandlers) {
+ this.returnValueHandlers = returnValueHandlers;
+ }
+
+ /**
+ * Creates a {@link ServletInvocableHandlerMethod} instance with the given bean and method.
+ * @param handler the object handler
+ * @param method the method
+ */
+ public ServletInvocableHandlerMethod(Object handler, Method method) {
+ super(handler, method);
+
+ ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class);
+ if (annotation != null) {
+ this.responseStatus = annotation.value();
+ this.responseReason = annotation.reason();
+
+ }
+ }
+
+ /**
+ * Invokes the method via {@link #invokeForRequest(NativeWebRequest, ModelMap, Object...)} and also handles the
+ * return value by invoking one of the {@link HandlerMethodReturnValueHandler} instances registered via
+ * {@link #setReturnValueHandlers(HandlerMethodReturnValueHandlerContainer)}.
+ * If the method is annotated with {@link SessionStatus} the response status will be set.
+ * @param request the current request
+ * @param model the model used throughout the current request
+ * @param providedArgs argument values to use as-is if they match to a method parameter's type
+ * @return ModelAndView object with the name of the view and the required model data, or null
+ * if the response was handled
+ */
+ public final ModelAndView invokeAndHandle(NativeWebRequest request,
+ ModelMap model,
+ Object... providedArgs) throws Exception {
+
+ if (!returnValueHandlers.supportsReturnType(getReturnType())) {
+ throw new IllegalStateException("No suitable HandlerMethodReturnValueHandler for method " + toString());
+ }
+
+ Object returnValue = invokeForRequest(request, model, providedArgs);
+
+ setResponseStatus((ServletWebRequest) request);
+
+ ModelAndViewContainer mavContainer = new ModelAndViewContainer(model);
+ returnValueHandlers.handleReturnValue(returnValue, getReturnType(), mavContainer, request);
+
+ return getModelAndView(request, mavContainer, returnValue);
+ }
+
+ /**
+ * Set the response status according to the {@link ResponseStatus} annotation.
+ */
+ private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
+ if (this.responseStatus != null) {
+ if (StringUtils.hasText(this.responseReason)) {
+ webRequest.getResponse().sendError(this.responseStatus.value(), this.responseReason);
+ }
+ else {
+ webRequest.getResponse().sendError(this.responseStatus.value());
+ }
+
+ // to be picked up by the RedirectView
+ webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, this.responseStatus);
+ }
+ }
+
+ /**
+ * Create a {@link ModelAndView} from a {@link ModelAndViewContainer}.
+ */
+ private ModelAndView getModelAndView(NativeWebRequest request,
+ ModelAndViewContainer mavContainer,
+ Object returnValue) {
+ if (returnValue == null && isResponseHandled(request)) {
+ return null;
+ }
+ else if (returnValueHandlerUsesResponseArgument()) {
+ return null;
+ }
+ else {
+ ModelAndView mav = new ModelAndView().addAllObjects(mavContainer.getModel());
+ mav.setViewName(mavContainer.getViewName());
+ if (mavContainer.getView() != null) {
+ mav.setView(mavContainer.getView());
+ }
+ return mav;
+ }
+ }
+
+ private boolean isResponseHandled(NativeWebRequest request) {
+ ServletWebRequest servletRequest = (ServletWebRequest) request;
+ return (servletRequest.isNotModified() || (responseStatus != null) || usesResponseArgument());
+ }
+
+ /**
+ * Whether any of the underlying {@link HandlerMethodArgumentResolver}s or
+ * {@link HandlerMethodReturnValueHandler}s use the response argument.
+ * @see HandlerMethodProcessor#usesResponseArgument(MethodParameter)
+ */
+ protected boolean usesResponseArgument() {
+ return (super.usesResponseArgument() || returnValueHandlerUsesResponseArgument());
+ }
+
+ private boolean returnValueHandlerUsesResponseArgument() {
+ return returnValueHandlers.usesResponseArgument(getReturnType());
+ }
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/AbstractMessageConverterMethodProcessor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/AbstractMessageConverterMethodProcessor.java
new file mode 100644
index 0000000000..62754a241a
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/AbstractMessageConverterMethodProcessor.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation.support;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.core.MethodParameter;
+import org.springframework.http.HttpInputMessage;
+import org.springframework.http.HttpOutputMessage;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.web.HttpMediaTypeNotAcceptableException;
+import org.springframework.web.HttpMediaTypeNotSupportedException;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
+
+/**
+ * @author Arjen Poutsma
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ */
+public abstract class AbstractMessageConverterMethodProcessor
+ implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ private final HttpMessageConverter>[] messageConverters;
+
+ protected AbstractMessageConverterMethodProcessor(HttpMessageConverter>... messageConverters) {
+ Assert.notNull(messageConverters, "'messageConverters' must not be null");
+ this.messageConverters = messageConverters;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected Object readWithMessageConverters(NativeWebRequest webRequest,
+ MethodParameter methodParam,
+ Class paramType)
+ throws IOException, HttpMediaTypeNotSupportedException {
+
+ HttpInputMessage inputMessage = createInputMessage(webRequest);
+
+ MediaType contentType = inputMessage.getHeaders().getContentType();
+ if (contentType == null) {
+ StringBuilder builder = new StringBuilder(ClassUtils.getShortName(methodParam.getParameterType()));
+ String paramName = methodParam.getParameterName();
+ if (paramName != null) {
+ builder.append(' ');
+ builder.append(paramName);
+ }
+ throw new HttpMediaTypeNotSupportedException("Cannot read parameter (" + builder.toString() +
+ ") using HttpMessageConverters: no Content-Type found in HTTP request");
+ }
+
+ List allSupportedMediaTypes = new ArrayList();
+ if (this.messageConverters != null) {
+ for (HttpMessageConverter> messageConverter : this.messageConverters) {
+ allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
+ if (messageConverter.canRead(paramType, contentType)) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Reading [" + paramType.getName() + "] as \"" + contentType + "\" using [" +
+ messageConverter + "]");
+ }
+ return ((HttpMessageConverter) messageConverter).read(paramType, inputMessage);
+ }
+ }
+ }
+
+ throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes);
+ }
+
+ protected abstract HttpInputMessage createInputMessage(NativeWebRequest webRequest);
+
+ protected void writeWithMessageConverters(NativeWebRequest webRequest, Object returnValue)
+ throws IOException, HttpMediaTypeNotAcceptableException {
+ writeWithMessageConverters(returnValue, createInputMessage(webRequest), createOutputMessage(webRequest));
+ }
+
+ protected abstract HttpOutputMessage createOutputMessage(NativeWebRequest webRequest);
+
+ @SuppressWarnings("unchecked")
+ protected void writeWithMessageConverters(T returnValue,
+ HttpInputMessage inputMessage,
+ HttpOutputMessage outputMessage)
+ throws IOException, HttpMediaTypeNotAcceptableException {
+
+ List acceptedMediaTypes = getAcceptedMediaTypes(inputMessage);
+
+ List allSupportedMediaTypes = new ArrayList();
+ if (this.messageConverters != null) {
+ for (MediaType acceptedMediaType : acceptedMediaTypes) {
+ for (HttpMessageConverter> messageConverter : this.messageConverters) {
+ if (!messageConverter.canWrite(returnValue.getClass(), acceptedMediaType)) {
+ continue;
+ }
+ ((HttpMessageConverter) messageConverter).write(returnValue, acceptedMediaType, outputMessage);
+ if (logger.isDebugEnabled()) {
+ MediaType contentType = outputMessage.getHeaders().getContentType();
+ if (contentType == null) {
+ contentType = acceptedMediaType;
+ }
+ logger.debug("Written [" + returnValue + "] as \"" + contentType + "\" using [" +
+ messageConverter + "]");
+ }
+ return;
+ }
+ }
+ for (HttpMessageConverter> messageConverter : messageConverters) {
+ allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
+ }
+ }
+ throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes);
+ }
+
+ private List getAcceptedMediaTypes(HttpInputMessage inputMessage) {
+ List acceptedMediaTypes = inputMessage.getHeaders().getAccept();
+ if (acceptedMediaTypes.isEmpty()) {
+ acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
+ }
+
+ MediaType.sortByQualityValue(acceptedMediaTypes);
+ return acceptedMediaTypes;
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandler.java
new file mode 100644
index 0000000000..0730e85d23
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/DefaultMethodReturnValueHandler.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation.support;
+
+import java.lang.reflect.Method;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.core.MethodParameter;
+import org.springframework.ui.ExtendedModelMap;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.method.annotation.ModelFactory;
+import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
+import org.springframework.web.method.support.ModelAndViewContainer;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver;
+
+/**
+ * A catch-all {@link HandlerMethodReturnValueHandler} to handle return values not handled by any other return
+ * value handler.
+ *
+ *
This handler should always be last in the order as {@link #supportsReturnType(MethodParameter)} always returns
+ * {@code true}. An attempt is made to handle the return value through a custom {@link ModelAndViewResolver}s or
+ * otherwise by treating it as a single model attribute.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ */
+public class DefaultMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
+
+ private final ModelAndViewResolver[] customModelAndViewResolvers;
+
+ public DefaultMethodReturnValueHandler(ModelAndViewResolver[] customResolvers) {
+ this.customModelAndViewResolvers = (customResolvers != null) ? customResolvers : new ModelAndViewResolver[] {};
+ }
+
+ public boolean supportsReturnType(MethodParameter returnType) {
+ return true;
+ }
+
+ public boolean usesResponseArgument(MethodParameter parameter) {
+ return false;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void handleReturnValue(Object returnValue,
+ MethodParameter returnType,
+ ModelAndViewContainer mavContainer,
+ NativeWebRequest webRequest) throws Exception {
+
+ for (ModelAndViewResolver resolver : this.customModelAndViewResolvers) {
+ Class> handlerType = returnType.getDeclaringClass();
+ Method method = returnType.getMethod();
+ ExtendedModelMap extModel = (ExtendedModelMap) mavContainer.getModel();
+ ModelAndView mav = resolver.resolveModelAndView(method, handlerType, returnValue, extModel, webRequest);
+ if (mav != ModelAndViewResolver.UNRESOLVED) {
+ mavContainer.setView((V) mav.getView());
+ mavContainer.setViewName(mav.getViewName());
+ mavContainer.addModelAttributes(mav.getModel());
+ return;
+ }
+ }
+
+ if (returnValue == null) {
+ return;
+ }
+ else if (!BeanUtils.isSimpleProperty(returnValue.getClass())) {
+ String name = ModelFactory.getNameForReturnValue(returnValue, returnType);
+ mavContainer.addModelAttribute(name, returnValue);
+ }
+
+ // should not happen
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java
new file mode 100644
index 0000000000..21fdff1066
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.mvc.method.annotation.support;
+
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpInputMessage;
+import org.springframework.http.HttpOutputMessage;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.http.server.ServletServerHttpResponse;
+import org.springframework.ui.ModelMap;
+import org.springframework.util.Assert;
+import org.springframework.web.HttpMediaTypeNotSupportedException;
+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;
+
+/**
+ * Implementation of {@link HandlerMethodArgumentResolver} and {@link HandlerMethodReturnValueHandler}
+ * that supports {@link HttpEntity} and {@link ResponseEntity}.
+ *
+ * @author Arjen Poutsma
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ */
+public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodProcessor {
+
+ public HttpEntityMethodProcessor(HttpMessageConverter>... messageConverters) {
+ super(messageConverters);
+ }
+
+ public boolean supportsParameter(MethodParameter parameter) {
+ Class> parameterType = parameter.getParameterType();
+ return HttpEntity.class.equals(parameterType);
+ }
+
+ public boolean supportsReturnType(MethodParameter returnType) {
+ Class> parameterType = returnType.getParameterType();
+ return HttpEntity.class.equals(parameterType) || ResponseEntity.class.equals(parameterType);
+ }
+
+ public boolean usesResponseArgument(MethodParameter parameterOrReturnType) {
+ // only when HttpEntity or ResponseEntity is used as a return type
+ return parameterOrReturnType.getParameterIndex() == -1;
+ }
+
+ public Object resolveArgument(MethodParameter parameter,
+ ModelMap model,
+ NativeWebRequest webRequest,
+ WebDataBinderFactory binderFactory)
+ throws IOException, HttpMediaTypeNotSupportedException {
+ Class> paramType = getHttpEntityType(parameter);
+ Object body = readWithMessageConverters(webRequest, parameter, paramType);
+ HttpInputMessage inputMessage = createInputMessage(webRequest);
+ return new HttpEntity