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