blob: 9e1090f6928f9aa1b6f9f0473cd943cfdfcfafe2 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server.deploy;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
import javax.websocket.DeploymentException;
import javax.websocket.Endpoint;
import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.DecoratedObjectFactory;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.jsr356.server.ServerContainer;
import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter;
@HandlesTypes(
{ ServerApplicationConfig.class, ServerEndpoint.class, Endpoint.class })
public class WebSocketServerContainerInitializer implements ServletContainerInitializer
{
public static final String ENABLE_KEY = "org.eclipse.jetty.websocket.jsr356";
private static final Logger LOG = Log.getLogger(WebSocketServerContainerInitializer.class);
/**
* DestroyListener
*
*
*/
public static class ContextDestroyListener implements ServletContextListener
{
@Override
public void contextInitialized(ServletContextEvent sce)
{
//noop
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
//remove any ServerContainer beans
if (sce.getServletContext() instanceof ContextHandler.Context)
{
ContextHandler handler = ((ContextHandler.Context)sce.getServletContext()).getContextHandler();
ServerContainer bean = handler.getBean(ServerContainer.class);
if (bean != null)
handler.removeBean(bean);
}
//remove reference in attributes
sce.getServletContext().removeAttribute(javax.websocket.server.ServerContainer.class.getName());
}
}
/**
* Jetty Native approach.
* <p>
* 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.
*
* @param context the servlet context handler
* @return the created websocket server container
* @throws ServletException if unable to create the websocket server container
*/
public static ServerContainer configureContext(ServletContextHandler context) throws ServletException
{
// Create Filter
WebSocketUpgradeFilter filter = WebSocketUpgradeFilter.configureContext(context);
// Create the Jetty ServerContainer implementation
ServerContainer jettyContainer = new ServerContainer(filter,filter.getFactory(),context.getServer().getThreadPool());
context.addBean(jettyContainer, true);
// Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);
return jettyContainer;
}
/**
* Servlet 3.1 approach.
* <p>
* This will use Servlet 3.1 techniques on the {@link ServletContext} to add a filter at the start of the filter chain.
*
* @param context the servlet context
* @param jettyContext the jetty servlet context handler
* @return the created websocket server container
* @throws ServletException if unable to create the websocket server container
*/
public static ServerContainer configureContext(ServletContext context, ServletContextHandler jettyContext) throws ServletException
{
// Create Filter
WebSocketUpgradeFilter filter = WebSocketUpgradeFilter.configureContext(context);
// Create the Jetty ServerContainer implementation
ServerContainer jettyContainer = new ServerContainer(filter,filter.getFactory(),jettyContext.getServer().getThreadPool());
jettyContext.addBean(jettyContainer, true);
// Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);
return jettyContainer;
}
private boolean isEnabled(Set<Class<?>> c, ServletContext context)
{
// Try context parameters first
String cp = context.getInitParameter(ENABLE_KEY);
if(TypeUtil.isTrue(cp))
{
// forced on
return true;
}
if(TypeUtil.isFalse(cp))
{
// forced off
LOG.warn("JSR-356 support disabled via parameter on context {} - {}",context.getContextPath(),context);
return false;
}
// Next, try attribute on context
Object enable = context.getAttribute(ENABLE_KEY);
if(TypeUtil.isTrue(enable))
{
// forced on
return true;
}
if (TypeUtil.isFalse(enable))
{
// forced off
LOG.warn("JSR-356 support disabled via attribute on context {} - {}",context.getContextPath(),context);
return false;
}
// if not forced on or off, determine behavior based on annotations.
if (c.isEmpty())
{
if (LOG.isDebugEnabled())
{
LOG.debug("No JSR-356 annotations or interfaces discovered. JSR-356 support disabled",context.getContextPath(),context);
}
return false;
}
return true;
}
@Override
public void onStartup(Set<Class<?>> c, ServletContext context) throws ServletException
{
if(!isEnabled(c,context))
{
return;
}
ContextHandler handler = ContextHandler.getContextHandler(context);
if (handler == null)
{
throw new ServletException("Not running on Jetty, JSR-356 support unavailable");
}
if (!(handler instanceof ServletContextHandler))
{
throw new ServletException("Not running in Jetty ServletContextHandler, JSR-356 support unavailable");
}
ServletContextHandler jettyContext = (ServletContextHandler)handler;
ClassLoader old = Thread.currentThread().getContextClassLoader();
try
{
Thread.currentThread().setContextClassLoader(context.getClassLoader());
// Create the Jetty ServerContainer implementation
ServerContainer jettyContainer = configureContext(context,jettyContext);
context.addListener(new ContextDestroyListener()); //make sure context is cleaned up when the context stops
// Establish the DecoratedObjectFactory thread local
// for various ServiceLoader initiated components to use.
DecoratedObjectFactory instantiator = (DecoratedObjectFactory)context.getAttribute(DecoratedObjectFactory.ATTR);
if (instantiator == null)
{
LOG.info("Using WebSocket local DecoratedObjectFactory - none found in ServletContext");
instantiator = new DecoratedObjectFactory();
}
if (LOG.isDebugEnabled())
{
LOG.debug("Found {} classes",c.size());
}
// Now process the incoming classes
Set<Class<? extends Endpoint>> discoveredExtendedEndpoints = new HashSet<>();
Set<Class<?>> discoveredAnnotatedEndpoints = new HashSet<>();
Set<Class<? extends ServerApplicationConfig>> serverAppConfigs = new HashSet<>();
filterClasses(c,discoveredExtendedEndpoints,discoveredAnnotatedEndpoints,serverAppConfigs);
if (LOG.isDebugEnabled())
{
LOG.debug("Discovered {} extends Endpoint classes",discoveredExtendedEndpoints.size());
LOG.debug("Discovered {} @ServerEndpoint classes",discoveredAnnotatedEndpoints.size());
LOG.debug("Discovered {} ServerApplicationConfig classes",serverAppConfigs.size());
}
// Process the server app configs to determine endpoint filtering
boolean wasFiltered = false;
Set<ServerEndpointConfig> deployableExtendedEndpointConfigs = new HashSet<>();
Set<Class<?>> deployableAnnotatedEndpoints = new HashSet<>();
for (Class<? extends ServerApplicationConfig> clazz : serverAppConfigs)
{
if (LOG.isDebugEnabled())
{
LOG.debug("Found ServerApplicationConfig: {}",clazz);
}
try
{
ServerApplicationConfig config = clazz.newInstance();
Set<ServerEndpointConfig> seconfigs = config.getEndpointConfigs(discoveredExtendedEndpoints);
if (seconfigs != null)
{
wasFiltered = true;
deployableExtendedEndpointConfigs.addAll(seconfigs);
}
Set<Class<?>> annotatedClasses = config.getAnnotatedEndpointClasses(discoveredAnnotatedEndpoints);
if (annotatedClasses != null)
{
wasFiltered = true;
deployableAnnotatedEndpoints.addAll(annotatedClasses);
}
}
catch (InstantiationException | IllegalAccessException e)
{
throw new ServletException("Unable to instantiate: " + clazz.getName(),e);
}
}
// Default behavior if nothing filtered
if (!wasFiltered)
{
deployableAnnotatedEndpoints.addAll(discoveredAnnotatedEndpoints);
// Note: it is impossible to determine path of "extends Endpoint" discovered classes
deployableExtendedEndpointConfigs = new HashSet<>();
}
if (LOG.isDebugEnabled())
{
LOG.debug("Deploying {} ServerEndpointConfig(s)",deployableExtendedEndpointConfigs.size());
}
// Deploy what should be deployed.
for (ServerEndpointConfig config : deployableExtendedEndpointConfigs)
{
try
{
jettyContainer.addEndpoint(config);
}
catch (DeploymentException e)
{
throw new ServletException(e);
}
}
if (LOG.isDebugEnabled())
{
LOG.debug("Deploying {} @ServerEndpoint(s)",deployableAnnotatedEndpoints.size());
}
for (Class<?> annotatedClass : deployableAnnotatedEndpoints)
{
try
{
jettyContainer.addEndpoint(annotatedClass);
}
catch (DeploymentException e)
{
throw new ServletException(e);
}
}
}
finally
{
Thread.currentThread().setContextClassLoader(old);
}
}
@SuppressWarnings("unchecked")
private void filterClasses(Set<Class<?>> c, Set<Class<? extends Endpoint>> discoveredExtendedEndpoints, Set<Class<?>> discoveredAnnotatedEndpoints,
Set<Class<? extends ServerApplicationConfig>> serverAppConfigs)
{
for (Class<?> clazz : c)
{
if (ServerApplicationConfig.class.isAssignableFrom(clazz))
{
serverAppConfigs.add((Class<? extends ServerApplicationConfig>)clazz);
}
if (Endpoint.class.isAssignableFrom(clazz))
{
discoveredExtendedEndpoints.add((Class<? extends Endpoint>)clazz);
}
ServerEndpoint endpoint = clazz.getAnnotation(ServerEndpoint.class);
if (endpoint != null)
{
discoveredAnnotatedEndpoints.add(clazz);
}
}
}
}