/*
* Copyright 2002-2014 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.List;
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.messaging.Message;
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.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.user.DefaultUserDestinationResolver;
import org.springframework.messaging.simp.user.DefaultUserSessionRegistry;
import org.springframework.messaging.simp.user.UserDestinationMessageHandler;
import org.springframework.messaging.simp.user.UserDestinationResolver;
import org.springframework.messaging.simp.user.UserSessionRegistry;
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.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.
*
* <p>{@link #clientInboundChannel()} and {@link #clientOutboundChannel()} deliver
* messages to and from remote clients to several message handlers such as
* <ul>
* <li>{@link #simpAnnotationMethodMessageHandler()}</li>
* <li>{@link #simpleBrokerMessageHandler()}</li>
* <li>{@link #stompBrokerRelayMessageHandler()}</li>
* <li>{@link #userDestinationMessageHandler()}</li>
* </ul>
* 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.
*
* <p>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());
private ChannelRegistration clientInboundChannelRegistration;
private ChannelRegistration clientOutboundChannelRegistration;
private MessageBrokerRegistry brokerRegistry;
private ApplicationContext applicationContext;
/**
* Protected constructor.
*/
protected AbstractMessageBrokerConfiguration() {
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public ApplicationContext getApplicationContext() {
return this.applicationContext;
}
@Bean
public AbstractSubscribableChannel clientInboundChannel() {
ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel(clientInboundChannelExecutor());
ChannelRegistration reg = getClientInboundChannelRegistration();
channel.setInterceptors(reg.getInterceptors());
return channel;
}
@Bean
public ThreadPoolTaskExecutor clientInboundChannelExecutor() {
TaskExecutorRegistration reg = getClientInboundChannelRegistration().getOrCreateTaskExecRegistration();
ThreadPoolTaskExecutor executor = reg.getTaskExecutor();
executor.setThreadNamePrefix("clientInboundChannel-");
return executor;
}
protected final ChannelRegistration getClientInboundChannelRegistration() {
if (this.clientInboundChannelRegistration == null) {
ChannelRegistration registration = new ChannelRegistration();
configureClientInboundChannel(registration);
registration.setInterceptors(new ImmutableMessageChannelInterceptor());
this.clientInboundChannelRegistration = registration;
}
return this.clientInboundChannelRegistration;
}
/**
* A hook for sub-classes 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());
ChannelRegistration reg = getClientOutboundChannelRegistration();
channel.setInterceptors(reg.getInterceptors());
return channel;
}
@Bean
public ThreadPoolTaskExecutor clientOutboundChannelExecutor() {
TaskExecutorRegistration reg = getClientOutboundChannelRegistration().getOrCreateTaskExecRegistration();
ThreadPoolTaskExecutor executor = reg.getTaskExecutor();
executor.setThreadNamePrefix("clientOutboundChannel-");
return executor;
}
protected final ChannelRegistration getClientOutboundChannelRegistration() {
if (this.clientOutboundChannelRegistration == null) {
ChannelRegistration registration = new ChannelRegistration();
configureClientOutboundChannel(registration);
registration.setInterceptors(new ImmutableMessageChannelInterceptor());
this.clientOutboundChannelRegistration = registration;
}
return this.clientOutboundChannelRegistration;
}
/**
* A hook for sub-classes 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.setInterceptors(new ImmutableMessageChannelInterceptor());
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 sub-classes to customize message broker configuration through the
* provided {@link MessageBrokerRegistry} instance.
*/
protected void configureMessageBroker(MessageBrokerRegistry registry) {
}
@Bean
public SimpAnnotationMethodMessageHandler simpAnnotationMethodMessageHandler() {
SimpAnnotationMethodMessageHandler handler = new SimpAnnotationMethodMessageHandler(
clientInboundChannel(), clientOutboundChannel(), brokerMessagingTemplate());
handler.setDestinationPrefixes(getBrokerRegistry().getApplicationDestinationPrefixes());
handler.setMessageConverter(brokerMessageConverter());
handler.setValidator(simpValidator());
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>();
addArgumentResolvers(argumentResolvers);
handler.setCustomArgumentResolvers(argumentResolvers);
List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
addReturnValueHandlers(returnValueHandlers);
handler.setCustomReturnValueHandlers(returnValueHandlers);
PathMatcher pathMatcher = this.getBrokerRegistry().getPathMatcher();
if (pathMatcher != null) {
handler.setPathMatcher(pathMatcher);
}
return handler;
}
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
}
protected void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
}
@Bean
public AbstractBrokerMessageHandler simpleBrokerMessageHandler() {
SimpleBrokerMessageHandler handler = getBrokerRegistry().getSimpleBroker(brokerChannel());
return (handler != null ? handler : new NoOpBrokerMessageHandler());
}
@Bean
public AbstractBrokerMessageHandler stompBrokerRelayMessageHandler() {
AbstractBrokerMessageHandler handler = getBrokerRegistry().getStompBrokerRelay(brokerChannel());
return (handler != null ? handler : new NoOpBrokerMessageHandler());
}
@Bean
public UserDestinationMessageHandler userDestinationMessageHandler() {
return new UserDestinationMessageHandler(clientInboundChannel(), brokerChannel(), userDestinationResolver());
}
@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<MessageConverter> converters = new ArrayList<MessageConverter>();
boolean registerDefaults = configureMessageConverters(converters);
if (registerDefaults) {
converters.add(new StringMessageConverter());
converters.add(new ByteArrayMessageConverter());
if (jackson2Present) {
DefaultContentTypeResolver resolver = new DefaultContentTypeResolver();
resolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON);
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setContentTypeResolver(resolver);
converters.add(converter);
}
}
return new CompositeMessageConverter(converters);
}
/**
* 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<MessageConverter> messageConverters) {
return true;
}
@Bean
public UserDestinationResolver userDestinationResolver() {
DefaultUserDestinationResolver resolver = new DefaultUserDestinationResolver(userSessionRegistry());
String prefix = getBrokerRegistry().getUserDestinationPrefix();
if (prefix != null) {
resolver.setUserDestinationPrefix(prefix);
}
return resolver;
}
@Bean
public UserSessionRegistry userSessionRegistry() {
return new DefaultUserSessionRegistry();
}
/**
* Return a {@link org.springframework.validation.Validator}s instance for validating
* {@code @Payload} method arguments.
* <p>In order, this method tries to get a Validator instance:
* <ul>
* <li>delegating to getValidator() first</li>
* <li>if none returned, getting an existing instance with its well-known name "mvcValidator",
* created by an MVC configuration</li>
* <li>if none returned, checking the classpath for the presence of a JSR-303 implementation
* before creating a {@code OptionalValidatorFactoryBean}</li>
* <li>returning a no-op Validator instance</li>
* </ul>
*/
protected Validator simpValidator() {
Validator validator = getValidator();
if (validator == null) {
if (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.instantiate(clazz);
}
else {
validator = new Validator() {
@Override
public boolean supports(Class<?> clazz) {
return false;
}
@Override
public void validate(Object target, Errors errors) {
}
};
}
}
return validator;
}
/**
* Override this method to provide a custom {@link Validator}.
* @since 4.0.1
*/
public Validator getValidator() {
return null;
}
private class NoOpBrokerMessageHandler extends AbstractBrokerMessageHandler {
public NoOpBrokerMessageHandler() {
super(clientInboundChannel(), clientOutboundChannel(), brokerChannel());
}
@Override
public void start() {
}
@Override
public void stop() {
}
@Override
public void handleMessage(Message<?> message) {
}
@Override
protected void handleMessageInternal(Message<?> message) {
}
}
}