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.server.standard; 018 019import java.util.Arrays; 020import java.util.LinkedHashSet; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024 025import javax.servlet.ServletContext; 026import javax.websocket.DeploymentException; 027import javax.websocket.server.ServerContainer; 028import javax.websocket.server.ServerEndpoint; 029import javax.websocket.server.ServerEndpointConfig; 030 031import org.springframework.beans.factory.InitializingBean; 032import org.springframework.beans.factory.SmartInitializingSingleton; 033import org.springframework.context.ApplicationContext; 034import org.springframework.lang.Nullable; 035import org.springframework.util.Assert; 036import org.springframework.web.context.support.WebApplicationObjectSupport; 037 038/** 039 * Detects beans of type {@link javax.websocket.server.ServerEndpointConfig} and registers 040 * with the standard Java WebSocket runtime. Also detects beans annotated with 041 * {@link ServerEndpoint} and registers them as well. Although not required, it is likely 042 * annotated endpoints should have their {@code configurator} property set to 043 * {@link SpringConfigurator}. 044 * 045 * <p>When this class is used, by declaring it in Spring configuration, it should be 046 * possible to turn off a Servlet container's scan for WebSocket endpoints. This can be 047 * done with the help of the {@code <absolute-ordering>} element in {@code web.xml}. 048 * 049 * @author Rossen Stoyanchev 050 * @author Juergen Hoeller 051 * @since 4.0 052 * @see ServerEndpointRegistration 053 * @see SpringConfigurator 054 * @see ServletServerContainerFactoryBean 055 */ 056public class ServerEndpointExporter extends WebApplicationObjectSupport 057 implements InitializingBean, SmartInitializingSingleton { 058 059 @Nullable 060 private List<Class<?>> annotatedEndpointClasses; 061 062 @Nullable 063 private ServerContainer serverContainer; 064 065 066 /** 067 * Explicitly list annotated endpoint types that should be registered on startup. This 068 * can be done if you wish to turn off a Servlet container's scan for endpoints, which 069 * goes through all 3rd party jars in the, and rely on Spring configuration instead. 070 * @param annotatedEndpointClasses {@link ServerEndpoint}-annotated types 071 */ 072 public void setAnnotatedEndpointClasses(Class<?>... annotatedEndpointClasses) { 073 this.annotatedEndpointClasses = Arrays.asList(annotatedEndpointClasses); 074 } 075 076 /** 077 * Set the JSR-356 {@link ServerContainer} to use for endpoint registration. 078 * If not set, the container is going to be retrieved via the {@code ServletContext}. 079 */ 080 public void setServerContainer(@Nullable ServerContainer serverContainer) { 081 this.serverContainer = serverContainer; 082 } 083 084 /** 085 * Return the JSR-356 {@link ServerContainer} to use for endpoint registration. 086 */ 087 @Nullable 088 protected ServerContainer getServerContainer() { 089 return this.serverContainer; 090 } 091 092 @Override 093 protected void initServletContext(ServletContext servletContext) { 094 if (this.serverContainer == null) { 095 this.serverContainer = 096 (ServerContainer) servletContext.getAttribute("javax.websocket.server.ServerContainer"); 097 } 098 } 099 100 @Override 101 protected boolean isContextRequired() { 102 return false; 103 } 104 105 @Override 106 public void afterPropertiesSet() { 107 Assert.state(getServerContainer() != null, "javax.websocket.server.ServerContainer not available"); 108 } 109 110 @Override 111 public void afterSingletonsInstantiated() { 112 registerEndpoints(); 113 } 114 115 116 /** 117 * Actually register the endpoints. Called by {@link #afterSingletonsInstantiated()}. 118 */ 119 protected void registerEndpoints() { 120 Set<Class<?>> endpointClasses = new LinkedHashSet<>(); 121 if (this.annotatedEndpointClasses != null) { 122 endpointClasses.addAll(this.annotatedEndpointClasses); 123 } 124 125 ApplicationContext context = getApplicationContext(); 126 if (context != null) { 127 String[] endpointBeanNames = context.getBeanNamesForAnnotation(ServerEndpoint.class); 128 for (String beanName : endpointBeanNames) { 129 endpointClasses.add(context.getType(beanName)); 130 } 131 } 132 133 for (Class<?> endpointClass : endpointClasses) { 134 registerEndpoint(endpointClass); 135 } 136 137 if (context != null) { 138 Map<String, ServerEndpointConfig> endpointConfigMap = context.getBeansOfType(ServerEndpointConfig.class); 139 for (ServerEndpointConfig endpointConfig : endpointConfigMap.values()) { 140 registerEndpoint(endpointConfig); 141 } 142 } 143 } 144 145 private void registerEndpoint(Class<?> endpointClass) { 146 ServerContainer serverContainer = getServerContainer(); 147 Assert.state(serverContainer != null, 148 "No ServerContainer set. Most likely the server's own WebSocket ServletContainerInitializer " + 149 "has not run yet. Was the Spring ApplicationContext refreshed through a " + 150 "org.springframework.web.context.ContextLoaderListener, " + 151 "i.e. after the ServletContext has been fully initialized?"); 152 try { 153 if (logger.isDebugEnabled()) { 154 logger.debug("Registering @ServerEndpoint class: " + endpointClass); 155 } 156 serverContainer.addEndpoint(endpointClass); 157 } 158 catch (DeploymentException ex) { 159 throw new IllegalStateException("Failed to register @ServerEndpoint class: " + endpointClass, ex); 160 } 161 } 162 163 private void registerEndpoint(ServerEndpointConfig endpointConfig) { 164 ServerContainer serverContainer = getServerContainer(); 165 Assert.state(serverContainer != null, "No ServerContainer set"); 166 try { 167 if (logger.isDebugEnabled()) { 168 logger.debug("Registering ServerEndpointConfig: " + endpointConfig); 169 } 170 serverContainer.addEndpoint(endpointConfig); 171 } 172 catch (DeploymentException ex) { 173 throw new IllegalStateException("Failed to register ServerEndpointConfig: " + endpointConfig, ex); 174 } 175 } 176 177}