001/*
002 * Copyright 2002-2015 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.servlet.mvc.annotation;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.io.OutputStream;
022import java.io.Reader;
023import java.io.Writer;
024import java.lang.reflect.Method;
025import java.security.Principal;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.Collections;
029import java.util.Comparator;
030import java.util.HashMap;
031import java.util.LinkedHashMap;
032import java.util.LinkedHashSet;
033import java.util.List;
034import java.util.Locale;
035import java.util.Map;
036import java.util.Set;
037import java.util.concurrent.ConcurrentHashMap;
038import javax.servlet.ServletException;
039import javax.servlet.ServletRequest;
040import javax.servlet.ServletResponse;
041import javax.servlet.http.Cookie;
042import javax.servlet.http.HttpServletRequest;
043import javax.servlet.http.HttpServletResponse;
044import javax.servlet.http.HttpSession;
045import javax.xml.transform.Source;
046
047import org.apache.commons.logging.Log;
048import org.apache.commons.logging.LogFactory;
049
050import org.springframework.beans.BeanUtils;
051import org.springframework.beans.factory.BeanFactory;
052import org.springframework.beans.factory.BeanFactoryAware;
053import org.springframework.beans.factory.config.BeanExpressionContext;
054import org.springframework.beans.factory.config.BeanExpressionResolver;
055import org.springframework.beans.factory.config.ConfigurableBeanFactory;
056import org.springframework.core.DefaultParameterNameDiscoverer;
057import org.springframework.core.Ordered;
058import org.springframework.core.ParameterNameDiscoverer;
059import org.springframework.core.annotation.AnnotatedElementUtils;
060import org.springframework.core.annotation.AnnotationUtils;
061import org.springframework.http.HttpEntity;
062import org.springframework.http.HttpHeaders;
063import org.springframework.http.HttpInputMessage;
064import org.springframework.http.HttpOutputMessage;
065import org.springframework.http.HttpStatus;
066import org.springframework.http.MediaType;
067import org.springframework.http.ResponseEntity;
068import org.springframework.http.converter.ByteArrayHttpMessageConverter;
069import org.springframework.http.converter.HttpMessageConverter;
070import org.springframework.http.converter.StringHttpMessageConverter;
071import org.springframework.http.converter.xml.SourceHttpMessageConverter;
072import org.springframework.http.server.ServerHttpRequest;
073import org.springframework.http.server.ServerHttpResponse;
074import org.springframework.http.server.ServletServerHttpRequest;
075import org.springframework.http.server.ServletServerHttpResponse;
076import org.springframework.ui.ExtendedModelMap;
077import org.springframework.ui.Model;
078import org.springframework.util.AntPathMatcher;
079import org.springframework.util.Assert;
080import org.springframework.util.ClassUtils;
081import org.springframework.util.ObjectUtils;
082import org.springframework.util.PathMatcher;
083import org.springframework.util.StringUtils;
084import org.springframework.validation.support.BindingAwareModelMap;
085import org.springframework.web.HttpMediaTypeNotAcceptableException;
086import org.springframework.web.HttpRequestMethodNotSupportedException;
087import org.springframework.web.HttpSessionRequiredException;
088import org.springframework.web.bind.MissingServletRequestParameterException;
089import org.springframework.web.bind.ServletRequestDataBinder;
090import org.springframework.web.bind.WebDataBinder;
091import org.springframework.web.bind.annotation.InitBinder;
092import org.springframework.web.bind.annotation.ModelAttribute;
093import org.springframework.web.bind.annotation.RequestMapping;
094import org.springframework.web.bind.annotation.RequestMethod;
095import org.springframework.web.bind.annotation.RequestParam;
096import org.springframework.web.bind.annotation.ResponseBody;
097import org.springframework.web.bind.annotation.ResponseStatus;
098import org.springframework.web.bind.annotation.SessionAttributes;
099import org.springframework.web.bind.support.DefaultSessionAttributeStore;
100import org.springframework.web.bind.support.SessionAttributeStore;
101import org.springframework.web.bind.support.WebArgumentResolver;
102import org.springframework.web.bind.support.WebBindingInitializer;
103import org.springframework.web.context.request.NativeWebRequest;
104import org.springframework.web.context.request.RequestScope;
105import org.springframework.web.context.request.ServletWebRequest;
106import org.springframework.web.multipart.MultipartRequest;
107import org.springframework.web.servlet.HandlerAdapter;
108import org.springframework.web.servlet.HandlerMapping;
109import org.springframework.web.servlet.ModelAndView;
110import org.springframework.web.servlet.View;
111import org.springframework.web.servlet.support.RequestContextUtils;
112import org.springframework.web.servlet.support.WebContentGenerator;
113import org.springframework.web.util.UrlPathHelper;
114import org.springframework.web.util.WebUtils;
115
116/**
117 * Implementation of the {@link org.springframework.web.servlet.HandlerAdapter} interface
118 * that maps handler methods based on HTTP paths, HTTP methods, and request parameters
119 * expressed through the {@link RequestMapping} annotation.
120 *
121 * <p>Supports request parameter binding through the {@link RequestParam} annotation.
122 * Also supports the {@link ModelAttribute} annotation for exposing model attribute
123 * values to the view, as well as {@link InitBinder} for binder initialization methods
124 * and {@link SessionAttributes} for automatic session management of specific attributes.
125 *
126 * <p>This adapter can be customized through various bean properties.
127 * A common use case is to apply shared binder initialization logic through
128 * a custom {@link #setWebBindingInitializer WebBindingInitializer}.
129 *
130 * @author Juergen Hoeller
131 * @author Arjen Poutsma
132 * @author Sam Brannen
133 * @since 2.5
134 * @see #setPathMatcher
135 * @see #setMethodNameResolver
136 * @see #setWebBindingInitializer
137 * @see #setSessionAttributeStore
138 * @deprecated as of Spring 3.2, in favor of
139 * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter RequestMappingHandlerAdapter}
140 */
141@Deprecated
142public class AnnotationMethodHandlerAdapter extends WebContentGenerator
143                implements HandlerAdapter, Ordered, BeanFactoryAware {
144
145        /**
146         * Log category to use when no mapped handler is found for a request.
147         * @see #pageNotFoundLogger
148         */
149        public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
150
151        /**
152         * Additional logger to use when no mapped handler is found for a request.
153         * @see #PAGE_NOT_FOUND_LOG_CATEGORY
154         */
155        protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
156
157
158        private UrlPathHelper urlPathHelper = new UrlPathHelper();
159
160        private PathMatcher pathMatcher = new AntPathMatcher();
161
162        private org.springframework.web.servlet.mvc.multiaction.MethodNameResolver methodNameResolver =
163                        new org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver();
164
165        private WebBindingInitializer webBindingInitializer;
166
167        private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();
168
169        private int cacheSecondsForSessionAttributeHandlers = 0;
170
171        private boolean synchronizeOnSession = false;
172
173        private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
174
175        private WebArgumentResolver[] customArgumentResolvers;
176
177        private ModelAndViewResolver[] customModelAndViewResolvers;
178
179        private HttpMessageConverter<?>[] messageConverters;
180
181        private int order = Ordered.LOWEST_PRECEDENCE;
182
183        private ConfigurableBeanFactory beanFactory;
184
185        private BeanExpressionContext expressionContext;
186
187        private final Map<Class<?>, ServletHandlerMethodResolver> methodResolverCache =
188                        new ConcurrentHashMap<Class<?>, ServletHandlerMethodResolver>(64);
189
190        private final Map<Class<?>, Boolean> sessionAnnotatedClassesCache = new ConcurrentHashMap<Class<?>, Boolean>(64);
191
192
193        public AnnotationMethodHandlerAdapter() {
194                // no restriction of HTTP methods by default
195                super(false);
196
197                // See SPR-7316
198                StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
199                stringHttpMessageConverter.setWriteAcceptCharset(false);
200                this.messageConverters = new HttpMessageConverter<?>[] {
201                        new ByteArrayHttpMessageConverter(), stringHttpMessageConverter,
202                        new SourceHttpMessageConverter<Source>(),
203                        new org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter() };
204        }
205
206
207        /**
208         * Set if URL lookup should always use the full path within the current servlet
209         * context. Else, the path within the current servlet mapping is used if applicable
210         * (that is, in the case of a ".../*" servlet mapping in web.xml).
211         * <p>Default is "false".
212         * @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
213         */
214        public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
215                this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
216        }
217
218        /**
219         * Set if context path and request URI should be URL-decoded. Both are returned
220         * <i>undecoded</i> by the Servlet API, in contrast to the servlet path.
221         * <p>Uses either the request encoding or the default encoding according
222         * to the Servlet spec (ISO-8859-1).
223         * @see org.springframework.web.util.UrlPathHelper#setUrlDecode
224         */
225        public void setUrlDecode(boolean urlDecode) {
226                this.urlPathHelper.setUrlDecode(urlDecode);
227        }
228
229        /**
230         * Set the UrlPathHelper to use for resolution of lookup paths.
231         * <p>Use this to override the default UrlPathHelper with a custom subclass,
232         * or to share common UrlPathHelper settings across multiple HandlerMappings and HandlerAdapters.
233         */
234        public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
235                Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
236                this.urlPathHelper = urlPathHelper;
237        }
238
239        /**
240         * Set the PathMatcher implementation to use for matching URL paths against registered URL patterns.
241         * <p>Default is {@link org.springframework.util.AntPathMatcher}.
242         */
243        public void setPathMatcher(PathMatcher pathMatcher) {
244                Assert.notNull(pathMatcher, "PathMatcher must not be null");
245                this.pathMatcher = pathMatcher;
246        }
247
248        /**
249         * Set the MethodNameResolver to use for resolving default handler methods
250         * (carrying an empty {@code @RequestMapping} annotation).
251         * <p>Will only kick in when the handler method cannot be resolved uniquely
252         * through the annotation metadata already.
253         */
254        public void setMethodNameResolver(org.springframework.web.servlet.mvc.multiaction.MethodNameResolver methodNameResolver) {
255                this.methodNameResolver = methodNameResolver;
256        }
257
258        /**
259         * Specify a WebBindingInitializer which will apply pre-configured
260         * configuration to every DataBinder that this controller uses.
261         */
262        public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
263                this.webBindingInitializer = webBindingInitializer;
264        }
265
266        /**
267         * Specify the strategy to store session attributes with.
268         * <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
269         * storing session attributes in the HttpSession, using the same attribute name as in the model.
270         */
271        public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
272                Assert.notNull(sessionAttributeStore, "SessionAttributeStore must not be null");
273                this.sessionAttributeStore = sessionAttributeStore;
274        }
275
276        /**
277         * Cache content produced by {@code @SessionAttributes} annotated handlers
278         * for the given number of seconds. Default is 0, preventing caching completely.
279         * <p>In contrast to the "cacheSeconds" property which will apply to all general handlers
280         * (but not to {@code @SessionAttributes} annotated handlers), this setting will
281         * apply to {@code @SessionAttributes} annotated handlers only.
282         * @see #setCacheSeconds
283         * @see org.springframework.web.bind.annotation.SessionAttributes
284         */
285        public void setCacheSecondsForSessionAttributeHandlers(int cacheSecondsForSessionAttributeHandlers) {
286                this.cacheSecondsForSessionAttributeHandlers = cacheSecondsForSessionAttributeHandlers;
287        }
288
289        /**
290         * Set if controller execution should be synchronized on the session,
291         * to serialize parallel invocations from the same client.
292         * <p>More specifically, the execution of the {@code handleRequestInternal}
293         * method will get synchronized if this flag is "true". The best available
294         * session mutex will be used for the synchronization; ideally, this will
295         * be a mutex exposed by HttpSessionMutexListener.
296         * <p>The session mutex is guaranteed to be the same object during
297         * the entire lifetime of the session, available under the key defined
298         * by the {@code SESSION_MUTEX_ATTRIBUTE} constant. It serves as a
299         * safe reference to synchronize on for locking on the current session.
300         * <p>In many cases, the HttpSession reference itself is a safe mutex
301         * as well, since it will always be the same object reference for the
302         * same active logical session. However, this is not guaranteed across
303         * different servlet containers; the only 100% safe way is a session mutex.
304         * @see org.springframework.web.util.HttpSessionMutexListener
305         * @see org.springframework.web.util.WebUtils#getSessionMutex(javax.servlet.http.HttpSession)
306         */
307        public void setSynchronizeOnSession(boolean synchronizeOnSession) {
308                this.synchronizeOnSession = synchronizeOnSession;
309        }
310
311        /**
312         * Set the ParameterNameDiscoverer to use for resolving method parameter names if needed
313         * (e.g. for default attribute names).
314         * <p>Default is a {@link org.springframework.core.DefaultParameterNameDiscoverer}.
315         */
316        public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
317                this.parameterNameDiscoverer = parameterNameDiscoverer;
318        }
319
320        /**
321         * Set a custom WebArgumentResolvers to use for special method parameter types.
322         * <p>Such a custom WebArgumentResolver will kick in first, having a chance to resolve
323         * an argument value before the standard argument handling kicks in.
324         */
325        public void setCustomArgumentResolver(WebArgumentResolver argumentResolver) {
326                this.customArgumentResolvers = new WebArgumentResolver[] {argumentResolver};
327        }
328
329        /**
330         * Set one or more custom WebArgumentResolvers to use for special method parameter types.
331         * <p>Any such custom WebArgumentResolver will kick in first, having a chance to resolve
332         * an argument value before the standard argument handling kicks in.
333         */
334        public void setCustomArgumentResolvers(WebArgumentResolver... argumentResolvers) {
335                this.customArgumentResolvers = argumentResolvers;
336        }
337
338        /**
339         * Set a custom ModelAndViewResolvers to use for special method return types.
340         * <p>Such a custom ModelAndViewResolver will kick in first, having a chance to resolve
341         * a return value before the standard ModelAndView handling kicks in.
342         */
343        public void setCustomModelAndViewResolver(ModelAndViewResolver customModelAndViewResolver) {
344                this.customModelAndViewResolvers = new ModelAndViewResolver[] {customModelAndViewResolver};
345        }
346
347        /**
348         * Set one or more custom ModelAndViewResolvers to use for special method return types.
349         * <p>Any such custom ModelAndViewResolver will kick in first, having a chance to resolve
350         * a return value before the standard ModelAndView handling kicks in.
351         */
352        public void setCustomModelAndViewResolvers(ModelAndViewResolver... customModelAndViewResolvers) {
353                this.customModelAndViewResolvers = customModelAndViewResolvers;
354        }
355
356        /**
357         * Set the message body converters to use.
358         * <p>These converters are used to convert from and to HTTP requests and responses.
359         */
360        public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) {
361                this.messageConverters = messageConverters;
362        }
363
364        /**
365         * Return the message body converters that this adapter has been configured with.
366         */
367        public HttpMessageConverter<?>[] getMessageConverters() {
368                return messageConverters;
369        }
370
371        /**
372         * Specify the order value for this HandlerAdapter bean.
373         * <p>Default value is {@code Integer.MAX_VALUE}, meaning that it's non-ordered.
374         * @see org.springframework.core.Ordered#getOrder()
375         */
376        public void setOrder(int order) {
377                this.order = order;
378        }
379
380        @Override
381        public int getOrder() {
382                return this.order;
383        }
384
385        @Override
386        public void setBeanFactory(BeanFactory beanFactory) {
387                if (beanFactory instanceof ConfigurableBeanFactory) {
388                        this.beanFactory = (ConfigurableBeanFactory) beanFactory;
389                        this.expressionContext = new BeanExpressionContext(this.beanFactory, new RequestScope());
390                }
391        }
392
393
394        @Override
395        public boolean supports(Object handler) {
396                return getMethodResolver(handler).hasHandlerMethods();
397        }
398
399        @Override
400        public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
401                        throws Exception {
402
403                Class<?> clazz = ClassUtils.getUserClass(handler);
404                Boolean annotatedWithSessionAttributes = this.sessionAnnotatedClassesCache.get(clazz);
405                if (annotatedWithSessionAttributes == null) {
406                        annotatedWithSessionAttributes = (AnnotationUtils.findAnnotation(clazz, SessionAttributes.class) != null);
407                        this.sessionAnnotatedClassesCache.put(clazz, annotatedWithSessionAttributes);
408                }
409
410                if (annotatedWithSessionAttributes) {
411                        checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
412                }
413                else {
414                        checkAndPrepare(request, response, true);
415                }
416
417                // Execute invokeHandlerMethod in synchronized block if required.
418                if (this.synchronizeOnSession) {
419                        HttpSession session = request.getSession(false);
420                        if (session != null) {
421                                Object mutex = WebUtils.getSessionMutex(session);
422                                synchronized (mutex) {
423                                        return invokeHandlerMethod(request, response, handler);
424                                }
425                        }
426                }
427
428                return invokeHandlerMethod(request, response, handler);
429        }
430
431        protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)
432                        throws Exception {
433
434                ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
435                Method handlerMethod = methodResolver.resolveHandlerMethod(request);
436                ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);
437                ServletWebRequest webRequest = new ServletWebRequest(request, response);
438                ExtendedModelMap implicitModel = new BindingAwareModelMap();
439
440                Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
441                ModelAndView mav =
442                                methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
443                methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
444                return mav;
445        }
446
447        /**
448         * This method always returns -1 since an annotated controller can have many methods,
449         * each requiring separate lastModified calculations. Instead, an
450         * {@link RequestMapping}-annotated method can calculate the lastModified value, call
451         * {@link org.springframework.web.context.request.WebRequest#checkNotModified(long)}
452         * to check it, and return {@code null} if that returns {@code true}.
453         * @see org.springframework.web.context.request.WebRequest#checkNotModified(long)
454         */
455        @Override
456        public long getLastModified(HttpServletRequest request, Object handler) {
457                return -1;
458        }
459
460
461        /**
462         * Build a HandlerMethodResolver for the given handler type.
463         */
464        private ServletHandlerMethodResolver getMethodResolver(Object handler) {
465                Class<?> handlerClass = ClassUtils.getUserClass(handler);
466                ServletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass);
467                if (resolver == null) {
468                        synchronized (this.methodResolverCache) {
469                                resolver = this.methodResolverCache.get(handlerClass);
470                                if (resolver == null) {
471                                        resolver = new ServletHandlerMethodResolver(handlerClass);
472                                        this.methodResolverCache.put(handlerClass, resolver);
473                                }
474                        }
475                }
476                return resolver;
477        }
478
479
480        /**
481         * Template method for creating a new ServletRequestDataBinder instance.
482         * <p>The default implementation creates a standard ServletRequestDataBinder.
483         * This can be overridden for custom ServletRequestDataBinder subclasses.
484         * @param request current HTTP request
485         * @param target the target object to bind onto (or {@code null}
486         * if the binder is just used to convert a plain parameter value)
487         * @param objectName the objectName of the target object
488         * @return the ServletRequestDataBinder instance to use
489         * @throws Exception in case of invalid state or arguments
490         * @see ServletRequestDataBinder#bind(javax.servlet.ServletRequest)
491         * @see ServletRequestDataBinder#convertIfNecessary(Object, Class, org.springframework.core.MethodParameter)
492         */
493        protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object target, String objectName) throws Exception {
494                return new ServletRequestDataBinder(target, objectName);
495        }
496
497        /**
498         * Template method for creating a new HttpInputMessage instance.
499         * <p>The default implementation creates a standard {@link ServletServerHttpRequest}.
500         * This can be overridden for custom {@code HttpInputMessage} implementations
501         * @param servletRequest current HTTP request
502         * @return the HttpInputMessage instance to use
503         * @throws Exception in case of errors
504         */
505        protected HttpInputMessage createHttpInputMessage(HttpServletRequest servletRequest) throws Exception {
506                return new ServletServerHttpRequest(servletRequest);
507        }
508
509        /**
510         * Template method for creating a new HttpOutputMessage instance.
511         * <p>The default implementation creates a standard {@link ServletServerHttpResponse}.
512         * This can be overridden for custom {@code HttpOutputMessage} implementations
513         * @param servletResponse current HTTP response
514         * @return the HttpInputMessage instance to use
515         * @throws Exception in case of errors
516         */
517        protected HttpOutputMessage createHttpOutputMessage(HttpServletResponse servletResponse) throws Exception {
518                return new ServletServerHttpResponse(servletResponse);
519        }
520
521
522        /**
523         * Servlet-specific subclass of {@code HandlerMethodResolver}.
524         */
525        @SuppressWarnings("deprecation")
526        private class ServletHandlerMethodResolver extends org.springframework.web.bind.annotation.support.HandlerMethodResolver {
527
528                private final Map<Method, RequestMappingInfo> mappings = new HashMap<Method, RequestMappingInfo>();
529
530                private ServletHandlerMethodResolver(Class<?> handlerType) {
531                        init(handlerType);
532                }
533
534                @Override
535                protected boolean isHandlerMethod(Method method) {
536                        if (this.mappings.containsKey(method)) {
537                                return true;
538                        }
539                        RequestMapping mapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
540                        if (mapping != null) {
541                                String[] patterns = mapping.value();
542                                RequestMethod[] methods = new RequestMethod[0];
543                                String[] params = new String[0];
544                                String[] headers = new String[0];
545                                if (!hasTypeLevelMapping() || !Arrays.equals(mapping.method(), getTypeLevelMapping().method())) {
546                                        methods = mapping.method();
547                                }
548                                if (!hasTypeLevelMapping() || !Arrays.equals(mapping.params(), getTypeLevelMapping().params())) {
549                                        params = mapping.params();
550                                }
551                                if (!hasTypeLevelMapping() || !Arrays.equals(mapping.headers(), getTypeLevelMapping().headers())) {
552                                        headers = mapping.headers();
553                                }
554                                RequestMappingInfo mappingInfo = new RequestMappingInfo(patterns, methods, params, headers);
555                                this.mappings.put(method, mappingInfo);
556                                return true;
557                        }
558                        return false;
559                }
560
561                public Method resolveHandlerMethod(HttpServletRequest request) throws ServletException {
562                        String lookupPath = urlPathHelper.getLookupPathForRequest(request);
563                        Comparator<String> pathComparator = pathMatcher.getPatternComparator(lookupPath);
564                        Map<RequestSpecificMappingInfo, Method> targetHandlerMethods = new LinkedHashMap<RequestSpecificMappingInfo, Method>();
565                        Set<String> allowedMethods = new LinkedHashSet<String>(7);
566                        String resolvedMethodName = null;
567                        for (Method handlerMethod : getHandlerMethods()) {
568                                RequestSpecificMappingInfo mappingInfo = new RequestSpecificMappingInfo(this.mappings.get(handlerMethod));
569                                boolean match = false;
570                                if (mappingInfo.hasPatterns()) {
571                                        for (String pattern : mappingInfo.getPatterns()) {
572                                                if (!hasTypeLevelMapping() && !pattern.startsWith("/")) {
573                                                        pattern = "/" + pattern;
574                                                }
575                                                String combinedPattern = getCombinedPattern(pattern, lookupPath, request);
576                                                if (combinedPattern != null) {
577                                                        if (mappingInfo.matches(request)) {
578                                                                match = true;
579                                                                mappingInfo.addMatchedPattern(combinedPattern);
580                                                        }
581                                                        else {
582                                                                if (!mappingInfo.matchesRequestMethod(request)) {
583                                                                        allowedMethods.addAll(mappingInfo.methodNames());
584                                                                }
585                                                                break;
586                                                        }
587                                                }
588                                        }
589                                        mappingInfo.sortMatchedPatterns(pathComparator);
590                                }
591                                else if (useTypeLevelMapping(request)) {
592                                        String[] typeLevelPatterns = getTypeLevelMapping().value();
593                                        for (String typeLevelPattern : typeLevelPatterns) {
594                                                if (!typeLevelPattern.startsWith("/")) {
595                                                        typeLevelPattern = "/" + typeLevelPattern;
596                                                }
597                                                boolean useSuffixPattern = useSuffixPattern(request);
598                                                if (getMatchingPattern(typeLevelPattern, lookupPath, useSuffixPattern) != null) {
599                                                        if (mappingInfo.matches(request)) {
600                                                                match = true;
601                                                                mappingInfo.addMatchedPattern(typeLevelPattern);
602                                                        }
603                                                        else {
604                                                                if (!mappingInfo.matchesRequestMethod(request)) {
605                                                                        allowedMethods.addAll(mappingInfo.methodNames());
606                                                                }
607                                                                break;
608                                                        }
609                                                }
610                                        }
611                                        mappingInfo.sortMatchedPatterns(pathComparator);
612                                }
613                                else {
614                                        // No paths specified: parameter match sufficient.
615                                        match = mappingInfo.matches(request);
616                                        if (match && mappingInfo.getMethodCount() == 0 && mappingInfo.getParamCount() == 0 &&
617                                                        resolvedMethodName != null && !resolvedMethodName.equals(handlerMethod.getName())) {
618                                                match = false;
619                                        }
620                                        else {
621                                                if (!mappingInfo.matchesRequestMethod(request)) {
622                                                        allowedMethods.addAll(mappingInfo.methodNames());
623                                                }
624                                        }
625                                }
626                                if (match) {
627                                        Method oldMappedMethod = targetHandlerMethods.put(mappingInfo, handlerMethod);
628                                        if (oldMappedMethod != null && oldMappedMethod != handlerMethod) {
629                                                if (methodNameResolver != null && !mappingInfo.hasPatterns()) {
630                                                        if (!oldMappedMethod.getName().equals(handlerMethod.getName())) {
631                                                                if (resolvedMethodName == null) {
632                                                                        resolvedMethodName = methodNameResolver.getHandlerMethodName(request);
633                                                                }
634                                                                if (!resolvedMethodName.equals(oldMappedMethod.getName())) {
635                                                                        oldMappedMethod = null;
636                                                                }
637                                                                if (!resolvedMethodName.equals(handlerMethod.getName())) {
638                                                                        if (oldMappedMethod != null) {
639                                                                                targetHandlerMethods.put(mappingInfo, oldMappedMethod);
640                                                                                oldMappedMethod = null;
641                                                                        }
642                                                                        else {
643                                                                                targetHandlerMethods.remove(mappingInfo);
644                                                                        }
645                                                                }
646                                                        }
647                                                }
648                                                if (oldMappedMethod != null) {
649                                                        throw new IllegalStateException(
650                                                                        "Ambiguous handler methods mapped for HTTP path '" + lookupPath + "': {" +
651                                                                        oldMappedMethod + ", " + handlerMethod +
652                                                                        "}. If you intend to handle the same path in multiple methods, then factor " +
653                                                                        "them out into a dedicated handler class with that path mapped at the type level!");
654                                                }
655                                        }
656                                }
657                        }
658                        if (!targetHandlerMethods.isEmpty()) {
659                                List<RequestSpecificMappingInfo> matches = new ArrayList<RequestSpecificMappingInfo>(targetHandlerMethods.keySet());
660                                RequestSpecificMappingInfoComparator requestMappingInfoComparator =
661                                                new RequestSpecificMappingInfoComparator(pathComparator, request);
662                                Collections.sort(matches, requestMappingInfoComparator);
663                                RequestSpecificMappingInfo bestMappingMatch = matches.get(0);
664                                String bestMatchedPath = bestMappingMatch.bestMatchedPattern();
665                                if (bestMatchedPath != null) {
666                                        extractHandlerMethodUriTemplates(bestMatchedPath, lookupPath, request);
667                                }
668                                return targetHandlerMethods.get(bestMappingMatch);
669                        }
670                        else {
671                                if (!allowedMethods.isEmpty()) {
672                                        throw new HttpRequestMethodNotSupportedException(request.getMethod(), StringUtils.toStringArray(allowedMethods));
673                                }
674                                throw new org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException(
675                                                lookupPath, request.getMethod(), request.getParameterMap());
676                        }
677                }
678
679                private boolean useTypeLevelMapping(HttpServletRequest request) {
680                        if (!hasTypeLevelMapping() || ObjectUtils.isEmpty(getTypeLevelMapping().value())) {
681                                return false;
682                        }
683                        Object value = request.getAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING);
684                        return (value != null) ? (Boolean) value : Boolean.TRUE;
685                }
686
687                private boolean useSuffixPattern(HttpServletRequest request) {
688                        Object value = request.getAttribute(DefaultAnnotationHandlerMapping.USE_DEFAULT_SUFFIX_PATTERN);
689                        return (value != null) ? (Boolean) value : Boolean.TRUE;
690                }
691
692                /**
693                 * Determines the combined pattern for the given methodLevelPattern and path.
694                 * <p>Uses the following algorithm:
695                 * <ol>
696                 * <li>If there is a type-level mapping with path information, it is {@linkplain
697                 * PathMatcher#combine(String, String) combined} with the method-level pattern.</li>
698                 * <li>If there is a {@linkplain HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE best matching pattern}
699                 * in the request, it is combined with the method-level pattern.</li>
700                 * <li>Otherwise, the method-level pattern is returned.</li>
701                 * </ol>
702                 */
703                private String getCombinedPattern(String methodLevelPattern, String lookupPath, HttpServletRequest request) {
704                        boolean useSuffixPattern = useSuffixPattern(request);
705                        if (useTypeLevelMapping(request)) {
706                                String[] typeLevelPatterns = getTypeLevelMapping().value();
707                                for (String typeLevelPattern : typeLevelPatterns) {
708                                        if (!typeLevelPattern.startsWith("/")) {
709                                                typeLevelPattern = "/" + typeLevelPattern;
710                                        }
711                                        String combinedPattern = pathMatcher.combine(typeLevelPattern, methodLevelPattern);
712                                        String matchingPattern = getMatchingPattern(combinedPattern, lookupPath, useSuffixPattern);
713                                        if (matchingPattern != null) {
714                                                return matchingPattern;
715                                        }
716                                }
717                                return null;
718                        }
719                        String bestMatchingPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
720                        if (StringUtils.hasText(bestMatchingPattern) && bestMatchingPattern.endsWith("*")) {
721                                String combinedPattern = pathMatcher.combine(bestMatchingPattern, methodLevelPattern);
722                                String matchingPattern = getMatchingPattern(combinedPattern, lookupPath, useSuffixPattern);
723                                if (matchingPattern != null && !matchingPattern.equals(bestMatchingPattern)) {
724                                        return matchingPattern;
725                                }
726                        }
727                        return getMatchingPattern(methodLevelPattern, lookupPath, useSuffixPattern);
728                }
729
730                private String getMatchingPattern(String pattern, String lookupPath, boolean useSuffixPattern) {
731                        if (pattern.equals(lookupPath)) {
732                                return pattern;
733                        }
734                        boolean hasSuffix = pattern.indexOf('.') != -1;
735                        if (useSuffixPattern && !hasSuffix) {
736                                String patternWithSuffix = pattern + ".*";
737                                if (pathMatcher.match(patternWithSuffix, lookupPath)) {
738                                        return patternWithSuffix;
739                                }
740                        }
741                        if (pathMatcher.match(pattern, lookupPath)) {
742                                return pattern;
743                        }
744                        boolean endsWithSlash = pattern.endsWith("/");
745                        if (useSuffixPattern && !endsWithSlash) {
746                                String patternWithSlash = pattern + "/";
747                                if (pathMatcher.match(patternWithSlash, lookupPath)) {
748                                        return patternWithSlash;
749                                }
750                        }
751                        return null;
752                }
753
754                @SuppressWarnings("unchecked")
755                private void extractHandlerMethodUriTemplates(String mappedPattern, String lookupPath, HttpServletRequest request) {
756                        Map<String, String> variables =
757                                        (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
758                        int patternVariableCount = StringUtils.countOccurrencesOf(mappedPattern, "{");
759                        if ((variables == null || patternVariableCount != variables.size()) && pathMatcher.match(mappedPattern, lookupPath)) {
760                                variables = pathMatcher.extractUriTemplateVariables(mappedPattern, lookupPath);
761                                Map<String, String> decodedVariables = urlPathHelper.decodePathVariables(request, variables);
762                                request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedVariables);
763                        }
764                }
765        }
766
767
768        /**
769         * Servlet-specific subclass of {@code HandlerMethodInvoker}.
770         */
771        @SuppressWarnings("deprecation")
772        private class ServletHandlerMethodInvoker extends org.springframework.web.bind.annotation.support.HandlerMethodInvoker {
773
774                private boolean responseArgumentUsed = false;
775
776                private ServletHandlerMethodInvoker(org.springframework.web.bind.annotation.support.HandlerMethodResolver resolver) {
777                        super(resolver, webBindingInitializer, sessionAttributeStore, parameterNameDiscoverer,
778                                        customArgumentResolvers, messageConverters);
779                }
780
781                @Override
782                protected void raiseMissingParameterException(String paramName, Class<?> paramType) throws Exception {
783                        throw new MissingServletRequestParameterException(paramName, paramType.getSimpleName());
784                }
785
786                @Override
787                protected void raiseSessionRequiredException(String message) throws Exception {
788                        throw new HttpSessionRequiredException(message);
789                }
790
791                @Override
792                protected WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName)
793                                throws Exception {
794
795                        return AnnotationMethodHandlerAdapter.this.createBinder(
796                                        webRequest.getNativeRequest(HttpServletRequest.class), target, objectName);
797                }
798
799                @Override
800                protected void doBind(WebDataBinder binder, NativeWebRequest webRequest) throws Exception {
801                        ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
802                        servletBinder.bind(webRequest.getNativeRequest(ServletRequest.class));
803                }
804
805                @Override
806                protected HttpInputMessage createHttpInputMessage(NativeWebRequest webRequest) throws Exception {
807                        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
808                        return AnnotationMethodHandlerAdapter.this.createHttpInputMessage(servletRequest);
809                }
810
811                @Override
812                protected HttpOutputMessage createHttpOutputMessage(NativeWebRequest webRequest) throws Exception {
813                        HttpServletResponse servletResponse = (HttpServletResponse) webRequest.getNativeResponse();
814                        return AnnotationMethodHandlerAdapter.this.createHttpOutputMessage(servletResponse);
815                }
816
817                @Override
818                protected Object resolveDefaultValue(String value) {
819                        if (beanFactory == null) {
820                                return value;
821                        }
822                        String placeholdersResolved = beanFactory.resolveEmbeddedValue(value);
823                        BeanExpressionResolver exprResolver = beanFactory.getBeanExpressionResolver();
824                        if (exprResolver == null) {
825                                return value;
826                        }
827                        return exprResolver.evaluate(placeholdersResolved, expressionContext);
828                }
829
830                @Override
831                protected Object resolveCookieValue(String cookieName, Class<?> paramType, NativeWebRequest webRequest)
832                                throws Exception {
833
834                        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
835                        Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName);
836                        if (Cookie.class.isAssignableFrom(paramType)) {
837                                return cookieValue;
838                        }
839                        else if (cookieValue != null) {
840                                return urlPathHelper.decodeRequestString(servletRequest, cookieValue.getValue());
841                        }
842                        else {
843                                return null;
844                        }
845                }
846
847                @Override
848                @SuppressWarnings({"unchecked"})
849                protected String resolvePathVariable(String pathVarName, Class<?> paramType, NativeWebRequest webRequest)
850                                throws Exception {
851
852                        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
853                        Map<String, String> uriTemplateVariables =
854                                        (Map<String, String>) servletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
855                        if (uriTemplateVariables == null || !uriTemplateVariables.containsKey(pathVarName)) {
856                                throw new IllegalStateException(
857                                                "Could not find @PathVariable [" + pathVarName + "] in @RequestMapping");
858                        }
859                        return uriTemplateVariables.get(pathVarName);
860                }
861
862                @Override
863                protected Object resolveStandardArgument(Class<?> parameterType, NativeWebRequest webRequest) throws Exception {
864                        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
865                        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
866
867                        if (ServletRequest.class.isAssignableFrom(parameterType) ||
868                                        MultipartRequest.class.isAssignableFrom(parameterType)) {
869                                Object nativeRequest = webRequest.getNativeRequest(parameterType);
870                                if (nativeRequest == null) {
871                                        throw new IllegalStateException(
872                                                        "Current request is not of type [" + parameterType.getName() + "]: " + request);
873                                }
874                                return nativeRequest;
875                        }
876                        else if (ServletResponse.class.isAssignableFrom(parameterType)) {
877                                this.responseArgumentUsed = true;
878                                Object nativeResponse = webRequest.getNativeResponse(parameterType);
879                                if (nativeResponse == null) {
880                                        throw new IllegalStateException(
881                                                        "Current response is not of type [" + parameterType.getName() + "]: " + response);
882                                }
883                                return nativeResponse;
884                        }
885                        else if (HttpSession.class.isAssignableFrom(parameterType)) {
886                                return request.getSession();
887                        }
888                        else if (Principal.class.isAssignableFrom(parameterType)) {
889                                return request.getUserPrincipal();
890                        }
891                        else if (Locale.class == parameterType) {
892                                return RequestContextUtils.getLocale(request);
893                        }
894                        else if (InputStream.class.isAssignableFrom(parameterType)) {
895                                return request.getInputStream();
896                        }
897                        else if (Reader.class.isAssignableFrom(parameterType)) {
898                                return request.getReader();
899                        }
900                        else if (OutputStream.class.isAssignableFrom(parameterType)) {
901                                this.responseArgumentUsed = true;
902                                return response.getOutputStream();
903                        }
904                        else if (Writer.class.isAssignableFrom(parameterType)) {
905                                this.responseArgumentUsed = true;
906                                return response.getWriter();
907                        }
908                        return super.resolveStandardArgument(parameterType, webRequest);
909                }
910
911                @SuppressWarnings("unchecked")
912                public ModelAndView getModelAndView(Method handlerMethod, Class<?> handlerType, Object returnValue,
913                                ExtendedModelMap implicitModel, ServletWebRequest webRequest) throws Exception {
914
915                        ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(handlerMethod, ResponseStatus.class);
916                        if (responseStatus != null) {
917                                HttpStatus statusCode = responseStatus.code();
918                                String reason = responseStatus.reason();
919                                if (!StringUtils.hasText(reason)) {
920                                        webRequest.getResponse().setStatus(statusCode.value());
921                                }
922                                else {
923                                        webRequest.getResponse().sendError(statusCode.value(), reason);
924                                }
925
926                                // to be picked up by the RedirectView
927                                webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, statusCode);
928
929                                this.responseArgumentUsed = true;
930                        }
931
932                        // Invoke custom resolvers if present...
933                        if (customModelAndViewResolvers != null) {
934                                for (ModelAndViewResolver mavResolver : customModelAndViewResolvers) {
935                                        ModelAndView mav = mavResolver.resolveModelAndView(
936                                                        handlerMethod, handlerType, returnValue, implicitModel, webRequest);
937                                        if (mav != ModelAndViewResolver.UNRESOLVED) {
938                                                return mav;
939                                        }
940                                }
941                        }
942
943                        if (returnValue instanceof HttpEntity) {
944                                handleHttpEntityResponse((HttpEntity<?>) returnValue, webRequest);
945                                return null;
946                        }
947                        else if (AnnotationUtils.findAnnotation(handlerMethod, ResponseBody.class) != null) {
948                                handleResponseBody(returnValue, webRequest);
949                                return null;
950                        }
951                        else if (returnValue instanceof ModelAndView) {
952                                ModelAndView mav = (ModelAndView) returnValue;
953                                mav.getModelMap().mergeAttributes(implicitModel);
954                                return mav;
955                        }
956                        else if (returnValue instanceof Model) {
957                                return new ModelAndView().addAllObjects(implicitModel).addAllObjects(((Model) returnValue).asMap());
958                        }
959                        else if (returnValue instanceof View) {
960                                return new ModelAndView((View) returnValue).addAllObjects(implicitModel);
961                        }
962                        else if (AnnotationUtils.findAnnotation(handlerMethod, ModelAttribute.class) != null) {
963                                addReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel);
964                                return new ModelAndView().addAllObjects(implicitModel);
965                        }
966                        else if (returnValue instanceof Map) {
967                                return new ModelAndView().addAllObjects(implicitModel).addAllObjects((Map<String, ?>) returnValue);
968                        }
969                        else if (returnValue instanceof String) {
970                                return new ModelAndView((String) returnValue).addAllObjects(implicitModel);
971                        }
972                        else if (returnValue == null) {
973                                // Either returned null or was 'void' return.
974                                if (this.responseArgumentUsed || webRequest.isNotModified()) {
975                                        return null;
976                                }
977                                else {
978                                        // Assuming view name translation...
979                                        return new ModelAndView().addAllObjects(implicitModel);
980                                }
981                        }
982                        else if (!BeanUtils.isSimpleProperty(returnValue.getClass())) {
983                                // Assume a single model attribute...
984                                addReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel);
985                                return new ModelAndView().addAllObjects(implicitModel);
986                        }
987                        else {
988                                throw new IllegalArgumentException("Invalid handler method return value: " + returnValue);
989                        }
990                }
991
992                private void handleResponseBody(Object returnValue, ServletWebRequest webRequest) throws Exception {
993                        if (returnValue == null) {
994                                return;
995                        }
996                        HttpInputMessage inputMessage = createHttpInputMessage(webRequest);
997                        HttpOutputMessage outputMessage = createHttpOutputMessage(webRequest);
998                        writeWithMessageConverters(returnValue, inputMessage, outputMessage);
999                }
1000
1001                private void handleHttpEntityResponse(HttpEntity<?> responseEntity, ServletWebRequest webRequest) throws Exception {
1002                        if (responseEntity == null) {
1003                                return;
1004                        }
1005                        HttpInputMessage inputMessage = createHttpInputMessage(webRequest);
1006                        HttpOutputMessage outputMessage = createHttpOutputMessage(webRequest);
1007                        if (responseEntity instanceof ResponseEntity && outputMessage instanceof ServerHttpResponse) {
1008                                ((ServerHttpResponse) outputMessage).setStatusCode(((ResponseEntity<?>) responseEntity).getStatusCode());
1009                        }
1010                        HttpHeaders entityHeaders = responseEntity.getHeaders();
1011                        if (!entityHeaders.isEmpty()) {
1012                                outputMessage.getHeaders().putAll(entityHeaders);
1013                        }
1014                        Object body = responseEntity.getBody();
1015                        if (body != null) {
1016                                writeWithMessageConverters(body, inputMessage, outputMessage);
1017                        }
1018                        else {
1019                                // flush headers
1020                                outputMessage.getBody();
1021                        }
1022                }
1023
1024                @SuppressWarnings({ "unchecked", "rawtypes" })
1025                private void writeWithMessageConverters(Object returnValue,
1026                                HttpInputMessage inputMessage, HttpOutputMessage outputMessage)
1027                                throws IOException, HttpMediaTypeNotAcceptableException {
1028
1029                        List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
1030                        if (acceptedMediaTypes.isEmpty()) {
1031                                acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
1032                        }
1033                        MediaType.sortByQualityValue(acceptedMediaTypes);
1034                        Class<?> returnValueType = returnValue.getClass();
1035                        List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
1036                        if (getMessageConverters() != null) {
1037                                for (MediaType acceptedMediaType : acceptedMediaTypes) {
1038                                        for (HttpMessageConverter messageConverter : getMessageConverters()) {
1039                                                if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {
1040                                                        messageConverter.write(returnValue, acceptedMediaType, outputMessage);
1041                                                        if (logger.isDebugEnabled()) {
1042                                                                MediaType contentType = outputMessage.getHeaders().getContentType();
1043                                                                if (contentType == null) {
1044                                                                        contentType = acceptedMediaType;
1045                                                                }
1046                                                                logger.debug("Written [" + returnValue + "] as \"" + contentType +
1047                                                                                "\" using [" + messageConverter + "]");
1048                                                        }
1049                                                        this.responseArgumentUsed = true;
1050                                                        return;
1051                                                }
1052                                        }
1053                                }
1054                                for (HttpMessageConverter messageConverter : messageConverters) {
1055                                        allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
1056                                }
1057                        }
1058                        throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes);
1059                }
1060        }
1061
1062
1063        /**
1064         * Holder for request mapping metadata.
1065         */
1066        static class RequestMappingInfo {
1067
1068                private final String[] patterns;
1069
1070                private final RequestMethod[] methods;
1071
1072                private final String[] params;
1073
1074                private final String[] headers;
1075
1076                RequestMappingInfo(String[] patterns, RequestMethod[] methods, String[] params, String[] headers) {
1077                        this.patterns = (patterns != null ? patterns : new String[0]);
1078                        this.methods = (methods != null ? methods : new RequestMethod[0]);
1079                        this.params = (params != null ? params : new String[0]);
1080                        this.headers = (headers != null ? headers : new String[0]);
1081                }
1082
1083                public boolean hasPatterns() {
1084                        return (this.patterns.length > 0);
1085                }
1086
1087                public String[] getPatterns() {
1088                        return this.patterns;
1089                }
1090
1091                public int getMethodCount() {
1092                        return this.methods.length;
1093                }
1094
1095                public int getParamCount() {
1096                        return this.params.length;
1097                }
1098
1099                public int getHeaderCount() {
1100                        return this.headers.length;
1101                }
1102
1103                public boolean matches(HttpServletRequest request) {
1104                        return matchesRequestMethod(request) && matchesParameters(request) && matchesHeaders(request);
1105                }
1106
1107                public boolean matchesHeaders(HttpServletRequest request) {
1108                        return ServletAnnotationMappingUtils.checkHeaders(this.headers, request);
1109                }
1110
1111                public boolean matchesParameters(HttpServletRequest request) {
1112                        return ServletAnnotationMappingUtils.checkParameters(this.params, request);
1113                }
1114
1115                public boolean matchesRequestMethod(HttpServletRequest request) {
1116                        return ServletAnnotationMappingUtils.checkRequestMethod(this.methods, request);
1117                }
1118
1119                public Set<String> methodNames() {
1120                        Set<String> methodNames = new LinkedHashSet<String>(this.methods.length);
1121                        for (RequestMethod method : this.methods) {
1122                                methodNames.add(method.name());
1123                        }
1124                        return methodNames;
1125                }
1126
1127                @Override
1128                public boolean equals(Object obj) {
1129                        RequestMappingInfo other = (RequestMappingInfo) obj;
1130                        return (Arrays.equals(this.patterns, other.patterns) && Arrays.equals(this.methods, other.methods) &&
1131                                        Arrays.equals(this.params, other.params) && Arrays.equals(this.headers, other.headers));
1132                }
1133
1134                @Override
1135                public int hashCode() {
1136                        return (Arrays.hashCode(this.patterns) * 23 + Arrays.hashCode(this.methods) * 29 +
1137                                        Arrays.hashCode(this.params) * 31 + Arrays.hashCode(this.headers));
1138                }
1139
1140                @Override
1141                public String toString() {
1142                        StringBuilder builder = new StringBuilder();
1143                        builder.append(Arrays.asList(this.patterns));
1144                        if (this.methods.length > 0) {
1145                                builder.append(',');
1146                                builder.append(Arrays.asList(this.methods));
1147                        }
1148                        if (this.headers.length > 0) {
1149                                builder.append(',');
1150                                builder.append(Arrays.asList(this.headers));
1151                        }
1152                        if (this.params.length > 0) {
1153                                builder.append(',');
1154                                builder.append(Arrays.asList(this.params));
1155                        }
1156                        return builder.toString();
1157                }
1158        }
1159
1160
1161        /**
1162         * Subclass of {@link RequestMappingInfo} that holds request-specific data.
1163         */
1164        static class RequestSpecificMappingInfo extends RequestMappingInfo {
1165
1166                private final List<String> matchedPatterns = new ArrayList<String>();
1167
1168                RequestSpecificMappingInfo(String[] patterns, RequestMethod[] methods, String[] params, String[] headers) {
1169                        super(patterns, methods, params, headers);
1170                }
1171
1172                RequestSpecificMappingInfo(RequestMappingInfo other) {
1173                        super(other.patterns, other.methods, other.params, other.headers);
1174                }
1175
1176                public void addMatchedPattern(String matchedPattern) {
1177                        matchedPatterns.add(matchedPattern);
1178                }
1179
1180                public void sortMatchedPatterns(Comparator<String> pathComparator) {
1181                        Collections.sort(matchedPatterns, pathComparator);
1182                }
1183
1184                public String bestMatchedPattern() {
1185                        return (!this.matchedPatterns.isEmpty() ? this.matchedPatterns.get(0) : null);
1186                }
1187        }
1188
1189
1190        /**
1191         * Comparator capable of sorting {@link RequestSpecificMappingInfo}s (RHIs) so that
1192         * sorting a list with this comparator will result in:
1193         * <ul>
1194         * <li>RHIs with {@linkplain AnnotationMethodHandlerAdapter.RequestSpecificMappingInfo#matchedPatterns better matched paths}
1195         * take precedence over those with a weaker match (as expressed by the {@linkplain PathMatcher#getPatternComparator(String)
1196         * path pattern comparator}.) Typically, this means that patterns without wild cards and uri templates
1197         * will be ordered before those without.</li>
1198         * <li>RHIs with one single {@linkplain RequestMappingInfo#methods request method} will be
1199         * ordered before those without a method, or with more than one method.</li>
1200         * <li>RHIs with more {@linkplain RequestMappingInfo#params request parameters} will be ordered
1201         * before those with less parameters</li>
1202         * </ol>
1203         */
1204        static class RequestSpecificMappingInfoComparator implements Comparator<RequestSpecificMappingInfo> {
1205
1206                private final Comparator<String> pathComparator;
1207
1208                private final ServerHttpRequest request;
1209
1210                RequestSpecificMappingInfoComparator(Comparator<String> pathComparator, HttpServletRequest request) {
1211                        this.pathComparator = pathComparator;
1212                        this.request = new ServletServerHttpRequest(request);
1213                }
1214
1215                @Override
1216                public int compare(RequestSpecificMappingInfo info1, RequestSpecificMappingInfo info2) {
1217                        int pathComparison = pathComparator.compare(info1.bestMatchedPattern(), info2.bestMatchedPattern());
1218                        if (pathComparison != 0) {
1219                                return pathComparison;
1220                        }
1221                        int info1ParamCount = info1.getParamCount();
1222                        int info2ParamCount = info2.getParamCount();
1223                        if (info1ParamCount != info2ParamCount) {
1224                                return info2ParamCount - info1ParamCount;
1225                        }
1226                        int info1HeaderCount = info1.getHeaderCount();
1227                        int info2HeaderCount = info2.getHeaderCount();
1228                        if (info1HeaderCount != info2HeaderCount) {
1229                                return info2HeaderCount - info1HeaderCount;
1230                        }
1231                        int acceptComparison = compareAcceptHeaders(info1, info2);
1232                        if (acceptComparison != 0) {
1233                                return acceptComparison;
1234                        }
1235                        int info1MethodCount = info1.getMethodCount();
1236                        int info2MethodCount = info2.getMethodCount();
1237                        if (info1MethodCount == 0 && info2MethodCount > 0) {
1238                                return 1;
1239                        }
1240                        else if (info2MethodCount == 0 && info1MethodCount > 0) {
1241                                return -1;
1242                        }
1243                        else if (info1MethodCount == 1 & info2MethodCount > 1) {
1244                                return -1;
1245                        }
1246                        else if (info2MethodCount == 1 & info1MethodCount > 1) {
1247                                return 1;
1248                        }
1249                        return 0;
1250                }
1251
1252                private int compareAcceptHeaders(RequestMappingInfo info1, RequestMappingInfo info2) {
1253                        List<MediaType> requestAccepts = request.getHeaders().getAccept();
1254                        MediaType.sortByQualityValue(requestAccepts);
1255
1256                        List<MediaType> info1Accepts = getAcceptHeaderValue(info1);
1257                        List<MediaType> info2Accepts = getAcceptHeaderValue(info2);
1258
1259                        for (MediaType requestAccept : requestAccepts) {
1260                                int pos1 = indexOfIncluded(info1Accepts, requestAccept);
1261                                int pos2 = indexOfIncluded(info2Accepts, requestAccept);
1262                                if (pos1 != pos2) {
1263                                        return pos2 - pos1;
1264                                }
1265                        }
1266                        return 0;
1267                }
1268
1269                private int indexOfIncluded(List<MediaType> infoAccepts, MediaType requestAccept) {
1270                        for (int i = 0; i < infoAccepts.size(); i++) {
1271                                MediaType info1Accept = infoAccepts.get(i);
1272                                if (requestAccept.includes(info1Accept)) {
1273                                        return i;
1274                                }
1275                        }
1276                        return -1;
1277                }
1278
1279                private List<MediaType> getAcceptHeaderValue(RequestMappingInfo info) {
1280                        for (String header : info.headers) {
1281                                int separator = header.indexOf('=');
1282                                if (separator != -1) {
1283                                        String key = header.substring(0, separator);
1284                                        String value = header.substring(separator + 1);
1285                                        if ("Accept".equalsIgnoreCase(key)) {
1286                                                return MediaType.parseMediaTypes(value);
1287                                        }
1288                                }
1289                        }
1290                        return Collections.emptyList();
1291                }
1292        }
1293
1294}