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}