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