blob: dcfc92bfe01441fdac0854a97b1c799c340adc14 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 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.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.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.NativeWebSocketConfiguration;
import org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer;
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";
public static final String ADD_DYNAMIC_FILTER_KEY = "org.eclipse.jetty.websocket.jsr356.addDynamicFilter";
private static final Logger LOG = Log.getLogger(WebSocketServerContainerInitializer.class);
/**
* Test a ServletContext for {@code init-param} or {@code attribute} at {@code keyName} for
* true or false setting that determines if the specified feature is enabled (or not).
*
* @param context the context to search
* @param keyName the key name
* @param defValue the default value, if the value is not specified in the context
* @return the value for the feature key
*/
public static boolean isEnabledViaContext(ServletContext context, String keyName, boolean defValue)
{
// Try context parameters first
String cp = context.getInitParameter(keyName);
if(cp != null)
{
if (TypeUtil.isTrue(cp))
{
return true;
}
if (TypeUtil.isFalse(cp))
{
return false;
}
return defValue;
}
// Next, try attribute on context
Object enable = context.getAttribute(keyName);
if(enable != null)
{
if (TypeUtil.isTrue(enable))
{
return true;
}
if (TypeUtil.isFalse(enable))
{
return false;
}
}
return defValue;
}
/**
* Embedded Jetty approach for non-bytecode scanning.
*/
public static ServerContainer configureContext(ServletContextHandler context) throws ServletException
{
// Create Basic components
NativeWebSocketConfiguration nativeWebSocketConfiguration = NativeWebSocketServletContainerInitializer.getDefaultFrom(context.getServletContext());
// Create the Jetty ServerContainer implementation
ServerContainer jettyContainer = new ServerContainer(nativeWebSocketConfiguration, context.getServer().getThreadPool());
context.addBean(jettyContainer);
// 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);
// Create Filter
if(isEnabledViaContext(context.getServletContext(), ADD_DYNAMIC_FILTER_KEY, true))
{
if (LOG.isDebugEnabled())
LOG.debug("Dynamic filter add to support JSR356/javax.websocket.server: {}", WebSocketUpgradeFilter.class.getName());
WebSocketUpgradeFilter.configureContext(context);
NativeWebSocketServletContainerInitializer.getDefaultFrom(context.getServletContext());
}
return jettyContainer;
}
/**
* @deprecated use {@link #configureContext(ServletContextHandler)} instead
*/
@Deprecated
public static ServerContainer configureContext(ServletContext context, ServletContextHandler jettyContext) throws ServletException
{
return configureContext(jettyContext);
}
@Override
public void onStartup(Set<Class<?>> c, ServletContext context) throws ServletException
{
if(!isEnabledViaContext(context, ENABLE_KEY, true))
{
LOG.info("JSR-356 is disabled by configuration");
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(jettyContext);
// 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);
if (c.isEmpty())
{
if (LOG.isDebugEnabled())
{
LOG.debug("No JSR-356 annotations or interfaces discovered");
}
return;
}
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);
}
}
}
}