org.eclipse.gyrex.http.jetty.internal.JettyEngineApplication.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.gyrex.http.jetty.internal.JettyEngineApplication.java

Source

/*******************************************************************************
 * Copyright (c) 2011, 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:
 *     Gunnar Wagenknecht - initial API and implementation
 *******************************************************************************/
package org.eclipse.gyrex.http.jetty.internal;

import java.lang.management.ManagementFactory;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.gyrex.cloud.environment.INodeEnvironment;
import org.eclipse.gyrex.http.internal.application.gateway.IHttpGateway;
import org.eclipse.gyrex.http.jetty.admin.ChannelDescriptor;
import org.eclipse.gyrex.http.jetty.admin.ICertificate;
import org.eclipse.gyrex.http.jetty.admin.IJettyManager;
import org.eclipse.gyrex.http.jetty.internal.app.JettyGateway;
import org.eclipse.gyrex.http.jetty.internal.connectors.CertificateSslContextFactory;
import org.eclipse.gyrex.monitoring.metrics.MetricSet;
import org.eclipse.gyrex.preferences.CloudScope;
import org.eclipse.gyrex.server.Platform;
import org.eclipse.gyrex.server.settings.SystemSetting;

import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.jmx.MBeanContainer;
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.util.log.Log;
import org.eclipse.jetty.util.ssl.SslContextFactory;

import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceRegistration;

import org.apache.commons.lang.exception.ExceptionUtils;

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

public class JettyEngineApplication implements IApplication {

    private static final Logger LOG = LoggerFactory.getLogger(JettyEngineApplication.class);

    // OSGi Http Service suggest these properties for setting the default ports
    private static final String ORG_OSGI_SERVICE_HTTP_PORT = "org.osgi.service.http.port"; //$NON-NLS-1$
    //   private static final String ORG_OSGI_SERVICE_HTTP_PORT_SECURE = "org.osgi.service.http.port.secure"; //$NON-NLS-1$

    /** INTEGER */
    private static final SystemSetting<Integer> httpServicePort = SystemSetting
            .newIntegerSetting(ORG_OSGI_SERVICE_HTTP_PORT,
                    "Port for the OSGi HttpService to listen on (will only be used if no channels are configured).")
            .usingDefault(8080).create();

    /** Exit object indicating error termination */
    private static final Integer EXIT_ERROR = Integer.valueOf(1);

    private static final AtomicReference<CountDownLatch> stopSignalRef = new AtomicReference<CountDownLatch>(null);
    private static final AtomicReference<Throwable> jettyErrorRef = new AtomicReference<Throwable>();

    private static Map<MetricSet, ServiceRegistration<MetricSet>> metricsRegistrations = new ConcurrentHashMap<>();

    /**
     * Force a shutdown of the ZooKeeper gate.
     */
    public static void forceShutdown() {
        final CountDownLatch stopSignal = stopSignalRef.get();
        if (stopSignal != null) {
            stopSignal.countDown();
        }
    }

    public static ServiceRegistration<MetricSet> registerMetrics(final MetricSet metricSet) {
        final ServiceRegistration<MetricSet> metricsRegistration = HttpJettyActivator.getInstance()
                .getServiceHelper().registerService(MetricSet.class, metricSet, "Eclipse Gyrex",
                        metricSet.getDescription(), null, null);
        final ServiceRegistration<MetricSet> oldRegistration = metricsRegistrations.put(metricSet,
                metricsRegistration);
        if ((null != oldRegistration) && (oldRegistration != metricsRegistration)) {
            try {
                oldRegistration.unregister();
            } catch (final IllegalStateException e) {
                // ignore
            }
        }
        return metricsRegistration;
    }

    public static void unregisterMetrics(final MetricSet metrics) {
        final ServiceRegistration<MetricSet> registration = metricsRegistrations.remove(metrics);
        if (null != registration) {
            try {
                registration.unregister();
            } catch (final IllegalStateException e) {
                // ignore
            }
        }
    }

