Drop Portlet MVC support

This commit also removes the corresponding deprecated Servlet MVC variant and updates DispatcherServlet.properties to point to RequestMappingHandlerMapping/Adapter by default.

Issue: SPR-14129
This commit is contained in:
Juergen Hoeller
2016-07-04 23:33:45 +02:00
parent ae0b7c26c5
commit 2b3445df81
235 changed files with 94 additions and 39747 deletions

View File

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

View File

@@ -1,452 +0,0 @@
/*
* Copyright 2002-2015 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.annotation;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.transform.Source;
import org.springframework.core.ExceptionDepthComparator;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
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.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.ui.Model;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
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.bind.support.WebArgumentResolver;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.springframework.web.servlet.support.RequestContextUtils;
/**
* Implementation of the {@link org.springframework.web.servlet.HandlerExceptionResolver} interface that handles
* exceptions through the {@link ExceptionHandler} annotation.
*
* <p>This exception resolver is enabled by default in the {@link org.springframework.web.servlet.DispatcherServlet}.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 3.0
* @deprecated as of Spring 3.2, in favor of
* {@link org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver ExceptionHandlerExceptionResolver}
*/
@Deprecated
public class AnnotationMethodHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
/**
* Arbitrary {@link Method} reference, indicating no method found in the cache.
*/
private static final Method NO_METHOD_FOUND = ClassUtils.getMethodIfAvailable(System.class, "currentTimeMillis");
private final Map<Class<?>, Map<Class<? extends Throwable>, Method>> exceptionHandlerCache =
new ConcurrentHashMap<Class<?>, Map<Class<? extends Throwable>, Method>>(64);
private WebArgumentResolver[] customArgumentResolvers;
private HttpMessageConverter<?>[] messageConverters =
new HttpMessageConverter<?>[] {new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(),
new SourceHttpMessageConverter<Source>(),
new org.springframework.http.converter.xml.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;
}
@Override
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (handler != null) {
Method handlerMethod = findBestExceptionHandlerMethod(handler, ex);
if (handlerMethod != null) {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
Object[] args = resolveHandlerArguments(handlerMethod, handler, webRequest, ex);
if (logger.isDebugEnabled()) {
logger.debug("Invoking request handler method: " + handlerMethod);
}
Object retVal = doInvokeMethod(handlerMethod, handler, args);
return getModelAndView(handlerMethod, retVal, webRequest);
}
catch (Exception invocationEx) {
logger.error("Invoking request method resulted in exception : " + handlerMethod, invocationEx);
}
}
}
return null;
}
/**
* Finds the handler method that matches the thrown exception best.
* @param handler the handler object
* @param thrownException the exception to be handled
* @return the best matching method; or {@code null} if none is found
*/
private Method findBestExceptionHandlerMethod(Object handler, final Exception thrownException) {
final Class<?> handlerType = ClassUtils.getUserClass(handler);
final Class<? extends Throwable> thrownExceptionType = thrownException.getClass();
Method handlerMethod = null;
Map<Class<? extends Throwable>, Method> handlers = this.exceptionHandlerCache.get(handlerType);
if (handlers != null) {
handlerMethod = handlers.get(thrownExceptionType);
if (handlerMethod != null) {
return (handlerMethod == NO_METHOD_FOUND ? null : handlerMethod);
}
}
else {
handlers = new ConcurrentHashMap<Class<? extends Throwable>, Method>(16);
this.exceptionHandlerCache.put(handlerType, handlers);
}
final Map<Class<? extends Throwable>, Method> matchedHandlers = new HashMap<Class<? extends Throwable>, Method>();
ReflectionUtils.doWithMethods(handlerType, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) {
method = ClassUtils.getMostSpecificMethod(method, handlerType);
List<Class<? extends Throwable>> handledExceptions = getHandledExceptions(method);
for (Class<? extends Throwable> handledException : handledExceptions) {
if (handledException.isAssignableFrom(thrownExceptionType)) {
if (!matchedHandlers.containsKey(handledException)) {
matchedHandlers.put(handledException, method);
}
else {
Method oldMappedMethod = matchedHandlers.get(handledException);
if (!oldMappedMethod.equals(method)) {
throw new IllegalStateException(
"Ambiguous exception handler mapped for " + handledException + "]: {" +
oldMappedMethod + ", " + method + "}.");
}
}
}
}
}
});
handlerMethod = getBestMatchingMethod(matchedHandlers, thrownException);
handlers.put(thrownExceptionType, (handlerMethod == null ? NO_METHOD_FOUND : handlerMethod));
return handlerMethod;
}
/**
* Returns all the exception classes handled by the given method.
* <p>The default implementation looks for exceptions in the annotation,
* or - if that annotation element is empty - any exceptions listed in the method parameters if the method
* is annotated with {@code @ExceptionHandler}.
* @param method the method
* @return the handled exceptions
*/
@SuppressWarnings("unchecked")
protected List<Class<? extends Throwable>> getHandledExceptions(Method method) {
List<Class<? extends Throwable>> result = new ArrayList<Class<? extends Throwable>>();
ExceptionHandler exceptionHandler = AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
if (exceptionHandler != null) {
if (!ObjectUtils.isEmpty(exceptionHandler.value())) {
result.addAll(Arrays.asList(exceptionHandler.value()));
}
else {
for (Class<?> param : method.getParameterTypes()) {
if (Throwable.class.isAssignableFrom(param)) {
result.add((Class<? extends Throwable>) param);
}
}
}
}
return result;
}
/**
* Uses the {@link ExceptionDepthComparator} to find the best matching method.
* @return the best matching method, or {@code null} if none found
*/
private Method getBestMatchingMethod(
Map<Class<? extends Throwable>, Method> resolverMethods, Exception thrownException) {
if (resolverMethods.isEmpty()) {
return null;
}
Class<? extends Throwable> closestMatch =
ExceptionDepthComparator.findClosestMatch(resolverMethods.keySet(), thrownException);
Method method = resolverMethods.get(closestMatch);
return ((method == null) || (NO_METHOD_FOUND == method)) ? null : method;
}
/**
* Resolves the arguments for the given method. Delegates to {@link #resolveCommonArgument}.
*/
private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,
NativeWebRequest webRequest, Exception thrownException) throws Exception {
Class<?>[] paramTypes = handlerMethod.getParameterTypes();
Object[] args = new Object[paramTypes.length];
Class<?> handlerType = handler.getClass();
for (int i = 0; i < args.length; i++) {
MethodParameter methodParam = new SynthesizingMethodParameter(handlerMethod, i);
GenericTypeResolver.resolveParameterType(methodParam, handlerType);
Class<?> paramType = methodParam.getParameterType();
Object argValue = resolveCommonArgument(methodParam, webRequest, thrownException);
if (argValue != WebArgumentResolver.UNRESOLVED) {
args[i] = argValue;
}
else {
throw new IllegalStateException("Unsupported argument [" + paramType.getName() +
"] for @ExceptionHandler method: " + handlerMethod);
}
}
return args;
}
/**
* Resolves common method arguments. Delegates to registered {@link #setCustomArgumentResolver(WebArgumentResolver)
* argumentResolvers} first, then checking {@link #resolveStandardArgument}.
* @param methodParameter the method parameter
* @param webRequest the request
* @param thrownException the exception thrown
* @return the argument value, or {@link WebArgumentResolver#UNRESOLVED}
*/
protected Object resolveCommonArgument(MethodParameter methodParameter, NativeWebRequest webRequest,
Exception thrownException) throws Exception {
// Invoke custom argument resolvers if present...
if (this.customArgumentResolvers != null) {
for (WebArgumentResolver argumentResolver : this.customArgumentResolvers) {
Object value = argumentResolver.resolveArgument(methodParameter, webRequest);
if (value != WebArgumentResolver.UNRESOLVED) {
return value;
}
}
}
// Resolution of standard parameter types...
Class<?> paramType = methodParameter.getParameterType();
Object value = resolveStandardArgument(paramType, webRequest, thrownException);
if (value != WebArgumentResolver.UNRESOLVED && !ClassUtils.isAssignableValue(paramType, value)) {
throw new IllegalStateException(
"Standard argument type [" + paramType.getName() + "] resolved to incompatible value of type [" +
(value != null ? value.getClass() : null) +
"]. Consider declaring the argument type in a less specific fashion.");
}
return value;
}
/**
* Resolves standard method arguments. The default implementation handles {@link NativeWebRequest},
* {@link ServletRequest}, {@link ServletResponse}, {@link HttpSession}, {@link Principal},
* {@link Locale}, request {@link InputStream}, request {@link Reader}, response {@link OutputStream},
* response {@link Writer}, and the given {@code thrownException}.
* @param parameterType the method parameter type
* @param webRequest the request
* @param thrownException the exception thrown
* @return the argument value, or {@link WebArgumentResolver#UNRESOLVED}
*/
protected Object resolveStandardArgument(Class<?> parameterType, NativeWebRequest webRequest,
Exception thrownException) throws Exception {
if (parameterType.isInstance(thrownException)) {
return thrownException;
}
else if (WebRequest.class.isAssignableFrom(parameterType)) {
return webRequest;
}
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
if (ServletRequest.class.isAssignableFrom(parameterType)) {
return request;
}
else if (ServletResponse.class.isAssignableFrom(parameterType)) {
return response;
}
else if (HttpSession.class.isAssignableFrom(parameterType)) {
return request.getSession();
}
else if (Principal.class.isAssignableFrom(parameterType)) {
return request.getUserPrincipal();
}
else if (Locale.class == parameterType) {
return RequestContextUtils.getLocale(request);
}
else if (InputStream.class.isAssignableFrom(parameterType)) {
return request.getInputStream();
}
else if (Reader.class.isAssignableFrom(parameterType)) {
return request.getReader();
}
else if (OutputStream.class.isAssignableFrom(parameterType)) {
return response.getOutputStream();
}
else if (Writer.class.isAssignableFrom(parameterType)) {
return response.getWriter();
}
else {
return WebArgumentResolver.UNRESOLVED;
}
}
private Object doInvokeMethod(Method method, Object target, Object[] args) throws Exception {
ReflectionUtils.makeAccessible(method);
try {
return method.invoke(target, args);
}
catch (InvocationTargetException ex) {
ReflectionUtils.rethrowException(ex.getTargetException());
}
throw new IllegalStateException("Should never get here");
}
@SuppressWarnings("unchecked")
private ModelAndView getModelAndView(Method handlerMethod, Object returnValue, ServletWebRequest webRequest)
throws Exception {
ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(handlerMethod, ResponseStatus.class);
if (responseStatus != null) {
HttpStatus statusCode = responseStatus.code();
String reason = responseStatus.reason();
if (!StringUtils.hasText(reason)) {
webRequest.getResponse().setStatus(statusCode.value());
}
else {
webRequest.getResponse().sendError(statusCode.value(), reason);
}
}
if (returnValue != null && AnnotationUtils.findAnnotation(handlerMethod, ResponseBody.class) != null) {
return handleResponseBody(returnValue, webRequest);
}
if (returnValue instanceof ModelAndView) {
return (ModelAndView) returnValue;
}
else if (returnValue instanceof Model) {
return new ModelAndView().addAllObjects(((Model) returnValue).asMap());
}
else if (returnValue instanceof Map) {
return new ModelAndView().addAllObjects((Map<String, Object>) returnValue);
}
else if (returnValue instanceof View) {
return new ModelAndView((View) returnValue);
}
else if (returnValue instanceof String) {
return new ModelAndView((String) returnValue);
}
else if (returnValue == null) {
return new ModelAndView();
}
else {
throw new IllegalArgumentException("Invalid handler method return value: " + returnValue);
}
}
@SuppressWarnings({ "unchecked", "rawtypes", "resource" })
private ModelAndView handleResponseBody(Object returnValue, ServletWebRequest webRequest)
throws ServletException, IOException {
HttpInputMessage inputMessage = new ServletServerHttpRequest(webRequest.getRequest());
List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
if (acceptedMediaTypes.isEmpty()) {
acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
}
MediaType.sortByQualityValue(acceptedMediaTypes);
HttpOutputMessage outputMessage = new ServletServerHttpResponse(webRequest.getResponse());
Class<?> returnValueType = returnValue.getClass();
if (this.messageConverters != null) {
for (MediaType acceptedMediaType : acceptedMediaTypes) {
for (HttpMessageConverter messageConverter : this.messageConverters) {
if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {
messageConverter.write(returnValue, acceptedMediaType, outputMessage);
return new ModelAndView();
}
}
}
}
if (logger.isWarnEnabled()) {
logger.warn("Could not find HttpMessageConverter that supports return type [" + returnValueType + "] and " +
acceptedMediaTypes);
}
return null;
}
}

