org.eclipse.gyrex.admin.ui.internal.AdminUiActivator.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.gyrex.admin.ui.internal.AdminUiActivator.java

Source

/*******************************************************************************
 * Copyright (c) 2010, 2013 AGETO Service GmbH and others.
 * 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:
 *      Mike Tschierschke - initial API and implementation
 *******************************************************************************/
package org.eclipse.gyrex.admin.ui.internal;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.gyrex.admin.ui.internal.application.AdminApplicationConfiguration;
import org.eclipse.gyrex.admin.ui.internal.jetty.AdminServletHolder;
import org.eclipse.gyrex.admin.ui.internal.jetty.SimpleAdminLoginService;
import org.eclipse.gyrex.admin.ui.internal.servlets.AdminServletTracker;
import org.eclipse.gyrex.boot.internal.app.ServerApplication;
import org.eclipse.gyrex.common.runtime.BaseBundleActivator;
import org.eclipse.gyrex.monitoring.diagnostics.StatusTracker;
import org.eclipse.gyrex.server.Platform;

import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.session.HashSessionManager;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.application.ApplicationRunner;
import org.eclipse.rap.rwt.engine.RWTServlet;
import org.eclipse.swt.widgets.Display;

import org.osgi.framework.BundleContext;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The activator of the admin ui bundle. Serves also images.
 */
public class AdminUiActivator extends BaseBundleActivator {

    public static final String SYMBOLIC_NAME = "org.eclipse.gyrex.admin.ui"; //$NON-NLS-1$

    private static final String IMAGE_REGISTRY = SYMBOLIC_NAME + "#imageRegistry";
    private static final int DEFAULT_ADMIN_PORT = 3110;
    private static final Logger LOG = LoggerFactory.getLogger(AdminUiActivator.class);

    private static volatile AdminUiActivator instance;
    private static volatile Server server;

    private static final String PROPERTY_ADMIN_SECURE = "gyrex.admin.secure";
    private static final String PROPERTY_ADMIN_AUTH = "gyrex.admin.auth";

    /**
     * Returns an image descriptor for the image file at the given plug-in
     * relative path
     * 
     * @param path
     *            the path
     * @return the image descriptor
     */
    public static ImageDescriptor getImageDescriptor(final String path) {
        return ImageDescriptor.createFromURL(FileLocator.find(instance.getBundle(), new Path(path), null));
    }

    /**
     * Returns the instance.
     * 
     * @return the instance
     */
    public static AdminUiActivator getInstance() {
        final AdminUiActivator activator = instance;
        if (activator == null)
            throw new IllegalStateException("inactive");
        return activator;
    }

    private ApplicationRunner adminApplicationRunner;
    private StatusTracker statusTracker;
    private int adminPort;
    private String adminHost;

    /**
     * The constructor
     */
    public AdminUiActivator() {
        super(SYMBOLIC_NAME);
    }

