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.messaging.handler.annotation.reactive; 018 019import java.lang.reflect.AnnotatedElement; 020import java.lang.reflect.Method; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collections; 024import java.util.Comparator; 025import java.util.LinkedHashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029import java.util.function.Predicate; 030 031import reactor.core.publisher.Mono; 032 033import org.springframework.beans.factory.config.ConfigurableBeanFactory; 034import org.springframework.context.ApplicationContext; 035import org.springframework.context.ConfigurableApplicationContext; 036import org.springframework.context.EmbeddedValueResolverAware; 037import org.springframework.core.KotlinDetector; 038import org.springframework.core.annotation.AnnotatedElementUtils; 039import org.springframework.core.codec.Decoder; 040import org.springframework.core.convert.ConversionService; 041import org.springframework.format.support.DefaultFormattingConversionService; 042import org.springframework.lang.Nullable; 043import org.springframework.messaging.Message; 044import org.springframework.messaging.handler.CompositeMessageCondition; 045import org.springframework.messaging.handler.DestinationPatternsMessageCondition; 046import org.springframework.messaging.handler.HandlerMethod; 047import org.springframework.messaging.handler.annotation.MessageMapping; 048import org.springframework.messaging.handler.annotation.support.AnnotationExceptionHandlerMethodResolver; 049import org.springframework.messaging.handler.invocation.AbstractExceptionHandlerMethodResolver; 050import org.springframework.messaging.handler.invocation.reactive.AbstractEncoderMethodReturnValueHandler; 051import org.springframework.messaging.handler.invocation.reactive.AbstractMethodMessageHandler; 052import org.springframework.messaging.handler.invocation.reactive.HandlerMethodArgumentResolver; 053import org.springframework.messaging.handler.invocation.reactive.HandlerMethodReturnValueHandler; 054import org.springframework.messaging.support.MessageHeaderAccessor; 055import org.springframework.stereotype.Controller; 056import org.springframework.util.AntPathMatcher; 057import org.springframework.util.Assert; 058import org.springframework.util.CollectionUtils; 059import org.springframework.util.RouteMatcher; 060import org.springframework.util.SimpleRouteMatcher; 061import org.springframework.util.StringValueResolver; 062import org.springframework.validation.Validator; 063 064/** 065 * Extension of {@link AbstractMethodMessageHandler} for reactive, non-blocking 066 * handling of messages via {@link MessageMapping @MessageMapping} methods. 067 * By default such methods are detected in {@code @Controller} Spring beans but 068 * that can be changed via {@link #setHandlerPredicate(Predicate)}. 069 * 070 * <p>Payloads for incoming messages are decoded through the configured 071 * {@link #setDecoders(List)} decoders, with the help of 072 * {@link PayloadMethodArgumentResolver}. 073 * 074 * <p>There is no default handling for return values but 075 * {@link #setReturnValueHandlerConfigurer} can be used to configure custom 076 * return value handlers. Sub-classes may also override 077 * {@link #initReturnValueHandlers()} to set up default return value handlers. 078 * 079 * @author Rossen Stoyanchev 080 * @since 5.2 081 * @see AbstractEncoderMethodReturnValueHandler 082 */ 083public class MessageMappingMessageHandler extends AbstractMethodMessageHandler<CompositeMessageCondition> 084 implements EmbeddedValueResolverAware { 085 086 private final List<Decoder<?>> decoders = new ArrayList<>(); 087 088 @Nullable 089 private Validator validator; 090 091 @Nullable 092 private RouteMatcher routeMatcher; 093 094 private ConversionService conversionService = new DefaultFormattingConversionService(); 095 096 @Nullable 097 private StringValueResolver valueResolver; 098 099 100 public MessageMappingMessageHandler() { 101 setHandlerPredicate(type -> AnnotatedElementUtils.hasAnnotation(type, Controller.class)); 102 } 103 104 105 /** 106 * Configure the decoders to use for incoming payloads. 107 */ 108 public void setDecoders(List<? extends Decoder<?>> decoders) { 109 this.decoders.clear(); 110 this.decoders.addAll(decoders); 111 } 112 113 /** 114 * Return the configured decoders. 115 */ 116 public List<? extends Decoder<?>> getDecoders() { 117 return this.decoders; 118 } 119 120 /** 121 * Set the Validator instance used for validating {@code @Payload} arguments. 122 * @see org.springframework.validation.annotation.Validated 123 * @see PayloadMethodArgumentResolver 124 */ 125 public void setValidator(@Nullable Validator validator) { 126 this.validator = validator; 127 } 128 129 /** 130 * Return the configured Validator instance. 131 */ 132 @Nullable 133 public Validator getValidator() { 134 return this.validator; 135 } 136 137 /** 138 * Set the {@code RouteMatcher} to use for mapping messages to handlers 139 * based on the route patterns they're configured with. 140 * <p>By default, {@link SimpleRouteMatcher} is used, backed by 141 * {@link AntPathMatcher} with "." as separator. For greater 142 * efficiency consider using the {@code PathPatternRouteMatcher} from 143 * {@code spring-web} instead. 144 */ 145 public void setRouteMatcher(@Nullable RouteMatcher routeMatcher) { 146 this.routeMatcher = routeMatcher; 147 } 148 149 /** 150 * Return the {@code RouteMatcher} used to map messages to handlers. 151 * May be {@code null} before the component is initialized. 152 */ 153 @Nullable 154 public RouteMatcher getRouteMatcher() { 155 return this.routeMatcher; 156 } 157 158 /** 159 * Obtain the {@code RouteMatcher} for actual use. 160 * @return the RouteMatcher (never {@code null}) 161 * @throws IllegalStateException in case of no RouteMatcher set 162 * @since 5.0 163 */ 164 protected RouteMatcher obtainRouteMatcher() { 165 RouteMatcher routeMatcher = getRouteMatcher(); 166 Assert.state(routeMatcher != null, "No RouteMatcher set"); 167 return routeMatcher; 168 } 169 170 /** 171 * Configure a {@link ConversionService} to use for type conversion of 172 * String based values, e.g. in destination variables or headers. 173 * <p>By default {@link DefaultFormattingConversionService} is used. 174 * @param conversionService the conversion service to use 175 */ 176 public void setConversionService(ConversionService conversionService) { 177 this.conversionService = conversionService; 178 } 179 180 /** 181 * Return the configured ConversionService. 182 */ 183 public ConversionService getConversionService() { 184 return this.conversionService; 185 } 186 187 @Override 188 public void setEmbeddedValueResolver(StringValueResolver resolver) { 189 this.valueResolver = resolver; 190 } 191 192 193 @Override 194 public void afterPropertiesSet() { 195 196 // Initialize RouteMatcher before parent initializes handler mappings 197 if (this.routeMatcher == null) { 198 AntPathMatcher pathMatcher = new AntPathMatcher(); 199 pathMatcher.setPathSeparator("."); 200 this.routeMatcher = new SimpleRouteMatcher(pathMatcher); 201 } 202 203 super.afterPropertiesSet(); 204 } 205 206 @Override 207 protected List<? extends HandlerMethodArgumentResolver> initArgumentResolvers() { 208 List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(); 209 210 ApplicationContext context = getApplicationContext(); 211 ConfigurableBeanFactory beanFactory = (context instanceof ConfigurableApplicationContext ? 212 ((ConfigurableApplicationContext) context).getBeanFactory() : null); 213 214 // Annotation-based resolvers 215 resolvers.add(new HeaderMethodArgumentResolver(this.conversionService, beanFactory)); 216 resolvers.add(new HeadersMethodArgumentResolver()); 217 resolvers.add(new DestinationVariableMethodArgumentResolver(this.conversionService)); 218 219 // Type-based... 220 if (KotlinDetector.isKotlinPresent()) { 221 resolvers.add(new ContinuationHandlerMethodArgumentResolver()); 222 } 223 224 // Custom resolvers 225 resolvers.addAll(getArgumentResolverConfigurer().getCustomResolvers()); 226 227 // Catch-all 228 resolvers.add(new PayloadMethodArgumentResolver( 229 getDecoders(), this.validator, getReactiveAdapterRegistry(), true)); 230 231 return resolvers; 232 } 233 234 @Override 235 protected List<? extends HandlerMethodReturnValueHandler> initReturnValueHandlers() { 236 return Collections.emptyList(); 237 } 238 239 240 @Override 241 protected CompositeMessageCondition getMappingForMethod(Method method, Class<?> handlerType) { 242 CompositeMessageCondition methodCondition = getCondition(method); 243 if (methodCondition != null) { 244 CompositeMessageCondition typeCondition = getCondition(handlerType); 245 if (typeCondition != null) { 246 return typeCondition.combine(methodCondition); 247 } 248 } 249 return methodCondition; 250 } 251 252 /** 253 * Determine the mapping condition for the given annotated element. 254 * @param element the element to check 255 * @return the condition, or {@code null} 256 */ 257 @Nullable 258 protected CompositeMessageCondition getCondition(AnnotatedElement element) { 259 MessageMapping ann = AnnotatedElementUtils.findMergedAnnotation(element, MessageMapping.class); 260 if (ann == null || ann.value().length == 0) { 261 return null; 262 } 263 String[] patterns = processDestinations(ann.value()); 264 return new CompositeMessageCondition( 265 new DestinationPatternsMessageCondition(patterns, obtainRouteMatcher())); 266 } 267 268 /** 269 * Resolve placeholders in the given destinations. 270 * @param destinations the destinations 271 * @return new array with the processed destinations or the same array 272 */ 273 protected String[] processDestinations(String[] destinations) { 274 if (this.valueResolver != null) { 275 destinations = Arrays.stream(destinations) 276 .map(s -> this.valueResolver.resolveStringValue(s)) 277 .toArray(String[]::new); 278 } 279 return destinations; 280 } 281 282 @Override 283 protected Set<String> getDirectLookupMappings(CompositeMessageCondition mapping) { 284 Set<String> result = new LinkedHashSet<>(); 285 for (String pattern : mapping.getCondition(DestinationPatternsMessageCondition.class).getPatterns()) { 286 if (!obtainRouteMatcher().isPattern(pattern)) { 287 result.add(pattern); 288 } 289 } 290 return result; 291 } 292 293 @Override 294 protected RouteMatcher.Route getDestination(Message<?> message) { 295 return (RouteMatcher.Route) message.getHeaders() 296 .get(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER); 297 } 298 299 @Override 300 protected CompositeMessageCondition getMatchingMapping(CompositeMessageCondition mapping, Message<?> message) { 301 return mapping.getMatchingCondition(message); 302 } 303 304 @Override 305 protected Comparator<CompositeMessageCondition> getMappingComparator(Message<?> message) { 306 return (info1, info2) -> info1.compareTo(info2, message); 307 } 308 309 @Override 310 protected AbstractExceptionHandlerMethodResolver createExceptionMethodResolverFor(Class<?> beanType) { 311 return new AnnotationExceptionHandlerMethodResolver(beanType); 312 } 313 314 @Override 315 protected Mono<Void> handleMatch( 316 CompositeMessageCondition mapping, HandlerMethod handlerMethod, Message<?> message) { 317 318 Set<String> patterns = mapping.getCondition(DestinationPatternsMessageCondition.class).getPatterns(); 319 if (!CollectionUtils.isEmpty(patterns)) { 320 String pattern = patterns.iterator().next(); 321 RouteMatcher.Route destination = getDestination(message); 322 Assert.state(destination != null, "Missing destination header"); 323 Map<String, String> vars = obtainRouteMatcher().matchAndExtract(pattern, destination); 324 if (!CollectionUtils.isEmpty(vars)) { 325 MessageHeaderAccessor mha = MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class); 326 Assert.state(mha != null && mha.isMutable(), "Mutable MessageHeaderAccessor required"); 327 mha.setHeader(DestinationVariableMethodArgumentResolver.DESTINATION_TEMPLATE_VARIABLES_HEADER, vars); 328 } 329 } 330 return super.handleMatch(mapping, handlerMethod, message); 331 } 332 333}