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.reactive;
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.apache.commons.logging.Log;
026import org.apache.commons.logging.LogFactory;
027import reactor.core.publisher.Mono;
028
029import org.springframework.core.MethodParameter;
030import org.springframework.lang.Nullable;
031import org.springframework.messaging.Message;
032
033/**
034 * Resolves method parameters by delegating to a list of registered
035 * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
036 * Previously resolved method parameters are cached for faster lookups.
037 *
038 * @author Rossen Stoyanchev
039 * @since 5.2
040 */
041public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
042
043        protected final Log logger = LogFactory.getLog(getClass());
044
045        private final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
046
047        private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
048                        new ConcurrentHashMap<>(256);
049
050
051        /**
052         * Add the given {@link HandlerMethodArgumentResolver}.
053         */
054        public HandlerMethodArgumentResolverComposite addResolver(HandlerMethodArgumentResolver resolver) {
055                this.argumentResolvers.add(resolver);
056                return this;
057        }
058
059        /**
060         * Add the given {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
061         */
062        public HandlerMethodArgumentResolverComposite addResolvers(@Nullable HandlerMethodArgumentResolver... resolvers) {
063                if (resolvers != null) {
064                        Collections.addAll(this.argumentResolvers, resolvers);
065                }
066                return this;
067        }
068
069        /**
070         * Add the given {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
071         */
072        public HandlerMethodArgumentResolverComposite addResolvers(
073                        @Nullable List<? extends HandlerMethodArgumentResolver> resolvers) {
074
075                if (resolvers != null) {
076                        this.argumentResolvers.addAll(resolvers);
077                }
078                return this;
079        }
080
081        /**
082         * Return a read-only list with the contained resolvers, or an empty list.
083         */
084        public List<HandlerMethodArgumentResolver> getResolvers() {
085                return Collections.unmodifiableList(this.argumentResolvers);
086        }
087
088        /**
089         * Clear the list of configured resolvers.
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} and
108         * invoke the one that supports it.
109         * @throws IllegalStateException if no suitable
110         * {@link HandlerMethodArgumentResolver} is found.
111         */
112        @Override
113        public Mono<Object> resolveArgument(MethodParameter parameter, Message<?> message) {
114                HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
115                if (resolver == null) {
116                        throw new IllegalArgumentException("Unsupported parameter type [" +
117                                        parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
118                }
119                return resolver.resolveArgument(parameter, message);
120        }
121
122        /**
123         * Find a registered {@link HandlerMethodArgumentResolver} that supports
124         * the given method parameter.
125         */
126        @Nullable
127        public HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
128                HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
129                if (result == null) {
130                        for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
131                                if (methodArgumentResolver.supportsParameter(parameter)) {
132                                        result = methodArgumentResolver;
133                                        this.argumentResolverCache.put(parameter, result);
134                                        break;
135                                }
136                        }
137                }
138                return result;
139        }
140
141}