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

io.bitsensor.plugins.shaded.org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2002-2017 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 io.bitsensor.plugins.shaded.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 io.bitsensor.plugins.shaded.org.springframework.beans.factory.config.ConfigurableBeanFactory;
import io.bitsensor.plugins.shaded.org.springframework.context.ConfigurableApplicationContext;
import io.bitsensor.plugins.shaded.org.springframework.context.EmbeddedValueResolverAware;
import io.bitsensor.plugins.shaded.org.springframework.context.SmartLifecycle;
import io.bitsensor.plugins.shaded.org.springframework.core.annotation.AnnotatedElementUtils;
import io.bitsensor.plugins.shaded.org.springframework.core.convert.ConversionService;
import io.bitsensor.plugins.shaded.org.springframework.format.support.DefaultFormattingConversionService;
import io.bitsensor.plugins.shaded.org.springframework.messaging.Message;
import io.bitsensor.plugins.shaded.org.springframework.messaging.MessageChannel;
import io.bitsensor.plugins.shaded.org.springframework.messaging.SubscribableChannel;
import io.bitsensor.plugins.shaded.org.springframework.messaging.converter.ByteArrayMessageConverter;
import io.bitsensor.plugins.shaded.org.springframework.messaging.converter.CompositeMessageConverter;
import io.bitsensor.plugins.shaded.org.springframework.messaging.converter.MessageConverter;
import io.bitsensor.plugins.shaded.org.springframework.messaging.converter.StringMessageConverter;
import io.bitsensor.plugins.shaded.org.springframework.messaging.core.AbstractMessageSendingTemplate;
import io.bitsensor.plugins.shaded.org.springframework.messaging.handler.DestinationPatternsMessageCondition;
import io.bitsensor.plugins.shaded.org.springframework.messaging.handler.HandlerMethod;
import io.bitsensor.plugins.shaded.org.springframework.messaging.handler.annotation.MessageMapping;
import io.bitsensor.plugins.shaded.org.springframework.messaging.handler.annotation.support.AnnotationExceptionHandlerMethodResolver;
import io.bitsensor.plugins.shaded.org.springframework.messaging.handler.annotation.support.DestinationVariableMethodArgumentResolver;
import io.bitsensor.plugins.shaded.org.springframework.messaging.handler.annotation.support.HeaderMethodArgumentResolver;
import io.bitsensor.plugins.shaded.org.springframework.messaging.handler.annotation.support.HeadersMethodArgumentResolver;
import io.bitsensor.plugins.shaded.org.springframework.messaging.handler.annotation.support.MessageMethodArgumentResolver;
import io.bitsensor.plugins.shaded.org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver;
import io.bitsensor.plugins.shaded.org.springframework.messaging.handler.invocation.AbstractExceptionHandlerMethodResolver;
import io.bitsensor.plugins.shaded.org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler;
import io.bitsensor.plugins.shaded.org.springframework.messaging.handler.invocation.CompletableFutureReturnValueHandler;
import io.bitsensor.plugins.shaded.org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import io.bitsensor.plugins.shaded.org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
import io.bitsensor.plugins.shaded.org.springframework.messaging.handler.invocation.ListenableFutureReturnValueHandler;
import io.bitsensor.plugins.shaded.org.springframework.messaging.simp.SimpAttributesContextHolder;
import io.bitsensor.plugins.shaded.org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import io.bitsensor.plugins.shaded.org.springframework.messaging.simp.SimpMessageMappingInfo;
import io.bitsensor.plugins.shaded.org.springframework.messaging.simp.SimpMessageSendingOperations;
import io.bitsensor.plugins.shaded.org.springframework.messaging.simp.SimpMessageTypeMessageCondition;
import io.bitsensor.plugins.shaded.org.springframework.messaging.simp.SimpMessagingTemplate;
import io.bitsensor.plugins.shaded.org.springframework.messaging.simp.annotation.SubscribeMapping;
import io.bitsensor.plugins.shaded.org.springframework.messaging.support.MessageHeaderAccessor;
import io.bitsensor.plugins.shaded.org.springframework.messaging.support.MessageHeaderInitializer;
import io.bitsensor.plugins.shaded.org.springframework.stereotype.Controller;
import io.bitsensor.plugins.shaded.org.springframework.util.AntPathMatcher;
import io.bitsensor.plugins.shaded.org.springframework.util.Assert;
import io.bitsensor.plugins.shaded.org.springframework.util.ClassUtils;
import io.bitsensor.plugins.shaded.org.springframework.util.CollectionUtils;
import io.bitsensor.plugins.shaded.org.springframework.util.PathMatcher;
import io.bitsensor.plugins.shaded.org.springframework.util.StringValueResolver;
import io.bitsensor.plugins.shaded.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 static final boolean completableFuturePresent = ClassUtils.isPresent( "java.util.concurrent.CompletableFuture", SimpAnnotationMethodMessageHandler.class.getClassLoader()); 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; private Validator validator; private StringValueResolver valueResolver; private MessageHeaderInitializer headerInitializer; private final Object lifecycleMonitor = new Object(); private volatile boolean running = false; /** * 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(Collection prefixes) { super.setDestinationPrefixes(appendSlashes(prefixes)); } private static Collection appendSlashes(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; if (converter != null) { ((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. */ public Validator getValidator() { return this.validator; } /** * Set the Validator instance used for validating @Payload arguments * @see io.bitsensor.plugins.shaded.org.springframework.validation.annotation.Validated * @see PayloadArgumentResolver */ public void setValidator(Validator validator) { this.validator = validator; } @Override public void setEmbeddedValueResolver(StringValueResolver resolver) { this.valueResolver = resolver; } /** * Configure a {@link MessageHeaderInitializer} to pass on to * {@link io.bitsensor.plugins.shaded.org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler}s * that send messages from controller return values. *

By default, this property is not set. */ public void setHeaderInitializer(MessageHeaderInitializer headerInitializer) { this.headerInitializer = headerInitializer; } /** * Return the configured header initializer. */ public MessageHeaderInitializer getHeaderInitializer() { return this.headerInitializer; } @Override public boolean isAutoStartup() { return true; } @Override public int getPhase() { return Integer.MAX_VALUE; } @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() { synchronized (this.lifecycleMonitor) { return this.running; } } protected List initArgumentResolvers() { ConfigurableBeanFactory beanFactory = (getApplicationContext() instanceof ConfigurableApplicationContext ? ((ConfigurableApplicationContext) getApplicationContext()).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()); if (completableFuturePresent) { handlers.add(new CompletableFutureReturnValueHandler()); } // 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 boolean isHandler(Class beanType) { return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class); } @Override 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 protected String getDestination(Message message) { return SimpMessageHeaderAccessor.getDestination(message.getHeaders()); } @Override protected String getLookupDestination(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 protected SimpMessageMappingInfo getMatchingMapping(SimpMessageMappingInfo mapping, Message message) { return mapping.getMatchingCondition(message); } @Override protected Comparator getMappingComparator(final Message message) { return new Comparator() { @Override public int compare(SimpMessageMappingInfo info1, SimpMessageMappingInfo info2) { return 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