001/* 002 * Copyright 2002-2018 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.simp.annotation.support; 018 019import java.lang.reflect.Method; 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.Comparator; 023import java.util.LinkedHashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Set; 027 028import org.springframework.beans.factory.config.ConfigurableBeanFactory; 029import org.springframework.context.ConfigurableApplicationContext; 030import org.springframework.context.EmbeddedValueResolverAware; 031import org.springframework.context.SmartLifecycle; 032import org.springframework.core.annotation.AnnotatedElementUtils; 033import org.springframework.core.convert.ConversionService; 034import org.springframework.format.support.DefaultFormattingConversionService; 035import org.springframework.messaging.Message; 036import org.springframework.messaging.MessageChannel; 037import org.springframework.messaging.SubscribableChannel; 038import org.springframework.messaging.converter.ByteArrayMessageConverter; 039import org.springframework.messaging.converter.CompositeMessageConverter; 040import org.springframework.messaging.converter.MessageConverter; 041import org.springframework.messaging.converter.StringMessageConverter; 042import org.springframework.messaging.core.AbstractMessageSendingTemplate; 043import org.springframework.messaging.handler.DestinationPatternsMessageCondition; 044import org.springframework.messaging.handler.HandlerMethod; 045import org.springframework.messaging.handler.annotation.MessageMapping; 046import org.springframework.messaging.handler.annotation.support.AnnotationExceptionHandlerMethodResolver; 047import org.springframework.messaging.handler.annotation.support.DestinationVariableMethodArgumentResolver; 048import org.springframework.messaging.handler.annotation.support.HeaderMethodArgumentResolver; 049import org.springframework.messaging.handler.annotation.support.HeadersMethodArgumentResolver; 050import org.springframework.messaging.handler.annotation.support.MessageMethodArgumentResolver; 051import org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver; 052import org.springframework.messaging.handler.invocation.AbstractExceptionHandlerMethodResolver; 053import org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler; 054import org.springframework.messaging.handler.invocation.CompletableFutureReturnValueHandler; 055import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; 056import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler; 057import org.springframework.messaging.handler.invocation.ListenableFutureReturnValueHandler; 058import org.springframework.messaging.simp.SimpAttributesContextHolder; 059import org.springframework.messaging.simp.SimpMessageHeaderAccessor; 060import org.springframework.messaging.simp.SimpMessageMappingInfo; 061import org.springframework.messaging.simp.SimpMessageSendingOperations; 062import org.springframework.messaging.simp.SimpMessageTypeMessageCondition; 063import org.springframework.messaging.simp.SimpMessagingTemplate; 064import org.springframework.messaging.simp.annotation.SubscribeMapping; 065import org.springframework.messaging.support.MessageHeaderAccessor; 066import org.springframework.messaging.support.MessageHeaderInitializer; 067import org.springframework.stereotype.Controller; 068import org.springframework.util.AntPathMatcher; 069import org.springframework.util.Assert; 070import org.springframework.util.ClassUtils; 071import org.springframework.util.CollectionUtils; 072import org.springframework.util.PathMatcher; 073import org.springframework.util.StringValueResolver; 074import org.springframework.validation.Validator; 075 076/** 077 * A handler for messages delegating to {@link MessageMapping @MessageMapping} 078 * and {@link SubscribeMapping @SubscribeMapping} annotated methods. 079 * 080 * <p>Supports Ant-style path patterns with template variables. 081 * 082 * @author Rossen Stoyanchev 083 * @author Brian Clozel 084 * @author Juergen Hoeller 085 * @since 4.0 086 */ 087public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHandler<SimpMessageMappingInfo> 088 implements EmbeddedValueResolverAware, SmartLifecycle { 089 090 private static final boolean completableFuturePresent = ClassUtils.isPresent( 091 "java.util.concurrent.CompletableFuture", SimpAnnotationMethodMessageHandler.class.getClassLoader()); 092 093 094 private final SubscribableChannel clientInboundChannel; 095 096 private final SimpMessageSendingOperations clientMessagingTemplate; 097 098 private final SimpMessageSendingOperations brokerTemplate; 099 100 private MessageConverter messageConverter; 101 102 private ConversionService conversionService = new DefaultFormattingConversionService(); 103 104 private PathMatcher pathMatcher = new AntPathMatcher(); 105 106 private boolean slashPathSeparator = true; 107 108 private Validator validator; 109 110 private StringValueResolver valueResolver; 111 112 private MessageHeaderInitializer headerInitializer; 113 114 private volatile boolean running = false; 115 116 private final Object lifecycleMonitor = new Object(); 117 118 119 /** 120 * Create an instance of SimpAnnotationMethodMessageHandler with the given 121 * message channels and broker messaging template. 122 * @param clientInboundChannel the channel for receiving messages from clients (e.g. WebSocket clients) 123 * @param clientOutboundChannel the channel for messages to clients (e.g. WebSocket clients) 124 * @param brokerTemplate a messaging template to send application messages to the broker 125 */ 126 public SimpAnnotationMethodMessageHandler(SubscribableChannel clientInboundChannel, 127 MessageChannel clientOutboundChannel, SimpMessageSendingOperations brokerTemplate) { 128 129 Assert.notNull(clientInboundChannel, "clientInboundChannel must not be null"); 130 Assert.notNull(clientOutboundChannel, "clientOutboundChannel must not be null"); 131 Assert.notNull(brokerTemplate, "brokerTemplate must not be null"); 132 133 this.clientInboundChannel = clientInboundChannel; 134 this.clientMessagingTemplate = new SimpMessagingTemplate(clientOutboundChannel); 135 this.brokerTemplate = brokerTemplate; 136 137 Collection<MessageConverter> converters = new ArrayList<MessageConverter>(); 138 converters.add(new StringMessageConverter()); 139 converters.add(new ByteArrayMessageConverter()); 140 this.messageConverter = new CompositeMessageConverter(converters); 141 } 142 143 144 /** 145 * {@inheritDoc} 146 * <p>Destination prefixes are expected to be slash-separated Strings and 147 * therefore a slash is automatically appended where missing to ensure a 148 * proper prefix-based match (i.e. matching complete segments). 149 * <p>Note however that the remaining portion of a destination after the 150 * prefix may use a different separator (e.g. commonly "." in messaging) 151 * depending on the configured {@code PathMatcher}. 152 */ 153 @Override 154 public void setDestinationPrefixes(Collection<String> prefixes) { 155 super.setDestinationPrefixes(appendSlashes(prefixes)); 156 } 157 158 private static Collection<String> appendSlashes(Collection<String> prefixes) { 159 if (CollectionUtils.isEmpty(prefixes)) { 160 return prefixes; 161 } 162 Collection<String> result = new ArrayList<String>(prefixes.size()); 163 for (String prefix : prefixes) { 164 if (!prefix.endsWith("/")) { 165 prefix = prefix + "/"; 166 } 167 result.add(prefix); 168 } 169 return result; 170 } 171 172 /** 173 * Configure a {@link MessageConverter} to use to convert the payload of a message from 174 * its serialized form with a specific MIME type to an Object matching the target method 175 * parameter. The converter is also used when sending a message to the message broker. 176 * @see CompositeMessageConverter 177 */ 178 public void setMessageConverter(MessageConverter converter) { 179 this.messageConverter = converter; 180 if (converter != null) { 181 ((AbstractMessageSendingTemplate<?>) this.clientMessagingTemplate).setMessageConverter(converter); 182 } 183 } 184 185 /** 186 * Return the configured {@link MessageConverter}. 187 */ 188 public MessageConverter getMessageConverter() { 189 return this.messageConverter; 190 } 191 192 /** 193 * Configure a {@link ConversionService} to use when resolving method arguments, 194 * for example message header values. 195 * <p>By default, {@link DefaultFormattingConversionService} is used. 196 */ 197 public void setConversionService(ConversionService conversionService) { 198 this.conversionService = conversionService; 199 } 200 201 /** 202 * Return the configured {@link ConversionService}. 203 */ 204 public ConversionService getConversionService() { 205 return this.conversionService; 206 } 207 208 /** 209 * Set the PathMatcher implementation to use for matching destinations 210 * against configured destination patterns. 211 * <p>By default, {@link AntPathMatcher} is used. 212 */ 213 public void setPathMatcher(PathMatcher pathMatcher) { 214 Assert.notNull(pathMatcher, "PathMatcher must not be null"); 215 this.pathMatcher = pathMatcher; 216 this.slashPathSeparator = this.pathMatcher.combine("a", "a").equals("a/a"); 217 } 218 219 /** 220 * Return the PathMatcher implementation to use for matching destinations. 221 */ 222 public PathMatcher getPathMatcher() { 223 return this.pathMatcher; 224 } 225 226 /** 227 * Return the configured Validator instance. 228 */ 229 public Validator getValidator() { 230 return this.validator; 231 } 232 233 /** 234 * Set the Validator instance used for validating {@code @Payload} arguments. 235 * @see org.springframework.validation.annotation.Validated 236 * @see PayloadArgumentResolver 237 */ 238 public void setValidator(Validator validator) { 239 this.validator = validator; 240 } 241 242 @Override 243 public void setEmbeddedValueResolver(StringValueResolver resolver) { 244 this.valueResolver = resolver; 245 } 246 247 /** 248 * Configure a {@link MessageHeaderInitializer} to pass on to 249 * {@link org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler}s 250 * that send messages from controller return values. 251 * <p>By default, this property is not set. 252 */ 253 public void setHeaderInitializer(MessageHeaderInitializer headerInitializer) { 254 this.headerInitializer = headerInitializer; 255 } 256 257 /** 258 * Return the configured header initializer. 259 */ 260 public MessageHeaderInitializer getHeaderInitializer() { 261 return this.headerInitializer; 262 } 263 264 265 @Override 266 public boolean isAutoStartup() { 267 return true; 268 } 269 270 @Override 271 public int getPhase() { 272 return Integer.MAX_VALUE; 273 } 274 275 @Override 276 public final void start() { 277 synchronized (this.lifecycleMonitor) { 278 this.clientInboundChannel.subscribe(this); 279 this.running = true; 280 } 281 } 282 283 @Override 284 public final void stop() { 285 synchronized (this.lifecycleMonitor) { 286 this.running = false; 287 this.clientInboundChannel.unsubscribe(this); 288 } 289 } 290 291 @Override 292 public final void stop(Runnable callback) { 293 synchronized (this.lifecycleMonitor) { 294 stop(); 295 callback.run(); 296 } 297 } 298 299 @Override 300 public final boolean isRunning() { 301 return this.running; 302 } 303 304 305 protected List<HandlerMethodArgumentResolver> initArgumentResolvers() { 306 ConfigurableBeanFactory beanFactory = (getApplicationContext() instanceof ConfigurableApplicationContext ? 307 ((ConfigurableApplicationContext) getApplicationContext()).getBeanFactory() : null); 308 309 List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>(); 310 311 // Annotation-based argument resolution 312 resolvers.add(new HeaderMethodArgumentResolver(this.conversionService, beanFactory)); 313 resolvers.add(new HeadersMethodArgumentResolver()); 314 resolvers.add(new DestinationVariableMethodArgumentResolver(this.conversionService)); 315 316 // Type-based argument resolution 317 resolvers.add(new PrincipalMethodArgumentResolver()); 318 resolvers.add(new MessageMethodArgumentResolver(this.messageConverter)); 319 320 resolvers.addAll(getCustomArgumentResolvers()); 321 resolvers.add(new PayloadArgumentResolver(this.messageConverter, this.validator)); 322 323 return resolvers; 324 } 325 326 @Override 327 protected List<? extends HandlerMethodReturnValueHandler> initReturnValueHandlers() { 328 List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>(); 329 330 // Single-purpose return value types 331 handlers.add(new ListenableFutureReturnValueHandler()); 332 if (completableFuturePresent) { 333 handlers.add(new CompletableFutureReturnValueHandler()); 334 } 335 336 // Annotation-based return value types 337 SendToMethodReturnValueHandler sendToHandler = 338 new SendToMethodReturnValueHandler(this.brokerTemplate, true); 339 sendToHandler.setHeaderInitializer(this.headerInitializer); 340 handlers.add(sendToHandler); 341 342 SubscriptionMethodReturnValueHandler subscriptionHandler = 343 new SubscriptionMethodReturnValueHandler(this.clientMessagingTemplate); 344 subscriptionHandler.setHeaderInitializer(this.headerInitializer); 345 handlers.add(subscriptionHandler); 346 347 // custom return value types 348 handlers.addAll(getCustomReturnValueHandlers()); 349 350 // catch-all 351 sendToHandler = new SendToMethodReturnValueHandler(this.brokerTemplate, false); 352 sendToHandler.setHeaderInitializer(this.headerInitializer); 353 handlers.add(sendToHandler); 354 355 return handlers; 356 } 357 358 359 @Override 360 protected boolean isHandler(Class<?> beanType) { 361 return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class); 362 } 363 364 @Override 365 protected SimpMessageMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { 366 MessageMapping messageAnn = AnnotatedElementUtils.findMergedAnnotation(method, MessageMapping.class); 367 if (messageAnn != null) { 368 MessageMapping typeAnn = AnnotatedElementUtils.findMergedAnnotation(handlerType, MessageMapping.class); 369 // Only actually register it if there are destinations specified; 370 // otherwise @MessageMapping is just being used as a (meta-annotation) marker. 371 if (messageAnn.value().length > 0 || (typeAnn != null && typeAnn.value().length > 0)) { 372 SimpMessageMappingInfo result = createMessageMappingCondition(messageAnn.value()); 373 if (typeAnn != null) { 374 result = createMessageMappingCondition(typeAnn.value()).combine(result); 375 } 376 return result; 377 } 378 } 379 380 SubscribeMapping subscribeAnn = AnnotatedElementUtils.findMergedAnnotation(method, SubscribeMapping.class); 381 if (subscribeAnn != null) { 382 MessageMapping typeAnn = AnnotatedElementUtils.findMergedAnnotation(handlerType, MessageMapping.class); 383 // Only actually register it if there are destinations specified; 384 // otherwise @SubscribeMapping is just being used as a (meta-annotation) marker. 385 if (subscribeAnn.value().length > 0 || (typeAnn != null && typeAnn.value().length > 0)) { 386 SimpMessageMappingInfo result = createSubscribeMappingCondition(subscribeAnn.value()); 387 if (typeAnn != null) { 388 result = createMessageMappingCondition(typeAnn.value()).combine(result); 389 } 390 return result; 391 } 392 } 393 394 return null; 395 } 396 397 private SimpMessageMappingInfo createMessageMappingCondition(String[] destinations) { 398 String[] resolvedDestinations = resolveEmbeddedValuesInDestinations(destinations); 399 return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.MESSAGE, 400 new DestinationPatternsMessageCondition(resolvedDestinations, this.pathMatcher)); 401 } 402 403 private SimpMessageMappingInfo createSubscribeMappingCondition(String[] destinations) { 404 String[] resolvedDestinations = resolveEmbeddedValuesInDestinations(destinations); 405 return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.SUBSCRIBE, 406 new DestinationPatternsMessageCondition(resolvedDestinations, this.pathMatcher)); 407 } 408 409 /** 410 * Resolve placeholder values in the given array of destinations. 411 * @return a new array with updated destinations 412 * @since 4.2 413 */ 414 protected String[] resolveEmbeddedValuesInDestinations(String[] destinations) { 415 if (this.valueResolver == null) { 416 return destinations; 417 } 418 String[] result = new String[destinations.length]; 419 for (int i = 0; i < destinations.length; i++) { 420 result[i] = this.valueResolver.resolveStringValue(destinations[i]); 421 } 422 return result; 423 } 424 425 @Override 426 protected Set<String> getDirectLookupDestinations(SimpMessageMappingInfo mapping) { 427 Set<String> result = new LinkedHashSet<String>(); 428 for (String pattern : mapping.getDestinationConditions().getPatterns()) { 429 if (!this.pathMatcher.isPattern(pattern)) { 430 result.add(pattern); 431 } 432 } 433 return result; 434 } 435 436 @Override 437 protected String getDestination(Message<?> message) { 438 return SimpMessageHeaderAccessor.getDestination(message.getHeaders()); 439 } 440 441 @Override 442 protected String getLookupDestination(String destination) { 443 if (destination == null) { 444 return null; 445 } 446 if (CollectionUtils.isEmpty(getDestinationPrefixes())) { 447 return destination; 448 } 449 for (String prefix : getDestinationPrefixes()) { 450 if (destination.startsWith(prefix)) { 451 if (this.slashPathSeparator) { 452 return destination.substring(prefix.length() - 1); 453 } 454 else { 455 return destination.substring(prefix.length()); 456 } 457 } 458 } 459 return null; 460 } 461 462 @Override 463 protected SimpMessageMappingInfo getMatchingMapping(SimpMessageMappingInfo mapping, Message<?> message) { 464 return mapping.getMatchingCondition(message); 465 466 } 467 468 @Override 469 protected Comparator<SimpMessageMappingInfo> getMappingComparator(final Message<?> message) { 470 return new Comparator<SimpMessageMappingInfo>() { 471 @Override 472 public int compare(SimpMessageMappingInfo info1, SimpMessageMappingInfo info2) { 473 return info1.compareTo(info2, message); 474 } 475 }; 476 } 477 478 @Override 479 protected void handleMatch(SimpMessageMappingInfo mapping, HandlerMethod handlerMethod, 480 String lookupDestination, Message<?> message) { 481 482 Set<String> patterns = mapping.getDestinationConditions().getPatterns(); 483 if (!CollectionUtils.isEmpty(patterns)) { 484 String pattern = patterns.iterator().next(); 485 Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(pattern, lookupDestination); 486 if (!CollectionUtils.isEmpty(vars)) { 487 MessageHeaderAccessor mha = MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class); 488 Assert.state(mha != null && mha.isMutable(), "Mutable MessageHeaderAccessor required"); 489 mha.setHeader(DestinationVariableMethodArgumentResolver.DESTINATION_TEMPLATE_VARIABLES_HEADER, vars); 490 } 491 } 492 493 try { 494 SimpAttributesContextHolder.setAttributesFromMessage(message); 495 super.handleMatch(mapping, handlerMethod, lookupDestination, message); 496 } 497 finally { 498 SimpAttributesContextHolder.resetAttributes(); 499 } 500 } 501 502 @Override 503 protected AbstractExceptionHandlerMethodResolver createExceptionHandlerMethodResolverFor(Class<?> beanType) { 504 return new AnnotationExceptionHandlerMethodResolver(beanType); 505 } 506 507}