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 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. /*
  2. * Copyright 2000-2016 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. if (!atmosphereAvailable) {
  92. return;
  93. }
  94. Map<String, ? extends ServletRegistration> regs = servletContext
  95. .getServletRegistrations();
  96. for (String servletName : regs.keySet()) {
  97. ServletRegistration servletRegistration = regs.get(servletName);
  98. if (isVaadinServlet(servletRegistration, servletContext)) {
  99. try {
  100. initAtmosphereForVaadinServlet(servletRegistration,
  101. servletContext);
  102. } catch (Exception e) {
  103. getLogger().log(Level.WARNING,
  104. "Failed to initialize Atmosphere for "
  105. + servletName,
  106. e);
  107. }
  108. }
  109. }
  110. }
  111. /**
  112. * Initializes Atmosphere for use with the given Vaadin servlet
  113. * <p>
  114. * For JSR 356 websockets to work properly, the initialization must be done
  115. * in the servlet context initialization phase.
  116. *
  117. * @param servletRegistration
  118. * The servlet registration info for the servlet
  119. * @param servletContext
  120. */
  121. public static void initAtmosphereForVaadinServlet(
  122. ServletRegistration servletRegistration,
  123. ServletContext servletContext) {
  124. String servletName = servletRegistration.getName();
  125. String attributeName = getAttributeName(servletName);
  126. if (servletContext.getAttribute(attributeName) != null) {
  127. // Already initialized
  128. getLogger().warning("Atmosphere already initialized");
  129. return;
  130. }
  131. getLogger().finer("Creating AtmosphereFramework for " + servletName);
  132. AtmosphereFramework framework = PushRequestHandler.initAtmosphere(
  133. new FakeServletConfig(servletRegistration, servletContext));
  134. servletContext.setAttribute(attributeName, framework);
  135. getLogger().finer("Created AtmosphereFramework for " + servletName);
  136. }
  137. /**
  138. * Returns the name of the attribute in the servlet context where the
  139. * pre-initialized Atmosphere object is stored
  140. *
  141. * @param servletName
  142. * The name of the servlet
  143. * @return The attribute name which contains the initialized Atmosphere
  144. * object
  145. */
  146. public static String getAttributeName(String servletName) {
  147. return JSR356WebsocketInitializer.class.getName() + "." + servletName;
  148. }
  149. /**
  150. * Checks if the given attribute name matches the convention used for
  151. * storing AtmosphereFramework references.
  152. *
  153. * @param attributeName
  154. * the attribute name to check
  155. * @return <code>true</code> if the attribute name matches the convention,
  156. * <code>false</code> otherwise
  157. */
  158. private static boolean isAtmosphereFrameworkAttribute(
  159. String attributeName) {
  160. return attributeName
  161. .startsWith(JSR356WebsocketInitializer.class.getName() + ".");
  162. }
  163. /**
  164. * Tries to determine if the given servlet registration refers to a Vaadin
  165. * servlet.
  166. *
  167. * @param servletRegistration
  168. * The servlet registration info for the servlet
  169. * @return false if the servlet is definitely not a Vaadin servlet, true
  170. * otherwise
  171. */
  172. protected boolean isVaadinServlet(ServletRegistration servletRegistration,
  173. ServletContext servletContext) {
  174. try {
  175. String servletClassName = servletRegistration.getClassName();
  176. if (servletClassName.equals("com.ibm.ws.wsoc.WsocServlet")) {
  177. // Websphere servlet which implements websocket endpoints,
  178. // dynamically added
  179. return false;
  180. }
  181. // Must use servletContext class loader to load servlet class to
  182. // work correctly in an OSGi environment (#20024)
  183. Class<?> servletClass = servletContext.getClassLoader()
  184. .loadClass(servletClassName);
  185. return VaadinServlet.class.isAssignableFrom(servletClass);
  186. } catch (Exception e) {
  187. // This will fail in OSGi environments, assume everything is a
  188. // VaadinServlet
  189. return true;
  190. }
  191. }
  192. private static final Logger getLogger() {
  193. return Logger.getLogger(JSR356WebsocketInitializer.class.getName());
  194. }
  195. @Override
  196. public void contextDestroyed(ServletContextEvent sce) {
  197. // Destroy any AtmosphereFramework instance we have initialized.
  198. // This must be done here to ensure that we cleanup Atmosphere instances
  199. // related to servlets which are never initialized
  200. ServletContext servletContext = sce.getServletContext();
  201. Enumeration<String> attributeNames = servletContext.getAttributeNames();
  202. while (attributeNames.hasMoreElements()) {
  203. String attributeName = attributeNames.nextElement();
  204. if (isAtmosphereFrameworkAttribute(attributeName)) {
  205. Object value = servletContext.getAttribute(attributeName);
  206. if (value instanceof AtmosphereFramework) {
  207. // This might result in calling destroy() twice, once from
  208. // here and once from PushRequestHandler but
  209. // AtmosphereFramework.destroy() deals with that
  210. ((AtmosphereFramework) value).destroy();
  211. }
  212. }
  213. }
  214. }
  215. }