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}