All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler Maven / Gradle / Ivy

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.messaging.simp.annotation.support;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.converter.ByteArrayMessageConverter;
import org.springframework.messaging.converter.CompositeMessageConverter;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.converter.StringMessageConverter;
import org.springframework.messaging.core.AbstractMessageSendingTemplate;
import org.springframework.messaging.handler.DestinationPatternsMessageCondition;
import org.springframework.messaging.handler.HandlerMethod;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.support.AnnotationExceptionHandlerMethodResolver;
import org.springframework.messaging.handler.annotation.support.DestinationVariableMethodArgumentResolver;
import org.springframework.messaging.handler.annotation.support.HeaderMethodArgumentResolver;
import org.springframework.messaging.handler.annotation.support.HeadersMethodArgumentResolver;
import org.springframework.messaging.handler.annotation.support.MessageMethodArgumentResolver;
import org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver;
import org.springframework.messaging.handler.invocation.AbstractExceptionHandlerMethodResolver;
import org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler;
import org.springframework.messaging.handler.invocation.CompletableFutureReturnValueHandler;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandlerComposite;
import org.springframework.messaging.handler.invocation.ListenableFutureReturnValueHandler;
import org.springframework.messaging.handler.invocation.ReactiveReturnValueHandler;
import org.springframework.messaging.simp.SimpAttributesContextHolder;
import org.springframework.messaging.simp.SimpLogging;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageMappingInfo;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.messaging.simp.SimpMessageTypeMessageCondition;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.messaging.support.MessageHeaderInitializer;
import org.springframework.stereotype.Controller;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringValueResolver;
import org.springframework.validation.Validator;

