001/*
002 * Copyright 2002-2019 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.method.support;
018
019import java.util.Collections;
020import java.util.LinkedList;
021import java.util.List;
022import java.util.Map;
023import java.util.concurrent.ConcurrentHashMap;
024
025import org.apache.commons.logging.Log;
026import org.apache.commons.logging.LogFactory;
027
028import org.springframework.core.MethodParameter;
029import org.springframework.web.bind.support.WebDataBinderFactory;
030import org.springframework.web.context.request.NativeWebRequest;
031
032/**
033 * Resolves method parameters by delegating to a list of registered
034 * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
035 * Previously resolved method parameters are cached for faster lookups.
036 *
037 * @author Rossen Stoyanchev
038 * @author Juergen Hoeller
039 * @since 3.1
040 */
041public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
042
043        @Deprecated
044        protected final Log logger = LogFactory.getLog(getClass());
045
046        private final List<HandlerMethodArgumentResolver> argumentResolvers =
047                        new LinkedList<HandlerMethodArgumentResolver>();
048
049        private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
050                        new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>(256);
051
052
053        /**
054         * Add the given {@link HandlerMethodArgumentResolver}.
055         */
056        public HandlerMethodArgumentResolverComposite addResolver(HandlerMethodArgumentResolver resolver) {
057                this.argumentResolvers.add(resolver);
058                return this;
059        }
060
061        /**
062         * Add the given {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
063         * @since 4.3
064         */
065        public HandlerMethodArgumentResolverComposite addResolvers(HandlerMethodArgumentResolver... resolvers) {
066                if (resolvers != null) {
067                        Collections.addAll(this.argumentResolvers, resolvers);
068                }
069                return this;
070        }
071
072        /**
073         * Add the given {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
074         */
075        public HandlerMethodArgumentResolverComposite addResolvers(List<? extends HandlerMethodArgumentResolver> resolvers) {
076                if (resolvers != null) {
077                        this.argumentResolvers.addAll(resolvers);
078                }
079                return this;
080        }
081
082        /**
083         * Return a read-only list with the contained resolvers, or an empty list.
084         */
085        public List<HandlerMethodArgumentResolver> getResolvers() {
086                return Collections.unmodifiableList(this.argumentResolvers);
087        }
088
089        /**
090         * Clear the list of configured resolvers.
091         * @since 4.3
092         */
093        public void clear() {
094                this.argumentResolvers.clear();
095        }
096
097
098        /**
099         * Whether the given {@linkplain MethodParameter method parameter} is
100         * supported by any registered {@link HandlerMethodArgumentResolver}.
101         */
102        @Override
103        public boolean supportsParameter(MethodParameter parameter) {
104                return getArgumentResolver(parameter) != null;
105        }
106
107        /**
108         * Iterate over registered
109         * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}
110         * and invoke the one that supports it.
111         * @throws IllegalArgumentException if no suitable argument resolver is found
112         */
113        @Override
114        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
115                        NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
116
117                HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
118                if (resolver == null) {
119                        throw new IllegalArgumentException("Unsupported parameter type [" +
120                                        parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
121                }
122                return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
123        }
124
125        /**
126         * Find a registered {@link HandlerMethodArgumentResolver} that supports
127         * the given method parameter.
128         */
129        private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
130                HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
131                if (result == null) {
132                        for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
133                                if (resolver.supportsParameter(parameter)) {
134                                        result = resolver;
135                                        this.argumentResolverCache.put(parameter, result);
136                                        break;
137                                }
138                        }
139                }
140                return result;
141        }
142
143}