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

org.springframework.messaging.simp.config.AbstractMessageBrokerConfiguration 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.config;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.lang.Nullable;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.converter.ByteArrayMessageConverter;
import org.springframework.messaging.converter.CompositeMessageConverter;
import org.springframework.messaging.converter.DefaultContentTypeResolver;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.converter.StringMessageConverter;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
import org.springframework.messaging.simp.SimpLogging;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
import org.springframework.messaging.simp.broker.AbstractBrokerMessageHandler;
import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;
import org.springframework.messaging.simp.user.DefaultUserDestinationResolver;
import org.springframework.messaging.simp.user.MultiServerUserRegistry;
import org.springframework.messaging.simp.user.SimpUserRegistry;
import org.springframework.messaging.simp.user.UserDestinationMessageHandler;
import org.springframework.messaging.simp.user.UserDestinationResolver;
import org.springframework.messaging.simp.user.UserRegistryMessageHandler;
import org.springframework.messaging.support.AbstractSubscribableChannel;
import org.springframework.messaging.support.ExecutorSubscribableChannel;
import org.springframework.messaging.support.ImmutableMessageChannelInterceptor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.PathMatcher;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

/**
 * Provides essential configuration for handling messages with simple messaging
 * protocols such as STOMP.
 *
 * 

{@link #clientInboundChannel()} and {@link #clientOutboundChannel()} deliver * messages to and from remote clients to several message handlers such as *

    *
  • {@link #simpAnnotationMethodMessageHandler()}
  • *
  • {@link #simpleBrokerMessageHandler()}
  • *
  • {@link #stompBrokerRelayMessageHandler()}
  • *
  • {@link #userDestinationMessageHandler()}
  • *
* while {@link #brokerChannel()} delivers messages from within the application to the * the respective message handlers. {@link #brokerMessagingTemplate()} can be injected * into any application component to send messages. * *

Subclasses are responsible for the part of the configuration that feed messages * to and from the client inbound/outbound channels (e.g. STOMP over WebSocket). * * @author Rossen Stoyanchev * @author Brian Clozel * @since 4.0 */ public abstract class AbstractMessageBrokerConfiguration implements ApplicationContextAware { private static final String MVC_VALIDATOR_NAME = "mvcValidator"; private static final boolean jackson2Present = ClassUtils.isPresent( "com.fasterxml.jackson.databind.ObjectMapper", AbstractMessageBrokerConfiguration.class.getClassLoader()); @Nullable private ApplicationContext applicationContext; @Nullable private ChannelRegistration clientInboundChannelRegistration; @Nullable private ChannelRegistration clientOutboundChannelRegistration; @Nullable private MessageBrokerRegistry brokerRegistry; /** * Protected constructor. */ protected AbstractMessageBrokerConfiguration() { } @Override public void setApplicationContext(@Nullable ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @Nullable public ApplicationContext getApplicationContext() { return this.applicationContext; } @Bean public AbstractSubscribableChannel clientInboundChannel() { ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel(clientInboundChannelExecutor()); channel.setLogger(SimpLogging.forLog(channel.getLogger())); ChannelRegistration reg = getClientInboundChannelRegistration(); if (reg.hasInterceptors()) { channel.setInterceptors(reg.getInterceptors()); } return channel; } @Bean public ThreadPoolTaskExecutor clientInboundChannelExecutor() { TaskExecutorRegistration reg = getClientInboundChannelRegistration().taskExecutor(); ThreadPoolTaskExecutor executor = reg.getTaskExecutor(); executor.setThreadNamePrefix("clientInboundChannel-"); return executor; } protected final ChannelRegistration getClientInboundChannelRegistration() { if (this.clientInboundChannelRegistration == null) { ChannelRegistration registration = new ChannelRegistration(); configureClientInboundChannel(registration); registration.interceptors(new ImmutableMessageChannelInterceptor()); this.clientInboundChannelRegistration = registration; } return this.clientInboundChannelRegistration; } /** * A hook for subclasses to customize the message channel for inbound messages * from WebSocket clients. */ protected void configureClientInboundChannel(ChannelRegistration registration) { } @Bean public AbstractSubscribableChannel clientOutboundChannel() { ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel(clientOutboundChannelExecutor()); channel.setLogger(SimpLogging.forLog(channel.getLogger())); ChannelRegistration reg = getClientOutboundChannelRegistration(); if (reg.hasInterceptors()) { channel.setInterceptors(reg.getInterceptors()); } return channel; } @Bean public ThreadPoolTaskExecutor clientOutboundChannelExecutor() { TaskExecutorRegistration reg = getClientOutboundChannelRegistration().taskExecutor(); ThreadPoolTaskExecutor executor = reg.getTaskExecutor(); executor.setThreadNamePrefix("clientOutboundChannel-"); return executor; } protected final ChannelRegistration getClientOutboundChannelRegistration() { if (this.clientOutboundChannelRegistration == null) { ChannelRegistration registration = new ChannelRegistration(); configureClientOutboundChannel(registration); registration.interceptors(new ImmutableMessageChannelInterceptor()); this.clientOutboundChannelRegistration = registration; } return this.clientOutboundChannelRegistration; } /** * A hook for subclasses to customize the message channel for messages from * the application or message broker to WebSocket clients. */ protected void configureClientOutboundChannel(ChannelRegistration registration) { } @Bean public AbstractSubscribableChannel brokerChannel() { ChannelRegistration reg = getBrokerRegistry().getBrokerChannelRegistration(); ExecutorSubscribableChannel channel = (reg.hasTaskExecutor() ? new ExecutorSubscribableChannel(brokerChannelExecutor()) : new ExecutorSubscribableChannel()); reg.interceptors(new ImmutableMessageChannelInterceptor()); channel.setLogger(SimpLogging.forLog(channel.getLogger())); channel.setInterceptors(reg.getInterceptors()); return channel; } @Bean public ThreadPoolTaskExecutor brokerChannelExecutor() { ChannelRegistration reg = getBrokerRegistry().getBrokerChannelRegistration(); ThreadPoolTaskExecutor executor; if (reg.hasTaskExecutor()) { executor = reg.taskExecutor().getTaskExecutor(); } else { // Should never be used executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(0); executor.setMaxPoolSize(1); executor.setQueueCapacity(0); } executor.setThreadNamePrefix("brokerChannel-"); return executor; } /** * An accessor for the {@link MessageBrokerRegistry} that ensures its one-time creation * and initialization through {@link #configureMessageBroker(MessageBrokerRegistry)}. */ protected final MessageBrokerRegistry getBrokerRegistry() { if (this.brokerRegistry == null) { MessageBrokerRegistry registry = new MessageBrokerRegistry(clientInboundChannel(), clientOutboundChannel()); configureMessageBroker(registry); this.brokerRegistry = registry; } return this.brokerRegistry; } /** * A hook for subclasses to customize message broker configuration through the * provided {@link MessageBrokerRegistry} instance. */ protected void configureMessageBroker(MessageBrokerRegistry registry) { } /** * Provide access to the configured PatchMatcher for access from other * configuration classes. */ @Nullable public final PathMatcher getPathMatcher() { return getBrokerRegistry().getPathMatcher(); } @Bean public SimpAnnotationMethodMessageHandler simpAnnotationMethodMessageHandler() { SimpAnnotationMethodMessageHandler handler = createAnnotationMethodMessageHandler(); handler.setDestinationPrefixes(getBrokerRegistry().getApplicationDestinationPrefixes()); handler.setMessageConverter(brokerMessageConverter()); handler.setValidator(simpValidator()); List argumentResolvers = new ArrayList<>(); addArgumentResolvers(argumentResolvers); handler.setCustomArgumentResolvers(argumentResolvers); List returnValueHandlers = new ArrayList<>(); addReturnValueHandlers(returnValueHandlers); handler.setCustomReturnValueHandlers(returnValueHandlers); PathMatcher pathMatcher = getBrokerRegistry().getPathMatcher(); if (pathMatcher != null) { handler.setPathMatcher(pathMatcher); } return handler; } /** * Protected method for plugging in a custom subclass of * {@link org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler * SimpAnnotationMethodMessageHandler}. * @since 4.2 */ protected SimpAnnotationMethodMessageHandler createAnnotationMethodMessageHandler() { return new SimpAnnotationMethodMessageHandler(clientInboundChannel(), clientOutboundChannel(), brokerMessagingTemplate()); } protected void addArgumentResolvers(List argumentResolvers) { } protected void addReturnValueHandlers(List returnValueHandlers) { } @Bean @Nullable public AbstractBrokerMessageHandler simpleBrokerMessageHandler() { SimpleBrokerMessageHandler handler = getBrokerRegistry().getSimpleBroker(brokerChannel()); if (handler == null) { return null; } updateUserDestinationResolver(handler); return handler; } private void updateUserDestinationResolver(AbstractBrokerMessageHandler handler) { Collection prefixes = handler.getDestinationPrefixes(); if (!prefixes.isEmpty() && !prefixes.iterator().next().startsWith("/")) { ((DefaultUserDestinationResolver) userDestinationResolver()).setRemoveLeadingSlash(true); } } @Bean @Nullable public AbstractBrokerMessageHandler stompBrokerRelayMessageHandler() { StompBrokerRelayMessageHandler handler = getBrokerRegistry().getStompBrokerRelay(brokerChannel()); if (handler == null) { return null; } Map subscriptions = new HashMap<>(4); String destination = getBrokerRegistry().getUserDestinationBroadcast(); if (destination != null) { subscriptions.put(destination, userDestinationMessageHandler()); } destination = getBrokerRegistry().getUserRegistryBroadcast(); if (destination != null) { subscriptions.put(destination, userRegistryMessageHandler()); } handler.setSystemSubscriptions(subscriptions); updateUserDestinationResolver(handler); return handler; } @Bean public UserDestinationMessageHandler userDestinationMessageHandler() { UserDestinationMessageHandler handler = new UserDestinationMessageHandler(clientInboundChannel(), brokerChannel(), userDestinationResolver()); String destination = getBrokerRegistry().getUserDestinationBroadcast(); if (destination != null) { handler.setBroadcastDestination(destination); } return handler; } @Bean @Nullable public MessageHandler userRegistryMessageHandler() { if (getBrokerRegistry().getUserRegistryBroadcast() == null) { return null; } SimpUserRegistry userRegistry = userRegistry(); Assert.isInstanceOf(MultiServerUserRegistry.class, userRegistry, "MultiServerUserRegistry required"); return new UserRegistryMessageHandler((MultiServerUserRegistry) userRegistry, brokerMessagingTemplate(), getBrokerRegistry().getUserRegistryBroadcast(), messageBrokerTaskScheduler()); } // Expose alias for 4.1 compatibility @Bean(name = {"messageBrokerTaskScheduler", "messageBrokerSockJsTaskScheduler"}) public ThreadPoolTaskScheduler messageBrokerTaskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setThreadNamePrefix("MessageBroker-"); scheduler.setPoolSize(Runtime.getRuntime().availableProcessors()); scheduler.setRemoveOnCancelPolicy(true); return scheduler; } @Bean public SimpMessagingTemplate brokerMessagingTemplate() { SimpMessagingTemplate template = new SimpMessagingTemplate(brokerChannel()); String prefix = getBrokerRegistry().getUserDestinationPrefix(); if (prefix != null) { template.setUserDestinationPrefix(prefix); } template.setMessageConverter(brokerMessageConverter()); return template; } @Bean public CompositeMessageConverter brokerMessageConverter() { List converters = new ArrayList<>(); boolean registerDefaults = configureMessageConverters(converters); if (registerDefaults) { converters.add(new StringMessageConverter()); converters.add(new ByteArrayMessageConverter()); if (jackson2Present) { converters.add(createJacksonConverter()); } } return new CompositeMessageConverter(converters); } protected MappingJackson2MessageConverter createJacksonConverter() { DefaultContentTypeResolver resolver = new DefaultContentTypeResolver(); resolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON); MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); converter.setContentTypeResolver(resolver); return converter; } /** * Override this method to add custom message converters. * @param messageConverters the list to add converters to, initially empty * @return {@code true} if default message converters should be added to list, * {@code false} if no more converters should be added. */ protected boolean configureMessageConverters(List messageConverters) { return true; } @Bean public UserDestinationResolver userDestinationResolver() { DefaultUserDestinationResolver resolver = new DefaultUserDestinationResolver(userRegistry()); String prefix = getBrokerRegistry().getUserDestinationPrefix(); if (prefix != null) { resolver.setUserDestinationPrefix(prefix); } return resolver; } @Bean @SuppressWarnings("deprecation") public SimpUserRegistry userRegistry() { SimpUserRegistry registry = createLocalUserRegistry(); if (registry == null) { registry = createLocalUserRegistry(getBrokerRegistry().getUserRegistryOrder()); } boolean broadcast = getBrokerRegistry().getUserRegistryBroadcast() != null; return (broadcast ? new MultiServerUserRegistry(registry) : registry); } /** * Create the user registry that provides access to local users. * @deprecated as of 5.1 in favor of {@link #createLocalUserRegistry(Integer)} */ @Deprecated @Nullable protected SimpUserRegistry createLocalUserRegistry() { return null; } /** * Create the user registry that provides access to local users. * @param order the order to use as a {@link SmartApplicationListener}. * @since 5.1 */ protected abstract SimpUserRegistry createLocalUserRegistry(@Nullable Integer order); /** * Return a {@link org.springframework.validation.Validator * org.springframework.validation.Validators} instance for validating * {@code @Payload} method arguments. *

In order, this method tries to get a Validator instance: *

    *
  • delegating to getValidator() first
  • *
  • if none returned, getting an existing instance with its well-known name "mvcValidator", * created by an MVC configuration
  • *
  • if none returned, checking the classpath for the presence of a JSR-303 implementation * before creating a {@code OptionalValidatorFactoryBean}
  • *
  • returning a no-op Validator instance
  • *
*/ protected Validator simpValidator() { Validator validator = getValidator(); if (validator == null) { if (this.applicationContext != null && this.applicationContext.containsBean(MVC_VALIDATOR_NAME)) { validator = this.applicationContext.getBean(MVC_VALIDATOR_NAME, Validator.class); } else if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) { Class clazz; try { String className = "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean"; clazz = ClassUtils.forName(className, AbstractMessageBrokerConfiguration.class.getClassLoader()); } catch (Throwable ex) { throw new BeanInitializationException("Could not find default validator class", ex); } validator = (Validator) BeanUtils.instantiateClass(clazz); } else { validator = new Validator() { @Override public boolean supports(Class clazz) { return false; } @Override public void validate(@Nullable Object target, Errors errors) { } }; } } return validator; } /** * Override this method to provide a custom {@link Validator}. * @since 4.0.1 */ @Nullable public Validator getValidator() { return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy