SPR-8215 Move HandlerMethod code into trunk

This commit is contained in:
Rossen Stoyanchev
2011-04-06 11:30:59 +00:00
parent 0f7d43ba90
commit acc75aa4b8
86 changed files with 11635 additions and 13 deletions

View File

@@ -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.
* <p>May be overridden in subclasses, in order to apply specific exception checks.
* Note that this template method will be invoked <i>after</i> checking whether this
* resolved applies ("mappedHandlers" etc), so an implementation may simply proceed
* with its actual exception handling.
* @param request current HTTP request
* @param response current HTTP response
* @param handlerMethod the executed handler method, or <code>null</code> if none chosen at the time
* of the exception (for example, if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to, or <code>null</code> for default processing
*/
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response,
HandlerMethod handlerMethod,
Exception ex) {
return null;
}
}

View File

@@ -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<T> extends AbstractHandlerMapping {
private final Map<T, HandlerMethod> handlerMethods = new LinkedHashMap<T, HandlerMethod>();
/**
* 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.
* <p>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<Method> 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.
*
* <p>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<Match> matches = new ArrayList<Match>();
for (Map.Entry<T, HandlerMethod> entry : handlerMethods.entrySet()) {
T match = getMatchingKey(entry.getKey(), request);
if (match != null) {
matches.add(new Match(match, entry.getValue()));
}
}
if (!matches.isEmpty()) {
Comparator<Match> 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<Match> getMatchComparator(HttpServletRequest request) {
final Comparator<T> keyComparator = getKeyComparator(request);
return new Comparator<Match>() {
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<T> 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<T> 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();
}
}
}

View File

@@ -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<HandlerInterceptor> 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()) {

View File

@@ -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<HandlerInterceptor> getInterceptors(String lookupPath, PathMatcher pathMatcher) {
public static MappedInterceptors createFromDeclaredBeans(ListableBeanFactory beanFactory) {
Map<String, MappedInterceptor> 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<HandlerInterceptor> interceptors = new LinkedHashSet<HandlerInterceptor>();
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;
}
}
}

View File

@@ -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.
* <p>Default value is <code>Integer.MAX_VALUE</code>, 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} <p>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} <p>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} <p>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);
}

View File

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

View File

@@ -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<RequestCondition> parseParams(String... params) {
if (params == null) {
return Collections.emptySet();
}
Set<RequestCondition> result = new LinkedHashSet<RequestCondition>(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<RequestCondition> parseHeaders(String... headers) {
if (headers == null) {
return Collections.emptySet();
}
Set<RequestCondition> result = new LinkedHashSet<RequestCondition>(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
* <code>@RequestMapping.params()</code> and <code>@RequestMapping.headers()</code>.
*/
private static abstract class AbstractNameValueCondition<T> 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<String> {
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<String> {
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<List<MediaType>> {
public MediaTypeHeaderNameValueCondition(String expression) {
super(expression);
}
@Override
protected List<MediaType> 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<MediaType> 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;
}
}
}

View File

@@ -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<String> patterns;
private final Set<RequestMethod> methods;
private final Set<RequestCondition> paramConditions;
private final Set<RequestCondition> headerConditions;
private int hash;
/**
* Creates a new {@code RequestKey} instance with the given parameters.
*
* <p/>Package protected for testing purposes.
*/
RequestKey(Collection<String> patterns,
Collection<RequestMethod> methods,
Collection<RequestCondition> paramConditions,
Collection<RequestCondition> headerConditions) {
this.patterns = asUnmodifiableSet(prependLeadingSlash(patterns));
this.methods = asUnmodifiableSet(methods);
this.paramConditions = asUnmodifiableSet(paramConditions);
this.headerConditions = asUnmodifiableSet(headerConditions);
}
private static Set<String> prependLeadingSlash(Collection<String> patterns) {
if (patterns == null) {
return Collections.emptySet();
}
Set<String> result = new LinkedHashSet<String>(patterns.size());
for (String pattern : patterns) {
if (!pattern.startsWith("/")) {
pattern = "/" + pattern;
}
result.add(pattern);
}
return result;
}
private static <T> Set<T> asUnmodifiableSet(Collection<T> collection) {
if (collection == null) {
return Collections.emptySet();
}
Set<T> result = new LinkedHashSet<T>(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<String> getPatterns() {
return patterns;
}
/**
* Returns the request methods of this request key.
*/
public Set<RequestMethod> getMethods() {
return methods;
}
/**
* Returns the request parameters of this request key.
*/
public Set<RequestCondition> getParams() {
return paramConditions;
}
/**
* Returns the request headers of this request key.
*/
public Set<RequestCondition> 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<String> patterns = combinePatterns(this.patterns, methodKey.patterns, pathMatcher);
Set<RequestMethod> methods = union(this.methods, methodKey.methods);
Set<RequestCondition> params = union(this.paramConditions, methodKey.paramConditions);
Set<RequestCondition> headers = union(this.headerConditions, methodKey.headerConditions);
return new RequestKey(patterns, methods, params, headers);
}
private static Set<String> combinePatterns(Collection<String> typePatterns,
Collection<String> methodPatterns,
PathMatcher pathMatcher) {
Set<String> result = new LinkedHashSet<String>();
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 <T> Set<T> union(Collection<T> s1, Collection<T> s2) {
Set<T> union = new LinkedHashSet<T>(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<String> matchingPatterns = getMatchingPatterns(request, pathMatcher, urlPathHelper);
if (!matchingPatterns.isEmpty()) {
Set<RequestMethod> matchingMethods = getMatchingMethods(request);
return new RequestKey(matchingPatterns, matchingMethods, this.paramConditions, this.headerConditions);
}
else {
return null;
}
}
}
private List<String> getMatchingPatterns(HttpServletRequest request,
PathMatcher pathMatcher,
UrlPathHelper urlPathHelper) {
String lookupPath = urlPathHelper.getLookupPathForRequest(request);
List<String> matchingPatterns = new ArrayList<String>();
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<RequestMethod> 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<RequestCondition> 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();
}
}

View File

@@ -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<Class<?>, SessionAttributesHandler> sessionAttributesHandlerCache =
new ConcurrentHashMap<Class<?>, SessionAttributesHandler>();
private final Map<Class<?>, Set<Method>> modelAttributeMethodCache = new ConcurrentHashMap<Class<?>, Set<Method>>();
private final Map<Class<?>, Set<Method>> initBinderMethodCache = new ConcurrentHashMap<Class<?>, Set<Method>>();
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<Source>(),
new XmlAwareFormHttpMessageConverter() };
}
/**
* Set a custom WebArgumentResolvers to use for special method parameter types.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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 <code>@SessionAttributes</code> annotated handlers
* for the given number of seconds. Default is 0, preventing caching completely.
* <p>In contrast to the "cacheSeconds" property which will apply to all general handlers
* (but not to <code>@SessionAttributes</code> annotated handlers), this setting will
* apply to <code>@SessionAttributes</code> 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.
* <p>More specifically, the execution of the <code>handleRequestInternal</code>
* method will get synchronized if this flag is "true". The best available
* session mutex will be used for the synchronization; ideally, this will
* be a mutex exposed by HttpSessionMutexListener.
* <p>The session mutex is guaranteed to be the same object during
* the entire lifetime of the session, available under the key defined
* by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a
* safe reference to synchronize on for locking on the current session.
* <p>In many cases, the HttpSession reference itself is a safe mutex
* as well, since it will always be the same object reference for the
* same active logical session. However, this is not guaranteed across
* different servlet containers; the only 100% safe way is a session mutex.
* @see org.springframework.web.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).
* <p>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<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
Class<?> handlerType = handlerMethod.getBeanType();
Set<Method> 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<InvocableHandlerMethod> modelAttrMethods = new ArrayList<InvocableHandlerMethod>();
Class<?> handlerType = handlerMethod.getBeanType();
Set<Method> 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));
}
};
}

View File

@@ -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.
*
* <p>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<Class<?>, ExceptionMethodMapping> exceptionMethodMappingCache =
new ConcurrentHashMap<Class<?>, 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<Source>(),
new XmlAwareFormHttpMessageConverter() };
}
/**
* Set a custom ArgumentResolvers to use for special method parameter types.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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<Method> 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;
}
};
}

View File

@@ -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<RequestKey> {
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).
* <p>Default is "false".
*
* @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
*/
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
}
/**
* Set if context path and request URI should be URL-decoded. Both are returned <i>undecoded</i> by the Servlet API, in
* contrast to the servlet path. <p>Uses either the request encoding or the default encoding according to the Servlet
* spec (ISO-8859-1).
*
* @see org.springframework.web.util.UrlPathHelper#setUrlDecode
*/
public void setUrlDecode(boolean urlDecode) {
this.urlPathHelper.setUrlDecode(urlDecode);
}
/**
* Set the UrlPathHelper to use for resolution of lookup paths. <p>Use this to override the default UrlPathHelper
* with a custom subclass, or to share common UrlPathHelper settings across multiple HandlerMappings and
* MethodNameResolvers.
*
*/
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.
* <p>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<RequestKey> 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<String, String> 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<RequestKey> requestKeys, HttpServletRequest request)
throws HttpRequestMethodNotSupportedException {
String lookupPath = urlPathHelper.getLookupPathForRequest(request);
Set<String> allowedMethods = new HashSet<String>(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: <ul> <li>Each RequestKey has been fully
* matched to the request <li>The RequestKey contains matched patterns only <li>Patterns are ordered with the best
* matching pattern at the top </ul>
*
* @see RequestMappingHandlerMethodMapping#getMatchingKey(RequestKey, HttpServletRequest)
*/
private class RequestKeyComparator implements Comparator<RequestKey> {
private Comparator<String> patternComparator;
private List<MediaType> 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<String> patterns, Set<String> otherPatterns) {
Iterator<String> iterator = patterns.iterator();
Iterator<String> 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<MediaType> accept, List<MediaType> 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<MediaType> accept) {
for (int i = 0; i < accept.size(); i++) {
if (requestAccept.includes(accept.get(i))) {
return i;
}
}
return -1;
}
}
}

View File

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

View File

@@ -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 <code>null</code>
* 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<View> mavContainer = new ModelAndViewContainer<View>(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<View> 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());
}
}

View File

