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.servlet.function.support;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.List;
022import java.util.Map;
023
024import javax.servlet.http.HttpServletRequest;
025
026import org.springframework.beans.factory.BeanFactoryUtils;
027import org.springframework.beans.factory.InitializingBean;
028import org.springframework.context.ApplicationContext;
029import org.springframework.http.converter.ByteArrayHttpMessageConverter;
030import org.springframework.http.converter.HttpMessageConverter;
031import org.springframework.http.converter.StringHttpMessageConverter;
032import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
033import org.springframework.http.converter.xml.SourceHttpMessageConverter;
034import org.springframework.lang.Nullable;
035import org.springframework.util.CollectionUtils;
036import org.springframework.web.servlet.function.RouterFunction;
037import org.springframework.web.servlet.function.RouterFunctions;
038import org.springframework.web.servlet.function.ServerRequest;
039import org.springframework.web.servlet.handler.AbstractHandlerMapping;
040
041/**
042 * {@code HandlerMapping} implementation that supports {@link RouterFunction RouterFunctions}.
043 *
044 * <p>If no {@link RouterFunction} is provided at
045 * {@linkplain #RouterFunctionMapping(RouterFunction) construction time}, this mapping
046 * will detect all router functions in the application context, and consult them in
047 * {@linkplain org.springframework.core.annotation.Order order}.
048 *
049 * @author Arjen Poutsma
050 * @since 5.2
051 */
052public class RouterFunctionMapping extends AbstractHandlerMapping implements InitializingBean {
053
054        @Nullable
055        private RouterFunction<?> routerFunction;
056
057        private List<HttpMessageConverter<?>> messageConverters = Collections.emptyList();
058
059        private boolean detectHandlerFunctionsInAncestorContexts = false;
060
061
062
063        /**
064         * Create an empty {@code RouterFunctionMapping}.
065         * <p>If this constructor is used, this mapping will detect all
066         * {@link RouterFunction} instances available in the application context.
067         */
068        public RouterFunctionMapping() {
069        }
070
071        /**
072         * Create a {@code RouterFunctionMapping} with the given {@link RouterFunction}.
073         * <p>If this constructor is used, no application context detection will occur.
074         * @param routerFunction the router function to use for mapping
075         */
076        public RouterFunctionMapping(RouterFunction<?> routerFunction) {
077                this.routerFunction = routerFunction;
078        }
079
080        /**
081         * Set the router function to map to.
082         * <p>If this property is used, no application context detection will occur.
083         */
084        public void setRouterFunction(@Nullable RouterFunction<?> routerFunction) {
085                this.routerFunction = routerFunction;
086        }
087
088        /**
089         * Return the configured {@link RouterFunction}.
090         * <p><strong>Note:</strong> When router functions are detected from the
091         * ApplicationContext, this method may return {@code null} if invoked
092         * prior to {@link #afterPropertiesSet()}.
093         * @return the router function or {@code null}
094         */
095        @Nullable
096        public RouterFunction<?> getRouterFunction() {
097                return this.routerFunction;
098        }
099
100        public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
101                this.messageConverters = messageConverters;
102        }
103
104        /**
105         * Set whether to detect handler functions in ancestor ApplicationContexts.
106         * <p>Default is "false": Only handler functions in the current ApplicationContext
107         * will be detected, i.e. only in the context that this HandlerMapping itself
108         * is defined in (typically the current DispatcherServlet's context).
109         * <p>Switch this flag on to detect handler beans in ancestor contexts
110         * (typically the Spring root WebApplicationContext) as well.
111         */
112        public void setDetectHandlerFunctionsInAncestorContexts(boolean detectHandlerFunctionsInAncestorContexts) {
113                this.detectHandlerFunctionsInAncestorContexts = detectHandlerFunctionsInAncestorContexts;
114        }
115
116        @Override
117        public void afterPropertiesSet() throws Exception {
118                if (this.routerFunction == null) {
119                        initRouterFunction();
120                }
121                if (CollectionUtils.isEmpty(this.messageConverters)) {
122                        initMessageConverters();
123                }
124        }
125
126        /**
127         * Detect a all {@linkplain RouterFunction router functions} in the
128         * current application context.
129         */
130        @SuppressWarnings({"rawtypes", "unchecked"})
131        private void initRouterFunction() {
132                ApplicationContext applicationContext = obtainApplicationContext();
133                Map<String, RouterFunction> beans =
134                                (this.detectHandlerFunctionsInAncestorContexts ?
135                                                BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RouterFunction.class) :
136                                                applicationContext.getBeansOfType(RouterFunction.class));
137
138                List<RouterFunction> routerFunctions = new ArrayList<>(beans.values());
139                if (!CollectionUtils.isEmpty(routerFunctions) && logger.isInfoEnabled()) {
140                        routerFunctions.forEach(routerFunction -> logger.info("Mapped " + routerFunction));
141                }
142                this.routerFunction = routerFunctions.stream()
143                                .reduce(RouterFunction::andOther)
144                                .orElse(null);
145        }
146
147        /**
148         * Initializes a default set of {@linkplain HttpMessageConverter message converters}.
149         */
150        private void initMessageConverters() {
151                List<HttpMessageConverter<?>> messageConverters = new ArrayList<>(4);
152                messageConverters.add(new ByteArrayHttpMessageConverter());
153                messageConverters.add(new StringHttpMessageConverter());
154
155                try {
156                        messageConverters.add(new SourceHttpMessageConverter<>());
157                }
158                catch (Error err) {
159                        // Ignore when no TransformerFactory implementation is available
160                }
161                messageConverters.add(new AllEncompassingFormHttpMessageConverter());
162
163                this.messageConverters = messageConverters;
164        }
165
166        @Nullable
167        @Override
168        protected Object getHandlerInternal(HttpServletRequest servletRequest) throws Exception {
169                String lookupPath = getUrlPathHelper().getLookupPathForRequest(servletRequest);
170                servletRequest.setAttribute(LOOKUP_PATH, lookupPath);
171                if (this.routerFunction != null) {
172                        ServerRequest request = ServerRequest.create(servletRequest, this.messageConverters);
173                        servletRequest.setAttribute(RouterFunctions.REQUEST_ATTRIBUTE, request);
174                        return this.routerFunction.route(request).orElse(null);
175                }
176                else {
177                        return null;
178                }
179        }
180
181}