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}