View File

@@ -1,275 +0,0 @@
/*
* Copyright 2002-2015 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.annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Controller;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping;
/**
* Implementation of the {@link org.springframework.web.servlet.HandlerMapping}
* interface that maps handlers based on HTTP paths expressed through the
* {@link RequestMapping} annotation at the type or method level.
*
* <p>Registered by default in {@link org.springframework.web.servlet.DispatcherServlet}
* on Java 5+. <b>NOTE:</b> If you define custom HandlerMapping beans in your
* DispatcherServlet context, you need to add a DefaultAnnotationHandlerMapping bean
* explicitly, since custom HandlerMapping beans replace the default mapping strategies.
* Defining a DefaultAnnotationHandlerMapping also allows for registering custom
* interceptors:
*
* <pre class="code">
* &lt;bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"&gt;
* &lt;property name="interceptors"&gt;
* ...
* &lt;/property&gt;
* &lt;/bean&gt;</pre>
*
* Annotated controllers are usually marked with the {@link Controller} stereotype
* at the type level. This is not strictly necessary when {@link RequestMapping} is
* applied at the type level (since such a handler usually implements the
* {@link org.springframework.web.servlet.mvc.Controller} interface). However,
* {@link Controller} is required for detecting {@link RequestMapping} annotations
* at the method level if {@link RequestMapping} is not present at the type level.
*
* <p><b>NOTE:</b> Method-level mappings are only allowed to narrow the mapping
* expressed at the class level (if any). HTTP paths need to uniquely map onto
* specific handler beans, with any given HTTP path only allowed to be mapped
* onto one specific handler bean (not spread across multiple handler beans).
* It is strongly recommended to co-locate related handler methods into the same bean.
*
* <p>The {@link AnnotationMethodHandlerAdapter} is responsible for processing
* annotated handler methods, as mapped by this HandlerMapping. For
* {@link RequestMapping} at the type level, specific HandlerAdapters such as
* {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter} apply.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
* @since 2.5
* @see RequestMapping
* @see AnnotationMethodHandlerAdapter
* @deprecated as of Spring 3.2, in favor of
* {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping RequestMappingHandlerMapping}
*/
@Deprecated
public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandlerMapping {
static final String USE_DEFAULT_SUFFIX_PATTERN = DefaultAnnotationHandlerMapping.class.getName() + ".useDefaultSuffixPattern";
private boolean useDefaultSuffixPattern = true;
private final Map<Class<?>, RequestMapping> cachedMappings = new HashMap<Class<?>, RequestMapping>();
/**
* Set whether to register paths using the default suffix pattern as well:
* i.e. whether "/users" should be registered as "/users.*" and "/users/" too.
* <p>Default is "true". Turn this convention off if you intend to interpret
* your {@code @RequestMapping} paths strictly.
* <p>Note that paths which include a ".xxx" suffix or end with "/" already will not be
* transformed using the default suffix pattern in any case.
*/
public void setUseDefaultSuffixPattern(boolean useDefaultSuffixPattern) {
this.useDefaultSuffixPattern = useDefaultSuffixPattern;
}
/**
* Checks for presence of the {@link org.springframework.web.bind.annotation.RequestMapping}
* annotation on the handler class and on any of its methods.
*/
@Override
protected String[] determineUrlsForHandler(String beanName) {
ApplicationContext context = getApplicationContext();
Class<?> handlerType = context.getType(beanName);
RequestMapping mapping = context.findAnnotationOnBean(beanName, RequestMapping.class);
if (mapping != null) {
// @RequestMapping found at type level
this.cachedMappings.put(handlerType, mapping);
Set<String> urls = new LinkedHashSet<String>();
String[] typeLevelPatterns = mapping.value();
if (typeLevelPatterns.length > 0) {
// @RequestMapping specifies paths at type level
String[] methodLevelPatterns = determineUrlsForHandlerMethods(handlerType, true);
for (String typeLevelPattern : typeLevelPatterns) {
if (!typeLevelPattern.startsWith("/")) {
typeLevelPattern = "/" + typeLevelPattern;
}
boolean hasEmptyMethodLevelMappings = false;
for (String methodLevelPattern : methodLevelPatterns) {
if (methodLevelPattern == null) {
hasEmptyMethodLevelMappings = true;
}
else {
String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern);
addUrlsForPath(urls, combinedPattern);
}
}
if (hasEmptyMethodLevelMappings ||
org.springframework.web.servlet.mvc.Controller.class.isAssignableFrom(handlerType)) {
addUrlsForPath(urls, typeLevelPattern);
}
}
return StringUtils.toStringArray(urls);
}
else {
// actual paths specified by @RequestMapping at method level
return determineUrlsForHandlerMethods(handlerType, false);
}
}
else if (AnnotationUtils.findAnnotation(handlerType, Controller.class) != null) {
// @RequestMapping to be introspected at method level
return determineUrlsForHandlerMethods(handlerType, false);
}
else {
return null;
}
}
/**
* Derive URL mappings from the handler's method-level mappings.
* @param handlerType the handler type to introspect
* @param hasTypeLevelMapping whether the method-level mappings are nested
* within a type-level mapping
* @return the array of mapped URLs
*/
protected String[] determineUrlsForHandlerMethods(Class<?> handlerType, final boolean hasTypeLevelMapping) {
String[] subclassResult = determineUrlsForHandlerMethods(handlerType);
if (subclassResult != null) {
return subclassResult;
}
final Set<String> urls = new LinkedHashSet<String>();
Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
handlerTypes.add(handlerType);
handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces()));
for (Class<?> currentHandlerType : handlerTypes) {
ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) {
RequestMapping mapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (mapping != null) {
String[] mappedPatterns = mapping.value();
if (mappedPatterns.length > 0) {
for (String mappedPattern : mappedPatterns) {
if (!hasTypeLevelMapping && !mappedPattern.startsWith("/")) {
mappedPattern = "/" + mappedPattern;
}
addUrlsForPath(urls, mappedPattern);
}
}
else if (hasTypeLevelMapping) {
// empty method-level RequestMapping
urls.add(null);
}
}
}
}, ReflectionUtils.USER_DECLARED_METHODS);
}
return StringUtils.toStringArray(urls);
}
/**
* Derive URL mappings from the handler's method-level mappings.
* @param handlerType the handler type to introspect
* @return the array of mapped URLs
*/
protected String[] determineUrlsForHandlerMethods(Class<?> handlerType) {
return null;
}
/**
* Add URLs and/or URL patterns for the given path.
* @param urls the Set of URLs for the current bean
* @param path the currently introspected path
*/
protected void addUrlsForPath(Set<String> urls, String path) {
urls.add(path);
if (this.useDefaultSuffixPattern && path.indexOf('.') == -1 && !path.endsWith("/")) {
urls.add(path + ".*");
urls.add(path + "/");
}
}
/**
* Validate the given annotated handler against the current request.
* @see #validateMapping
*/
@Override
protected void validateHandler(Object handler, HttpServletRequest request) throws Exception {
RequestMapping mapping = this.cachedMappings.get(handler.getClass());
if (mapping == null) {
mapping = AnnotationUtils.findAnnotation(handler.getClass(), RequestMapping.class);
}
if (mapping != null) {
validateMapping(mapping, request);
}
request.setAttribute(USE_DEFAULT_SUFFIX_PATTERN, this.useDefaultSuffixPattern);
}
/**
* Validate the given type-level mapping metadata against the current request,
* checking HTTP request method and parameter conditions.
* @param mapping the mapping metadata to validate
* @param request current HTTP request
* @throws Exception if validation failed
*/
protected void validateMapping(RequestMapping mapping, HttpServletRequest request) throws Exception {
RequestMethod[] mappedMethods = mapping.method();
if (!ServletAnnotationMappingUtils.checkRequestMethod(mappedMethods, request)) {
String[] supportedMethods = new String[mappedMethods.length];
for (int i = 0; i < mappedMethods.length; i++) {
supportedMethods[i] = mappedMethods[i].name();
}
throw new HttpRequestMethodNotSupportedException(request.getMethod(), supportedMethods);
}
String[] mappedParams = mapping.params();
if (!ServletAnnotationMappingUtils.checkParameters(mappedParams, request)) {
throw new UnsatisfiedServletRequestParameterException(mappedParams, request.getParameterMap());
}
String[] mappedHeaders = mapping.headers();
if (!ServletAnnotationMappingUtils.checkHeaders(mappedHeaders, request)) {
throw new ServletRequestBindingException("Header conditions \"" +
StringUtils.arrayToDelimitedString(mappedHeaders, ", ") +
"\" not met for actual request");
}
}
@Override
protected boolean supportsTypeLevelMappings() {
return true;
}
}

View File

@@ -1,161 +0,0 @@
/*
* Copyright 2002-2016 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.annotation;
import java.util.Iterator;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.util.WebUtils;
/**
* Helper class for annotation-based request mapping.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 2.5.2
* @deprecated as of Spring 3.2, together with {@link DefaultAnnotationHandlerMapping},
* {@link AnnotationMethodHandlerAdapter}, and {@link AnnotationMethodHandlerExceptionResolver}.
*/
@Deprecated
abstract class ServletAnnotationMappingUtils {
/**
* Check whether the given request matches the specified request methods.
* @param methods the HTTP request methods to check against
* @param request the current HTTP request to check
*/
public static boolean checkRequestMethod(RequestMethod[] methods, HttpServletRequest request) {
String inputMethod = request.getMethod();
if (ObjectUtils.isEmpty(methods) && !RequestMethod.OPTIONS.name().equals(inputMethod)) {
return true;
}
for (RequestMethod method : methods) {
if (method.name().equals(inputMethod)) {
return true;
}
}
return false;
}
/**
* Check whether the given request matches the specified parameter conditions.
* @param params the parameter conditions, following
* {@link org.springframework.web.bind.annotation.RequestMapping#params() RequestMapping.#params()}
* @param request the current HTTP request to check
*/
public static boolean checkParameters(String[] params, HttpServletRequest request) {
if (!ObjectUtils.isEmpty(params)) {
for (String param : params) {
int separator = param.indexOf('=');
if (separator == -1) {
if (param.startsWith("!")) {
if (WebUtils.hasSubmitParameter(request, param.substring(1))) {
return false;
}
}
else if (!WebUtils.hasSubmitParameter(request, param)) {
return false;
}
}
else {
boolean negated = separator > 0 && param.charAt(separator - 1) == '!';
String key = !negated ? param.substring(0, separator) : param.substring(0, separator - 1);
String value = param.substring(separator + 1);
boolean match = value.equals(request.getParameter(key));
if (negated) {
match = !match;
}
if (!match) {
return false;
}
}
}
}
return true;
}
/**
* Check whether the given request matches the specified header conditions.
* @param headers the header conditions, following
* {@link org.springframework.web.bind.annotation.RequestMapping#headers() RequestMapping.headers()}
* @param request the current HTTP request to check
*/
public static boolean checkHeaders(String[] headers, HttpServletRequest request) {
if (!ObjectUtils.isEmpty(headers)) {
for (String header : headers) {
int separator = header.indexOf('=');
if (separator == -1) {
if (header.startsWith("!")) {
if (request.getHeader(header.substring(1)) != null) {
return false;
}
}
else if (request.getHeader(header) == null) {
return false;
}
}
else {
boolean negated = (separator > 0 && header.charAt(separator - 1) == '!');
String key = !negated ? header.substring(0, separator) : header.substring(0, separator - 1);
String value = header.substring(separator + 1);
if (isMediaTypeHeader(key)) {
List<MediaType> requestMediaTypes = MediaType.parseMediaTypes(request.getHeader(key));
List<MediaType> valueMediaTypes = MediaType.parseMediaTypes(value);
boolean found = false;
for (Iterator<MediaType> valIter = valueMediaTypes.iterator(); valIter.hasNext() && !found;) {
MediaType valueMediaType = valIter.next();
for (Iterator<MediaType> reqIter = requestMediaTypes.iterator();
reqIter.hasNext() && !found;) {
MediaType requestMediaType = reqIter.next();
if (valueMediaType.includes(requestMediaType)) {
found = true;
}
}
}
if (negated) {
found = !found;
}
if (!found) {
return false;
}
}
else {
boolean match = value.equals(request.getHeader(key));
if (negated) {
match = !match;
}
if (!match) {
return false;
}
}
}
}
}
return true;
}
private static boolean isMediaTypeHeader(String headerName) {
return ("Accept".equalsIgnoreCase(headerName) || "Content-Type".equalsIgnoreCase(headerName));
}
}