/**
 * A handler for messages delegating to {@link MessageMapping @MessageMapping}
 * and {@link SubscribeMapping @SubscribeMapping} annotated methods.
 *
 * 

Supports Ant-style path patterns with template variables. * * @author Rossen Stoyanchev * @author Brian Clozel * @author Juergen Hoeller * @since 4.0 */ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHandler implements EmbeddedValueResolverAware, SmartLifecycle { private final SubscribableChannel clientInboundChannel; private final SimpMessageSendingOperations clientMessagingTemplate; private final SimpMessageSendingOperations brokerTemplate; private MessageConverter messageConverter; private ConversionService conversionService = new DefaultFormattingConversionService(); private PathMatcher pathMatcher = new AntPathMatcher(); private boolean slashPathSeparator = true; @Nullable private Validator validator; @Nullable private StringValueResolver valueResolver; @Nullable private MessageHeaderInitializer headerInitializer; private volatile boolean running = false; private final Object lifecycleMonitor = new Object(); /** * Create an instance of SimpAnnotationMethodMessageHandler with the given * message channels and broker messaging template. * @param clientInboundChannel the channel for receiving messages from clients (e.g. WebSocket clients) * @param clientOutboundChannel the channel for messages to clients (e.g. WebSocket clients) * @param brokerTemplate a messaging template to send application messages to the broker */ public SimpAnnotationMethodMessageHandler(SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel, SimpMessageSendingOperations brokerTemplate) { Assert.notNull(clientInboundChannel, "clientInboundChannel must not be null"); Assert.notNull(clientOutboundChannel, "clientOutboundChannel must not be null"); Assert.notNull(brokerTemplate, "brokerTemplate must not be null"); this.clientInboundChannel = clientInboundChannel; this.clientMessagingTemplate = new SimpMessagingTemplate(clientOutboundChannel); this.brokerTemplate = brokerTemplate; Collection converters = new ArrayList<>(); converters.add(new StringMessageConverter()); converters.add(new ByteArrayMessageConverter()); this.messageConverter = new CompositeMessageConverter(converters); } /** * {@inheritDoc} *

Destination prefixes are expected to be slash-separated Strings and * therefore a slash is automatically appended where missing to ensure a * proper prefix-based match (i.e. matching complete segments). *

Note however that the remaining portion of a destination after the * prefix may use a different separator (e.g. commonly "." in messaging) * depending on the configured {@code PathMatcher}. */ @Override public void setDestinationPrefixes(@Nullable Collection prefixes) { super.setDestinationPrefixes(appendSlashes(prefixes)); } @Nullable private static Collection appendSlashes(@Nullable Collection prefixes) { if (CollectionUtils.isEmpty(prefixes)) { return prefixes; } Collection result = new ArrayList<>(prefixes.size()); for (String prefix : prefixes) { if (!prefix.endsWith("/")) { prefix = prefix + "/"; } result.add(prefix); } return result; } /** * Configure a {@link MessageConverter} to use to convert the payload of a message from * its serialized form with a specific MIME type to an Object matching the target method * parameter. The converter is also used when sending a message to the message broker. * @see CompositeMessageConverter */ public void setMessageConverter(MessageConverter converter) { this.messageConverter = converter; ((AbstractMessageSendingTemplate) this.clientMessagingTemplate).setMessageConverter(converter); } /** * Return the configured {@link MessageConverter}. */ public MessageConverter getMessageConverter() { return this.messageConverter; } /** * Configure a {@link ConversionService} to use when resolving method arguments, * for example message header values. *

By default, {@link DefaultFormattingConversionService} is used. */ public void setConversionService(ConversionService conversionService) { this.conversionService = conversionService; } /** * Return the configured {@link ConversionService}. */ public ConversionService getConversionService() { return this.conversionService; } /** * Set the PathMatcher implementation to use for matching destinations * against configured destination patterns. *

By default, {@link AntPathMatcher} is used. */ public void setPathMatcher(PathMatcher pathMatcher) { Assert.notNull(pathMatcher, "PathMatcher must not be null"); this.pathMatcher = pathMatcher; this.slashPathSeparator = this.pathMatcher.combine("a", "a").equals("a/a"); } /** * Return the PathMatcher implementation to use for matching destinations. */ public PathMatcher getPathMatcher() { return this.pathMatcher; } /** * Return the configured Validator instance. */ @Nullable public Validator getValidator() { return this.validator; } /** * Set the Validator instance used for validating {@code @Payload} arguments. * @see org.springframework.validation.annotation.Validated * @see PayloadArgumentResolver */ public void setValidator(@Nullable Validator validator) { this.validator = validator; } @Override public void setEmbeddedValueResolver(StringValueResolver resolver) { this.valueResolver = resolver; } /** * Configure a {@link MessageHeaderInitializer} to pass on to * {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers} * that send messages from controller return values. *

By default, this property is not set. */ public void setHeaderInitializer(@Nullable MessageHeaderInitializer headerInitializer) { this.headerInitializer = headerInitializer; } /** * Return the configured header initializer. */ @Nullable public MessageHeaderInitializer getHeaderInitializer() { return this.headerInitializer; } @Override public final void start() { synchronized (this.lifecycleMonitor) { this.clientInboundChannel.subscribe(this); this.running = true; } } @Override public final void stop() { synchronized (this.lifecycleMonitor) { this.running = false; this.clientInboundChannel.unsubscribe(this); } } @Override public final void stop(Runnable callback) { synchronized (this.lifecycleMonitor) { stop(); callback.run(); } } @Override public final boolean isRunning() { return this.running; } protected List initArgumentResolvers() { ApplicationContext context = getApplicationContext(); ConfigurableBeanFactory beanFactory = (context instanceof ConfigurableApplicationContext ? ((ConfigurableApplicationContext) context).getBeanFactory() : null); List resolvers = new ArrayList<>(); // Annotation-based argument resolution resolvers.add(new HeaderMethodArgumentResolver(this.conversionService, beanFactory)); resolvers.add(new HeadersMethodArgumentResolver()); resolvers.add(new DestinationVariableMethodArgumentResolver(this.conversionService)); // Type-based argument resolution resolvers.add(new PrincipalMethodArgumentResolver()); resolvers.add(new MessageMethodArgumentResolver(this.messageConverter)); resolvers.addAll(getCustomArgumentResolvers()); resolvers.add(new PayloadArgumentResolver(this.messageConverter, this.validator)); return resolvers; } @Override protected List initReturnValueHandlers() { List handlers = new ArrayList<>(); // Single-purpose return value types handlers.add(new ListenableFutureReturnValueHandler()); handlers.add(new CompletableFutureReturnValueHandler()); handlers.add(new ReactiveReturnValueHandler()); // Annotation-based return value types SendToMethodReturnValueHandler sendToHandler = new SendToMethodReturnValueHandler(this.brokerTemplate, true); sendToHandler.setHeaderInitializer(this.headerInitializer); handlers.add(sendToHandler); SubscriptionMethodReturnValueHandler subscriptionHandler = new SubscriptionMethodReturnValueHandler(this.clientMessagingTemplate); subscriptionHandler.setHeaderInitializer(this.headerInitializer); handlers.add(subscriptionHandler); // Custom return value types handlers.addAll(getCustomReturnValueHandlers()); // Catch-all sendToHandler = new SendToMethodReturnValueHandler(this.brokerTemplate, false); sendToHandler.setHeaderInitializer(this.headerInitializer); handlers.add(sendToHandler); return handlers; } @Override protected Log getReturnValueHandlerLogger() { return SimpLogging.forLog(HandlerMethodReturnValueHandlerComposite.defaultLogger); } @Override protected Log getHandlerMethodLogger() { return SimpLogging.forLog(HandlerMethod.defaultLogger); } @Override protected boolean isHandler(Class beanType) { return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class); } @Override @Nullable protected SimpMessageMappingInfo getMappingForMethod(Method method, Class handlerType) { MessageMapping messageAnn = AnnotatedElementUtils.findMergedAnnotation(method, MessageMapping.class); if (messageAnn != null) { MessageMapping typeAnn = AnnotatedElementUtils.findMergedAnnotation(handlerType, MessageMapping.class); // Only actually register it if there are destinations specified; // otherwise @MessageMapping is just being used as a (meta-annotation) marker. if (messageAnn.value().length > 0 || (typeAnn != null && typeAnn.value().length > 0)) { SimpMessageMappingInfo result = createMessageMappingCondition(messageAnn.value()); if (typeAnn != null) { result = createMessageMappingCondition(typeAnn.value()).combine(result); } return result; } } SubscribeMapping subscribeAnn = AnnotatedElementUtils.findMergedAnnotation(method, SubscribeMapping.class); if (subscribeAnn != null) { MessageMapping typeAnn = AnnotatedElementUtils.findMergedAnnotation(handlerType, MessageMapping.class); // Only actually register it if there are destinations specified; // otherwise @SubscribeMapping is just being used as a (meta-annotation) marker. if (subscribeAnn.value().length > 0 || (typeAnn != null && typeAnn.value().length > 0)) { SimpMessageMappingInfo result = createSubscribeMappingCondition(subscribeAnn.value()); if (typeAnn != null) { result = createMessageMappingCondition(typeAnn.value()).combine(result); } return result; } } return null; } private SimpMessageMappingInfo createMessageMappingCondition(String[] destinations) { String[] resolvedDestinations = resolveEmbeddedValuesInDestinations(destinations); return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.MESSAGE, new DestinationPatternsMessageCondition(resolvedDestinations, this.pathMatcher)); } private SimpMessageMappingInfo createSubscribeMappingCondition(String[] destinations) { String[] resolvedDestinations = resolveEmbeddedValuesInDestinations(destinations); return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.SUBSCRIBE, new DestinationPatternsMessageCondition(resolvedDestinations, this.pathMatcher)); } /** * Resolve placeholder values in the given array of destinations. * @return a new array with updated destinations * @since 4.2 */ protected String[] resolveEmbeddedValuesInDestinations(String[] destinations) { if (this.valueResolver == null) { return destinations; } String[] result = new String[destinations.length]; for (int i = 0; i < destinations.length; i++) { result[i] = this.valueResolver.resolveStringValue(destinations[i]); } return result; } @Override protected Set getDirectLookupDestinations(SimpMessageMappingInfo mapping) { Set result = new LinkedHashSet<>(); for (String pattern : mapping.getDestinationConditions().getPatterns()) { if (!this.pathMatcher.isPattern(pattern)) { result.add(pattern); } } return result; } @Override @Nullable protected String getDestination(Message message) { return SimpMessageHeaderAccessor.getDestination(message.getHeaders()); } @Override protected String getLookupDestination(@Nullable String destination) { if (destination == null) { return null; } if (CollectionUtils.isEmpty(getDestinationPrefixes())) { return destination; } for (String prefix : getDestinationPrefixes()) { if (destination.startsWith(prefix)) { if (this.slashPathSeparator) { return destination.substring(prefix.length() - 1); } else { return destination.substring(prefix.length()); } } } return null; } @Override @Nullable protected SimpMessageMappingInfo getMatchingMapping(SimpMessageMappingInfo mapping, Message message) { return mapping.getMatchingCondition(message); } @Override protected Comparator getMappingComparator(final Message message) { return (info1, info2) -> info1.compareTo(info2, message); } @Override protected void handleMatch(SimpMessageMappingInfo mapping, HandlerMethod handlerMethod, String lookupDestination, Message message) { Set patterns = mapping.getDestinationConditions().getPatterns(); if (!CollectionUtils.isEmpty(patterns)) { String pattern = patterns.iterator().next(); Map vars = getPathMatcher().extractUriTemplateVariables(pattern, lookupDestination); if (!CollectionUtils.isEmpty(vars)) { MessageHeaderAccessor mha = MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class); Assert.state(mha != null && mha.isMutable(), "Mutable MessageHeaderAccessor required"); mha.setHeader(DestinationVariableMethodArgumentResolver.DESTINATION_TEMPLATE_VARIABLES_HEADER, vars); } } try { SimpAttributesContextHolder.setAttributesFromMessage(message); super.handleMatch(mapping, handlerMethod, lookupDestination, message); } finally { SimpAttributesContextHolder.resetAttributes(); } } @Override protected AbstractExceptionHandlerMethodResolver createExceptionHandlerMethodResolverFor(Class beanType) { return new AnnotationExceptionHandlerMethodResolver(beanType); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy