You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

JSR356WebsocketInitializer.java 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. /*
  2. * Copyright 2000-2018 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.server.communication;
  17. import java.util.Collections;
  18. import java.util.Enumeration;
  19. import java.util.Map;
  20. import java.util.logging.Level;
  21. import java.util.logging.Logger;
  22. import javax.servlet.ServletConfig;
  23. import javax.servlet.ServletContext;
  24. import javax.servlet.ServletContextEvent;
  25. import javax.servlet.ServletContextListener;
  26. import javax.servlet.ServletRegistration;
  27. import javax.servlet.annotation.WebListener;
  28. import org.atmosphere.cpr.AtmosphereFramework;
  29. import com.vaadin.server.VaadinServlet;
  30. /**
  31. * Initializer class for JSR 356 websockets.
  32. * <p>
  33. * Websocket specification says that initialization of websocket end points
  34. * should be done in the servlet context initialization phase. Some servers
  35. * implement this strictly so that end points cannot be registered after the
  36. * context initialization phase.
  37. * <p>
  38. * Note that {@link WebListener} is Servlet 3.0 API so this will not be run for
  39. * older servers (unless added to web.xml), but these servers do not support JSR
  40. * 356 websockets either.
  41. *
  42. * @since 7.5.0
  43. * @author Vaadin Ltd
  44. */
  45. @WebListener
  46. public class JSR356WebsocketInitializer implements ServletContextListener {
  47. private static boolean atmosphereAvailable = false;
  48. static {
  49. try {
  50. org.atmosphere.util.Version.getRawVersion();
  51. atmosphereAvailable = true;
  52. } catch (NoClassDefFoundError e) {
  53. }
  54. }
  55. /**
  56. * "ServletConfig" which only provides information from a
  57. * {@link ServletRegistration} and its {@link ServletContext}.
  58. */
  59. public static class FakeServletConfig implements ServletConfig {
  60. private final ServletRegistration servletRegistration;
  61. private final ServletContext servletContext;
  62. public FakeServletConfig(ServletRegistration servletRegistration,
  63. ServletContext servletContext) {
  64. this.servletContext = servletContext;
  65. this.servletRegistration = servletRegistration;
  66. }
  67. @Override
  68. public String getServletName() {
  69. return servletRegistration.getName();
  70. }
  71. @Override
  72. public ServletContext getServletContext() {
  73. return servletContext;
  74. }
  75. @Override
  76. public String getInitParameter(String name) {
  77. return servletRegistration.getInitParameter(name);
  78. }
  79. @Override
  80. public Enumeration<String> getInitParameterNames() {
  81. return Collections.enumeration(
  82. servletRegistration.getInitParameters().keySet());
  83. }
  84. }
  85. @Override
  86. public void contextInitialized(ServletContextEvent sce) {
  87. ServletContext servletContext = sce.getServletContext();
  88. if (servletContext.getMajorVersion() < 3) {
  89. return;
  90. }
  91. init(servletContext);
  92. }
  93. /**
  94. * Initializes Atmosphere for use with Vaadin servlets found in the given
  95. * context.
  96. * <p>
  97. * For JSR 356 websockets to work properly, the initialization must be done
  98. * in the servlet context initialization phase.
  99. *
  100. * @param servletContext
  101. * The servlet context
  102. */
  103. public void init(ServletContext servletContext) {
  104. if (!atmosphereAvailable) {
  105. return;
  106. }
  107. Map<String, ? extends ServletRegistration> regs = servletContext
  108. .getServletRegistrations();
  109. for (String servletName : regs.keySet()) {
  110. ServletRegistration servletRegistration = regs.get(servletName);
  111. if (isVaadinServlet(servletRegistration, servletContext)) {
  112. try {
  113. initAtmosphereForVaadinServlet(servletRegistration,
  114. servletContext);
  115. } catch (Exception e) {
  116. getLogger().log(Level.WARNING,
  117. "Failed to initialize Atmosphere for "
  118. + servletName,
  119. e);
  120. }
  121. }
  122. }
  123. }
  124. /**
  125. * Initializes Atmosphere for use with the given Vaadin servlet
  126. * <p>
  127. * For JSR 356 websockets to work properly, the initialization must be done
  128. * in the servlet context initialization phase.
  129. *
  130. * @param servletRegistration
  131. * The servlet registration info for the servlet
  132. * @param servletContext
  133. */
  134. public static void initAtmosphereForVaadinServlet(
  135. ServletRegistration servletRegistration,
  136. ServletContext servletContext) {
  137. String servletName = servletRegistration.getName();
  138. String attributeName = getAttributeName(servletName);
  139. if (servletContext.getAttribute(attributeName) != null) {
  140. // Already initialized
  141. getLogger().warning("Atmosphere already initialized");
  142. return;
  143. }
  144. getLogger().finer("Creating AtmosphereFramework for " + servletName);
  145. AtmosphereFramework framework = PushRequestHandler.initAtmosphere(
  146. new FakeServletConfig(servletRegistration, servletContext));
  147. servletContext.setAttribute(attributeName, framework);
  148. getLogger().finer("Created AtmosphereFramework for " + servletName);
  149. }
  150. /**
  151. * Returns the name of the attribute in the servlet context where the
  152. * pre-initialized Atmosphere object is stored.
  153. *
  154. * @param servletName
  155. * The name of the servlet
  156. * @return The attribute name which contains the initialized Atmosphere
  157. * object
  158. */
  159. public static String getAttributeName(String servletName) {
  160. return JSR356WebsocketInitializer.class.getName() + "." + servletName;
  161. }
  162. /**
  163. * Checks if the given attribute name matches the convention used for
  164. * storing AtmosphereFramework references.
  165. *
  166. * @param attributeName
  167. * the attribute name to check
  168. * @return <code>true</code> if the attribute name matches the convention,
  169. * <code>false</code> otherwise
  170. */
  171. private static boolean isAtmosphereFrameworkAttribute(
  172. String attributeName) {
  173. return attributeName
  174. .startsWith(JSR356WebsocketInitializer.class.getName() + ".");
  175. }
  176. /**
  177. * Tries to determine if the given servlet registration refers to a Vaadin
  178. * servlet.
  179. *
  180. * @param servletRegistration
  181. * The servlet registration info for the servlet
  182. * @return false if the servlet is definitely not a Vaadin servlet, true
  183. * otherwise
  184. */
  185. protected boolean isVaadinServlet(ServletRegistration servletRegistration,
  186. ServletContext servletContext) {
  187. try {
  188. String servletClassName = servletRegistration.getClassName();
  189. if (servletClassName.equals("com.ibm.ws.wsoc.WsocServlet")) {
  190. // Websphere servlet which implements websocket endpoints,
  191. // dynamically added
  192. return false;
  193. }
  194. // Must use servletContext class loader to load servlet class to
  195. // work correctly in an OSGi environment (#20024)
  196. Class<?> servletClass = servletContext.getClassLoader()
  197. .loadClass(servletClassName);
  198. return VaadinServlet.class.isAssignableFrom(servletClass);
  199. } catch (Exception e) {
  200. // This will fail in OSGi environments, assume everything is a
  201. // VaadinServlet
  202. return true;
  203. }
  204. }
  205. private static final Logger getLogger() {
  206. return Logger.getLogger(JSR356WebsocketInitializer.class.getName());
  207. }
  208. @Override
  209. public void contextDestroyed(ServletContextEvent sce) {
  210. // Destroy any AtmosphereFramework instance we have initialized.
  211. // This must be done here to ensure that we cleanup Atmosphere instances
  212. // related to servlets which are never initialized
  213. ServletContext servletContext = sce.getServletContext();
  214. Enumeration<String> attributeNames = servletContext.getAttributeNames();
  215. while (attributeNames.hasMoreElements()) {
  216. String attributeName = attributeNames.nextElement();
  217. if (isAtmosphereFrameworkAttribute(attributeName)) {
  218. Object value = servletContext.getAttribute(attributeName);
  219. if (value instanceof AtmosphereFramework) {
  220. // This might result in calling destroy() twice, once from
  221. // here and once from PushRequestHandler but
  222. // AtmosphereFramework.destroy() deals with that
  223. ((AtmosphereFramework) value).destroy();
  224. }
  225. }
  226. }
  227. }
  228. /**
  229. * Checks if Atmosphere is available on the classpath.
  230. *
  231. * @return <code>true</code> if Atmosphere is available, <code>false</code>
  232. * otherwise
  233. */
  234. public static boolean isAtmosphereAvailable() {
  235. return atmosphereAvailable;
  236. }
  237. }