Add support for custom message converters

The Java and XML config for STOMP WebSocket applications now supports
configuring message converters.

Issue: SPR-11184
This commit is contained in:
Rossen Stoyanchev 2014-01-07 16:16:29 -05:00
parent 1fa4d9169f
commit 4342497305
14 changed files with 406 additions and 169 deletions

View File

@ -392,6 +392,7 @@ project("spring-messaging") {
testCompile("commons-dbcp:commons-dbcp:1.2.2")
testCompile("javax.inject:javax.inject-tck:1")
testCompile("javax.servlet:javax.servlet-api:3.1.0")
testCompile("javax.validation:validation-api:1.0.0.GA")
testCompile("log4j:log4j:1.2.17")
testCompile("org.apache.activemq:activemq-broker:5.8.0")
testCompile("org.apache.activemq:activemq-kahadb-store:5.8.0") {

View File

@ -26,12 +26,7 @@ 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.converter.*;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
import org.springframework.messaging.simp.broker.AbstractBrokerMessageHandler;
@ -127,7 +122,8 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
* A hook for sub-classes to customize the message channel for inbound messages
* from WebSocket clients.
*/
protected abstract void configureClientInboundChannel(ChannelRegistration registration);
protected void configureClientInboundChannel(ChannelRegistration registration) {
}
@Bean
@ -161,7 +157,8 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
* A hook for sub-classes to customize the message channel for messages from
* the application or message broker to WebSocket clients.
*/
protected abstract void configureClientOutboundChannel(ChannelRegistration registration);
protected void configureClientOutboundChannel(ChannelRegistration registration) {
}
@Bean
public AbstractSubscribableChannel brokerChannel() {
@ -204,8 +201,8 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
* A hook for sub-classes to customize message broker configuration through the
* provided {@link MessageBrokerRegistry} instance.
*/
protected abstract void configureMessageBroker(MessageBrokerRegistry registry);
protected void configureMessageBroker(MessageBrokerRegistry registry) {
}
@Bean
public SimpAnnotationMethodMessageHandler simpAnnotationMethodMessageHandler() {
@ -251,18 +248,27 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
@Bean
public CompositeMessageConverter brokerMessageConverter() {
DefaultContentTypeResolver contentTypeResolver = new DefaultContentTypeResolver();
List<MessageConverter> converters = new ArrayList<MessageConverter>();
if (configureMessageConverters(converters)) {
if (jackson2Present) {
converters.add(new MappingJackson2MessageConverter());
contentTypeResolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON);
}
converters.add(new StringMessageConverter());
converters.add(new ByteArrayMessageConverter());
}
return new CompositeMessageConverter(converters, getContentTypeResolver());
}
return new CompositeMessageConverter(converters, contentTypeResolver);
protected boolean configureMessageConverters(List<MessageConverter> messageConverters) {
return true;
}
protected ContentTypeResolver getContentTypeResolver() {
DefaultContentTypeResolver contentTypeResolver = new DefaultContentTypeResolver();
if (jackson2Present) {
contentTypeResolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON);
}
return contentTypeResolver;
}
@Bean
@ -280,14 +286,6 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
return new DefaultUserSessionRegistry();
}
/**
* Override this method to provide a custom {@link Validator}.
* @since 4.0.1
*/
public Validator getValidator() {
return null;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
@ -330,12 +328,29 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
validator = (Validator) BeanUtils.instantiate(clazz);
}
else {
validator = noopValidator;
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 static final AbstractBrokerMessageHandler noopBroker = new AbstractBrokerMessageHandler(null) {
@Override
@ -352,15 +367,4 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
};
private static final Validator noopValidator = new Validator() {
@Override
public boolean supports(Class<?> clazz) {
return false;
}
@Override
public void validate(Object target, Errors errors) {
}
};
}

View File

@ -17,19 +17,21 @@
package org.springframework.messaging.simp.config;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.converter.CompositeMessageConverter;
import org.springframework.messaging.converter.DefaultContentTypeResolver;
import org.springframework.messaging.converter.*;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageType;
@ -51,6 +53,7 @@ import org.springframework.stereotype.Controller;
import org.springframework.util.MimeTypeUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean;
import static org.junit.Assert.*;
@ -62,66 +65,61 @@ import static org.junit.Assert.*;
*/
public class MessageBrokerConfigurationTests {
private AnnotationConfigApplicationContext cxtSimpleBroker;
private AnnotationConfigApplicationContext simpleContext;
private AnnotationConfigApplicationContext cxtStompBroker;
private AnnotationConfigApplicationContext brokerRelayContext;
private AnnotationConfigApplicationContext cxtCustomizedChannelConfig;
private AnnotationConfigApplicationContext customChannelContext;
private AnnotationConfigApplicationContext cxtCustomizedValidator;
@Before
public void setupOnce() {
this.cxtSimpleBroker = new AnnotationConfigApplicationContext();
this.cxtSimpleBroker.register(TestMessageBrokerConfiguration.class);
this.cxtSimpleBroker.refresh();
this.simpleContext = new AnnotationConfigApplicationContext();
this.simpleContext.register(SimpleConfig.class);
this.simpleContext.refresh();
this.cxtStompBroker = new AnnotationConfigApplicationContext();
this.cxtStompBroker.register(TestStompMessageBrokerConfig.class);
this.cxtStompBroker.refresh();
this.brokerRelayContext = new AnnotationConfigApplicationContext();
this.brokerRelayContext.register(BrokerRelayConfig.class);
this.brokerRelayContext.refresh();
this.cxtCustomizedChannelConfig = new AnnotationConfigApplicationContext();
this.cxtCustomizedChannelConfig.register(CustomizedChannelConfig.class);
this.cxtCustomizedChannelConfig.refresh();
this.cxtCustomizedValidator = new AnnotationConfigApplicationContext();
this.cxtCustomizedValidator.register(ValidationConfig.class);
this.cxtCustomizedValidator.refresh();
this.customChannelContext = new AnnotationConfigApplicationContext();
this.customChannelContext.register(CustomChannelConfig.class);
this.customChannelContext.refresh();
}
@Test
public void clientInboundChannel() {
TestChannel channel = this.cxtSimpleBroker.getBean("clientInboundChannel", TestChannel.class);
TestChannel channel = this.simpleContext.getBean("clientInboundChannel", TestChannel.class);
List<MessageHandler> handlers = channel.handlers;
assertEquals(3, handlers.size());
assertTrue(handlers.contains(cxtSimpleBroker.getBean(SimpAnnotationMethodMessageHandler.class)));
assertTrue(handlers.contains(cxtSimpleBroker.getBean(UserDestinationMessageHandler.class)));
assertTrue(handlers.contains(cxtSimpleBroker.getBean(SimpleBrokerMessageHandler.class)));
assertTrue(handlers.contains(simpleContext.getBean(SimpAnnotationMethodMessageHandler.class)));
assertTrue(handlers.contains(simpleContext.getBean(UserDestinationMessageHandler.class)));
assertTrue(handlers.contains(simpleContext.getBean(SimpleBrokerMessageHandler.class)));
}
@Test
public void clientInboundChannelWithStompBroker() {
TestChannel channel = this.cxtStompBroker.getBean("clientInboundChannel", TestChannel.class);
public void clientInboundChannelWithBrokerRelay() {
TestChannel channel = this.brokerRelayContext.getBean("clientInboundChannel", TestChannel.class);
List<MessageHandler> values = channel.handlers;
assertEquals(3, values.size());
assertTrue(values.contains(cxtStompBroker.getBean(SimpAnnotationMethodMessageHandler.class)));
assertTrue(values.contains(cxtStompBroker.getBean(UserDestinationMessageHandler.class)));
assertTrue(values.contains(cxtStompBroker.getBean(StompBrokerRelayMessageHandler.class)));
assertTrue(values.contains(brokerRelayContext.getBean(SimpAnnotationMethodMessageHandler.class)));
assertTrue(values.contains(brokerRelayContext.getBean(UserDestinationMessageHandler.class)));
assertTrue(values.contains(brokerRelayContext.getBean(StompBrokerRelayMessageHandler.class)));
}
@Test
public void clientInboundChannelCustomized() {
AbstractSubscribableChannel channel = this.cxtCustomizedChannelConfig.getBean(
AbstractSubscribableChannel channel = this.customChannelContext.getBean(
"clientInboundChannel", AbstractSubscribableChannel.class);
assertEquals(1, channel.getInterceptors().size());
ThreadPoolTaskExecutor taskExecutor = this.cxtCustomizedChannelConfig.getBean(
ThreadPoolTaskExecutor taskExecutor = this.customChannelContext.getBean(
"clientInboundChannelExecutor", ThreadPoolTaskExecutor.class);
assertEquals(11, taskExecutor.getCorePoolSize());
@ -131,8 +129,8 @@ public class MessageBrokerConfigurationTests {
@Test
public void clientOutboundChannelUsedByAnnotatedMethod() {
TestChannel channel = this.cxtSimpleBroker.getBean("clientOutboundChannel", TestChannel.class);
SimpAnnotationMethodMessageHandler messageHandler = this.cxtSimpleBroker.getBean(SimpAnnotationMethodMessageHandler.class);
TestChannel channel = this.simpleContext.getBean("clientOutboundChannel", TestChannel.class);
SimpAnnotationMethodMessageHandler messageHandler = this.simpleContext.getBean(SimpAnnotationMethodMessageHandler.class);
StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.SUBSCRIBE);
headers.setSessionId("sess1");
@ -152,8 +150,8 @@ public class MessageBrokerConfigurationTests {
@Test
public void clientOutboundChannelUsedBySimpleBroker() {
TestChannel channel = this.cxtSimpleBroker.getBean("clientOutboundChannel", TestChannel.class);
SimpleBrokerMessageHandler broker = this.cxtSimpleBroker.getBean(SimpleBrokerMessageHandler.class);
TestChannel channel = this.simpleContext.getBean("clientOutboundChannel", TestChannel.class);
SimpleBrokerMessageHandler broker = this.simpleContext.getBean(SimpleBrokerMessageHandler.class);
StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.SUBSCRIBE);
headers.setSessionId("sess1");
@ -183,12 +181,12 @@ public class MessageBrokerConfigurationTests {
@Test
public void clientOutboundChannelCustomized() {
AbstractSubscribableChannel channel = this.cxtCustomizedChannelConfig.getBean(
AbstractSubscribableChannel channel = this.customChannelContext.getBean(
"clientOutboundChannel", AbstractSubscribableChannel.class);
assertEquals(2, channel.getInterceptors().size());
ThreadPoolTaskExecutor taskExecutor = this.cxtCustomizedChannelConfig.getBean(
ThreadPoolTaskExecutor taskExecutor = this.customChannelContext.getBean(
"clientOutboundChannelExecutor", ThreadPoolTaskExecutor.class);
assertEquals(21, taskExecutor.getCorePoolSize());
@ -198,28 +196,28 @@ public class MessageBrokerConfigurationTests {
@Test
public void brokerChannel() {
TestChannel channel = this.cxtSimpleBroker.getBean("brokerChannel", TestChannel.class);
TestChannel channel = this.simpleContext.getBean("brokerChannel", TestChannel.class);
List<MessageHandler> handlers = channel.handlers;
assertEquals(2, handlers.size());
assertTrue(handlers.contains(cxtSimpleBroker.getBean(UserDestinationMessageHandler.class)));
assertTrue(handlers.contains(cxtSimpleBroker.getBean(SimpleBrokerMessageHandler.class)));
assertTrue(handlers.contains(simpleContext.getBean(UserDestinationMessageHandler.class)));
assertTrue(handlers.contains(simpleContext.getBean(SimpleBrokerMessageHandler.class)));
}
@Test
public void brokerChannelWithStompBroker() {
TestChannel channel = this.cxtStompBroker.getBean("brokerChannel", TestChannel.class);
public void brokerChannelWithBrokerRelay() {
TestChannel channel = this.brokerRelayContext.getBean("brokerChannel", TestChannel.class);
List<MessageHandler> handlers = channel.handlers;
assertEquals(2, handlers.size());
assertTrue(handlers.contains(cxtStompBroker.getBean(UserDestinationMessageHandler.class)));
assertTrue(handlers.contains(cxtStompBroker.getBean(StompBrokerRelayMessageHandler.class)));
assertTrue(handlers.contains(brokerRelayContext.getBean(UserDestinationMessageHandler.class)));
assertTrue(handlers.contains(brokerRelayContext.getBean(StompBrokerRelayMessageHandler.class)));
}
@Test
public void brokerChannelUsedByAnnotatedMethod() {
TestChannel channel = this.cxtSimpleBroker.getBean("brokerChannel", TestChannel.class);
SimpAnnotationMethodMessageHandler messageHandler = this.cxtSimpleBroker.getBean(SimpAnnotationMethodMessageHandler.class);
TestChannel channel = this.simpleContext.getBean("brokerChannel", TestChannel.class);
SimpAnnotationMethodMessageHandler messageHandler = this.simpleContext.getBean(SimpAnnotationMethodMessageHandler.class);
StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.SEND);
headers.setDestination("/foo");
@ -237,10 +235,10 @@ public class MessageBrokerConfigurationTests {
@Test
public void brokerChannelUsedByUserDestinationMessageHandler() {
TestChannel channel = this.cxtSimpleBroker.getBean("brokerChannel", TestChannel.class);
UserDestinationMessageHandler messageHandler = this.cxtSimpleBroker.getBean(UserDestinationMessageHandler.class);
TestChannel channel = this.simpleContext.getBean("brokerChannel", TestChannel.class);
UserDestinationMessageHandler messageHandler = this.simpleContext.getBean(UserDestinationMessageHandler.class);
this.cxtSimpleBroker.getBean(UserSessionRegistry.class).registerSessionId("joe", "s1");
this.simpleContext.getBean(UserSessionRegistry.class).registerSessionId("joe", "s1");
StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.SEND);
headers.setDestination("/user/joe/foo");
@ -258,12 +256,12 @@ public class MessageBrokerConfigurationTests {
@Test
public void brokerChannelCustomized() {
AbstractSubscribableChannel channel = this.cxtCustomizedChannelConfig.getBean(
AbstractSubscribableChannel channel = this.customChannelContext.getBean(
"brokerChannel", AbstractSubscribableChannel.class);
assertEquals(3, channel.getInterceptors().size());
ThreadPoolTaskExecutor taskExecutor = this.cxtCustomizedChannelConfig.getBean(
ThreadPoolTaskExecutor taskExecutor = this.customChannelContext.getBean(
"brokerChannelExecutor", ThreadPoolTaskExecutor.class);
assertEquals(31, taskExecutor.getCorePoolSize());
@ -273,7 +271,7 @@ public class MessageBrokerConfigurationTests {
@Test
public void messageConverter() {
CompositeMessageConverter messageConverter = this.cxtStompBroker.getBean(
CompositeMessageConverter messageConverter = this.brokerRelayContext.getBean(
"brokerMessageConverter", CompositeMessageConverter.class);
DefaultContentTypeResolver resolver = (DefaultContentTypeResolver) messageConverter.getContentTypeResolver();
@ -281,19 +279,97 @@ public class MessageBrokerConfigurationTests {
}
@Test
public void defaultValidator() {
SimpAnnotationMethodMessageHandler messageHandler =
this.cxtSimpleBroker.getBean(SimpAnnotationMethodMessageHandler.class);
assertThat(messageHandler.getValidator(),Matchers.notNullValue(Validator.class));
public void configureMessageConvertersDefault() {
AbstractMessageBrokerConfiguration config = new AbstractMessageBrokerConfiguration() {};
CompositeMessageConverter compositeConverter = config.brokerMessageConverter();
assertThat(compositeConverter.getConverters().size(), Matchers.is(3));
Iterator<MessageConverter> iterator = compositeConverter.getConverters().iterator();
assertThat(iterator.next(), Matchers.instanceOf(MappingJackson2MessageConverter.class));
assertThat(iterator.next(), Matchers.instanceOf(StringMessageConverter.class));
assertThat(iterator.next(), Matchers.instanceOf(ByteArrayMessageConverter.class));
}
@Test
public void customValidator() {
SimpAnnotationMethodMessageHandler messageHandler =
this.cxtCustomizedValidator.getBean(SimpAnnotationMethodMessageHandler.class);
assertThat(messageHandler.getValidator(),Matchers.notNullValue(Validator.class));
assertThat(messageHandler.getValidator(),Matchers.instanceOf(Validator.class));
public void configureMessageConvertersCustom() {
final MessageConverter testConverter = Mockito.mock(MessageConverter.class);
AbstractMessageBrokerConfiguration config = new AbstractMessageBrokerConfiguration() {
@Override
protected boolean configureMessageConverters(List<MessageConverter> messageConverters) {
messageConverters.add(testConverter);
return false;
}
};
CompositeMessageConverter compositeConverter = config.brokerMessageConverter();
assertThat(compositeConverter.getConverters().size(), Matchers.is(1));
Iterator<MessageConverter> iterator = compositeConverter.getConverters().iterator();
assertThat(iterator.next(), Matchers.is(testConverter));
}
@Test
public void configureMessageConvertersCustomAndDefault() {
final MessageConverter testConverter = Mockito.mock(MessageConverter.class);
AbstractMessageBrokerConfiguration config = new AbstractMessageBrokerConfiguration() {
@Override
protected boolean configureMessageConverters(List<MessageConverter> messageConverters) {
messageConverters.add(testConverter);
return true;
}
};
CompositeMessageConverter compositeConverter = config.brokerMessageConverter();
assertThat(compositeConverter.getConverters().size(), Matchers.is(4));
Iterator<MessageConverter> iterator = compositeConverter.getConverters().iterator();
assertThat(iterator.next(), Matchers.is(testConverter));
assertThat(iterator.next(), Matchers.instanceOf(MappingJackson2MessageConverter.class));
assertThat(iterator.next(), Matchers.instanceOf(StringMessageConverter.class));
assertThat(iterator.next(), Matchers.instanceOf(ByteArrayMessageConverter.class));
}
@Test
public void simpValidatorDefault() {
AbstractMessageBrokerConfiguration config = new AbstractMessageBrokerConfiguration() {};
config.setApplicationContext(new StaticApplicationContext());
assertThat(config.simpValidator(), Matchers.notNullValue());
assertThat(config.simpValidator(), Matchers.instanceOf(OptionalValidatorFactoryBean.class));
}
@Test
public void simpValidatorCustom() {
final Validator validator = Mockito.mock(Validator.class);
AbstractMessageBrokerConfiguration config = new AbstractMessageBrokerConfiguration() {
@Override
public Validator getValidator() {
return validator;
}
};
assertSame(validator, config.simpValidator());
}
@Test
public void simpValidatorMvc() {
StaticApplicationContext appCxt = new StaticApplicationContext();
appCxt.registerSingleton("mvcValidator", TestValidator.class);
AbstractMessageBrokerConfiguration config = new AbstractMessageBrokerConfiguration() {};
config.setApplicationContext(appCxt);
assertThat(config.simpValidator(), Matchers.notNullValue());
assertThat(config.simpValidator(), Matchers.instanceOf(TestValidator.class));
}
@Test
public void simpValidatorInjected() {
SimpAnnotationMethodMessageHandler messageHandler =
this.simpleContext.getBean(SimpAnnotationMethodMessageHandler.class);
assertThat(messageHandler.getValidator(), Matchers.notNullValue(Validator.class));
}
@Controller
static class TestController {
@ -311,48 +387,34 @@ public class MessageBrokerConfigurationTests {
}
@Configuration
static class TestMessageBrokerConfiguration extends AbstractMessageBrokerConfiguration {
static class SimpleConfig extends AbstractMessageBrokerConfiguration {
@Bean
public TestController subscriptionController() {
return new TestController();
}
@Override
@Bean
public AbstractSubscribableChannel clientInboundChannel() {
return new TestChannel();
}
@Override
protected void configureClientInboundChannel(ChannelRegistration registration) {
}
@Override
@Bean
public AbstractSubscribableChannel clientOutboundChannel() {
return new TestChannel();
}
@Override
protected void configureClientOutboundChannel(ChannelRegistration registration) {
}
@Override
@Bean
public AbstractSubscribableChannel brokerChannel() {
return new TestChannel();
}
@Override
protected void configureMessageBroker(MessageBrokerRegistry registry) {
}
}
@Configuration
static class TestStompMessageBrokerConfig extends TestMessageBrokerConfiguration {
static class BrokerRelayConfig extends SimpleConfig {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
@ -361,10 +423,9 @@ public class MessageBrokerConfigurationTests {
}
@Configuration
static class CustomizedChannelConfig extends AbstractMessageBrokerConfiguration {
static class CustomChannelConfig extends AbstractMessageBrokerConfiguration {
private ChannelInterceptor interceptor = new ChannelInterceptorAdapter() {
};
private ChannelInterceptor interceptor = new ChannelInterceptorAdapter() {};
@Override
protected void configureClientInboundChannel(ChannelRegistration registration) {
@ -380,30 +441,13 @@ public class MessageBrokerConfigurationTests {
@Override
protected void configureMessageBroker(MessageBrokerRegistry registry) {
registry.configureBrokerChannel().setInterceptors(this.interceptor, this.interceptor, this.interceptor);
registry.configureBrokerChannel().setInterceptors(
this.interceptor, this.interceptor, this.interceptor);
registry.configureBrokerChannel().taskExecutor()
.corePoolSize(31).maxPoolSize(32).keepAliveSeconds(33).queueCapacity(34);
}
}
@Configuration
static class ValidationConfig extends TestMessageBrokerConfiguration {
@Override
public Validator getValidator() {
return new TestValidator();
}
}
private static class TestValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return false;
}
@Override
public void validate(Object target, Errors errors) {}
}
private static class TestChannel extends ExecutorSubscribableChannel {
@ -425,4 +469,16 @@ public class MessageBrokerConfigurationTests {
}
}
private static class TestValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return false;
}
@Override
public void validate(Object target, Errors errors) {
}
}
}

View File

@ -153,7 +153,8 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
RuntimeBeanReference brokerChannel = getMessageChannel(beanName, channelElem, parserCxt, source);
registerMessageBroker(element, clientInChannel, clientOutChannel, brokerChannel, parserCxt, source);
RuntimeBeanReference messageConverter = registerBrokerMessageConverter(parserCxt, source);
RuntimeBeanReference messageConverter = registerBrokerMessageConverter(element, parserCxt, source);
RuntimeBeanReference messagingTemplate = registerBrokerMessagingTemplate(element, brokerChannel,
messageConverter, parserCxt, source);
@ -339,24 +340,39 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
}
private RuntimeBeanReference registerBrokerMessageConverter(ParserContext parserCxt, Object source) {
private RuntimeBeanReference registerBrokerMessageConverter(Element element,
ParserContext parserCxt, Object source) {
RootBeanDefinition contentTypeResolverDef = new RootBeanDefinition(DefaultContentTypeResolver.class);
Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
ManagedList<? super Object> convertersDef = new ManagedList<Object>();
if (convertersElement != null) {
convertersDef.setSource(source);
for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) {
Object object = parserCxt.getDelegate().parsePropertySubElement(beanElement, null);
convertersDef.add(object);
}
}
ManagedList<RootBeanDefinition> convertersDef = new ManagedList<RootBeanDefinition>();
if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {
convertersDef.setSource(source);
if (jackson2Present) {
convertersDef.add(new RootBeanDefinition(MappingJackson2MessageConverter.class));
contentTypeResolverDef.getPropertyValues().add("defaultMimeType", MimeTypeUtils.APPLICATION_JSON);
}
convertersDef.add(new RootBeanDefinition(StringMessageConverter.class));
convertersDef.add(new RootBeanDefinition(ByteArrayMessageConverter.class));
}
RootBeanDefinition contentTypeResolverDef = new RootBeanDefinition(DefaultContentTypeResolver.class);
if (jackson2Present) {
contentTypeResolverDef.getPropertyValues().add("defaultMimeType", MimeTypeUtils.APPLICATION_JSON);
}
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
cavs.addIndexedArgumentValue(0, convertersDef);
cavs.addIndexedArgumentValue(1, contentTypeResolverDef);
RootBeanDefinition brokerMessage = new RootBeanDefinition(CompositeMessageConverter.class, cavs, null);
return new RuntimeBeanReference(registerBeanDef(brokerMessage,parserCxt, source));
return new RuntimeBeanReference(registerBeanDef(brokerMessage, parserCxt, source));
}
private RuntimeBeanReference registerBrokerMessagingTemplate(

View File

@ -0,0 +1,51 @@
/*
* Copyright 2002-2013 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.web.socket.config.annotation;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import java.util.List;
/**
* A convenient abstract base class for {@link WebSocketMessageBrokerConfigurer}
* implementations providing empty method implementations for optional methods.
*
* @author Rossen Stoyanchev
* @since 4.0.1
*/
public abstract class AbstractWebSocketMessageBrokerConfigurer implements WebSocketMessageBrokerConfigurer {
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
}
@Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
}
@Override
public boolean configureMessageConverters(List<MessageConverter> messageConverters) {
return true;
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
}
}

View File

@ -21,6 +21,7 @@ import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.util.CollectionUtils;
@ -71,6 +72,17 @@ public class DelegatingWebSocketMessageBrokerConfiguration extends WebSocketMess
}
}
@Override
protected boolean configureMessageConverters(List<MessageConverter> messageConverters) {
boolean registerDefaults = true;
for (WebSocketMessageBrokerConfigurer c : this.configurers) {
if (!c.configureMessageConverters(messageConverters)) {
registerDefaults = false;
}
}
return registerDefaults;
}
@Override
protected void configureMessageBroker(MessageBrokerRegistry registry) {
for (WebSocketMessageBrokerConfigurer c : this.configurers) {

View File

@ -34,12 +34,13 @@ import org.springframework.context.annotation.Import;
* </pre>
* <p>
* Customize the imported configuration by implementing the
* {@link WebSocketMessageBrokerConfigurer} interface:
* {@link WebSocketMessageBrokerConfigurer} interface or more likely the convenient
* base class {@link AbstractWebSocketMessageBrokerConfigurer}:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableWebSocketMessageBroker
* public class MyConfiguration implements implements WebSocketMessageBrokerConfigurer {
* public class MyConfiguration implements extends AbstractWebSocketMessageBrokerConfigurer {
*
* &#064;Override
* public void registerStompEndpoints(StompEndpointRegistry registry) {

View File

@ -17,9 +17,12 @@
package org.springframework.web.socket.config.annotation;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import java.util.List;
/**
* Defines methods for configuring message handling with simple messaging
* protocols (e.g. STOMP) from WebSocket clients. Typically used to customize
@ -52,6 +55,20 @@ public interface WebSocketMessageBrokerConfigurer {
*/
void configureClientOutboundChannel(ChannelRegistration registration);
/**
* Configure the message converters to use when extracting the payload of
* messages in annotated methods and when sending messages (e.g. through the
* "broker" SimpMessagingTemplate).
* <p>
* The provided list, initially empty, can be used to add message converters
* while the boolean return value is used to determine if default message should
* be added as well.
*
* @param messageConverters initially an empty list of converters
* @return whether to also add default converter or not
*/
boolean configureMessageConverters(List<MessageConverter> messageConverters);
/**
* Configure message broker options.
*/

View File

@ -481,6 +481,43 @@
<xsd:element name="simple-broker" type="simple-broker" />
<xsd:element name="stomp-broker-relay" type="stomp-broker-relay" />
</xsd:choice>
<xsd:element name="message-converters" minOccurs="0">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configure the message converters to use when extracting the payload of messages in annotated methods
and when sending messages (e.g. through the "broker" SimpMessagingTemplate.
MessageConverter registrations provided here will take precedence over MessageConverter types registered by default.
Also see the register-defaults attribute if you want to turn off default registrations entirely.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:choice maxOccurs="unbounded">
<xsd:element ref="beans:bean">
<xsd:annotation>
<xsd:documentation><![CDATA[
A MessageConverter bean definition.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="beans:ref">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to an HttpMessageConverter bean.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:choice>
</xsd:sequence>
<xsd:attribute name="register-defaults" type="xsd:boolean" default="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Whether or not default MessageConverter registrations should be added in addition to the ones provided within this element.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="client-inbound-channel" type="channel" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[

View File

@ -29,6 +29,7 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.converter.CompositeMessageConverter;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.converter.StringMessageConverter;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
import org.springframework.messaging.simp.user.DefaultUserDestinationResolver;
@ -246,6 +247,29 @@ public class MessageBrokerBeanDefinitionParserTests {
testExecutor("brokerChannel", 102, 202, 602);
}
@Test
public void messageConverters() {
loadBeanDefinitions("websocket-config-broker-converters.xml");
CompositeMessageConverter compositeConverter = this.appContext.getBean(CompositeMessageConverter.class);
assertNotNull(compositeConverter);
assertEquals(4, compositeConverter.getConverters().size());
assertEquals(StringMessageConverter.class, compositeConverter.getConverters().iterator().next().getClass());
}
@Test
public void messageConvertersDefaultsOff() {
loadBeanDefinitions("websocket-config-broker-converters-defaults-off.xml");
CompositeMessageConverter compositeConverter = this.appContext.getBean(CompositeMessageConverter.class);
assertNotNull(compositeConverter);
assertEquals(1, compositeConverter.getConverters().size());
assertEquals(StringMessageConverter.class, compositeConverter.getConverters().iterator().next().getClass());
}
private void testChannel(String channelName, List<Class<? extends MessageHandler>> subscriberTypes,
int interceptorCount) {

View File

@ -118,7 +118,7 @@ public class WebSocketMessageBrokerConfigurationSupportTests {
}
@Configuration
static class TestSimpleMessageBrokerConfig implements WebSocketMessageBrokerConfigurer {
static class TestSimpleMessageBrokerConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Bean
public TestController subscriptionController() {
@ -130,19 +130,6 @@ public class WebSocketMessageBrokerConfigurationSupportTests {
registry.addEndpoint("/simpleBroker");
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
}
@Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// SimpleBroker used by default
}
}
@Configuration

View File

@ -47,6 +47,7 @@ import org.springframework.web.socket.JettyWebSocketTestServer;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.TomcatWebSocketTestServer;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.client.jetty.JettyWebSocketClient;
@ -205,7 +206,7 @@ public class SimpAnnotationMethodIntegrationTests extends AbstractWebSocketInteg
@ComponentScan(basePackageClasses=SimpAnnotationMethodIntegrationTests.class,
useDefaultFilters=false,
includeFilters=@ComponentScan.Filter(IntegrationTestController.class))
static class TestMessageBrokerConfigurer implements WebSocketMessageBrokerConfigurer {
static class TestMessageBrokerConfigurer extends AbstractWebSocketMessageBrokerConfigurer {
@Autowired
private HandshakeHandler handshakeHandler; // can't rely on classpath for server detection
@ -215,14 +216,6 @@ public class SimpAnnotationMethodIntegrationTests extends AbstractWebSocketInteg
registry.addEndpoint("/ws").setHandshakeHandler(this.handshakeHandler);
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
}
@Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
}
@Override
public void configureMessageBroker(MessageBrokerRegistry configurer) {
configurer.setApplicationDestinationPrefixes("/app");

View File

@ -0,0 +1,19 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.0.xsd">
<websocket:message-broker>
<websocket:stomp-endpoint path="/foo" />
<websocket:simple-broker />
<websocket:message-converters register-defaults="false">
<bean class="org.springframework.messaging.converter.StringMessageConverter"/>
</websocket:message-converters>
</websocket:message-broker>
</beans>

View File

@ -0,0 +1,19 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.0.xsd">
<websocket:message-broker>
<websocket:stomp-endpoint path="/foo" />
<websocket:simple-broker />
<websocket:message-converters>
<bean class="org.springframework.messaging.converter.StringMessageConverter"/>
</websocket:message-converters>
</websocket:message-broker>
</beans>