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