001/*
002 * Copyright 2002-2018 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.reactive.result.method;
018
019import java.lang.reflect.Method;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.stream.Collectors;
023
024import reactor.core.publisher.MonoProcessor;
025
026import org.springframework.core.DefaultParameterNameDiscoverer;
027import org.springframework.core.ParameterNameDiscoverer;
028import org.springframework.lang.Nullable;
029import org.springframework.web.method.HandlerMethod;
030import org.springframework.web.reactive.BindingContext;
031import org.springframework.web.reactive.HandlerResult;
032import org.springframework.web.server.ServerErrorException;
033import org.springframework.web.server.ServerWebExchange;
034
035/**
036 * Extension of {@link HandlerMethod} that invokes the underlying method via
037 * {@link InvocableHandlerMethod} but uses sync argument resolvers only and
038 * thus can return directly a {@link HandlerResult} with no async wrappers.
039 *
040 * @author Rossen Stoyanchev
041 * @since 5.0
042 */
043public class SyncInvocableHandlerMethod extends HandlerMethod {
044
045        private final InvocableHandlerMethod delegate;
046
047
048        public SyncInvocableHandlerMethod(HandlerMethod handlerMethod) {
049                super(handlerMethod);
050                this.delegate = new InvocableHandlerMethod(handlerMethod);
051        }
052
053        public SyncInvocableHandlerMethod(Object bean, Method method) {
054                super(bean, method);
055                this.delegate = new InvocableHandlerMethod(bean, method);
056        }
057
058
059        /**
060         * Configure the argument resolvers to use to use for resolving method
061         * argument values against a {@code ServerWebExchange}.
062         */
063        public void setArgumentResolvers(List<SyncHandlerMethodArgumentResolver> resolvers) {
064                this.delegate.setArgumentResolvers(new ArrayList<>(resolvers));
065        }
066
067        /**
068         * Return the configured argument resolvers.
069         */
070        public List<SyncHandlerMethodArgumentResolver> getResolvers() {
071                return this.delegate.getResolvers().stream()
072                                .map(resolver -> (SyncHandlerMethodArgumentResolver) resolver)
073                                .collect(Collectors.toList());
074        }
075
076        /**
077         * Set the ParameterNameDiscoverer for resolving parameter names when needed
078         * (e.g. default request attribute name).
079         * <p>Default is a {@link DefaultParameterNameDiscoverer}.
080         */
081        public void setParameterNameDiscoverer(ParameterNameDiscoverer nameDiscoverer) {
082                this.delegate.setParameterNameDiscoverer(nameDiscoverer);
083        }
084
085        /**
086         * Return the configured parameter name discoverer.
087         */
088        public ParameterNameDiscoverer getParameterNameDiscoverer() {
089                return this.delegate.getParameterNameDiscoverer();
090        }
091
092
093        /**
094         * Invoke the method for the given exchange.
095         * @param exchange the current exchange
096         * @param bindingContext the binding context to use
097         * @param providedArgs optional list of argument values to match by type
098         * @return a Mono with a {@link HandlerResult}.
099         * @throws ServerErrorException if method argument resolution or method invocation fails
100         */
101        @Nullable
102        public HandlerResult invokeForHandlerResult(ServerWebExchange exchange,
103                        BindingContext bindingContext, Object... providedArgs) {
104
105                MonoProcessor<HandlerResult> processor = MonoProcessor.create();
106                this.delegate.invoke(exchange, bindingContext, providedArgs).subscribeWith(processor);
107
108                if (processor.isTerminated()) {
109                        Throwable ex = processor.getError();
110                        if (ex != null) {
111                                throw (ex instanceof ServerErrorException ? (ServerErrorException) ex :
112                                                new ServerErrorException("Failed to invoke: " + getShortLogMessage(), getMethod(), ex));
113                        }
114                        return processor.peek();
115                }
116                else {
117                        // Should never happen...
118                        throw new IllegalStateException(
119                                        "SyncInvocableHandlerMethod should have completed synchronously.");
120                }
121        }
122
123}