001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.socket.config.annotation;
018
019import org.springframework.beans.factory.config.CustomScopeConfigurer;
020import org.springframework.context.ApplicationContext;
021import org.springframework.context.annotation.Bean;
022import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
023import org.springframework.lang.Nullable;
024import org.springframework.messaging.converter.MappingJackson2MessageConverter;
025import org.springframework.messaging.simp.SimpSessionScope;
026import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
027import org.springframework.messaging.simp.broker.AbstractBrokerMessageHandler;
028import org.springframework.messaging.simp.config.AbstractMessageBrokerConfiguration;
029import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;
030import org.springframework.messaging.simp.user.SimpUserRegistry;
031import org.springframework.web.servlet.HandlerMapping;
032import org.springframework.web.socket.WebSocketHandler;
033import org.springframework.web.socket.config.WebSocketMessageBrokerStats;
034import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory;
035import org.springframework.web.socket.messaging.DefaultSimpUserRegistry;
036import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler;
037import org.springframework.web.socket.messaging.WebSocketAnnotationMethodMessageHandler;
038
039/**
040 * Extends {@link AbstractMessageBrokerConfiguration} and adds configuration for
041 * receiving and responding to STOMP messages from WebSocket clients.
042 *
043 * <p>Typically used in conjunction with
044 * {@link EnableWebSocketMessageBroker @EnableWebSocketMessageBroker} but can
045 * also be extended directly.
046 *
047 * @author Rossen Stoyanchev
048 * @author Artem Bilan
049 * @since 4.0
050 */
051public abstract class WebSocketMessageBrokerConfigurationSupport extends AbstractMessageBrokerConfiguration {
052
053        @Nullable
054        private WebSocketTransportRegistration transportRegistration;
055
056
057        @Override
058        protected SimpAnnotationMethodMessageHandler createAnnotationMethodMessageHandler() {
059                return new WebSocketAnnotationMethodMessageHandler(
060                                clientInboundChannel(), clientOutboundChannel(), brokerMessagingTemplate());
061        }
062
063        @Override
064        protected SimpUserRegistry createLocalUserRegistry(@Nullable Integer order) {
065                DefaultSimpUserRegistry registry = new DefaultSimpUserRegistry();
066                if (order != null) {
067                        registry.setOrder(order);
068                }
069                return registry;
070        }
071
072        @Bean
073        public HandlerMapping stompWebSocketHandlerMapping() {
074                WebSocketHandler handler = decorateWebSocketHandler(subProtocolWebSocketHandler());
075                WebMvcStompEndpointRegistry registry = new WebMvcStompEndpointRegistry(
076                                handler, getTransportRegistration(), messageBrokerTaskScheduler());
077                ApplicationContext applicationContext = getApplicationContext();
078                if (applicationContext != null) {
079                        registry.setApplicationContext(applicationContext);
080                }
081                registerStompEndpoints(registry);
082                return registry.getHandlerMapping();
083        }
084
085        @Bean
086        public WebSocketHandler subProtocolWebSocketHandler() {
087                return new SubProtocolWebSocketHandler(clientInboundChannel(), clientOutboundChannel());
088        }
089
090        protected WebSocketHandler decorateWebSocketHandler(WebSocketHandler handler) {
091                for (WebSocketHandlerDecoratorFactory factory : getTransportRegistration().getDecoratorFactories()) {
092                        handler = factory.decorate(handler);
093                }
094                return handler;
095        }
096
097        protected final WebSocketTransportRegistration getTransportRegistration() {
098                if (this.transportRegistration == null) {
099                        this.transportRegistration = new WebSocketTransportRegistration();
100                        configureWebSocketTransport(this.transportRegistration);
101                }
102                return this.transportRegistration;
103        }
104
105        protected void configureWebSocketTransport(WebSocketTransportRegistration registry) {
106        }
107
108        protected abstract void registerStompEndpoints(StompEndpointRegistry registry);
109
110        @Bean
111        public static CustomScopeConfigurer webSocketScopeConfigurer() {
112                CustomScopeConfigurer configurer = new CustomScopeConfigurer();
113                configurer.addScope("websocket", new SimpSessionScope());
114                return configurer;
115        }
116
117        @Bean
118        public WebSocketMessageBrokerStats webSocketMessageBrokerStats() {
119                AbstractBrokerMessageHandler relayBean = stompBrokerRelayMessageHandler();
120
121                // Ensure STOMP endpoints are registered
122                stompWebSocketHandlerMapping();
123
124                WebSocketMessageBrokerStats stats = new WebSocketMessageBrokerStats();
125                stats.setSubProtocolWebSocketHandler((SubProtocolWebSocketHandler) subProtocolWebSocketHandler());
126                if (relayBean instanceof StompBrokerRelayMessageHandler) {
127                        stats.setStompBrokerRelay((StompBrokerRelayMessageHandler) relayBean);
128                }
129                stats.setInboundChannelExecutor(clientInboundChannelExecutor());
130                stats.setOutboundChannelExecutor(clientOutboundChannelExecutor());
131                stats.setSockJsTaskScheduler(messageBrokerTaskScheduler());
132                return stats;
133        }
134
135        @Override
136        protected MappingJackson2MessageConverter createJacksonConverter() {
137                MappingJackson2MessageConverter messageConverter = super.createJacksonConverter();
138                // Use Jackson builder in order to have JSR-310 and Joda-Time modules registered automatically
139                Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
140                ApplicationContext applicationContext = getApplicationContext();
141                if (applicationContext != null) {
142                        builder.applicationContext(applicationContext);
143                }
144                messageConverter.setObjectMapper(builder.build());
145                return messageConverter;
146        }
147
148}