@@ -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 <T> Object readWithMessageConverters(NativeWebRequest webRequest,
MethodParameter methodParam,
Class<T> 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<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
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<T>) 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 <T> void writeWithMessageConverters(T returnValue,
HttpInputMessage inputMessage,
HttpOutputMessage outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException {
List<MediaType> acceptedMediaTypes = getAcceptedMediaTypes(inputMessage);
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
if (this.messageConverters != null) {
for (MediaType acceptedMediaType : acceptedMediaTypes) {
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (!messageConverter.canWrite(returnValue.getClass(), acceptedMediaType)) {
continue;
}
((HttpMessageConverter<T>) 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<MediaType> getAcceptedMediaTypes(HttpInputMessage inputMessage) {
List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
if (acceptedMediaTypes.isEmpty()) {
acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
}
MediaType.sortByQualityValue(acceptedMediaTypes);
return acceptedMediaTypes;
}
}

View File

@@ -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.
*
* <p>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 <V> void handleReturnValue(Object returnValue,
MethodParameter returnType,
ModelAndViewContainer<V> 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();
}
}

View File

@@ -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<Object>(body, inputMessage.getHeaders());
}
@Override
protected HttpInputMessage createInputMessage(NativeWebRequest webRequest) {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
return new ServletServerHttpRequest(servletRequest);
}
private Class<?> getHttpEntityType(MethodParameter methodParam) {
Assert.isAssignable(HttpEntity.class, methodParam.getParameterType());
ParameterizedType type = (ParameterizedType) methodParam.getGenericParameterType();
if (type.getActualTypeArguments().length == 1) {
Type typeArgument = type.getActualTypeArguments()[0];
if (typeArgument instanceof Class) {
return (Class<?>) typeArgument;
}
else if (typeArgument instanceof GenericArrayType) {
Type componentType = ((GenericArrayType) typeArgument).getGenericComponentType();
if (componentType instanceof Class) {
// Surely, there should be a nicer way to do this
Object array = Array.newInstance((Class<?>) componentType, 0);
return array.getClass();
}
}
}
throw new IllegalArgumentException(
"HttpEntity parameter (" + methodParam.getParameterName() + ") is not parameterized");
}
public <V> void handleReturnValue(Object returnValue,
MethodParameter returnType,
ModelAndViewContainer<V> mavContainer,
NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
return;
}
Assert.isInstanceOf(HttpEntity.class, returnValue);
HttpEntity<?> responseEntity = (HttpEntity<?>) returnValue;
HttpOutputMessage outputMessage = createOutputMessage(webRequest);
if (responseEntity instanceof ResponseEntity) {
((ServerHttpResponse) outputMessage).setStatusCode(((ResponseEntity<?>) responseEntity).getStatusCode());
}
HttpHeaders entityHeaders = responseEntity.getHeaders();
if (!entityHeaders.isEmpty()) {
outputMessage.getHeaders().putAll(entityHeaders);
}
Object body = responseEntity.getBody();
if (body != null) {
writeWithMessageConverters(body, createInputMessage(webRequest), outputMessage);
}
else {
// flush headers to the HttpServletResponse
outputMessage.getBody();
}
}
@Override
protected HttpOutputMessage createOutputMessage(NativeWebRequest webRequest) {
HttpServletResponse servletResponse = (HttpServletResponse) webRequest.getNativeResponse();
return new ServletServerHttpResponse(servletResponse);
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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 org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.ModelAndView;
/**
* Handles {@link ModelAndView} return values.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class ModelAndViewMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
public boolean supportsReturnType(MethodParameter returnType) {
return ModelAndView.class.isAssignableFrom(returnType.getParameterType());
}
public boolean usesResponseArgument(MethodParameter parameter) {
return false;
}
@SuppressWarnings("unchecked")
public <V> void handleReturnValue(Object returnValue,
MethodParameter returnType,
ModelAndViewContainer<V> mavContainer,
NativeWebRequest webRequest) throws Exception {
ModelAndView mav = (ModelAndView) returnValue;
mavContainer.setView((V) mav.getView());
mavContainer.setViewName(mav.getViewName());
mavContainer.addModelAttributes(mav.getModel());
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.annotation.support;
import java.util.Map;
import javax.servlet.ServletException;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ValueConstants;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.method.annotation.support.AbstractNamedValueMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.HandlerMapping;
/**
* Implementation of {@link HandlerMethodArgumentResolver} that supports arguments annotated with
* {@link PathVariable @PathVariable}.
*
* @author Rossen Stoyanchev
* @author Arjen Poutsma
* @since 3.1
*/
public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
public PathVariableMethodArgumentResolver(ConfigurableBeanFactory beanFactory) {
super(beanFactory);
}
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(PathVariable.class);
}
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
PathVariable annotation = parameter.getParameterAnnotation(PathVariable.class);
return new PathVariableNamedValueInfo(annotation);
}
@Override
@SuppressWarnings("unchecked")
protected Object resolveNamedValueArgument(NativeWebRequest webRequest, MethodParameter parameter, String name)
throws Exception {
Map<String, String> uriTemplateVariables = (Map<String, String>) webRequest.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
return (uriTemplateVariables != null) ? uriTemplateVariables.get(name) : null;
}
@Override
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
throw new IllegalStateException("Could not find @PathVariable [" + name + "] in @RequestMapping");
}
private static class PathVariableNamedValueInfo extends NamedValueInfo {
private PathVariableNamedValueInfo(PathVariable annotation) {
super(annotation.value(), true, ValueConstants.DEFAULT_NONE);
}
}
}

View File

