From 4342497305d54091ea327fc4368b96daade556a0 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 7 Jan 2014 16:16:29 -0500 Subject: [PATCH] Add support for custom message converters The Java and XML config for STOMP WebSocket applications now supports configuring message converters. Issue: SPR-11184 --- build.gradle | 1 + .../AbstractMessageBrokerConfiguration.java | 80 +++--- .../MessageBrokerConfigurationTests.java | 252 +++++++++++------- .../MessageBrokerBeanDefinitionParser.java | 32 ++- ...tractWebSocketMessageBrokerConfigurer.java | 51 ++++ ...ngWebSocketMessageBrokerConfiguration.java | 12 + .../EnableWebSocketMessageBroker.java | 5 +- .../WebSocketMessageBrokerConfigurer.java | 17 ++ .../socket/config/spring-websocket-4.0.xsd | 37 +++ ...essageBrokerBeanDefinitionParserTests.java | 24 ++ ...essageBrokerConfigurationSupportTests.java | 15 +- .../SimpAnnotationMethodIntegrationTests.java | 11 +- ...-config-broker-converters-defaults-off.xml | 19 ++ .../websocket-config-broker-converters.xml | 19 ++ 14 files changed, 406 insertions(+), 169 deletions(-) create mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/AbstractWebSocketMessageBrokerConfigurer.java create mode 100644 spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-converters-defaults-off.xml create mode 100644 spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-converters.xml diff --git a/build.gradle b/build.gradle index f2c4ff1b02f..69ef8757cf3 100644 --- a/build.gradle +++ b/build.gradle @@ -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") { diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java index 53e4a9537dd..fbf6b3523f8 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java @@ -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 converters = new ArrayList(); + if (configureMessageConverters(converters)) { + if (jackson2Present) { + converters.add(new MappingJackson2MessageConverter()); + } + converters.add(new StringMessageConverter()); + converters.add(new ByteArrayMessageConverter()); + } + return new CompositeMessageConverter(converters, getContentTypeResolver()); + } + + protected boolean configureMessageConverters(List messageConverters) { + return true; + } + + protected ContentTypeResolver getContentTypeResolver() { + DefaultContentTypeResolver contentTypeResolver = new DefaultContentTypeResolver(); if (jackson2Present) { - converters.add(new MappingJackson2MessageConverter()); contentTypeResolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON); } - converters.add(new StringMessageConverter()); - converters.add(new ByteArrayMessageConverter()); - - return new CompositeMessageConverter(converters, contentTypeResolver); + 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) { - } - }; - - } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java index 7888cc43f19..b7c931e7432 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java @@ -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 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 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 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 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,20 +279,98 @@ 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 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 messageConverters) { + messageConverters.add(testConverter); + return false; + } + }; + CompositeMessageConverter compositeConverter = config.brokerMessageConverter(); + + assertThat(compositeConverter.getConverters().size(), Matchers.is(1)); + Iterator 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 messageConverters) { + messageConverters.add(testConverter); + return true; + } + }; + CompositeMessageConverter compositeConverter = config.brokerMessageConverter(); + + assertThat(compositeConverter.getConverters().size(), Matchers.is(4)); + Iterator 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) { + } + } + } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java index 64dcbe97f11..de27c798301 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java @@ -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) { + + Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters"); + ManagedList convertersDef = new ManagedList(); + if (convertersElement != null) { + convertersDef.setSource(source); + for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) { + Object object = parserCxt.getDelegate().parsePropertySubElement(beanElement, null); + convertersDef.add(object); + } + } + + if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) { + convertersDef.setSource(source); + if (jackson2Present) { + convertersDef.add(new RootBeanDefinition(MappingJackson2MessageConverter.class)); + } + convertersDef.add(new RootBeanDefinition(StringMessageConverter.class)); + convertersDef.add(new RootBeanDefinition(ByteArrayMessageConverter.class)); + } RootBeanDefinition contentTypeResolverDef = new RootBeanDefinition(DefaultContentTypeResolver.class); - - ManagedList convertersDef = new ManagedList(); 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)); 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( diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/AbstractWebSocketMessageBrokerConfigurer.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/AbstractWebSocketMessageBrokerConfigurer.java new file mode 100644 index 00000000000..9d235711fad --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/AbstractWebSocketMessageBrokerConfigurer.java @@ -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 messageConverters) { + return true; + } + + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/DelegatingWebSocketMessageBrokerConfiguration.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/DelegatingWebSocketMessageBrokerConfiguration.java index 6f2be10e72f..45ef5e95660 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/DelegatingWebSocketMessageBrokerConfiguration.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/DelegatingWebSocketMessageBrokerConfiguration.java @@ -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 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) { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/EnableWebSocketMessageBroker.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/EnableWebSocketMessageBroker.java index 6d6a8697288..f9de1c9d769 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/EnableWebSocketMessageBroker.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/EnableWebSocketMessageBroker.java @@ -34,12 +34,13 @@ import org.springframework.context.annotation.Import; * *

* Customize the imported configuration by implementing the - * {@link WebSocketMessageBrokerConfigurer} interface: + * {@link WebSocketMessageBrokerConfigurer} interface or more likely the convenient + * base class {@link AbstractWebSocketMessageBrokerConfigurer}: * *

  * @Configuration
  * @EnableWebSocketMessageBroker
- * public class MyConfiguration implements implements WebSocketMessageBrokerConfigurer {
+ * public class MyConfiguration implements extends AbstractWebSocketMessageBrokerConfigurer {
  *
  * 	@Override
  * 	public void registerStompEndpoints(StompEndpointRegistry registry) {
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebSocketMessageBrokerConfigurer.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebSocketMessageBrokerConfigurer.java
index e8de3fb717e..002b967a5a8 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebSocketMessageBrokerConfigurer.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebSocketMessageBrokerConfigurer.java
@@ -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).
+	 * 

+ * 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 messageConverters); + /** * Configure message broker options. */ diff --git a/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.0.xsd b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.0.xsd index 08fa7288417..7717f345c21 100644 --- a/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.0.xsd +++ b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.0.xsd @@ -481,6 +481,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + > subscriberTypes, int interceptorCount) { diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebSocketMessageBrokerConfigurationSupportTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebSocketMessageBrokerConfigurationSupportTests.java index b50a75c0675..eb95b538b1d 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebSocketMessageBrokerConfigurationSupportTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebSocketMessageBrokerConfigurationSupportTests.java @@ -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 diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/messaging/SimpAnnotationMethodIntegrationTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/messaging/SimpAnnotationMethodIntegrationTests.java index 200f491e05b..6f3b34a4669 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/messaging/SimpAnnotationMethodIntegrationTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/messaging/SimpAnnotationMethodIntegrationTests.java @@ -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"); diff --git a/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-converters-defaults-off.xml b/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-converters-defaults-off.xml new file mode 100644 index 00000000000..c2c6d5e4358 --- /dev/null +++ b/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-converters-defaults-off.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + diff --git a/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-converters.xml b/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-converters.xml new file mode 100644 index 00000000000..83db0823992 --- /dev/null +++ b/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-converters.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + +