/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.everrest; import com.google.common.base.MoreObjects; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.everrest.core.DependencySupplier; import org.everrest.core.ResourceBinder; import org.everrest.core.impl.ApplicationProviderBinder; import org.everrest.core.impl.EverrestConfiguration; import org.everrest.core.impl.EverrestProcessor; import org.everrest.core.impl.ProviderBinder; import org.everrest.core.impl.RequestDispatcher; import org.everrest.core.impl.RequestHandlerImpl; import org.everrest.core.impl.provider.json.JsonException; import org.everrest.core.tools.SimplePrincipal; import org.everrest.core.tools.SimpleSecurityContext; import org.everrest.core.tools.WebApplicationDeclaredRoles; import org.everrest.websockets.message.BaseTextDecoder; import org.everrest.websockets.message.BaseTextEncoder; import org.everrest.websockets.message.JsonMessageConverter; import org.everrest.websockets.message.OutputMessage; import org.everrest.websockets.message.RestInputMessage; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.http.HttpSession; import javax.websocket.DecodeException; import javax.websocket.Decoder; import javax.websocket.DeploymentException; import javax.websocket.EncodeException; import javax.websocket.Encoder; import javax.websocket.HandshakeResponse; import javax.websocket.server.HandshakeRequest; import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerEndpointConfig; import javax.ws.rs.core.SecurityContext; import java.security.Principal; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static javax.websocket.server.ServerEndpointConfig.Builder.create; import static javax.websocket.server.ServerEndpointConfig.Configurator; /** * @author andrew00x */ public class ServerContainerInitializeListener implements ServletContextListener { public static final String ENVIRONMENT_CONTEXT = "ide.websocket." + EnvironmentContext.class.getName(); public static final String EVERREST_PROCESSOR_ATTRIBUTE = EverrestProcessor.class.getName(); public static final String HTTP_SESSION_ATTRIBUTE = HttpSession.class.getName(); public static final String EVERREST_CONFIG_ATTRIBUTE = EverrestConfiguration.class.getName(); public static final String EXECUTOR_ATTRIBUTE = "everrest.Executor"; public static final String SECURITY_CONTEXT = SecurityContext.class.getName(); private WebApplicationDeclaredRoles webApplicationDeclaredRoles; private EverrestConfiguration everrestConfiguration; private ServerEndpointConfig wsServerEndpointConfig; private ServerEndpointConfig eventbusServerEndpointConfig; private String websocketContext; private String websocketEndPoint; private String eventBusEndPoint; @Override public final void contextInitialized(ServletContextEvent sce) { final ServletContext servletContext = sce.getServletContext(); websocketContext = MoreObjects.firstNonNull(servletContext.getInitParameter("org.everrest.websocket.context"), ""); websocketEndPoint = MoreObjects.firstNonNull(servletContext.getInitParameter("org.eclipse.che.websocket.endpoint"), ""); eventBusEndPoint = MoreObjects.firstNonNull(servletContext.getInitParameter("org.eclipse.che.eventbus.endpoint"), ""); webApplicationDeclaredRoles = new WebApplicationDeclaredRoles(servletContext); everrestConfiguration = (EverrestConfiguration)servletContext.getAttribute(EVERREST_CONFIG_ATTRIBUTE); if (everrestConfiguration == null) { everrestConfiguration = new EverrestConfiguration(); } final ServerContainer serverContainer = (ServerContainer)servletContext.getAttribute("javax.websocket.server.ServerContainer"); try { wsServerEndpointConfig = createWsServerEndpointConfig(servletContext); eventbusServerEndpointConfig = createEventbusServerEndpointConfig(servletContext); serverContainer.addEndpoint(wsServerEndpointConfig); serverContainer.addEndpoint(eventbusServerEndpointConfig); } catch (DeploymentException e) { throw new IllegalStateException(e.getMessage(), e); } } @Override public void contextDestroyed(ServletContextEvent sce) { if (wsServerEndpointConfig != null) { ExecutorService executor = (ExecutorService)wsServerEndpointConfig.getUserProperties().get(EXECUTOR_ATTRIBUTE); if (executor != null) { executor.shutdownNow(); } } if (eventbusServerEndpointConfig != null) { ExecutorService executor = (ExecutorService)eventbusServerEndpointConfig.getUserProperties().get(EXECUTOR_ATTRIBUTE); if (executor != null) { executor.shutdownNow(); } } } protected ServerEndpointConfig createWsServerEndpointConfig(ServletContext servletContext) { final List<Class<? extends Encoder>> encoders = new LinkedList<>(); final List<Class<? extends Decoder>> decoders = new LinkedList<>(); encoders.add(OutputMessageEncoder.class); decoders.add(InputMessageDecoder.class); final ServerEndpointConfig endpointConfig = create(CheWSConnection.class, websocketContext + websocketEndPoint) .configurator(createConfigurator()).encoders(encoders).decoders(decoders).build(); endpointConfig.getUserProperties().put(EVERREST_PROCESSOR_ATTRIBUTE, getEverrestProcessor(servletContext)); endpointConfig.getUserProperties().put(EVERREST_CONFIG_ATTRIBUTE, getEverrestConfiguration(servletContext)); endpointConfig.getUserProperties().put(EXECUTOR_ATTRIBUTE, createExecutor(servletContext)); return endpointConfig; } protected ServerEndpointConfig createEventbusServerEndpointConfig(ServletContext servletContext) { final List<Class<? extends Encoder>> encoders = new LinkedList<>(); final List<Class<? extends Decoder>> decoders = new LinkedList<>(); encoders.add(OutputMessageEncoder.class); decoders.add(InputMessageDecoder.class); final ServerEndpointConfig endpointConfig = create(CheWSConnection.class, websocketContext + eventBusEndPoint) .configurator(createConfigurator()).encoders(encoders).decoders(decoders).build(); endpointConfig.getUserProperties().put(EVERREST_PROCESSOR_ATTRIBUTE, getEverrestProcessor(servletContext)); endpointConfig.getUserProperties().put(EVERREST_CONFIG_ATTRIBUTE, getEverrestConfiguration(servletContext)); endpointConfig.getUserProperties().put(EXECUTOR_ATTRIBUTE, createExecutor(servletContext)); return endpointConfig; } private Configurator createConfigurator() { return new Configurator() { @Override public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { super.modifyHandshake(sec, request, response); final HttpSession httpSession = (HttpSession)request.getHttpSession(); if (httpSession != null) { sec.getUserProperties().put(HTTP_SESSION_ATTRIBUTE, httpSession); } sec.getUserProperties().put(SECURITY_CONTEXT, createSecurityContext(request)); sec.getUserProperties().put(ENVIRONMENT_CONTEXT, EnvironmentContext.getCurrent()); } }; } protected EverrestProcessor getEverrestProcessor(ServletContext servletContext) { final DependencySupplier dependencies = (DependencySupplier)servletContext.getAttribute(DependencySupplier.class.getName()); final ResourceBinder resources = (ResourceBinder)servletContext.getAttribute(ResourceBinder.class.getName()); final ProviderBinder providers = (ProviderBinder)servletContext.getAttribute(ApplicationProviderBinder.class.getName()); final EverrestConfiguration copyOfEverrestConfiguration = new EverrestConfiguration(getEverrestConfiguration(servletContext)); copyOfEverrestConfiguration.setProperty(EverrestConfiguration.METHOD_INVOKER_DECORATOR_FACTORY, WebSocketMethodInvokerDecoratorFactory.class.getName()); final RequestHandlerImpl requestHandler = new RequestHandlerImpl(new RequestDispatcher(resources), providers); return new EverrestProcessor(copyOfEverrestConfiguration, dependencies, requestHandler, null); } protected EverrestConfiguration getEverrestConfiguration(ServletContext servletContext) { return everrestConfiguration; } protected ExecutorService createExecutor(final ServletContext servletContext) { final EverrestConfiguration everrestConfiguration = getEverrestConfiguration(servletContext); final String threadNameFormat = "everrest.WSConnection." + servletContext.getServletContextName() + "-%d"; return Executors.newFixedThreadPool(everrestConfiguration.getAsynchronousPoolSize(), new ThreadFactoryBuilder().setNameFormat(threadNameFormat) .setUncaughtExceptionHandler( LoggingUncaughtExceptionHandler.getInstance()) .setDaemon(true) .build()); } protected SecurityContext createSecurityContext(final HandshakeRequest req) { final boolean isSecure = false; //todo: get somehow from request final String authType = "BASIC"; final Subject subject = EnvironmentContext.getCurrent().getSubject(); final Principal principal = new SimplePrincipal(subject.getUserName()); return new SecurityContext() { @Override public Principal getUserPrincipal() { return principal; } @Override public boolean isUserInRole(String role) { return false; } @Override public boolean isSecure() { return isSecure; } @Override public String getAuthenticationScheme() { return authType; } }; } public static class InputMessageDecoder extends BaseTextDecoder<RestInputMessage> { private final JsonMessageConverter jsonMessageConverter = new JsonMessageConverter(); @Override public RestInputMessage decode(String s) throws DecodeException { try { return jsonMessageConverter.fromString(s, RestInputMessage.class); } catch (JsonException e) { throw new DecodeException(s, e.getMessage(), e); } } @Override public boolean willDecode(String s) { return true; } } public static class OutputMessageEncoder extends BaseTextEncoder<OutputMessage> { private final JsonMessageConverter jsonMessageConverter = new JsonMessageConverter(); @Override public String encode(OutputMessage output) throws EncodeException { try { return jsonMessageConverter.toString(output); } catch (JsonException e) { throw new EncodeException(output, e.getMessage(), e); } } } }