org.chililog.server.workbench.WorkbenchService.java Source code

Java tutorial

Introduction

Here is the source code for org.chililog.server.workbench.WorkbenchService.java

Source

//
// Copyright 2010 Cinch Logic Pty Ltd.
//
// http://www.chililog.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package org.chililog.server.workbench;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.StringUtils;
import org.chililog.server.common.AppProperties;
import org.chililog.server.common.Log4JLogger;
import org.hornetq.api.core.TransportConfiguration;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.ChannelGroupFuture;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor;

/**
 * <p>
 * The WorkbenchService controls the embedded Netty web server used to provide ChiliLog's management, analysis and
 * notification functions to users.
 * </p>
 * 
 * <pre class="example">
 * // Start web server
 * WorkbenchService.getInstance().start();
 * 
 * // Stop web server
 * WorkbenchService.getInstance().stop();
 * </pre>
 * 
 * <p>
 * The web server's request handling pipeline is setup by {@link HttpServerPipelineFactory}. Two Netty thread pools are
 * used:
 * <ul>
 * <li>One for the channel bosses (the server channel acceptors). See NioServerSocketChannelFactory javadoc.</li>
 * <li>One for the accepted channels (called workers). See NioServerSocketChannelFactory javadoc.</li>
 * </ul>
 * </p>
 * <p>
 * Here's a description of how it all works from http://www.jboss.org/netty/community#nabble-td3434933.
 * </p>
 * 
 * <pre class="example">
 * For posterity, updated notes on Netty's concurrency architecture:
 * 
 * After calling ServerBootstrap.bind(), Netty starts a boss thread that just accepts new connections and registers them
 * with one of the workers from the worker pool in round-robin fashion (pool size defaults to CPU count). Each worker
 * runs its own select loop over just the set of keys that have been registered with it. Workers start lazily on demand
 * and run only so long as there are interested fd's/keys. All selected events are handled in the same thread and sent
 * up the pipeline attached to the channel (this association is established by the boss as soon as a new connection is
 * accepted).
 * 
 * All workers, and the boss, run via the executor thread pool; hence, the executor must support at least two
 * simultaneous threads.
 * 
 * A pipeline implements the intercepting filter pattern. A pipeline is a sequence of handlers. Whenever a packet is
 * read from the wire, it travels up the stream, stopping at each handler that can handle upstream events. Vice-versa
 * for writes. Between each filter, control flows back through the centralized pipeline, and a linked list of contexts
 * keeps track of where we are in the pipeline (one context object per handler).
 * </pre>
 * <p>
 * The pipeline uses {@link HttpRequestHandler} to route requests to services for processing. Routing is based on the
 * request URI. Example of servers are {@link EchoRequestHandler} and {@link StaticFileRequestHandler}.
 * </p>
 * 
 * <p>
 * We stopped using the {@link OrderedMemoryAwareThreadPoolExecutor} as a third level worker pool because it created <a
 * href="http://www.jboss.org/netty/community#nabble-td6303816">errors with compression</a> and <a
 * href="http://web.archiveorange.com/archive/v/ZVMdIF9d6poqpmuvDOuq">performance issues</a>. We've since removed it.
 * Not sure if we really need to run in another thread pool since we are using the Executors.newCachedThreadPool() which
 * creates a new thread as needed.
 * </p>
 * 
 * @author vibul
 * 
 */
public class WorkbenchService {

    private static Log4JLogger _logger = Log4JLogger.getLogger(WorkbenchService.class);
    private static final ChannelGroup _allChannels = new DefaultChannelGroup("WorkbenchWebServerManager");
    private ChannelFactory _channelFactory = null;

    /**
     * Returns the singleton instance for this class
     */
    public static WorkbenchService getInstance() {
        return SingletonHolder.INSTANCE;
    }

    /**
     * SingletonHolder is loaded on the first execution of Singleton.getInstance() or the first access to
     * SingletonHolder.INSTANCE, not before.
     * 
     * See http://en.wikipedia.org/wiki/Singleton_pattern
     */
    private static class SingletonHolder {

        public static final WorkbenchService INSTANCE = new WorkbenchService();
    }

    /**
     * <p>
     * Singleton constructor
     * </p>
     * <p>
     * If there is an exception, we log the error and exit because there's no point continuing without MQ client session
     * </p>
     * 
     * @throws Exception
     */
    private WorkbenchService() {
        return;
    }

    /**
     * Start the web server
     */
    public synchronized void start() {
        AppProperties appProperties = AppProperties.getInstance();

        if (_channelFactory != null) {
            _logger.info("Workbench Web Sever Already Started.");
            return;
        }

        if (!appProperties.getWorkbenchEnabled()) {
            _logger.info("Workbench Web Sever not enabled and will not be started.");
            return;
        }

        _logger.info("Starting Workbench Web Sever on " + appProperties.getWorkbenchHost() + ":"
                + appProperties.getWorkbenchPort() + "...");

        // Create socket factory
        int workerCount = appProperties.getWorkbenchNettyWorkerThreadPoolSize();
        if (workerCount == 0) {
            _channelFactory = new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
                    Executors.newCachedThreadPool());
        } else {
            _channelFactory = new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
                    Executors.newCachedThreadPool(), workerCount);
        }

        // Configure the server
        ServerBootstrap bootstrap = new ServerBootstrap(_channelFactory);

        // Setup thread pool to run our handler
        // It makes sure the events from the same Channel are executed sequentially; i.e. event 1 is executed before
        // event 2. However event 1 may be executed on a different thread to event 2.
        OrderedMemoryAwareThreadPoolExecutor pipelineExecutor = new OrderedMemoryAwareThreadPoolExecutor(
                appProperties.getWorkbenchNettyHandlerThreadPoolSize(),
                appProperties.getWorkbenchNettyHandlerThreadPoolMaxChannelMemorySize(),
                appProperties.getWorkbenchNettyHandlerThreadPoolMaxTotalMemorySize(),
                appProperties.getWorkbenchNettyHandlerThreadPoolKeepAliveSeconds(), TimeUnit.SECONDS,
                Executors.defaultThreadFactory());

        // Set up the event pipeline factory.
        bootstrap.setPipelineFactory(new HttpServerPipelineFactory(pipelineExecutor));

        // Bind and start to accept incoming connections.
        String[] hosts = TransportConfiguration.splitHosts(appProperties.getWorkbenchHost());
        for (String h : hosts) {
            if (StringUtils.isBlank(h)) {
                if (hosts.length == 1) {
                    h = "0.0.0.0";
                } else {
                    continue;
                }
            }

            SocketAddress address = h.equals("0.0.0.0") ? new InetSocketAddress(appProperties.getWorkbenchPort())
                    : new InetSocketAddress(h, appProperties.getWorkbenchPort());
            Channel channel = bootstrap.bind(address);
            _allChannels.add(channel);
        }

        _logger.info("Workbench Web Sever Started.");
    }

    /**
     * Stop the web server
     */
    public synchronized void stop() {
        _logger.info("Stopping Workbench Web Sever ...");

        ChannelGroupFuture future = _allChannels.close();
        future.awaitUninterruptibly();

        _channelFactory.releaseExternalResources();
        _channelFactory = null;

        _logger.info("Workbench Web Sever Stopped.");
    }

    /**
     * Returns the group holding all channels so we can shutdown without hanging
     */
    ChannelGroup getAllChannels() {
        return _allChannels;
    }

}