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.reactive.function.server.support;
018
019import java.util.Collections;
020import java.util.List;
021import java.util.Map;
022import java.util.stream.Collectors;
023
024import reactor.core.publisher.Mono;
025
026import org.springframework.beans.factory.InitializingBean;
027import org.springframework.http.codec.HttpMessageReader;
028import org.springframework.http.codec.ServerCodecConfigurer;
029import org.springframework.lang.Nullable;
030import org.springframework.util.CollectionUtils;
031import org.springframework.web.reactive.function.server.HandlerFunction;
032import org.springframework.web.reactive.function.server.RouterFunction;
033import org.springframework.web.reactive.function.server.RouterFunctions;
034import org.springframework.web.reactive.function.server.ServerRequest;
035import org.springframework.web.reactive.handler.AbstractHandlerMapping;
036import org.springframework.web.server.ServerWebExchange;
037import org.springframework.web.util.pattern.PathPattern;
038
039/**
040 * {@code HandlerMapping} implementation that supports {@link RouterFunction RouterFunctions}.
041 *
042 * <p>If no {@link RouterFunction} is provided at
043 * {@linkplain #RouterFunctionMapping(RouterFunction) construction time}, this mapping
044 * will detect all router functions in the application context, and consult them in
045 * {@linkplain org.springframework.core.annotation.Order order}.
046 *
047 * @author Arjen Poutsma
048 * @since 5.0
049 */
050public class RouterFunctionMapping extends AbstractHandlerMapping implements InitializingBean {
051
052        @Nullable
053        private RouterFunction<?> routerFunction;
054
055        private List<HttpMessageReader<?>> messageReaders = Collections.emptyList();
056
057
058        /**
059         * Create an empty {@code RouterFunctionMapping}.
060         * <p>If this constructor is used, this mapping will detect all
061         * {@link RouterFunction} instances available in the application context.
062         */
063        public RouterFunctionMapping() {
064        }
065
066        /**
067         * Create a {@code RouterFunctionMapping} with the given {@link RouterFunction}.
068         * <p>If this constructor is used, no application context detection will occur.
069         * @param routerFunction the router function to use for mapping
070         */
071        public RouterFunctionMapping(RouterFunction<?> routerFunction) {
072                this.routerFunction = routerFunction;
073        }
074
075
076        /**
077         * Return the configured {@link RouterFunction}.
078         * <p><strong>Note:</strong> When router functions are detected from the
079         * ApplicationContext, this method may return {@code null} if invoked
080         * prior to {@link #afterPropertiesSet()}.
081         * @return the router function or {@code null}
082         */
083        @Nullable
084        public RouterFunction<?> getRouterFunction() {
085                return this.routerFunction;
086        }
087
088        /**
089         * Configure HTTP message readers to de-serialize the request body with.
090         * <p>By default this is set to the {@link ServerCodecConfigurer}'s defaults.
091         */
092        public void setMessageReaders(List<HttpMessageReader<?>> messageReaders) {
093                this.messageReaders = messageReaders;
094        }
095
096        @Override
097        public void afterPropertiesSet() throws Exception {
098                if (CollectionUtils.isEmpty(this.messageReaders)) {
099                        ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create();
100                        this.messageReaders = codecConfigurer.getReaders();
101                }
102
103                if (this.routerFunction == null) {
104                        initRouterFunctions();
105                }
106        }
107
108        /**
109         * Initialized the router functions by detecting them in the application context.
110         */
111        protected void initRouterFunctions() {
112                List<RouterFunction<?>> routerFunctions = routerFunctions();
113                this.routerFunction = routerFunctions.stream().reduce(RouterFunction::andOther).orElse(null);
114                logRouterFunctions(routerFunctions);
115        }
116
117        private List<RouterFunction<?>> routerFunctions() {
118                List<RouterFunction<?>> functions = obtainApplicationContext()
119                                .getBeanProvider(RouterFunction.class)
120                                .orderedStream()
121                                .map(router -> (RouterFunction<?>)router)
122                                .collect(Collectors.toList());
123                return (!CollectionUtils.isEmpty(functions) ? functions : Collections.emptyList());
124        }
125
126        private void logRouterFunctions(List<RouterFunction<?>> routerFunctions) {
127                if (logger.isDebugEnabled()) {
128                        int total = routerFunctions.size();
129                        String message = total + " RouterFunction(s) in " + formatMappingName();
130                        if (logger.isTraceEnabled()) {
131                                if (total > 0) {
132                                        routerFunctions.forEach(routerFunction -> logger.trace("Mapped " + routerFunction));
133                                }
134                                else {
135                                        logger.trace(message);
136                                }
137                        }
138                        else if (total > 0) {
139                                logger.debug(message);
140                        }
141                }
142        }
143
144
145        @Override
146        protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
147                if (this.routerFunction != null) {
148                        ServerRequest request = ServerRequest.create(exchange, this.messageReaders);
149                        return this.routerFunction.route(request)
150                                        .doOnNext(handler -> setAttributes(exchange.getAttributes(), request, handler));
151                }
152                else {
153                        return Mono.empty();
154                }
155        }
156
157        @SuppressWarnings("unchecked")
158        private void setAttributes(
159                        Map<String, Object> attributes, ServerRequest serverRequest, HandlerFunction<?> handlerFunction) {
160
161                attributes.put(RouterFunctions.REQUEST_ATTRIBUTE, serverRequest);
162                attributes.put(BEST_MATCHING_HANDLER_ATTRIBUTE, handlerFunction);
163
164                PathPattern matchingPattern = (PathPattern) attributes.get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE);
165                if (matchingPattern != null) {
166                        attributes.put(BEST_MATCHING_PATTERN_ATTRIBUTE, matchingPattern);
167                }
168                Map<String, String> uriVariables =
169                                (Map<String, String>) attributes.get(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
170                if (uriVariables != null) {
171                        attributes.put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
172                }
173        }
174
175}