    private void configureServer(final Server server) {
        if (JettyDebug.engine) {
            LOG.debug("Configuring server {}", server);
        }

        // collect node properties for filtering
        final Map<String, Object> nodeProperties = getNodeProperties();

        // create channels
        final IJettyManager jettyManager = HttpJettyActivator.getInstance().getJettyManager();
        final Collection<ChannelDescriptor> channels = jettyManager.getChannels();
        if (!channels.isEmpty()) {
            for (final ChannelDescriptor channel : channels) {
                createConnector(server, channel, jettyManager, nodeProperties);
            }
        } else {
            // start a default channel in development mode
            // or if OSGi property is explicitly set (bug 358859)
            if (Platform.inDevelopmentMode()) {
                final int port = httpServicePort.get();
                LOG.info("No channels configured. Enabling default channel on port {} in development mode.", port);
                final ChannelDescriptor defaultChannel = new ChannelDescriptor();
                defaultChannel.setId("default");
                defaultChannel.setPort(port);
                createConnector(server, defaultChannel, jettyManager, nodeProperties);
            } else if (httpServicePort.isSet()) {
                final int port = httpServicePort.get();
                LOG.info("No channels configured. Enabling channel on port {} configured via system property '{}'.",
                        port, ORG_OSGI_SERVICE_HTTP_PORT);
                final ChannelDescriptor defaultChannel = new ChannelDescriptor();
                defaultChannel.setId("default");
                defaultChannel.setPort(port);
                createConnector(server, defaultChannel, jettyManager, nodeProperties);
            }
        }

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

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

    private void createConnector(final Server server, final ChannelDescriptor channel,
            final IJettyManager jettyManager, final Map<String, Object> nodeProperties) {
        if ((channel.getPort() <= 0) || (channel.getPort() > 65535)) {
            if (JettyDebug.engine) {
                LOG.debug("Ignoring disabled channel {}", channel);
            }
            return;
        }

        try {
            final String filter = channel.getNodeFilter();
            if (filter != null) {
                final Filter nodeFilter = FrameworkUtil.createFilter(filter);
                if (!nodeFilter.matches(nodeProperties)) {
                    if (JettyDebug.engine) {
                        LOG.debug("Ignoring channel {} which has a node filter that does not match this node.",
                                channel);
                    }
                    return;
                }
            }

            if (JettyDebug.engine) {
                LOG.debug("Configuring channel {}", channel);
            }

            SslContextFactory sslFactory = null;
            if (channel.isSecure()) {
                final ICertificate certificate = jettyManager.getCertificate(channel.getCertificateId());
                sslFactory = new CertificateSslContextFactory(certificate);
            }

            final HttpConfiguration httpConfig = new HttpConfiguration();
            if (null != channel.getSecureChannelId()) {
                final ChannelDescriptor secureChannel = jettyManager.getChannel(channel.getSecureChannelId());
                if (secureChannel != null) {
                    httpConfig.setSecurePort(secureChannel.getPort());
                    httpConfig.setSecureScheme(HttpScheme.HTTPS.asString());
                }
            }

            final ServerConnector connector = createJettyConnector(server, sslFactory, httpConfig);
            connector.setPort(channel.getPort());
            connector.setIdleTimeout(200000);
            // TODO: (Jetty9?) connector.setAcceptors(2);
            // TODO: (Jetty9?) connector.setStatsOn(false);
            // TODO: (Jetty9?) connector.setLowResourcesMaxIdleTime(5000);
            // TODO: (Jetty9?) connector.setForwarded(true);
            // TODO: (Jetty9?) if (connector instanceof SelectChannelConnector) {
            // TODO: (Jetty9?)    ((SelectChannelConnector) connector).setLowResourcesConnections(20000);
            // TODO: (Jetty9?) }

            server.addConnector(connector);
        } catch (final Exception e) {
            LOG.warn("Error configuring channel {}. Please check the channel configuration. {}", channel.getId(),
                    ExceptionUtils.getRootCauseMessage(e));
        }
    }

    private ServerConnector createJettyConnector(final Server server, final SslContextFactory sslFactory,
            final HttpConfiguration httpConfig) {
        try {
            // use SPDY if NPN is available
            final Class<?> npnClass = HttpJettyActivator.getInstance().getBundle()
                    .loadClass("org.eclipse.jetty.npn.NextProtoNego");
            if (npnClass.getClassLoader() == null) {
                // NPN is available; try loading the SPDY connector
                // note, we still use reflection because SPDY is optional
                final Class<?> spdyConnectorClass = HttpJettyActivator.getInstance().getBundle()
                        .loadClass("org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnector");
                // HTTPSPDYServerConnector(Server server, HttpConfiguration config, SslContextFactory sslContextFactory, Map<Short, PushStrategy> pushStrategies)
                final Constructor<?> constructor = spdyConnectorClass.getConstructor(Server.class,
                        HttpConfiguration.class, SslContextFactory.class, Map.class);
                return (ServerConnector) constructor.newInstance(server, httpConfig, sslFactory, null);
            } else {
                // log error and fall through; will force standard connector belog
                LOG.error(
                        "Jetty NPN not loaded via boot class loader. Falling back to non-SPDY setup. Please check your server setup (see http://wiki.eclipse.org/Jetty/Feature/NPN for details)! ({})",
                        npnClass.getClassLoader());
            }
        } catch (AssertionError | LinkageError | ClassNotFoundException e) {
            if (JettyDebug.engine) {
                LOG.debug("Jetty SPDY environment not available: {}", ExceptionUtils.getRootCauseMessage(e), e);
            }
        } catch (final Exception e) {
            LOG.error("Error loading the Jetty SPDY implementation. {}", ExceptionUtils.getRootCauseMessage(e), e);
        }
        LOG.info("Jetty SPDY environment not available. Using non SPDY implementation.");
        return new ServerConnector(server, sslFactory, new HttpConnectionFactory(httpConfig));
    }

    private Map<String, Object> getNodeProperties() {
        final INodeEnvironment nodeEnvironment = HttpJettyActivator.getInstance().getNodeEnvironment();
        final Map<String, Object> nodeProperties = new HashMap<String, Object>(2);
        nodeProperties.put("id", nodeEnvironment.getNodeId());
        final Set<String> tags = nodeEnvironment.getTags();
        if (!tags.isEmpty()) {
            nodeProperties.put("tag", tags.toArray(new String[tags.size()]));
        }
        return nodeProperties;
    }

    boolean isActive() {
        final CountDownLatch stopSignal = stopSignalRef.get();
        return (stopSignal != null) && (stopSignal.getCount() > 0);
    }

    void signalStopped(final Throwable jettyError) {
        if (JettyDebug.engine) {
            LOG.debug("Received stop signal for Jetty engine.");
        }
        final CountDownLatch signal = stopSignalRef.get();
        if (null != signal) {
            jettyErrorRef.set(jettyError);
            signal.countDown();
        }
    }

    @Override
    public Object start(final IApplicationContext context) throws Exception {
        if (JettyDebug.engine) {
            LOG.debug("Starting Jetty engine.");
        }

        // set stop signal
        final CountDownLatch stopSignal = new CountDownLatch(1);
        if (!stopSignalRef.compareAndSet(null, stopSignal))
            throw new IllegalStateException("Jetty engine already running!");

        try {
            // FIXME timing issue with "ON_CLOUD_CONNECT" and ZooKeeperBasedPreferences
            // there is a bit of a timing issue here; we need to wait a bit in order
            // for the PlatformPreferences to be available
            int timeout = 5000;
            while (timeout > 0) {
                try {
                    CloudScope.INSTANCE.getNode(HttpJettyActivator.SYMBOLIC_NAME);
                    break;
                } catch (final IllegalStateException e) {
                    if (JettyDebug.engine) {
                        LOG.debug("Platform preferences not available. Jetty start will be delayed.");
                        try {
                            timeout -= 500;
                            Thread.sleep(500);
                        } catch (final Exception e1) {
                            // interrupted
                            Thread.currentThread().interrupt();
                        }
                    }
                }
            }

            // initialize (but do not start) the Jetty server
            final Server server = new Server();

            // enable Jetty JMX support
            final MBeanContainer mbContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
            server.addBean(mbContainer);

            // register Jetty loggers as MBeans
            mbContainer.beanAdded(server, Log.getRootLogger());

            // create gateway
            JettyGateway gateway = new JettyGateway(server);

            // tweak server config
            configureServer(server);

            // start the server
            server.start();

            // don't expose too detailed version info
            // (must be set after server started)
            HttpGenerator.setServerVersion("7");

            if (JettyDebug.engine) {
                LOG.debug("Jetty server started!");
                LOG.debug(server.dump());
            }

            // activate HTTP gateway
            final ServiceRegistration<IHttpGateway> gatewayServiceRegistration = HttpJettyActivator.getInstance()
                    .getServiceHelper().registerService(IHttpGateway.class, gateway, "Eclipse Gyrex",
                            "Jetty based HTTP gateway.", null, null);

            if (JettyDebug.engine) {
                LOG.debug("Jetty HTTP gateway registered!");
                LOG.debug(server.dump());
            }

            // signal running
            context.applicationRunning();

            // wait for stop
            try {
                stopSignal.await();
            } catch (final InterruptedException e) {
                Thread.currentThread().interrupt();
            }

            // remove gateway
            gatewayServiceRegistration.unregister();

            // remove metrics
            while (!metricsRegistrations.isEmpty()) {
                for (final MetricSet metric : metricsRegistrations.keySet()) {
                    unregisterMetrics(metric);
                }
            }

            // shutdown Jetty
            try {
                server.stop();
            } catch (final Exception e) {
                if (JettyDebug.debug) {
                    LOG.warn("Error while stopping Jetty. {}",
                            new Object[] { ExceptionUtils.getRootCauseMessage(e), e });
                } else {
                    LOG.warn("Error while stopping Jetty. {}", ExceptionUtils.getRootCauseMessage(e));
                }
            }

            // destroy gateway
            if (null != gateway) {
                gateway.close();
                gateway = null;
            }

            if (JettyDebug.engine) {
                LOG.debug("Jetty engine shutdown complete.");
            }

            // exit
            final Throwable error = jettyErrorRef.getAndSet(null);
            return error == null ? IApplication.EXIT_OK : EXIT_ERROR;
        } catch (final Exception e) {
            // shutdown the whole server when Jetty does not come up
            LOG.error("Unable to start Jetty. Please check the log files. System will be shutdown.", e);
            //         ServerApplication.signalShutdown(new Exception("Could not start the Jetty server. " + ExceptionUtils.getRootCauseMessage(e), e));
            return EXIT_ERROR;
        } finally {
            // done, now reset signal to allow further starts
            stopSignalRef.compareAndSet(stopSignal, null);
        }
    }

    @Override
    public void stop() {
        signalStopped(null);
    }

}