@@ -0,0 +1,98 @@
/*
* 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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.ui.ModelMap;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
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
* parameters annotated with {@link RequestBody} and return values annotated with {@link ResponseBody}.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
*/
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
public RequestResponseBodyMethodProcessor(HttpMessageConverter<?>... messageConverters) {
super(messageConverters);
}
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
public boolean supportsReturnType(MethodParameter returnType) {
return returnType.getMethodAnnotation(ResponseBody.class) != null;
}
public boolean usesResponseArgument(MethodParameter parameterOrReturnType) {
return parameterOrReturnType.getParameterIndex() == -1 &&
parameterOrReturnType.getMethodAnnotation(ResponseBody.class) != null;
}
public Object resolveArgument(MethodParameter parameter,
ModelMap model,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory)
throws IOException, HttpMediaTypeNotSupportedException {
return readWithMessageConverters(webRequest, parameter, parameter.getParameterType());
}
@Override
protected HttpInputMessage createInputMessage(NativeWebRequest webRequest) {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
return new ServletServerHttpRequest(servletRequest);
}
public <V> void handleReturnValue(Object returnValue,
MethodParameter returnType,
ModelAndViewContainer<V> mavContainer,
NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException {
if (returnValue != null) {
writeWithMessageConverters(webRequest, returnValue);
}
}
@Override
protected HttpOutputMessage createOutputMessage(NativeWebRequest webRequest) {
HttpServletResponse servletResponse = (HttpServletResponse) webRequest.getNativeResponse();
return new ServletServerHttpResponse(servletResponse);
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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 javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.annotation.support.CookieValueMethodArgumentResolver;
import org.springframework.web.util.WebUtils;
/**
* A {@link CookieValueMethodArgumentResolver} for Servlet environments.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class ServletCookieValueMethodArgumentResolver extends CookieValueMethodArgumentResolver {
public ServletCookieValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory) {
super(beanFactory);
}
@Override
protected Object resolveNamedValueArgument(NativeWebRequest webRequest,
MethodParameter parameter,
String cookieName) throws Exception {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName);
if (Cookie.class.isAssignableFrom(parameter.getParameterType())) {
return cookieValue;
}
else if (cookieValue != null) {
return getUrlPathHelper().decodeRequestString(servletRequest, cookieValue.getValue());
}
else {
return null;
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.annotation.support;
import javax.servlet.ServletRequest;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.annotation.support.ModelAttributeMethodProcessor;
/**
* A {@link ModelAttributeMethodProcessor} for Servlet environments.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor {
/**
* Creates a {@link ServletModelAttributeMethodProcessor} instance.
* @param resolveWithoutAnnotations enable default resolution mode in which parameters without
* annotations that aren't simple types (see {@link BeanUtils#isSimpleProperty(Class)})
* are also treated as model attributes with a default name based on the model attribute type.
*/
public ServletModelAttributeMethodProcessor(boolean resolveWithoutAnnotations) {
super(resolveWithoutAnnotations);
}
/**
* Expects the data binder to be an instance of {@link ServletRequestDataBinder}.
*/
@Override
protected void doBind(WebDataBinder binder, NativeWebRequest request) {
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
((ServletRequestDataBinder) binder).bind(servletRequest);
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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.io.InputStream;
import java.io.Reader;
import java.security.Principal;
import java.util.Locale;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.core.MethodParameter;
import org.springframework.ui.ModelMap;
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.multipart.MultipartRequest;
import org.springframework.web.servlet.support.RequestContextUtils;
/**
* Implementation of {@link HandlerMethodArgumentResolver} that supports {@link ServletRequest} and related arguments.
*
* @author Arjen Poutsma
*/
public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
public boolean supportsParameter(MethodParameter parameter) {
Class<?> parameterType = parameter.getParameterType();
return ServletRequest.class.isAssignableFrom(parameterType) ||
MultipartRequest.class.isAssignableFrom(parameterType) ||
HttpSession.class.isAssignableFrom(parameterType) || Principal.class.isAssignableFrom(parameterType) ||
Locale.class.equals(parameterType) || InputStream.class.isAssignableFrom(parameterType) ||
Reader.class.isAssignableFrom(parameterType);
}
public boolean usesResponseArgument(MethodParameter parameter) {
return false;
}
public Object resolveArgument(MethodParameter parameter,
ModelMap model,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws IOException {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
Class<?> parameterType = parameter.getParameterType();
if (ServletRequest.class.isAssignableFrom(parameterType) ||
MultipartRequest.class.isAssignableFrom(parameterType)) {
Object nativeRequest = webRequest.getNativeRequest(parameterType);
if (nativeRequest == null) {
throw new IllegalStateException(
"Current request is not of type [" + parameterType.getName() + "]: " + request);
}
return nativeRequest;
}
else if (HttpSession.class.isAssignableFrom(parameterType)) {
return request.getSession();
}
else if (Principal.class.isAssignableFrom(parameterType)) {
return request.getUserPrincipal();
}
else if (Locale.class.equals(parameterType)) {
return RequestContextUtils.getLocale(request);
}
else if (InputStream.class.isAssignableFrom(parameterType)) {
return request.getInputStream();
}
else if (Reader.class.isAssignableFrom(parameterType)) {
return request.getReader();
}
// should not happen
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.io.OutputStream;
import java.io.Writer;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.MethodParameter;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
/**
* Implementation of {@link HandlerMethodArgumentResolver} that supports {@link ServletResponse} and related arguments.
*
* @author Arjen Poutsma
*/
public class ServletResponseMethodArgumentResolver implements HandlerMethodArgumentResolver {
public boolean supportsParameter(MethodParameter parameter) {
Class<?> parameterType = parameter.getParameterType();
return ServletResponse.class.isAssignableFrom(parameterType) ||
OutputStream.class.isAssignableFrom(parameterType) || Writer.class.isAssignableFrom(parameterType);
}
public boolean usesResponseArgument(MethodParameter parameter) {
return true;
}
public Object resolveArgument(MethodParameter parameter,
ModelMap model,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws IOException {
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
Class<?> parameterType = parameter.getParameterType();
if (ServletResponse.class.isAssignableFrom(parameterType)) {
Object nativeResponse = webRequest.getNativeResponse(parameterType);
if (nativeResponse == null) {
throw new IllegalStateException(
"Current response is not of type [" + parameterType.getName() + "]: " + response);
}
return nativeResponse;
}
else if (OutputStream.class.isAssignableFrom(parameterType)) {
return response.getOutputStream();
}
else if (Writer.class.isAssignableFrom(parameterType)) {
return response.getWriter();
}
// should not happen
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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 org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.View;
/**
* Handles {@link View} and view name return values.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class ViewMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return (View.class.isAssignableFrom(paramType) || (String.class.equals(paramType)));
}
public boolean usesResponseArgument(MethodParameter parameter) {
return false;
}
@SuppressWarnings("unchecked")
public <V> void handleReturnValue(Object returnValue,
MethodParameter returnType,
ModelAndViewContainer<V> mavContainer,
NativeWebRequest webRequest) throws Exception {
if (returnValue instanceof String) {
mavContainer.setViewName((String) returnValue);
}
else {
V view = (V) returnValue;
mavContainer.setView(view);
}
}
}

View File

@@ -0,0 +1,134 @@
/*
* 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.Comparator;
import javax.servlet.http.HttpServletRequest;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.util.UrlPathHelper;
import static org.junit.Assert.*;
/**
* Test for {@link AbstractHandlerMethodMapping}.
*
* @author Arjen Poutsma
*/
public class HandlerMethodMappingTests {
private AbstractHandlerMethodMapping<String> mapping;
private HandlerMethod handlerMethod1;
private HandlerMethod handlerMethod2;
@Before
public void setUp() throws Exception {
mapping = new MyHandlerMethodMapping();
MyHandler handler = new MyHandler();
handlerMethod1 = new HandlerMethod(handler, "handlerMethod1");
handlerMethod2 = new HandlerMethod(handler, "handlerMethod2");
}
@Test(expected = IllegalStateException.class)
public void registerDuplicates() {
mapping.registerHandlerMethod("foo", handlerMethod1);
mapping.registerHandlerMethod("foo", handlerMethod2);
}
@Test
public void directMatch() throws Exception {
String key = "foo";
mapping.registerHandlerMethod(key, handlerMethod1);
HandlerMethod result = mapping.getHandlerInternal(new MockHttpServletRequest("GET", key));
assertEquals(handlerMethod1, result);
}
@Test
public void patternMatch() throws Exception {
mapping.registerHandlerMethod("/fo*", handlerMethod1);
mapping.registerHandlerMethod("/f*", handlerMethod1);
HandlerMethod result = mapping.getHandlerInternal(new MockHttpServletRequest("GET", "/foo"));
assertEquals(handlerMethod1, result);
}
@Test(expected = IllegalStateException.class)
public void ambiguousMatch() throws Exception {
mapping.registerHandlerMethod("/f?o", handlerMethod1);
mapping.registerHandlerMethod("/fo?", handlerMethod2);
mapping.getHandlerInternal(new MockHttpServletRequest("GET", "/foo"));
}
private static class MyHandlerMethodMapping extends AbstractHandlerMethodMapping<String> {
private UrlPathHelper urlPathHelper = new UrlPathHelper();
private PathMatcher pathMatcher = new AntPathMatcher();
@Override
protected String getKeyForRequest(HttpServletRequest request) throws Exception {
return urlPathHelper.getLookupPathForRequest(request);
}
@Override
protected String getMatchingKey(String pattern, HttpServletRequest request) {
String lookupPath = urlPathHelper.getLookupPathForRequest(request);
return pathMatcher.match(pattern, lookupPath) ? pattern : null;
}
@Override
protected String getKeyForMethod(Method method) {
String methodName = method.getName();
return methodName.startsWith("handler") ? methodName : null;
}
@Override
protected Comparator<String> getKeyComparator(HttpServletRequest request) {
String lookupPath = urlPathHelper.getLookupPathForRequest(request);
return pathMatcher.getPatternComparator(lookupPath);
}
@Override
protected boolean isHandler(String beanName) {
return true;
}
}
private static class MyHandler {
@SuppressWarnings("unused")
public void handlerMethod1() {
}
@SuppressWarnings("unused")
public void handlerMethod2() {
}
}
}

View File

@@ -0,0 +1,251 @@
/*
* 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 static org.junit.Assert.assertEquals;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.interceptor.SimpleTraceInterceptor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.TestBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.ModelMap;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
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.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.method.HandlerMethodSelector;
import org.springframework.web.method.annotation.support.ModelAttributeMethodProcessor;
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.mvc.method.annotation.ServletInvocableHandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.support.DefaultMethodReturnValueHandler;
/**
* Test various scenarios for detecting method-level and method parameter annotations depending
* on where they are located -- on interfaces, parent classes, in parameterized methods, or in
* combination with proxies.
*
* Note the following:
* <ul>
* <li>Parameterized methods cannot be used in combination with JDK dynamic proxies since the
* proxy interface does not contain the bridged methods that need to be invoked.
* <li>When using JDK dynamic proxies, the proxied interface must contain all required method
* and method parameter annotations.
* <li>Method-level annotations can be placed on super types (interface or parent class) while
* method parameter annotations must be present on the method being invoked.
* </ul>
*
* @author Rossen Stoyanchev
*/
@RunWith(Parameterized.class)
public class ControllerMethodAnnotationDetectionTests {
@Parameters
public static Collection<Object[]> handlerTypes() {
return Arrays.asList(new Object[][] {
{ new MappingIfcController(), false },
{ new MappingAbstractClassController(), false },
{ new ParameterizedIfcController(), false },
{ new MappingParameterizedIfcController(), false },
{ new MappingIfcProxyController(), true },
{ new PlainController(), true },
{ new MappingAbstractClassController(), true }
});
}
private Object handler;
private boolean useAutoProxy;
public ControllerMethodAnnotationDetectionTests(Object handler, boolean useAutoProxy) {
this.handler = handler;
this.useAutoProxy = useAutoProxy;
}
@Test
public void invokeModelAttributeMethod() throws Exception {
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handler, useAutoProxy);
ModelAttribute annot = requestMappingMethod.getMethodAnnotation(ModelAttribute.class);
assertEquals("Failed to detect method annotation", "attrName", annot.value());
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
NativeWebRequest webRequest = new ServletWebRequest(servletRequest, new MockHttpServletResponse());
servletRequest.setParameter("name", "Chad");
ModelMap model = new ExtendedModelMap();
ModelAndView mav = requestMappingMethod.invokeAndHandle(webRequest, model);
Object modelAttr = mav.getModelMap().get("attrName");
assertEquals(TestBean.class, modelAttr.getClass());
assertEquals("Chad", ((TestBean) modelAttr).getName());
}
private ServletInvocableHandlerMethod createRequestMappingMethod(Object handler, boolean useAutoProxy) {
if (useAutoProxy) {
handler = getProxyBean(handler);
}
HandlerMethodArgumentResolverContainer argResolvers = new HandlerMethodArgumentResolverContainer();
argResolvers.registerArgumentResolver(new ModelAttributeMethodProcessor(false));
HandlerMethodReturnValueHandlerContainer handlers = new HandlerMethodReturnValueHandlerContainer();
handlers.registerReturnValueHandler(new ModelAttributeMethodProcessor(false));
handlers.registerReturnValueHandler(new DefaultMethodReturnValueHandler(null));
Class<?> handlerType = ClassUtils.getUserClass(handler.getClass());
Set<Method> methods = HandlerMethodSelector.selectMethods(handlerType, REQUEST_MAPPING_METHODS);
Method method = methods.iterator().next();
ServletInvocableHandlerMethod attrMethod = new ServletInvocableHandlerMethod(handler, method);
attrMethod.setArgumentResolverContainer(argResolvers);
attrMethod.setReturnValueHandlers(handlers);
attrMethod.setDataBinderFactory(new DefaultDataBinderFactory(null));
return attrMethod;
}
private Object getProxyBean(Object handler) {
GenericWebApplicationContext wac = new GenericWebApplicationContext();
wac.registerBeanDefinition("controller", new RootBeanDefinition(handler.getClass()));
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
autoProxyCreator.setBeanFactory(wac.getBeanFactory());
wac.getBeanFactory().addBeanPostProcessor(autoProxyCreator);
wac.getBeanFactory().registerSingleton("advsr", new DefaultPointcutAdvisor(new SimpleTraceInterceptor()));
wac.refresh();
return wac.getBean("controller");
}
public static MethodFilter REQUEST_MAPPING_METHODS = new MethodFilter() {
public boolean matches(Method method) {
return AnnotationUtils.findAnnotation(method, RequestMapping.class) != null;
}
};
private interface MappingIfc {
@RequestMapping
@ModelAttribute("attrName")
TestBean model(TestBean input);
}
private static class MappingIfcController implements MappingIfc {
public TestBean model(@ModelAttribute TestBean input) {
return new TestBean(input.getName());
}
}
private interface MappingProxyIfc {
@RequestMapping
@ModelAttribute("attrName")
TestBean model(@ModelAttribute TestBean input);
}
private static class MappingIfcProxyController implements MappingProxyIfc {
@ModelAttribute("attrName")
public TestBean model(@ModelAttribute TestBean input) {
return new TestBean(input.getName());
}
}
public static abstract class MappingAbstractClass {
@RequestMapping
@ModelAttribute("attrName")
TestBean model(TestBean input) {
return new TestBean(input.getName());
}
}
public static class MappingAbstractClassController extends MappingAbstractClass {
public TestBean model(@ModelAttribute TestBean input) {
TestBean testBean = super.model(input);
testBean.setAge(14);
return testBean;
}
}
public interface ParameterizedIfc<TB, S> {
TB model(S input);
}
public static class ParameterizedIfcController implements ParameterizedIfc<TestBean, TestBean> {
@RequestMapping
@ModelAttribute("attrName")
public TestBean model(@ModelAttribute TestBean input) {
return new TestBean(input.getName());
}
}
public interface MappingParameterizedIfc<TB, S> {
@RequestMapping
@ModelAttribute("attrName")
TB model(S input);
}
public static class MappingParameterizedIfcController implements MappingParameterizedIfc<TestBean, TestBean> {
public TestBean model(@ModelAttribute TestBean input) {
return new TestBean(input.getName());
}
}
public interface MappingParameterizedProxyIfc<TB, S> {
@RequestMapping
@ModelAttribute("attrName")
TB model(@ModelAttribute("inputName") S input);
}
public static class MappingParameterizedProxyIfcController implements MappingParameterizedProxyIfc<TestBean, TestBean> {
@RequestMapping
@ModelAttribute("attrName")
public TestBean model(@ModelAttribute TestBean input) {
return new TestBean(input.getName());
}
}
public static class PlainController {
public PlainController() {
}
@RequestMapping
@ModelAttribute("attrName")
public TestBean model(@ModelAttribute TestBean input) {
return new TestBean(input.getName());
}
}
}

View File

@@ -0,0 +1,155 @@
/*
* 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.Set;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.servlet.mvc.method.annotation.RequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestConditionFactory;
import static org.junit.Assert.*;
/**
* @author Arjen Poutsma
*/
public class RequestConditionFactoryTests {
@Test
public void paramEquals() {
assertEquals(getSingleParamCondition("foo"), getSingleParamCondition("foo"));
assertFalse(getSingleParamCondition("foo").equals(getSingleParamCondition("bar")));
assertFalse(getSingleParamCondition("foo").equals(getSingleParamCondition("FOO")));
assertEquals(getSingleParamCondition("foo=bar"), getSingleParamCondition("foo=bar"));
assertFalse(getSingleParamCondition("foo=bar").equals(getSingleParamCondition("FOO=bar")));
}
@Test
public void headerEquals() {
assertEquals(getSingleHeaderCondition("foo"), getSingleHeaderCondition("foo"));
assertEquals(getSingleHeaderCondition("foo"), getSingleHeaderCondition("FOO"));
assertFalse(getSingleHeaderCondition("foo").equals(getSingleHeaderCondition("bar")));
assertEquals(getSingleHeaderCondition("foo=bar"), getSingleHeaderCondition("foo=bar"));
assertEquals(getSingleHeaderCondition("foo=bar"), getSingleHeaderCondition("FOO=bar"));
assertEquals(getSingleHeaderCondition("content-type=text/xml"),
getSingleHeaderCondition("Content-Type=TEXT/XML"));
}
@Test
public void headerPresent() {
RequestCondition condition = getSingleHeaderCondition("accept");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "");
assertTrue(condition.match(request));
}
@Test
public void headerPresentNoMatch() {
RequestCondition condition = getSingleHeaderCondition("foo");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("bar", "");
assertFalse(condition.match(request));
}
@Test
public void headerNotPresent() {
RequestCondition condition = getSingleHeaderCondition("!accept");
MockHttpServletRequest request = new MockHttpServletRequest();
assertTrue(condition.match(request));
}
@Test
public void headerValueMatch() {
RequestCondition condition = getSingleHeaderCondition("foo=bar");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("foo", "bar");
assertTrue(condition.match(request));
}
@Test
public void headerValueNoMatch() {
RequestCondition condition = getSingleHeaderCondition("foo=bar");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("foo", "bazz");
assertFalse(condition.match(request));
}
@Test
public void headerCaseSensitiveValueMatch() {
RequestCondition condition = getSingleHeaderCondition("foo=Bar");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("foo", "bar");
assertFalse(condition.match(request));
}
@Test
public void headerValueMatchNegated() {
RequestCondition condition = getSingleHeaderCondition("foo!=bar");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("foo", "baz");
assertTrue(condition.match(request));
}
@Test
public void mediaTypeHeaderValueMatch() {
RequestCondition condition = getSingleHeaderCondition("accept=text/html");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "text/html");
assertTrue(condition.match(request));
}
@Test
public void mediaTypeHeaderValueMatchNegated() {
RequestCondition condition = getSingleHeaderCondition("accept!=text/html");
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Accept", "application/html");
assertTrue(condition.match(request));
}
private RequestCondition getSingleHeaderCondition(String expression) {
Set<RequestCondition> conditions = RequestConditionFactory.parseHeaders(expression);
assertEquals(1, conditions.size());
return conditions.iterator().next();
}
private RequestCondition getSingleParamCondition(String expression) {
Set<RequestCondition> conditions = RequestConditionFactory.parseParams(expression);
assertEquals(1, conditions.size());
return conditions.iterator().next();
}
}

View File

@@ -0,0 +1,149 @@
/*
* 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 static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestConditionFactory;
import org.springframework.web.servlet.mvc.method.annotation.RequestKey;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodMapping;
/**
* @author Arjen Poutsma
* @author Rossen Stoyanchev
*/
public class RequestKeyComparatorTests {
private RequestMappingHandlerMethodMapping handlerMapping;
private MockHttpServletRequest request;
@Before
public void setup() {
this.handlerMapping = new RequestMappingHandlerMethodMapping();
this.request = new MockHttpServletRequest();
}
@Test
public void moreSpecificPatternWins() {
request.setRequestURI("/foo");
Comparator<RequestKey> comparator = handlerMapping.getKeyComparator(request);
RequestKey key1 = new RequestKey(asList("/fo*"), null, null, null);
RequestKey key2 = new RequestKey(asList("/foo"), null, null, null);
assertEquals(1, comparator.compare(key1, key2));
}
@Test
public void equalPatterns() {
request.setRequestURI("/foo");
Comparator<RequestKey> comparator = handlerMapping.getKeyComparator(request);
RequestKey key1 = new RequestKey(asList("/foo*"), null, null, null);
RequestKey key2 = new RequestKey(asList("/foo*"), null, null, null);
assertEquals(0, comparator.compare(key1, key2));
}
@Test
public void greaterNumberOfMatchingPatternsWins() throws Exception {
request.setRequestURI("/foo.html");
RequestKey key1 = new RequestKey(asList("/foo", "*.jpeg"), null, null, null);
RequestKey key2 = new RequestKey(asList("/foo", "*.html"), null, null, null);
RequestKey match1 = handlerMapping.getMatchingKey(key1, request);
RequestKey match2 = handlerMapping.getMatchingKey(key2, request);
List<RequestKey> matches = asList(match1, match2);
Collections.sort(matches, handlerMapping.getKeyComparator(request));
assertSame(match2.getPatterns(), matches.get(0).getPatterns());
}
@Test
public void oneMethodWinsOverNone() {
Comparator<RequestKey> comparator = handlerMapping.getKeyComparator(request);
RequestKey key1 = new RequestKey(null, null, null, null);
RequestKey key2 = new RequestKey(null, asList(RequestMethod.GET), null, null);
assertEquals(1, comparator.compare(key1, key2));
}
@Test
public void methodsAndParams() {
RequestKey empty = new RequestKey(null, null, null, null);
RequestKey oneMethod = new RequestKey(null, asList(RequestMethod.GET), null, null);
RequestKey oneMethodOneParam =
new RequestKey(null, asList(RequestMethod.GET), RequestConditionFactory.parseParams("foo"), null);
List<RequestKey> list = asList(empty, oneMethod, oneMethodOneParam);
Collections.shuffle(list);
Collections.sort(list, handlerMapping.getKeyComparator(request));
assertEquals(oneMethodOneParam, list.get(0));
assertEquals(oneMethod, list.get(1));
assertEquals(empty, list.get(2));
}
@Test
@Ignore // TODO : remove ignore
public void acceptHeaders() {
RequestKey html = new RequestKey(null, null, null, RequestConditionFactory.parseHeaders("accept=text/html"));
RequestKey xml = new RequestKey(null, null, null, RequestConditionFactory.parseHeaders("accept=application/xml"));
RequestKey none = new RequestKey(null, null, null, null);
request.addHeader("Accept", "application/xml, text/html");
Comparator<RequestKey> comparator = handlerMapping.getKeyComparator(request);
assertTrue(comparator.compare(html, xml) > 0);
assertTrue(comparator.compare(xml, html) < 0);
assertTrue(comparator.compare(xml, none) < 0);
assertTrue(comparator.compare(none, xml) > 0);
assertTrue(comparator.compare(html, none) < 0);
assertTrue(comparator.compare(none, html) > 0);
request = new MockHttpServletRequest();
request.addHeader("Accept", "application/xml, text/*");
comparator = handlerMapping.getKeyComparator(request);
assertTrue(comparator.compare(html, xml) > 0);
assertTrue(comparator.compare(xml, html) < 0);
request = new MockHttpServletRequest();
request.addHeader("Accept", "application/pdf");
comparator = handlerMapping.getKeyComparator(request);
assertTrue(comparator.compare(html, xml) == 0);
assertTrue(comparator.compare(xml, html) == 0);
// See SPR-7000
request = new MockHttpServletRequest();
request.addHeader("Accept", "text/html;q=0.9,application/xml");
comparator = handlerMapping.getKeyComparator(request);
assertTrue(comparator.compare(html, xml) > 0);
assertTrue(comparator.compare(xml, html) < 0);
}
}

View File

@@ -0,0 +1,218 @@
/*
* 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 static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestConditionFactory;
import org.springframework.web.servlet.mvc.method.annotation.RequestKey;
import org.springframework.web.util.UrlPathHelper;
/**
* @author Arjen Poutsma
* @author Rossen Stoyanchev
*/
public class RequestKeyTests {
@Test
public void equals() {
RequestKey key1 = new RequestKey(asList("/foo"), asList(GET), null, null);
RequestKey key2 = new RequestKey(asList("/foo"), asList(GET), null, null);
assertEquals(key1, key2);
assertEquals(key1.hashCode(), key2.hashCode());
}
@Test
public void equalsPrependSlash() {
RequestKey key1 = new RequestKey(asList("/foo"), asList(GET), null, null);
RequestKey key2 = new RequestKey(asList("foo"), asList(GET), null, null);
assertEquals(key1, key2);
assertEquals(key1.hashCode(), key2.hashCode());
}
@Test
public void combinePatterns() {
AntPathMatcher pathMatcher = new AntPathMatcher();
RequestKey key1 = createKeyFromPatterns("/t1", "/t2");
RequestKey key2 = createKeyFromPatterns("/m1", "/m2");
RequestKey key3 = createKeyFromPatterns("/t1/m1", "/t1/m2", "/t2/m1", "/t2/m2");
assertEquals(key3, key1.combine(key2, pathMatcher));
key1 = createKeyFromPatterns("/t1");
key2 = createKeyFromPatterns(new String[] {});
key3 = createKeyFromPatterns("/t1");
assertEquals(key3, key1.combine(key2, pathMatcher));
key1 = createKeyFromPatterns(new String[] {});
key2 = createKeyFromPatterns("/m1");
key3 = createKeyFromPatterns("/m1");
assertEquals(key3, key1.combine(key2, pathMatcher));
key1 = createKeyFromPatterns(new String[] {});
key2 = createKeyFromPatterns(new String[] {});
key3 = createKeyFromPatterns("/");
assertEquals(key3, key1.combine(key2, pathMatcher));
}
@Test
public void matchPatternsToRequest() {
UrlPathHelper urlPathHelper = new UrlPathHelper();
PathMatcher pathMatcher = new AntPathMatcher();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
RequestKey key = new RequestKey(asList("/foo"), null, null, null);
RequestKey match = key.getMatchingKey(request, pathMatcher, urlPathHelper);
assertNotNull(match);
request = new MockHttpServletRequest("GET", "/foo/bar");
key = new RequestKey(asList("/foo/*"), null, null, null);
match = key.getMatchingKey(request, pathMatcher, urlPathHelper);
assertNotNull("Pattern match", match);
request = new MockHttpServletRequest("GET", "/foo.html");
key = new RequestKey(asList("/foo"), null, null, null);
match = key.getMatchingKey(request, pathMatcher, urlPathHelper);
assertNotNull("Implicit match by extension", match);
assertEquals("Contains matched pattern", "/foo.*", match.getPatterns().iterator().next());
request = new MockHttpServletRequest("GET", "/foo/");
key = new RequestKey(asList("/foo"), null, null, null);
match = key.getMatchingKey(request, pathMatcher, urlPathHelper);
assertNotNull("Implicit match by trailing slash", match);
assertEquals("Contains matched pattern", "/foo/", match.getPatterns().iterator().next());
request = new MockHttpServletRequest("GET", "/foo.html");
key = new RequestKey(asList("/foo.jpg"), null, null, null);
match = key.getMatchingKey(request, pathMatcher, urlPathHelper);
assertNull("Implicit match ignored if pattern has extension", match);
request = new MockHttpServletRequest("GET", "/foo.html");
key = new RequestKey(asList("/foo.jpg"), null, null, null);
match = key.getMatchingKey(request, pathMatcher, urlPathHelper);
assertNull("Implicit match ignored on pattern with trailing slash", match);
}
@Test
public void matchRequestMethods() {
UrlPathHelper urlPathHelper = new UrlPathHelper();
PathMatcher pathMatcher = new AntPathMatcher();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
RequestKey key = new RequestKey(asList("/foo"), null, null, null);
RequestKey match = key.getMatchingKey(request, pathMatcher, urlPathHelper);
assertNotNull("No method matches any method", match);
key = new RequestKey(asList("/foo"), asList(GET), null, null);
match = key.getMatchingKey(request, pathMatcher, urlPathHelper);
assertNotNull("Exact match", match);
key = new RequestKey(asList("/foo"), asList(POST), null, null);
match = key.getMatchingKey(request, pathMatcher, urlPathHelper);
assertNull("No match", match);
}
@Test
public void testMatchingKeyContent() {
UrlPathHelper urlPathHelper = new UrlPathHelper();
PathMatcher pathMatcher = new AntPathMatcher();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
RequestKey key = new RequestKey(asList("/foo*", "/bar"), asList(GET, POST), null, null);
RequestKey match = key.getMatchingKey(request, pathMatcher, urlPathHelper);
RequestKey expected = new RequestKey(asList("/foo*"), asList(GET), null, null);
assertEquals("Matching RequestKey contains matched patterns and methods only", expected, match);
key = new RequestKey(asList("/**", "/foo*", "/foo"), null, null, null);
match = key.getMatchingKey(request, pathMatcher, urlPathHelper);
expected = new RequestKey(asList("/foo", "/foo*", "/**"), null, null, null);
assertEquals("Matched patterns are sorted with best match at the top", expected, match);
}
@Test
public void testParamConditions() {
UrlPathHelper urlPathHelper = new UrlPathHelper();
PathMatcher pathMatcher = new AntPathMatcher();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.setParameter("foo", "bar");
RequestKey key = new RequestKey(asList("/foo"), null, RequestConditionFactory.parseParams("foo=bar"), null);
RequestKey match = key.getMatchingKey(request, pathMatcher, urlPathHelper);
assertNotNull(match);
key = new RequestKey(asList("/foo"), null, RequestConditionFactory.parseParams("foo!=bar"), null);
match = key.getMatchingKey(request, pathMatcher, urlPathHelper);
assertNull(match);
}
@Test
public void testHeaderConditions() {
UrlPathHelper urlPathHelper = new UrlPathHelper();
PathMatcher pathMatcher = new AntPathMatcher();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.addHeader("foo", "bar");
RequestKey key = new RequestKey(asList("/foo"), null, null, RequestConditionFactory.parseHeaders("foo=bar"));
RequestKey match = key.getMatchingKey(request, pathMatcher, urlPathHelper);
assertNotNull(match);
key = new RequestKey(asList("/foo"), null, null, RequestConditionFactory.parseHeaders("foo!=bar"));
match = key.getMatchingKey(request, pathMatcher, urlPathHelper);
assertNull(match);
}
@Test
public void testCreateFromServletRequest() {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
RequestKey key = RequestKey.createFromServletRequest(request, new UrlPathHelper());
assertEquals(new RequestKey(asList("/foo"), asList(RequestMethod.GET), null, null), key);
}
private RequestKey createKeyFromPatterns(String... patterns) {
return new RequestKey(asList(patterns), null, null, null);
}
}

View File

@@ -0,0 +1,349 @@
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.awt.Color;
import java.lang.reflect.Method;
import java.security.Principal;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.TestBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
/**
* A test fixture for higher-level {@link RequestMappingHandlerAdapter} tests.
*
* <p>The aim here is not to test {@link RequestMappingHandlerAdapter} itself nor to exercise
* every {@link Controller @Controller} method feature but to have a place to try any feature
* related to {@link Controller @Controller} invocations. Preferably actual tests should be
* added to the components that provide that respective functionality.
*
* <p>The following integration tests for detecting annotations on super types, parameterized
* methods, and proxies may also be of interest:
* <ul>
* <li>{@link RequestMappingHandlerMethodDetectionTests}
* <li>{@link ControllerMethodAnnotationDetectionTests}
* </ul>
*
* @author Rossen Stoyanchev
*/
public class RequestMappingHandlerAdapterIntegrationTests {
private RequestMappingHandlerAdapter handlerAdapter;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@Before
public void setup() throws Exception {
ConfigurableWebBindingInitializer bindingInitializer = new ConfigurableWebBindingInitializer();
bindingInitializer.setValidator(new StubValidator());
this.handlerAdapter = new RequestMappingHandlerAdapter();
this.handlerAdapter.setWebBindingInitializer(bindingInitializer);
this.handlerAdapter.setCustomArgumentResolvers(new WebArgumentResolver[] { new ColorArgumentResolver() });
GenericWebApplicationContext context = new GenericWebApplicationContext();
context.refresh();
this.handlerAdapter.setApplicationContext(context);
this.handlerAdapter.setBeanFactory(context.getBeanFactory());
this.handlerAdapter.afterPropertiesSet();
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
// Expose request to the current thread (for SpEL expressions)
RequestContextHolder.setRequestAttributes(new ServletWebRequest(request));
}
@After
public void teardown() {
RequestContextHolder.resetRequestAttributes();
}
@Test
public void handleMvc() throws Exception {
Class<?>[] paramTypes = new Class<?>[] { int.class, String.class, String.class, String.class, Map.class,
Date.class, Map.class, String.class, String.class, TestBean.class, Errors.class, TestBean.class,
Color.class, HttpServletRequest.class, HttpServletResponse.class, User.class, OtherUser.class,
Model.class };
/* URI template vars (see RequestMappingHandlerMethodMapping) */
Map<String, String> uriTemplateVars = new HashMap<String, String>();
uriTemplateVars.put("pathvar", "pathvarValue");
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
Date date = new GregorianCalendar(2011, Calendar.MARCH, 16).getTime();
String formattedDate = "2011.03.16";
System.setProperty("systemHeader", "systemHeaderValue");
request.setCookies(new Cookie("cookie", "99"));
request.addHeader("header", "headerValue");
request.addHeader("anotherHeader", "anotherHeaderValue");
request.addParameter("datePattern", "yyyy.MM.dd");
request.addParameter("dateParam", formattedDate);
request.addParameter("paramByConvention", "paramByConventionValue");
request.addParameter("age", "25");
request.setContextPath("/contextPath");
request.addHeader("Content-Type", "text/plain; charset=utf-8");
request.setContent("Hello World".getBytes("UTF-8"));
request.setUserPrincipal(new User());
HandlerMethod handlerMethod = handlerMethod(new RequestMappingHandler(), "handleMvc", paramTypes);
ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod);
ModelMap model = mav.getModelMap();
assertEquals("viewName", mav.getViewName());
assertEquals(99, model.get("cookie"));
assertEquals("pathvarValue", model.get("pathvar"));
assertEquals("headerValue", model.get("header"));
assertEquals(date, model.get("dateParam"));
Map<?,?> map = (Map<?,?>) model.get("headerMap");
assertEquals("headerValue", map.get("header"));
assertEquals("anotherHeaderValue", map.get("anotherHeader"));
assertEquals("systemHeaderValue", model.get("systemHeader"));
map = (Map<?,?>) model.get("paramMap");
assertEquals(formattedDate, map.get("dateParam"));
assertEquals("paramByConventionValue", map.get("paramByConvention"));
assertEquals("/contextPath", model.get("value"));
TestBean modelAttr = (TestBean) model.get("modelAttr");
assertEquals(25, modelAttr.getAge());
assertEquals("Set by model method [modelAttr]", modelAttr.getName());
assertSame(modelAttr, request.getSession().getAttribute("modelAttr"));
BindingResult bindingResult = (BindingResult) model.get(BindingResult.MODEL_KEY_PREFIX + "modelAttr");
assertSame(modelAttr, bindingResult.getTarget());
assertEquals(1, bindingResult.getErrorCount());
String conventionAttrName = "testBean";
TestBean modelAttrByConvention = (TestBean) model.get(conventionAttrName);
assertEquals(25, modelAttrByConvention.getAge());
assertEquals("Set by model method [modelAttrByConvention]", modelAttrByConvention.getName());
assertSame(modelAttrByConvention, request.getSession().getAttribute(conventionAttrName));
bindingResult = (BindingResult) model.get(BindingResult.MODEL_KEY_PREFIX + conventionAttrName);
assertSame(modelAttrByConvention, bindingResult.getTarget());
assertTrue(model.get("customArg") instanceof Color);
assertEquals(User.class, model.get("user").getClass());
assertEquals(OtherUser.class, model.get("otherUser").getClass());
}
@Test
public void handleRequestBody() throws Exception {
Class<?>[] paramTypes = new Class<?>[] { byte[].class };
request.addHeader("Content-Type", "text/plain; charset=utf-8");
request.setContent("Hello Server".getBytes("UTF-8"));
HandlerMethod handlerMethod = handlerMethod(new RequestMappingHandler(), "handleRequestBody", paramTypes);
ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod);
assertNull(mav);
assertEquals("Handled requestBody=[Hello Server]", new String(response.getContentAsByteArray(), "UTF-8"));
assertEquals(HttpStatus.ACCEPTED.value(), response.getStatus());
}
@Test
public void handleHttpEntity() throws Exception {
Class<?>[] paramTypes = new Class<?>[] { HttpEntity.class };
request.addHeader("Content-Type", "text/plain; charset=utf-8");
request.setContent("Hello Server".getBytes("UTF-8"));
HandlerMethod handlerMethod = handlerMethod(new RequestMappingHandler(), "handleHttpEntity", paramTypes);
ModelAndView mav = handlerAdapter.handle(request, response, handlerMethod);
assertNull(mav);
assertEquals(HttpStatus.ACCEPTED.value(), response.getStatus());
assertEquals("Handled requestBody=[Hello Server]", new String(response.getContentAsByteArray(), "UTF-8"));
assertEquals("headerValue", response.getHeader("header"));
}
private HandlerMethod handlerMethod(Object handler, String methodName, Class<?>... paramTypes) throws Exception {
Method method = handler.getClass().getDeclaredMethod(methodName, paramTypes);
return new InvocableHandlerMethod(handler, method);
}
@SessionAttributes(types=TestBean.class)
private static class RequestMappingHandler {
@SuppressWarnings("unused")
@InitBinder("dateParam")
public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String datePattern) {
SimpleDateFormat dateFormat = new SimpleDateFormat(datePattern);
dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
@SuppressWarnings("unused")
@ModelAttribute
public void model(Model model) {
TestBean modelAttr = new TestBean();
modelAttr.setName("Set by model method [modelAttr]");
model.addAttribute("modelAttr", modelAttr);
modelAttr = new TestBean();
modelAttr.setName("Set by model method [modelAttrByConvention]");
model.addAttribute(modelAttr);
model.addAttribute(new OtherUser());
}
@SuppressWarnings("unused")
public String handleMvc(
@CookieValue("cookie") int cookie,
@PathVariable("pathvar") String pathvar,
@RequestHeader("header") String header,
@RequestHeader(defaultValue="#{systemProperties.systemHeader}") String systemHeader,
@RequestHeader Map<String, Object> headerMap,
@RequestParam("dateParam") Date dateParam,
@RequestParam Map<String, Object> paramMap,
String paramByConvention,
@Value("#{request.contextPath}") String value,
@ModelAttribute("modelAttr") @Valid TestBean modelAttr,
Errors errors,
TestBean modelAttrByConvention,
Color customArg,
HttpServletRequest request,
HttpServletResponse response,
User user,
@ModelAttribute OtherUser otherUser,
Model model) throws Exception {
model.addAttribute("cookie", cookie).addAttribute("pathvar", pathvar).addAttribute("header", header)
.addAttribute("systemHeader", systemHeader).addAttribute("headerMap", headerMap)
.addAttribute("dateParam", dateParam).addAttribute("paramMap", paramMap)
.addAttribute("paramByConvention", paramByConvention).addAttribute("value", value)
.addAttribute("customArg", customArg).addAttribute(user);
assertNotNull(request);
assertNotNull(response);
return "viewName";
}
@SuppressWarnings("unused")
@ResponseStatus(value=HttpStatus.ACCEPTED)
@ResponseBody
public String handleRequestBody(@RequestBody byte[] bytes) throws Exception {
String requestBody = new String(bytes, "UTF-8");
return "Handled requestBody=[" + requestBody + "]";
}
@SuppressWarnings("unused")
public ResponseEntity<String> handleHttpEntity(HttpEntity<byte[]> httpEntity) throws Exception {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("header", "headerValue");
String responseBody = "Handled requestBody=[" + new String(httpEntity.getBody(), "UTF-8") + "]";
return new ResponseEntity<String>(responseBody, responseHeaders, HttpStatus.ACCEPTED);
}
}
private static class StubValidator implements Validator {
public boolean supports(Class<?> clazz) {
return true;
}
public void validate(Object target, Errors errors) {
errors.reject("error");
}
}
private static class ColorArgumentResolver implements WebArgumentResolver {
public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception {
return new Color(0);
}
}
private static class User implements Principal {
public String getName() {
return "user";
}
}
private static class OtherUser implements Principal {
public String getName() {
return "other user";
}
}
}

View File

@@ -0,0 +1,97 @@
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
/**
* Test fixture for {@link RequestMappingHandlerAdapter} unit tests.
*
* The tests in this class focus on {@link RequestMappingHandlerAdapter} functionality exclusively.
* Also see {@link RequestMappingHandlerAdapterIntegrationTests} for higher-level tests invoking
* {@link Controller @Controller} methods.
*
* @author Rossen Stoyanchev
*/
public class RequestMappingHandlerAdapterTests {
private RequestMappingHandlerAdapter handlerAdapter;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@Before
public void setup() throws Exception {
this.handlerAdapter = new RequestMappingHandlerAdapter();
this.handlerAdapter.setApplicationContext(new GenericWebApplicationContext());
this.handlerAdapter.afterPropertiesSet();
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
}
@Test
public void cacheControlWithoutSessionAttributes() throws Exception {
handlerAdapter.setCacheSeconds(100);
handlerAdapter.handle(request, response, handlerMethod(new SimpleHandler(), "handle"));
assertTrue(response.getHeader("Cache-Control").toString().contains("max-age"));
}
@Test
public void cacheControlWithSessionAttributes() throws Exception {
handlerAdapter.setCacheSeconds(100);
handlerAdapter.handle(request, response, handlerMethod(new SessionAttributeHandler(), "handle"));
assertEquals("no-cache", response.getHeader("Cache-Control"));
}
private HandlerMethod handlerMethod(Object handler, String methodName, Class<?>... paramTypes) throws Exception {
Method method = handler.getClass().getDeclaredMethod(methodName, paramTypes);
return new InvocableHandlerMethod(handler, method);
}
private static class SimpleHandler {
@SuppressWarnings("unused")
public void handle() {
}
}
@SessionAttributes("attr1")
private static class SessionAttributeHandler {
@SuppressWarnings("unused")
public void handle() {
}
}
}

View File

@@ -0,0 +1,167 @@
/*
* 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 static org.junit.Assert.assertNotNull;
import java.util.Arrays;
import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.interceptor.SimpleTraceInterceptor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.TestBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodMapping;
/**
* Test various scenarios for detecting handler methods depending on where @RequestMapping annotations
* are located -- super types, parameterized methods, or in combination with proxies.
*
* Note the following:
* <ul>
* <li>Parameterized methods cannot be used in combination with JDK dynamic proxies since the
* proxy interface does not contain the bridged methods that need to be invoked.
* <li>When using JDK dynamic proxies, the proxied interface must contain all required annotations.
* <li>Method-level annotations can be placed on parent classes or interfaces.
* </ul>
*
* @author Rossen Stoyanchev
*/
@RunWith(Parameterized.class)
public class RequestMappingHandlerMethodDetectionTests {
@Parameters
public static Collection<Object[]> handlerTypes() {
return Arrays.asList(new Object[][] {
{ new MappingInterfaceController(), false},
{ new MappingAbstractClassController(), false},
{ new ParameterizedInterfaceController(), false },
{ new MappingParameterizedInterfaceController(), false },
{ new MappingAbstractClassController(), true},
{ new PlainController(), true}
});
}
private Object handler;
private boolean useAutoProxy;
public RequestMappingHandlerMethodDetectionTests(Object handler, boolean useAutoProxy) {
this.handler = handler;
this.useAutoProxy = useAutoProxy;
}
@Test
public void detectAndMapHandlerMethod() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/handle");
TestRequestMappingHandlerMethodMapping mapping = createHandlerMapping(handler.getClass(), useAutoProxy);
HandlerMethod handlerMethod = mapping.getHandlerInternal(request);
assertNotNull("Failed to detect and map @RequestMapping handler method", handlerMethod);
}
private TestRequestMappingHandlerMethodMapping createHandlerMapping(Class<?> controllerType, boolean useAutoProxy) {
GenericWebApplicationContext wac = new GenericWebApplicationContext();
wac.registerBeanDefinition("controller", new RootBeanDefinition(controllerType));
if (useAutoProxy) {
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
autoProxyCreator.setBeanFactory(wac.getBeanFactory());
wac.getBeanFactory().addBeanPostProcessor(autoProxyCreator);
wac.getBeanFactory().registerSingleton("advsr", new DefaultPointcutAdvisor(new SimpleTraceInterceptor()));
}
TestRequestMappingHandlerMethodMapping mapping = new TestRequestMappingHandlerMethodMapping();
mapping.setApplicationContext(wac);
return mapping;
}
public static class TestRequestMappingHandlerMethodMapping extends RequestMappingHandlerMethodMapping {
public HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
return super.getHandlerInternal(request);
}
}
@Controller
public interface MappingInterface {
@RequestMapping(value="/handle", method = RequestMethod.GET)
void handle();
}
public static class MappingInterfaceController implements MappingInterface {
public void handle() {
}
}
@Controller
public static abstract class MappingAbstractClass {
@RequestMapping(value = "/handle", method = RequestMethod.GET)
public abstract void handle();
}
public static class MappingAbstractClassController extends MappingAbstractClass {
public void handle() {
}
}
@Controller
public interface ParameterizedInterface<T> {
void handle(T object);
}
public static class ParameterizedInterfaceController implements ParameterizedInterface<TestBean> {
@RequestMapping(value = "/handle", method = RequestMethod.GET)
public void handle(TestBean object) {
}
}
@Controller
public interface MappingParameterizedInterface<T> {
@RequestMapping(value = "/handle", method = RequestMethod.GET)
void handle(T object);
}
public static class MappingParameterizedInterfaceController implements MappingParameterizedInterface<TestBean> {
public void handle(TestBean object) {
}
}
@Controller
public static class PlainController {
public PlainController() {
}
@RequestMapping(value = "/handle", method = RequestMethod.GET)
public void handle() {
}
}
}

View File

@@ -0,0 +1,239 @@
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.BindException;
import java.net.SocketException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodExceptionResolver;
/**
* Test fixture for {@link RequestMappingHandlerMethodExceptionResolver} unit tests.
*
* @author Rossen Stoyanchev
* @author Arjen Poutsma
* @since 3.1
*/
public class RequestMappingHandlerMethodExceptionResolverTests {
private RequestMappingHandlerMethodExceptionResolver exceptionResolver;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@Before
public void setUp() throws Exception {
exceptionResolver = new RequestMappingHandlerMethodExceptionResolver();
exceptionResolver.afterPropertiesSet();
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
request.setMethod("GET");
}
@Test
public void simpleWithIOException() throws NoSuchMethodException {
IOException ex = new IOException();
HandlerMethod handlerMethod = new InvocableHandlerMethod(new SimpleController(), "handle");
ModelAndView mav = exceptionResolver.resolveException(request, response, handlerMethod, ex);
assertNotNull("No ModelAndView returned", mav);
assertEquals("Invalid view name returned", "X:IOException", mav.getViewName());
assertEquals("Invalid status code returned", 500, response.getStatus());
}
@Test
public void simpleWithSocketException() throws NoSuchMethodException {
SocketException ex = new SocketException();
HandlerMethod handlerMethod = new InvocableHandlerMethod(new SimpleController(), "handle");
ModelAndView mav = exceptionResolver.resolveException(request, response, handlerMethod, ex);
assertNotNull("No ModelAndView returned", mav);
assertEquals("Invalid view name returned", "Y:SocketException", mav.getViewName());
assertEquals("Invalid status code returned", 406, response.getStatus());
assertEquals("Invalid status reason returned", "This is simply unacceptable!", response.getErrorMessage());
}
@Test
public void simpleWithFileNotFoundException() throws NoSuchMethodException {
FileNotFoundException ex = new FileNotFoundException();
HandlerMethod handlerMethod = new InvocableHandlerMethod(new SimpleController(), "handle");
ModelAndView mav = exceptionResolver.resolveException(request, response, handlerMethod, ex);
assertNotNull("No ModelAndView returned", mav);
assertEquals("Invalid view name returned", "X:FileNotFoundException", mav.getViewName());
assertEquals("Invalid status code returned", 500, response.getStatus());
}
@Test
public void simpleWithBindException() throws NoSuchMethodException {
BindException ex = new BindException();
HandlerMethod handlerMethod = new InvocableHandlerMethod(new SimpleController(), "handle");
ModelAndView mav = exceptionResolver.resolveException(request, response, handlerMethod, ex);
assertNotNull("No ModelAndView returned", mav);
assertEquals("Invalid view name returned", "Y:BindException", mav.getViewName());
assertEquals("Invalid status code returned", 406, response.getStatus());
}
@Test
public void inherited() throws NoSuchMethodException {
IOException ex = new IOException();
HandlerMethod handlerMethod = new InvocableHandlerMethod(new InheritedController(), "handle");
ModelAndView mav = exceptionResolver.resolveException(request, response, handlerMethod, ex);
assertNotNull("No ModelAndView returned", mav);
assertEquals("Invalid view name returned", "GenericError", mav.getViewName());
assertEquals("Invalid status code returned", 500, response.getStatus());
}
@Test(expected = IllegalStateException.class)
public void ambiguous() throws NoSuchMethodException {
IllegalArgumentException ex = new IllegalArgumentException();
HandlerMethod handlerMethod = new InvocableHandlerMethod(new AmbiguousController(), "handle");
exceptionResolver.resolveException(request, response, handlerMethod, ex);
}
@Test
public void noModelAndView() throws UnsupportedEncodingException, NoSuchMethodException {
IllegalArgumentException ex = new IllegalArgumentException();
HandlerMethod handlerMethod = new InvocableHandlerMethod(new NoMAVReturningController(), "handle");
ModelAndView mav = exceptionResolver.resolveException(request, response, handlerMethod, ex);
assertNotNull("No ModelAndView returned", mav);
assertTrue("ModelAndView not empty", mav.isEmpty());
assertEquals("Invalid response written", "IllegalArgumentException", response.getContentAsString());
}
@Test
public void responseBody() throws UnsupportedEncodingException, NoSuchMethodException {
IllegalArgumentException ex = new IllegalArgumentException();
HandlerMethod handlerMethod = new InvocableHandlerMethod(new ResponseBodyController(), "handle");
request.addHeader("Accept", "text/plain");
ModelAndView mav = exceptionResolver.resolveException(request, response, handlerMethod, ex);
assertNotNull("No ModelAndView returned", mav);
assertTrue("ModelAndView not empty", mav.isEmpty());
assertEquals("Invalid response written", "IllegalArgumentException", response.getContentAsString());
}
@Controller
private static class SimpleController {
@SuppressWarnings("unused")
public void handle() {}
@SuppressWarnings("unused")
@ExceptionHandler(IOException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleIOException(IOException ex, HttpServletRequest request) {
return "X:" + ClassUtils.getShortName(ex.getClass());
}
@SuppressWarnings("unused")
@ExceptionHandler(SocketException.class)
@ResponseStatus(value = HttpStatus.NOT_ACCEPTABLE, reason = "This is simply unacceptable!")
public String handleSocketException(Exception ex, HttpServletResponse response) {
return "Y:" + ClassUtils.getShortName(ex.getClass());
}
@SuppressWarnings("unused")
@ExceptionHandler(IllegalArgumentException.class)
public String handleIllegalArgumentException(Exception ex) {
return ClassUtils.getShortName(ex.getClass());
}
}
@Controller
private static class InheritedController extends SimpleController {
@Override
public String handleIOException(IOException ex, HttpServletRequest request) {
return "GenericError";
}
}
@Controller
private static class AmbiguousController {
@SuppressWarnings("unused")
public void handle() {}
@SuppressWarnings("unused")
@ExceptionHandler({BindException.class, IllegalArgumentException.class})
public String handle1(Exception ex, HttpServletRequest request, HttpServletResponse response)
throws IOException {
return ClassUtils.getShortName(ex.getClass());
}
@SuppressWarnings("unused")
@ExceptionHandler
public String handle2(IllegalArgumentException ex) {
return ClassUtils.getShortName(ex.getClass());
}
}
@Controller
private static class NoMAVReturningController {
@SuppressWarnings("unused")
public void handle() {}
@SuppressWarnings("unused")
@ExceptionHandler(Exception.class)
public void handle(Exception ex, Writer writer) throws IOException {
writer.write(ClassUtils.getShortName(ex.getClass()));
}
}
@Controller
private static class ResponseBodyController {
@SuppressWarnings("unused")
public void handle() {}
@SuppressWarnings("unused")
@ExceptionHandler(Exception.class)
@ResponseBody
public String handle(Exception ex) {
return ClassUtils.getShortName(ex.getClass());
}
}
}

View File

@@ -0,0 +1,153 @@
/*
* 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 static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;
import java.util.Arrays;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.stereotype.Controller;
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.HandlerInterceptorAdapter;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.mvc.method.annotation.RequestKey;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodMapping;
/**
* @author Arjen Poutsma
*/
public class RequestMappingHandlerMethodMappingTests {
private MyRequestMappingHandlerMethodMapping mapping;
private MyHandler handler;
private HandlerMethod fooMethod;
private HandlerMethod barMethod;
@Before
public void setUp() throws Exception {
handler = new MyHandler();
fooMethod = new HandlerMethod(handler, "foo");
barMethod = new HandlerMethod(handler, "bar");
StaticApplicationContext context = new StaticApplicationContext();
context.registerSingleton("handler", handler.getClass());
mapping = new MyRequestMappingHandlerMethodMapping();
mapping.setApplicationContext(context);
}
@Test
public void directMatch() throws Exception {
HandlerMethod result = mapping.getHandlerInternal(new MockHttpServletRequest("GET", "/foo"));
assertEquals(fooMethod.getMethod(), result.getMethod());
}
@Test
public void globMatch() throws Exception {
HandlerMethod result = mapping.getHandlerInternal(new MockHttpServletRequest("GET", "/bar"));
assertEquals(barMethod.getMethod(), result.getMethod());
}
@Test
public void methodNotAllowed() throws Exception {
try {
mapping.getHandlerInternal(new MockHttpServletRequest("POST", "/bar"));
fail("HttpRequestMethodNotSupportedException expected");
}
catch (HttpRequestMethodNotSupportedException ex) {
assertArrayEquals("Invalid supported methods", new String[]{"GET", "HEAD"}, ex.getSupportedMethods());
}
}
@Test
public void uriTemplateVariables() {
RequestKey key = new RequestKey(Arrays.asList("/{path1}/{path2}"), null, null, null);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/1/2");
mapping.handleMatch(key, request);
@SuppressWarnings("unchecked")
Map<String, String> actual = (Map<String, String>) request.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
assertNotNull(actual);
assertEquals("1", actual.get("path1"));
assertEquals("2", actual.get("path2"));
}
@Test
public void mappedInterceptors() {
String path = "/handle";
HandlerInterceptor interceptor = new HandlerInterceptorAdapter() {};
MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[] {path}, interceptor);
MyRequestMappingHandlerMethodMapping mapping = new MyRequestMappingHandlerMethodMapping();
mapping.setMappedInterceptors(new MappedInterceptor[] { mappedInterceptor });
HandlerExecutionChain chain = mapping.getHandlerExecutionChain(handler, new MockHttpServletRequest("GET", path));
assertNotNull(chain.getInterceptors());
assertSame(interceptor, chain.getInterceptors()[0]);
chain = mapping.getHandlerExecutionChain(handler, new MockHttpServletRequest("GET", "/invalid"));
assertNull(chain.getInterceptors());
}
private static class MyRequestMappingHandlerMethodMapping extends RequestMappingHandlerMethodMapping {
@Override
public HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
return super.getHandlerInternal(request);
}
}
@Controller
private static class MyHandler {
@SuppressWarnings("unused")
@RequestMapping(value = "/foo", method = RequestMethod.GET)
public void foo() {
}
@SuppressWarnings("unused")
@RequestMapping(value = "/ba*", method = { RequestMethod.GET, RequestMethod.HEAD })
public void bar() {
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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 static org.junit.Assert.assertEquals;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerContainer;
import org.springframework.web.method.support.StubReturnValueHandler;
import org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod;
/**
* Test fixture for {@link ServletInvocableHandlerMethod} unit tests.
*
* @author Rossen Stoyanchev
*/
public class ServletInvocableHandlerMethodTests {
private ServletWebRequest webRequest;
private MockHttpServletResponse response;
@Before
public void setUp() throws Exception {
response = new MockHttpServletResponse();
this.webRequest = new ServletWebRequest(new MockHttpServletRequest(), response);
}
@Test
public void setResponseStatus() throws Exception {
HandlerMethodReturnValueHandlerContainer handlers = new HandlerMethodReturnValueHandlerContainer();
handlers.registerReturnValueHandler(new StubReturnValueHandler(void.class, false));
Method method = Handler.class.getDeclaredMethod("responseStatus");
ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(new Handler(), method);
handlerMethod.setReturnValueHandlers(handlers);
handlerMethod.invokeAndHandle(webRequest, null);
assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatus());
assertEquals("400 Bad Request", response.getErrorMessage());
}
private static class Handler {
@SuppressWarnings("unused")
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "400 Bad Request")
public void responseStatus() {
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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 static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.method.annotation.support.DefaultMethodReturnValueHandler;
/**
* Test fixture for {@link DefaultMethodReturnValueHandler} unit tests.
*
* @author Rossen Stoyanchev
*/
public class DefaultMethodReturnValueHandlerTests {
private DefaultMethodReturnValueHandler handler;
private ServletWebRequest webRequest;
private ModelAndViewContainer<View> mavContainer;
@Before
public void setUp() {
this.handler = new DefaultMethodReturnValueHandler(null);
this.mavContainer = new ModelAndViewContainer<View>(new ExtendedModelMap());
this.webRequest = new ServletWebRequest(new MockHttpServletRequest());
}
@Test(expected=UnsupportedOperationException.class)
public void returnSimpleType() throws Exception {
handler.handleReturnValue(55, createMethodParam("simpleType"), mavContainer, webRequest);
}
@Test
public void returnVoid() throws Exception {
handler.handleReturnValue(null, null, mavContainer, webRequest);
assertNull(mavContainer.getView());
assertNull(mavContainer.getViewName());
assertTrue(mavContainer.getModel().isEmpty());
}
private MethodParameter createMethodParam(String methodName) throws Exception {
Method method = getClass().getDeclaredMethod(methodName);
return new MethodParameter(method, -1);
}
@SuppressWarnings("unused")
private int simpleType() {
return 0;
}
@SuppressWarnings("unused")
private void voidReturnValue() {
}
}

View File

@@ -0,0 +1,243 @@
/*
* 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 static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.isA;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
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.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.mvc.method.annotation.support.HttpEntityMethodProcessor;
/**
* @author Arjen Poutsma
*/
public class HttpEntityMethodProcessorTests {
private HttpEntityMethodProcessor processor;
private HttpMessageConverter<String> messageConverter;
private MethodParameter httpEntityParam;
private MethodParameter responseEntityReturnValue;
private MethodParameter responseEntityParameter;
private MethodParameter intReturnValue;
private ServletWebRequest request;
private MockHttpServletRequest servletRequest;
private MockHttpServletResponse servletResponse;
private MethodParameter httpEntityReturnValue;
private MethodParameter intParameter;
@SuppressWarnings("unchecked")
@Before
public void setUp() throws Exception {
messageConverter = createMock(HttpMessageConverter.class);
processor = new HttpEntityMethodProcessor(messageConverter);
Method handle1 = getClass().getMethod("handle1", HttpEntity.class, ResponseEntity.class, Integer.TYPE);
httpEntityParam = new MethodParameter(handle1, 0);
responseEntityParameter = new MethodParameter(handle1, 1);
intParameter = new MethodParameter(handle1, 2);
responseEntityReturnValue = new MethodParameter(handle1, -1);
Method handle2 = getClass().getMethod("handle2", HttpEntity.class);
httpEntityReturnValue = new MethodParameter(handle2, -1);
Method other = getClass().getMethod("otherMethod");
intReturnValue = new MethodParameter(other, -1);
servletRequest = new MockHttpServletRequest();
servletResponse = new MockHttpServletResponse();
request = new ServletWebRequest(servletRequest, servletResponse);
}
@Test
public void supportsParameter() {
assertTrue("HttpEntity parameter not supported", processor.supportsParameter(httpEntityParam));
assertFalse("ResponseEntity parameter supported", processor.supportsParameter(responseEntityParameter));
assertFalse("non-entity parameter supported", processor.supportsParameter(intParameter));
}
@Test
public void supportsReturnType() {
assertTrue("ResponseEntity return type not supported", processor.supportsReturnType(responseEntityReturnValue));
assertTrue("HttpEntity return type not supported", processor.supportsReturnType(httpEntityReturnValue));
assertFalse("non-ResponseBody return type supported", processor.supportsReturnType(intReturnValue));
}
@Test
public void usesResponseArgument() {
assertFalse("HttpEntity parameter uses response argument", processor.usesResponseArgument(httpEntityParam));
assertTrue("ResponseBody return type does not use response argument",
processor.usesResponseArgument(responseEntityReturnValue));
assertTrue("HttpEntity return type does not use response argument",
processor.usesResponseArgument(httpEntityReturnValue));
}
@Test
@SuppressWarnings("unchecked")
public void resolveArgument() throws Exception {
MediaType contentType = MediaType.TEXT_PLAIN;
String expected = "Foo";
servletRequest.addHeader("Content-Type", contentType.toString());
expect(messageConverter.getSupportedMediaTypes()).andReturn(Arrays.asList(contentType));
expect(messageConverter.canRead(String.class, contentType)).andReturn(true);
expect(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).andReturn(expected);
replay(messageConverter);
HttpEntity<?> result = (HttpEntity<String>) processor.resolveArgument(httpEntityParam, null, request, null);
assertEquals("Invalid argument", expected, result.getBody());
verify(messageConverter);
}
@Test(expected = HttpMediaTypeNotSupportedException.class)
public void resolveArgumentNotReadable() throws Exception {
MediaType contentType = MediaType.TEXT_PLAIN;
servletRequest.addHeader("Content-Type", contentType.toString());
expect(messageConverter.getSupportedMediaTypes()).andReturn(Arrays.asList(contentType));
expect(messageConverter.canRead(String.class, contentType)).andReturn(false);
replay(messageConverter);
processor.resolveArgument(httpEntityParam, null, request, null);
verify(messageConverter);
}
@Test(expected = HttpMediaTypeNotSupportedException.class)
public void resolveArgumentNoContentType() throws Exception {
processor.resolveArgument(httpEntityParam, null, request, null);
}
@Test
public void handleReturnValue() throws Exception {
MediaType accepted = MediaType.TEXT_PLAIN;
String s = "Foo";
ResponseEntity<String> returnValue = new ResponseEntity<String>(s, HttpStatus.OK);
servletRequest.addHeader("Accept", accepted.toString());
expect(messageConverter.canWrite(String.class, accepted)).andReturn(true);
messageConverter.write(eq(s), eq(accepted), isA(HttpOutputMessage.class));
replay(messageConverter);
processor.handleReturnValue(returnValue, responseEntityReturnValue, null, request);
verify(messageConverter);
}
@Test(expected = HttpMediaTypeNotAcceptableException.class)
public void handleReturnValueNotAcceptable() throws Exception {
MediaType accepted = MediaType.TEXT_PLAIN;
String s = "Foo";
ResponseEntity<String> returnValue = new ResponseEntity<String>(s, HttpStatus.OK);
servletRequest.addHeader("Accept", accepted.toString());
expect(messageConverter.canWrite(String.class, accepted)).andReturn(false);
expect(messageConverter.getSupportedMediaTypes()).andReturn(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM));
replay(messageConverter);
processor.handleReturnValue(returnValue, responseEntityReturnValue, null, request);
verify(messageConverter);
}
@Test
public void responseHeaderNoBody() throws Exception {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("header", "headerValue");
ResponseEntity<String> returnValue = new ResponseEntity<String>(responseHeaders, HttpStatus.ACCEPTED);
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(new StringHttpMessageConverter());
processor.handleReturnValue(returnValue, responseEntityReturnValue, null, request);
assertEquals("headerValue", servletResponse.getHeader("header"));
}
@Test
public void responseHeaderAndBody() throws Exception {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("header", "headerValue");
ResponseEntity<String> returnValue = new ResponseEntity<String>("body", responseHeaders, HttpStatus.ACCEPTED);
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(new StringHttpMessageConverter());
processor.handleReturnValue(returnValue, responseEntityReturnValue, null, request);
assertEquals("headerValue", servletResponse.getHeader("header"));
}
public ResponseEntity<String> handle1(HttpEntity<String> httpEntity, ResponseEntity<String> responseEntity, int i) {
return responseEntity;
}
public HttpEntity<?> handle2(HttpEntity<?> entity) {
return entity;
}
public HttpEntity<?> handle3() {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("header", "headerValue");
return new ResponseEntity<String>(responseHeaders, HttpStatus.OK);
}
public int otherMethod() {
return 42;
}
}

View File

@@ -0,0 +1,97 @@
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.support.PathVariableMethodArgumentResolver;
/**
* @author Rossen Stoyanchev
*/
public class PathVariableMethodArgumentResolverTests {
private PathVariableMethodArgumentResolver resolver;
private MethodParameter pathVarParam;
private MethodParameter stringParam;
private MockHttpServletRequest servletRequest;
private ServletWebRequest webRequest;
@Before
public void setUp() throws Exception {
resolver = new PathVariableMethodArgumentResolver(null);
Method method = getClass().getMethod("handle", String.class, String.class);
pathVarParam = new MethodParameter(method, 0);
stringParam = new MethodParameter(method, 1);
servletRequest = new MockHttpServletRequest();
MockHttpServletResponse servletResponse = new MockHttpServletResponse();
webRequest = new ServletWebRequest(servletRequest, servletResponse);
}
@Test
public void usesResponseArgument() {
assertFalse(resolver.usesResponseArgument(null));
}
@Test
public void supportsParameter() {
assertTrue("Parameter with @PathVariable annotation", resolver.supportsParameter(pathVarParam));
assertFalse("Parameter without @PathVariable annotation", resolver.supportsParameter(stringParam));
}
@Test
public void resolveStringArgument() throws Exception {
String expected = "foo";
Map<String, String> uriTemplateVars = new HashMap<String, String>();
uriTemplateVars.put("name", expected);
servletRequest.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
String result = (String) resolver.resolveArgument(pathVarParam, null, webRequest, null);
assertEquals(expected, result);
}
@Test(expected = IllegalStateException.class)
public void handleMissingValue() throws Exception {
resolver.resolveArgument(pathVarParam, null, webRequest, null);
fail("Unresolved path variable should lead to exception.");
}
public void handle(@PathVariable(value = "name") String param1, String param2) {
}
}

View File

@@ -0,0 +1,190 @@
/*
* 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 static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.isA;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
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.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.mvc.method.annotation.support.RequestResponseBodyMethodProcessor;
/**
* @author Arjen Poutsma
*/
public class RequestResponseBodyMethodProcessorTests {
private RequestResponseBodyMethodProcessor processor;
private HttpMessageConverter<String> messageConverter;
private MethodParameter stringParameter;
private MethodParameter stringReturnValue;
private MethodParameter intParameter;
private MethodParameter intReturnValue;
private NativeWebRequest webRequest;
private MockHttpServletRequest servletRequest;
@SuppressWarnings("unchecked")
@Before
public void setUp() throws Exception {
messageConverter = createMock(HttpMessageConverter.class);
processor = new RequestResponseBodyMethodProcessor(messageConverter);
Method handle = getClass().getMethod("handle", String.class, Integer.TYPE);
stringParameter = new MethodParameter(handle, 0);
intParameter = new MethodParameter(handle, 1);
stringReturnValue = new MethodParameter(handle, -1);
Method other = getClass().getMethod("otherMethod");
intReturnValue = new MethodParameter(other, -1);
servletRequest = new MockHttpServletRequest();
MockHttpServletResponse servletResponse = new MockHttpServletResponse();
webRequest = new ServletWebRequest(servletRequest, servletResponse);
}
@Test
public void supportsParameter() {
assertTrue("RequestBody parameter not supported", processor.supportsParameter(stringParameter));
assertFalse("non-RequestBody parameter supported", processor.supportsParameter(intParameter));
}
@Test
public void supportsReturnType() {
assertTrue("ResponseBody return type not supported", processor.supportsReturnType(stringReturnValue));
assertFalse("non-ResponseBody return type supported", processor.supportsReturnType(intReturnValue));
}
@Test
public void usesResponseArgument() {
assertFalse("RequestBody parameter uses response argument", processor.usesResponseArgument(stringParameter));
assertTrue("ResponseBody return type does not use response argument",
processor.usesResponseArgument(stringReturnValue));
}
@Test
public void resolveArgument() throws Exception {
MediaType contentType = MediaType.TEXT_PLAIN;
String expected = "Foo";
servletRequest.addHeader("Content-Type", contentType.toString());
expect(messageConverter.getSupportedMediaTypes()).andReturn(Arrays.asList(contentType));
expect(messageConverter.canRead(String.class, contentType)).andReturn(true);
expect(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).andReturn(expected);
replay(messageConverter);
Object result = processor.resolveArgument(stringParameter, null, webRequest, null);
assertEquals("Invalid argument", expected, result);
verify(messageConverter);
}
@Test(expected = HttpMediaTypeNotSupportedException.class)
public void resolveArgumentNotReadable() throws Exception {
MediaType contentType = MediaType.TEXT_PLAIN;
servletRequest.addHeader("Content-Type", contentType.toString());
expect(messageConverter.getSupportedMediaTypes()).andReturn(Arrays.asList(contentType));
expect(messageConverter.canRead(String.class, contentType)).andReturn(false);
replay(messageConverter);
processor.resolveArgument(stringParameter, null, webRequest, null);
verify(messageConverter);
}
@Test(expected = HttpMediaTypeNotSupportedException.class)
public void resolveArgumentNoContentType() throws Exception {
processor.resolveArgument(stringParameter, null, webRequest, null);
}
@Test
public void handleReturnValue() throws Exception {
MediaType accepted = MediaType.TEXT_PLAIN;
String returnValue = "Foo";
servletRequest.addHeader("Accept", accepted.toString());
expect(messageConverter.canWrite(String.class, accepted)).andReturn(true);
messageConverter.write(eq(returnValue), eq(accepted), isA(HttpOutputMessage.class));
replay(messageConverter);
processor.handleReturnValue(returnValue, stringReturnValue, null, webRequest);
verify(messageConverter);
}
@Test(expected = HttpMediaTypeNotAcceptableException.class)
public void handleReturnValueNotAcceptable() throws Exception {
MediaType accepted = MediaType.TEXT_PLAIN;
String returnValue = "Foo";
servletRequest.addHeader("Accept", accepted.toString());
expect(messageConverter.canWrite(String.class, accepted)).andReturn(false);
expect(messageConverter.getSupportedMediaTypes()).andReturn(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM));
replay(messageConverter);
processor.handleReturnValue(returnValue, stringReturnValue, null, webRequest);
verify(messageConverter);
}
@ResponseBody
public String handle(@RequestBody String s, int i) {
return s;
}
public int otherMethod() {
return 42;
}
}

View File

@@ -0,0 +1,117 @@
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Method;
import javax.servlet.http.Cookie;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.annotation.support.CookieValueMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.support.ServletCookieValueMethodArgumentResolver;
/**
* @author Arjen Poutsma
*/
public class ServletCookieValueMethodArgumentResolverTests {
private CookieValueMethodArgumentResolver resolver;
private MethodParameter cookieParameter;
private MethodParameter cookieStringParameter;
private MethodParameter otherParameter;
private MockHttpServletRequest servletRequest;
private ServletWebRequest webRequest;
@Before
public void setUp() throws Exception {
resolver = new ServletCookieValueMethodArgumentResolver(null);
Method method = getClass().getMethod("params", Cookie.class, String.class, String.class);
cookieParameter = new MethodParameter(method, 0);
cookieStringParameter = new MethodParameter(method, 1);
otherParameter = new MethodParameter(method, 2);
servletRequest = new MockHttpServletRequest();
MockHttpServletResponse servletResponse = new MockHttpServletResponse();
webRequest = new ServletWebRequest(servletRequest, servletResponse);
}
@Test
public void usesResponseArgument() throws NoSuchMethodException {
assertFalse("resolver uses response argument", resolver.usesResponseArgument(null));
}
@Test
public void supportsParameter() {
assertTrue("Cookie parameter not supported", resolver.supportsParameter(cookieParameter));
assertTrue("Cookie string parameter not supported", resolver.supportsParameter(cookieStringParameter));
assertFalse("non-@CookieValue parameter supported", resolver.supportsParameter(otherParameter));
}
@Test
public void resolveCookieArgument() throws Exception {
Cookie expected = new Cookie("name", "foo");
servletRequest.setCookies(expected);
Cookie result = (Cookie) resolver.resolveArgument(cookieParameter, null, webRequest, null);
assertEquals("Invalid result", expected, result);
}
@Test
public void resolveCookieStringArgument() throws Exception {
Cookie cookie = new Cookie("name", "foo");
servletRequest.setCookies(cookie);
String result = (String) resolver.resolveArgument(cookieStringParameter, null, webRequest, null);
assertEquals("Invalid result", cookie.getValue(), result);
}
@Test
public void resolveCookieDefaultValue() throws Exception {
String result = (String) resolver.resolveArgument(cookieStringParameter, null, webRequest, null);
assertEquals("Invalid result", "bar", result);
}
@Test(expected = IllegalStateException.class)
public void notFound() throws Exception {
String result = (String) resolver.resolveArgument(cookieParameter, null, webRequest, null);
assertEquals("Invalid result", "bar", result);
}
public void params(@CookieValue("name") Cookie cookie,
@CookieValue(value = "name", defaultValue = "bar") String cookieString,
String unsupported) {
}
}

View File

@@ -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.InputStream;
import java.io.Reader;
import java.lang.reflect.Method;
import java.security.Principal;
import java.util.Locale;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpSession;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.multipart.MultipartRequest;
import org.springframework.web.servlet.mvc.method.annotation.support.ServletRequestMethodArgumentResolver;
import static org.junit.Assert.*;
/**
* @author Arjen Poutsma
*/
public class ServletRequestMethodArgumentResolverTests {
private ServletRequestMethodArgumentResolver resolver;
private Method supportedParams;
private ServletWebRequest webRequest;
private MockHttpServletRequest servletRequest;
@Before
public void setUp() throws Exception {
resolver = new ServletRequestMethodArgumentResolver();
supportedParams = getClass()
.getMethod("supportedParams", ServletRequest.class, MultipartRequest.class, HttpSession.class,
Principal.class, Locale.class, InputStream.class, Reader.class);
servletRequest = new MockHttpServletRequest();
webRequest = new ServletWebRequest(servletRequest, new MockHttpServletResponse());
}
@Test
public void usesResponseArgument() {
assertFalse("resolver uses response argument", resolver.usesResponseArgument(null));
}
@Test
public void servletRequest() throws Exception {
MethodParameter servletRequestParameter = new MethodParameter(supportedParams, 0);
assertTrue("ServletRequest not supported", resolver.supportsParameter(servletRequestParameter));
Object result = resolver.resolveArgument(servletRequestParameter, null, webRequest, null);
assertSame("Invalid result", servletRequest, result);
}
@Test
public void session() throws Exception {
MockHttpSession session = new MockHttpSession();
servletRequest.setSession(session);
MethodParameter sessionParameter = new MethodParameter(supportedParams, 2);
assertTrue("Session not supported", resolver.supportsParameter(sessionParameter));
Object result = resolver.resolveArgument(sessionParameter, null, webRequest, null);
assertSame("Invalid result", session, result);
}
@Test
public void principal() throws Exception {
Principal principal = new Principal() {
public String getName() {
return "Foo";
}
};
servletRequest.setUserPrincipal(principal);
MethodParameter principalParameter = new MethodParameter(supportedParams, 3);
assertTrue("Principal not supported", resolver.supportsParameter(principalParameter));
Object result = resolver.resolveArgument(principalParameter, null, webRequest, null);
assertSame("Invalid result", principal, result);
}
@Test
public void locale() throws Exception {
Locale locale = Locale.ENGLISH;
servletRequest.addPreferredLocale(locale);
MethodParameter localeParameter = new MethodParameter(supportedParams, 4);
assertTrue("Locale not supported", resolver.supportsParameter(localeParameter));
Object result = resolver.resolveArgument(localeParameter, null, webRequest, null);
assertSame("Invalid result", locale, result);
}
@Test
public void inputStream() throws Exception {
MethodParameter inputStreamParameter = new MethodParameter(supportedParams, 5);
assertTrue("InputStream not supported", resolver.supportsParameter(inputStreamParameter));
Object result = resolver.resolveArgument(inputStreamParameter, null, webRequest, null);
assertSame("Invalid result", webRequest.getRequest().getInputStream(), result);
}
@Test
public void reader() throws Exception {
MethodParameter readerParameter = new MethodParameter(supportedParams, 6);
assertTrue("Reader not supported", resolver.supportsParameter(readerParameter));
Object result = resolver.resolveArgument(readerParameter, null, webRequest, null);
assertSame("Invalid result", webRequest.getRequest().getReader(), result);
}
public void supportedParams(ServletRequest p0,
MultipartRequest p1,
HttpSession p2,
Principal p3,
Locale p4,
InputStream p5,
Reader p9) {
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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.OutputStream;
import java.io.Writer;
import java.lang.reflect.Method;
import javax.servlet.ServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.mvc.method.annotation.support.ServletResponseMethodArgumentResolver;
import static org.junit.Assert.*;
/**
* @author Arjen Poutsma
*/
public class ServletResponseMethodArgumentResolverTests {
private ServletResponseMethodArgumentResolver resolver;
private Method supportedParams;
private ServletWebRequest webRequest;
private MockHttpServletResponse servletResponse;
@Before
public void setUp() throws Exception {
resolver = new ServletResponseMethodArgumentResolver();
supportedParams =
getClass().getMethod("supportedParams", ServletResponse.class, OutputStream.class, Writer.class);
servletResponse = new MockHttpServletResponse();
webRequest = new ServletWebRequest(new MockHttpServletRequest(), servletResponse);
}
@Test
public void usesResponseArgument() {
assertTrue("resolver uses response argument", resolver.usesResponseArgument(null));
}
@Test
public void servletResponse() throws Exception {
MethodParameter servletResponseParameter = new MethodParameter(supportedParams, 0);
assertTrue("ServletResponse not supported", resolver.supportsParameter(servletResponseParameter));
Object result = resolver.resolveArgument(servletResponseParameter, null, webRequest, null);
assertSame("Invalid result", servletResponse, result);
}
@Test
public void outputStream() throws Exception {
MethodParameter outputStreamParameter = new MethodParameter(supportedParams, 1);
assertTrue("OutputStream not supported", resolver.supportsParameter(outputStreamParameter));
Object result = resolver.resolveArgument(outputStreamParameter, null, webRequest, null);
assertSame("Invalid result", servletResponse.getOutputStream(), result);
}
@Test
public void writer() throws Exception {
MethodParameter writerParameter = new MethodParameter(supportedParams, 2);
assertTrue("Writer not supported", resolver.supportsParameter(writerParameter));
Object result = resolver.resolveArgument(writerParameter, null, webRequest, null);
assertSame("Invalid result", servletResponse.getWriter(), result);
}
public void supportedParams(ServletResponse p0, OutputStream p1, Writer p2) {
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.method.annotation.support.DefaultMethodReturnValueHandler;
import org.springframework.web.servlet.mvc.method.annotation.support.ViewMethodReturnValueHandler;
import org.springframework.web.servlet.view.InternalResourceView;
/**
* Test fixture for {@link DefaultMethodReturnValueHandler} unit tests.
*
* @author Rossen Stoyanchev
*/
public class ViewMethodReturnValueHandlerTests {
private ViewMethodReturnValueHandler handler;
private ServletWebRequest webRequest;
private ModelAndViewContainer<View> mavContainer;
@Before
public void setUp() {
this.handler = new ViewMethodReturnValueHandler();
this.mavContainer = new ModelAndViewContainer<View>(new ExtendedModelMap());
this.webRequest = new ServletWebRequest(new MockHttpServletRequest());
}
@Test
public void supportsReturnType() throws Exception {
assertTrue(handler.supportsReturnType(createMethodParam("view")));
assertTrue(handler.supportsReturnType(createMethodParam("viewName")));
}
@Test
public void returnView() throws Exception {
InternalResourceView view = new InternalResourceView("testView");
handler.handleReturnValue(view, createMethodParam("view"), mavContainer, webRequest);
assertSame(view, mavContainer.getView());
}
@Test
public void returnViewName() throws Exception {
handler.handleReturnValue("testView", createMethodParam("viewName"), mavContainer, webRequest);
assertEquals("testView", mavContainer.getViewName());
}
private MethodParameter createMethodParam(String methodName) throws Exception {
Method method = getClass().getDeclaredMethod(methodName);
return new MethodParameter(method, -1);
}
@SuppressWarnings("unused")
private View view() {
return null;
}
@SuppressWarnings("unused")
private String viewName() {
return null;
}
}