    private void addNonSslConnector(final Server server) {
        final HttpConfiguration httpConfiguration = new HttpConfiguration();
        httpConfiguration.setSendServerVersion(false);
        httpConfiguration.setSendDateHeader(false);

        final ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));

        connector.setPort(adminPort);
        if (null != adminHost) {
            connector.setHost(adminHost);
        }
        connector.setIdleTimeout(60000);
        // TODO: (Jetty9?) connector.setLowResourcesConnections(20000);
        // TODO: (Jetty9?) connector.setLowResourcesMaxIdleTime(5000);
        // TODO: (Jetty9?) connector.setForwarded(true);

        // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=356988 for an issue
        // with configuring the connector

        server.addConnector(connector);
    }

    private void addSslConnector(final Server server) {

        try {

            final File keystoreFile = Platform.getStateLocation(AdminUiActivator.getInstance().getBundle())
                    .append("jettycerts").toFile();
            if (!keystoreFile.isFile()) {
                if (!keystoreFile.getParentFile().isDirectory() && !keystoreFile.getParentFile().mkdirs())
                    throw new IllegalStateException("Error creating directory for jetty ssl certificates");

                final InputStream stream = getBundle().getEntry("cert/jettycerts.jks").openStream();
                FileUtils.copyInputStreamToFile(stream, keystoreFile);
                IOUtils.closeQuietly(stream);
            }

            final SslContextFactory sslContextFactory = new SslContextFactory(keystoreFile.getCanonicalPath());
            sslContextFactory.setKeyStorePassword("changeit");
            sslContextFactory.setKeyManagerPassword("changeit");

            final HttpConfiguration httpConfiguration = new HttpConfiguration();
            httpConfiguration.setSendServerVersion(false);
            httpConfiguration.setSendDateHeader(false);
            httpConfiguration.setSecurePort(adminPort);

            final ServerConnector connector = new ServerConnector(server, sslContextFactory,
                    new HttpConnectionFactory(httpConfiguration));

            connector.setPort(adminPort);
            if (null != adminHost) {
                connector.setHost(adminHost);
            }
            connector.setIdleTimeout(60000);
            // TODO: (Jetty9?) connector.setLowResourcesConnections(20000);
            // TODO: (Jetty9?) connector.setLowResourcesMaxIdleTime(5000);
            // TODO: (Jetty9?) connector.setForwarded(true);

            server.addConnector(connector);
        } catch (final Exception e) {
            throw new IllegalStateException("Error configuring jetty ssl connector for admin ui.", e);
        }

    }

    private void configureContextWithServletsAndResources(final ServletContextHandler contextHandler)
            throws MalformedURLException, IOException {
        // configure context base directory (required for RAP/RWT resources)
        final IPath contextBase = Platform.getStateLocation(getBundle()).append("context");
        contextHandler.setBaseResource(Resource.newResource(contextBase.toFile()));

        // configure defaults for resources served by Jetty's DefaultServlet
        if (Platform.inDevelopmentMode()) {
            contextHandler.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "true");
            contextHandler.setInitParameter("org.eclipse.jetty.servlet.Default.useFileMappedBuffer", "false");
            contextHandler.setInitParameter("org.eclipse.jetty.servlet.Default.maxCachedFiles", "0");
        } else {
            contextHandler.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
            contextHandler.setInitParameter("org.eclipse.jetty.servlet.Default.maxCacheSize", "2000000");
            contextHandler.setInitParameter("org.eclipse.jetty.servlet.Default.maxCachedFileSize", "254000");
            contextHandler.setInitParameter("org.eclipse.jetty.servlet.Default.maxCachedFiles", "1000");
            contextHandler.setInitParameter("org.eclipse.jetty.servlet.Default.useFileMappedBuffer", "true");
        }

        // initialize and start RWT application
        adminApplicationRunner = new ApplicationRunner(new AdminApplicationConfiguration(),
                contextHandler.getServletContext());
        adminApplicationRunner.start();

        // serve admin application directly
        contextHandler.addServlet(new AdminServletHolder(new RWTServlet()), "/admin");

        // register additional static resources references in body html
        final ServletHolder staticResources = new AdminServletHolder(new DefaultServlet());
        staticResources.setInitParameter("resourceBase",
                FileLocator.resolve(FileLocator.find(getBundle(), new Path("html"), null)).toExternalForm());
        contextHandler.addServlet(staticResources, "/static/*");

        // redirect to admin
        contextHandler.addServlet(new AdminServletHolder(new HttpServlet() {
            private static final long serialVersionUID = 1L;

            @Override
            protected void doGet(final HttpServletRequest req, final HttpServletResponse resp)
                    throws ServletException, IOException {
                resp.sendRedirect("/admin");
            }
        }), "");

        // serve context resources (required for RAP/RWT resources)
        contextHandler.addServlet(new AdminServletHolder(new DefaultServlet()), "/*");

        // register Logback status servlet
        try {
            // note, we don't reference the class directly because the package import is optional
            final Class<?> servletClass = AdminUiActivator.getInstance().getBundle()
                    .loadClass("ch.qos.logback.classic.ViewStatusMessagesServlet");
            contextHandler.addServlet(new AdminServletHolder((Servlet) servletClass.newInstance()),
                    "/logbackstatus");
        } catch (final ClassNotFoundException | LinkageError e) {
            LOG.warn("Logback status servlet not available. {}", e.getMessage(), e);
        } catch (final Exception e) {
            LOG.error("An error occurred while registering the Logback status servlet. {}", e.getMessage(), e);
        }

        // allow extension using custom servlets
        final AdminServletTracker adminServletTracker = new AdminServletTracker(getBundle().getBundleContext(),
                contextHandler);
        contextHandler.addBean(new AbstractLifeCycle() {
            @Override
            protected void doStart() throws Exception {
                adminServletTracker.open();
            }

            @Override
            protected void doStop() throws Exception {
                adminServletTracker.close();
            }
        });
    }

    private SecurityHandler createSecurityHandler(final Handler baseHandler, final String username,
            final String password) {
        final ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
        final ConstraintMapping authenticationContraintMapping = new ConstraintMapping();
        final Constraint constraint = new Constraint(Constraint.__BASIC_AUTH, AdminServletHolder.ADMIN_ROLE);
        constraint.setAuthenticate(true);
        authenticationContraintMapping.setConstraint(constraint);
        authenticationContraintMapping.setPathSpec("/*");
        securityHandler.addConstraintMapping(authenticationContraintMapping);
        securityHandler.setAuthenticator(new BasicAuthenticator());
        securityHandler.setHandler(baseHandler);
        securityHandler.setLoginService(new SimpleAdminLoginService(username, password));
        return securityHandler;
    }

    private HashSessionManager createSessionManager() {
        final HashSessionManager sessionManager = new HashSessionManager();
        sessionManager.setMaxInactiveInterval(1200);
        sessionManager.setUsingCookies(false); // allows to use RAP in multiple tabs
        return sessionManager;
    }

    @Override
    protected void doStart(final BundleContext context) throws Exception {
        instance = this;

        try {
            // set to default admin port if null or not a number
            adminPort = Integer.valueOf(context.getProperty("gyrex.admin.http.port"));
        } catch (final NumberFormatException nfe) {
            adminPort = DEFAULT_ADMIN_PORT;
        }
        adminHost = context.getProperty("gyrex.admin.http.host");

        statusTracker = new StatusTracker(context);
        statusTracker.open();

        // start the admin server asynchronously
        final Job jettyStartJob = new Job("Start Jetty Admin Server") {

            @Override
            protected IStatus run(final IProgressMonitor monitor) {
                try {
                    startServer();
                } catch (final Exception e) {
                    LOG.error("Failed to start Jetty Admin server.", e);
                    ServerApplication.shutdown(new IllegalStateException("Unable to start Jetty admin server.", e));
                    return Status.CANCEL_STATUS;
                }
                return Status.OK_STATUS;
            }
        };
        jettyStartJob.setSystem(true);
        jettyStartJob.setPriority(Job.LONG);
        jettyStartJob.schedule();
    }

    @Override
    protected void doStop(final BundleContext context) throws Exception {
        instance = null;

        statusTracker.close();
        statusTracker = null;

        stopServer();
    }

    public ImageRegistry getImageRegistry() {
        // ImageRegistry must be session scoped in RAP
        ImageRegistry imageRegistry = (ImageRegistry) RWT.getUISession().getAttribute(IMAGE_REGISTRY);
        if (imageRegistry == null) {
            imageRegistry = new ImageRegistry(Display.getCurrent());
            AdminUiImages.initializeImageRegistry(imageRegistry);
            RWT.getUISession().setAttribute(IMAGE_REGISTRY, imageRegistry);
        }
        return imageRegistry;
    }

    public IStatus getSystemStatus() {
        final StatusTracker tracker = statusTracker;
        if (tracker == null)
            throw createBundleInactiveException();
        return tracker.getSystemStatus();
    }

    private void startServer() {
        try {
            server = new Server();

            if (Boolean.getBoolean(PROPERTY_ADMIN_SECURE)) {
                addSslConnector(server);
            } else {
                addNonSslConnector(server);
            }

            // tweak server
            server.setStopAtShutdown(true);
            server.setStopTimeout(5000);

            // set thread pool
            // TODO: (Jetty9?) final QueuedThreadPool threadPool = new QueuedThreadPool(5);
            // TODO: (Jetty9?) threadPool.setName("jetty-server-admin");
            // TODO: (Jetty9?) server.setThreadPool(threadPool);

            // create context
            final ServletContextHandler contextHandler = new ServletContextHandler();
            contextHandler.setSessionHandler(new SessionHandler(createSessionManager()));
            configureContextWithServletsAndResources(contextHandler);

            // enable authentication if configured
            final String authenticationPhrase = System.getProperty(PROPERTY_ADMIN_AUTH);
            if (Boolean.getBoolean(PROPERTY_ADMIN_SECURE) && StringUtils.isNotBlank(authenticationPhrase)) {
                final String[] segments = authenticationPhrase.split("/");
                if (segments.length != 3)
                    throw new IllegalArgumentException(
                            "Illegal authentication configuration. Must be three string separated by '/'");
                else if (!StringUtils.equals(segments[0], "BASIC"))
                    throw new IllegalArgumentException(
                            "Illegal authentication configuration. Only method 'BASIC' is supported. Found "
                                    + segments[0]);

                server.setHandler(createSecurityHandler(contextHandler, segments[1], segments[2]));
            } else {
                server.setHandler(contextHandler);
            }
            server.start();

        } catch (final Exception e) {
            throw new IllegalStateException("Error starting jetty for admin ui", e);
        }
    }

    private void stopServer() {
        try {
            adminApplicationRunner.stop();
            adminApplicationRunner = null;

            server.stop();
            server = null;
        } catch (final Exception e) {
            throw new IllegalStateException("Error stopping jetty for admin ui", e);
        }
    }
}