View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.websocket.jsr356.server.deploy;
20  
21  import java.util.HashSet;
22  import java.util.Set;
23  import javax.servlet.ServletContainerInitializer;
24  import javax.servlet.ServletContext;
25  import javax.servlet.ServletException;
26  import javax.servlet.annotation.HandlesTypes;
27  import javax.websocket.DeploymentException;
28  import javax.websocket.Endpoint;
29  import javax.websocket.server.ServerApplicationConfig;
30  import javax.websocket.server.ServerEndpoint;
31  import javax.websocket.server.ServerEndpointConfig;
32  
33  import org.eclipse.jetty.server.handler.ContextHandler;
34  import org.eclipse.jetty.servlet.ServletContextHandler;
35  import org.eclipse.jetty.util.TypeUtil;
36  import org.eclipse.jetty.util.log.Log;
37  import org.eclipse.jetty.util.log.Logger;
38  import org.eclipse.jetty.websocket.jsr356.server.ServerContainer;
39  import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter;
40  
41  @HandlesTypes(
42  { ServerApplicationConfig.class, ServerEndpoint.class, Endpoint.class })
43  public class WebSocketServerContainerInitializer implements ServletContainerInitializer
44  {
45      public static final String ENABLE_KEY = "org.eclipse.jetty.websocket.jsr356";
46      private static final Logger LOG = Log.getLogger(WebSocketServerContainerInitializer.class);
47  
48      /**
49       * Jetty Native approach.
50       * <p>
51       * Note: this will add the Upgrade filter to the existing list, with no regard for order.  It will just be tacked onto the end of the list.
52       */
53      public static ServerContainer configureContext(ServletContextHandler context) throws ServletException
54      {
55          // Create Filter
56          WebSocketUpgradeFilter filter = WebSocketUpgradeFilter.configureContext(context);
57  
58          // Create the Jetty ServerContainer implementation
59          ServerContainer jettyContainer = new ServerContainer(filter,filter.getFactory(),context.getServer().getThreadPool());
60          context.addBean(jettyContainer);
61  
62          // Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
63          context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);
64  
65          return jettyContainer;
66      }
67  
68      /**
69       * Servlet 3.1 approach.
70       * <p>
71       * This will use Servlet 3.1 techniques on the {@link ServletContext} to add a filter at the start of the filter chain.
72       */
73      public static ServerContainer configureContext(ServletContext context, ServletContextHandler jettyContext) throws ServletException
74      {
75          // Create Filter
76          WebSocketUpgradeFilter filter = WebSocketUpgradeFilter.configureContext(context);
77  
78          // Create the Jetty ServerContainer implementation
79          ServerContainer jettyContainer = new ServerContainer(filter,filter.getFactory(),jettyContext.getServer().getThreadPool());
80          jettyContext.addBean(jettyContainer);
81  
82          // Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
83          context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);
84  
85          return jettyContainer;
86      }
87      
88      private boolean isEnabled(Set<Class<?>> c, ServletContext context)
89      {
90          // Try context parameters first
91          String cp = context.getInitParameter(ENABLE_KEY);
92          if(TypeUtil.isTrue(cp))
93          {
94              // forced on
95              return true;
96          }
97          
98          if(TypeUtil.isFalse(cp))
99          {
100             // forced off
101             LOG.warn("JSR-356 support disabled via parameter on context {} - {}",context.getContextPath(),context);
102             return false;
103         }
104         
105         // Next, try attribute on context
106         Object enable = context.getAttribute(ENABLE_KEY);
107         
108         if(TypeUtil.isTrue(enable))
109         {
110             // forced on
111             return true;
112         }
113         
114         if (TypeUtil.isFalse(enable))
115         {
116             // forced off
117             LOG.warn("JSR-356 support disabled via attribute on context {} - {}",context.getContextPath(),context);
118             return false;
119         }
120         
121         // if not forced on or off, determine behavior based on annotations.
122         if (c.isEmpty())
123         {
124             if (LOG.isDebugEnabled())
125             {
126                 LOG.debug("No JSR-356 annotations or interfaces discovered. JSR-356 support disabled",context.getContextPath(),context);
127             }
128             return false;
129         }
130         
131         return true;
132     }
133 
134     @Override
135     public void onStartup(Set<Class<?>> c, ServletContext context) throws ServletException
136     {
137         if(!isEnabled(c,context))
138         {
139             return;
140         }
141         
142         ContextHandler handler = ContextHandler.getContextHandler(context);
143 
144         if (handler == null)
145         {
146             throw new ServletException("Not running on Jetty, JSR-356 support unavailable");
147         }
148 
149         if (!(handler instanceof ServletContextHandler))
150         {
151             throw new ServletException("Not running in Jetty ServletContextHandler, JSR-356 support unavailable");
152         }
153 
154         ServletContextHandler jettyContext = (ServletContextHandler)handler;
155 
156         ClassLoader old = Thread.currentThread().getContextClassLoader();
157         try
158         {
159             Thread.currentThread().setContextClassLoader(context.getClassLoader());
160 
161             // Create the Jetty ServerContainer implementation
162             ServerContainer jettyContainer = configureContext(context,jettyContext);
163 
164             // Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
165             context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);
166 
167             if (LOG.isDebugEnabled())
168             {
169                 LOG.debug("Found {} classes",c.size());
170             }
171 
172             // Now process the incoming classes
173             Set<Class<? extends Endpoint>> discoveredExtendedEndpoints = new HashSet<>();
174             Set<Class<?>> discoveredAnnotatedEndpoints = new HashSet<>();
175             Set<Class<? extends ServerApplicationConfig>> serverAppConfigs = new HashSet<>();
176 
177             filterClasses(c,discoveredExtendedEndpoints,discoveredAnnotatedEndpoints,serverAppConfigs);
178 
179             if (LOG.isDebugEnabled())
180             {
181                 LOG.debug("Discovered {} extends Endpoint classes",discoveredExtendedEndpoints.size());
182                 LOG.debug("Discovered {} @ServerEndpoint classes",discoveredAnnotatedEndpoints.size());
183                 LOG.debug("Discovered {} ServerApplicationConfig classes",serverAppConfigs.size());
184             }
185 
186             // Process the server app configs to determine endpoint filtering
187             boolean wasFiltered = false;
188             Set<ServerEndpointConfig> deployableExtendedEndpointConfigs = new HashSet<>();
189             Set<Class<?>> deployableAnnotatedEndpoints = new HashSet<>();
190 
191             for (Class<? extends ServerApplicationConfig> clazz : serverAppConfigs)
192             {
193                 if (LOG.isDebugEnabled())
194                 {
195                     LOG.debug("Found ServerApplicationConfig: {}",clazz);
196                 }
197                 try
198                 {
199                     ServerApplicationConfig config = clazz.newInstance();
200 
201                     Set<ServerEndpointConfig> seconfigs = config.getEndpointConfigs(discoveredExtendedEndpoints);
202                     if (seconfigs != null)
203                     {
204                         wasFiltered = true;
205                         deployableExtendedEndpointConfigs.addAll(seconfigs);
206                     }
207 
208                     Set<Class<?>> annotatedClasses = config.getAnnotatedEndpointClasses(discoveredAnnotatedEndpoints);
209                     if (annotatedClasses != null)
210                     {
211                         wasFiltered = true;
212                         deployableAnnotatedEndpoints.addAll(annotatedClasses);
213                     }
214                 }
215                 catch (InstantiationException | IllegalAccessException e)
216                 {
217                     throw new ServletException("Unable to instantiate: " + clazz.getName(),e);
218                 }
219             }
220 
221             // Default behavior if nothing filtered
222             if (!wasFiltered)
223             {
224                 deployableAnnotatedEndpoints.addAll(discoveredAnnotatedEndpoints);
225                 // Note: it is impossible to determine path of "extends Endpoint" discovered classes
226                 deployableExtendedEndpointConfigs = new HashSet<>();
227             }
228 
229             if (LOG.isDebugEnabled())
230             {
231                 LOG.debug("Deploying {} ServerEndpointConfig(s)",deployableExtendedEndpointConfigs.size());
232             }
233             // Deploy what should be deployed.
234             for (ServerEndpointConfig config : deployableExtendedEndpointConfigs)
235             {
236                 try
237                 {
238                     jettyContainer.addEndpoint(config);
239                 }
240                 catch (DeploymentException e)
241                 {
242                     throw new ServletException(e);
243                 }
244             }
245 
246             if (LOG.isDebugEnabled())
247             {
248                 LOG.debug("Deploying {} @ServerEndpoint(s)",deployableAnnotatedEndpoints.size());
249             }
250             for (Class<?> annotatedClass : deployableAnnotatedEndpoints)
251             {
252                 try
253                 {
254                     jettyContainer.addEndpoint(annotatedClass);
255                 }
256                 catch (DeploymentException e)
257                 {
258                     throw new ServletException(e);
259                 }
260             }
261         } finally {
262             Thread.currentThread().setContextClassLoader(old);
263         }
264     }
265 
266     @SuppressWarnings("unchecked")
267     private void filterClasses(Set<Class<?>> c, Set<Class<? extends Endpoint>> discoveredExtendedEndpoints, Set<Class<?>> discoveredAnnotatedEndpoints,
268             Set<Class<? extends ServerApplicationConfig>> serverAppConfigs)
269     {
270         for (Class<?> clazz : c)
271         {
272             if (ServerApplicationConfig.class.isAssignableFrom(clazz))
273             {
274                 serverAppConfigs.add((Class<? extends ServerApplicationConfig>)clazz);
275             }
276 
277             if (Endpoint.class.isAssignableFrom(clazz))
278             {
279                 discoveredExtendedEndpoints.add((Class<? extends Endpoint>)clazz);
280             }
281             
282             ServerEndpoint endpoint = clazz.getAnnotation(ServerEndpoint.class);
283 
284             if (endpoint != null)
285             {
286                 discoveredAnnotatedEndpoints.add(clazz);
287             }
288         }
289     }
290 }