org.araqne.main.Araqne.java Source code

Java tutorial

Introduction

Here is the source code for org.araqne.main.Araqne.java

Source

/*
 * Copyright 2009 NCHOVY
 * 
 * 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.araqne.main;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.instrument.Instrumentation;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.apache.felix.framework.Felix;
import org.apache.felix.framework.util.FelixConstants;
import org.apache.felix.prefs.impl.PreferencesManager;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.DailyRollingFileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.PatternLayout;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.apache.sshd.SshServer;
import org.apache.sshd.common.Channel;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.future.CloseFuture;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.UserAuth;
import org.apache.sshd.server.auth.UserAuthPassword;
import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import org.apache.sshd.server.sftp.SftpSubsystem;
import org.araqne.account.AccountScriptFactory;
import org.araqne.api.BannerService;
import org.araqne.api.Environment;
import org.araqne.api.InstrumentationService;
import org.araqne.api.LoggerControlService;
import org.araqne.api.ScriptFactory;
import org.araqne.auth.AuthScriptFactory;
import org.araqne.auth.DefaultAuthService;
import org.araqne.auth.api.AuthService;
import org.araqne.bundle.BundleManagerService;
import org.araqne.bundle.BundleScript;
import org.araqne.bundle.BundleScriptFactory;
import org.araqne.confdb.ConfigService;
import org.araqne.confdb.file.FileConfigService;
import org.araqne.console.DefaultBannerService;
import org.araqne.console.TelnetCodecFactory;
import org.araqne.console.TelnetHandler;
import org.araqne.cron.CronService;
import org.araqne.cron.TickService;
import org.araqne.cron.impl.CronScriptFactory;
import org.araqne.cron.impl.CronServiceImpl;
import org.araqne.cron.impl.TickScriptFactory;
import org.araqne.cron.impl.TickServiceImpl;
import org.araqne.instrumentation.InstrumentationServiceImpl;
import org.araqne.keystore.KeyStoreScriptFactory;
import org.araqne.logger.AraqneLogService;
import org.araqne.logger.LogCleaner;
import org.araqne.logger.LoggerScriptFactory;
import org.araqne.pkg.PackageScriptFactory;
import org.araqne.script.ConfScriptFactory;
import org.araqne.script.CoreScriptFactory;
import org.araqne.script.HistoryScriptFactory;
import org.araqne.script.OsgiScriptFactory;
import org.araqne.script.OutputOnlyScriptContext;
import org.araqne.script.PerfScriptFactory;
import org.araqne.script.SunPerfScriptFactory;
import org.araqne.script.batch.BatchScriptFactory;
import org.araqne.ssh.SshCommandFactory;
import org.araqne.ssh.SshFileSystemFactory;
import org.araqne.ssh.SshPasswordAuthenticator;
import org.araqne.thread.ThreadScriptFactory;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.launch.Framework;
import org.osgi.service.log.LogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.impl.AraqneLoggerFactory;
import org.slf4j.impl.StaticLoggerBinder;

import sun.misc.Signal;
import sun.misc.SignalHandler;

/**
 * Create the OSGi world using apache felix framework. Araqne class acts as a
 * system bundle.
 * 
 * @author xeraph
 * 
 */
@SuppressWarnings("restriction")
public class Araqne implements BundleActivator, SignalHandler {
    private static BundleContext context = null;
    private static Felix felix = null;

    public static Instrumentation instrumentation = null;
    public static final long BOOT_TIME = System.currentTimeMillis();
    private Logger logger = null;

    private LogCleaner logCleaner;

    private PreferencesManager prefsManager;
    private ConfigService conf;
    private AuthService auth;
    private CronServiceImpl cron;
    private TickServiceImpl tick;
    private BannerService banner;

    // telnetd
    private NioSocketAcceptor acceptor;

    // sshd
    private SshServer sshd;

    private static boolean serviceMode = false;
    private static boolean restart;

    public static void reboot() {
        restart = true;
        try {
            felix.stop();
        } catch (BundleException e) {
            e.printStackTrace();
        }
    }

    // temporal access. remind cyclic dependency.
    public static BundleContext getContext() {
        return context;
    }

    public static void premain(String args, Instrumentation inst) {
        instrumentation = inst;
    }

    /**
     * Entry point.
     * 
     * @param args
     *            use system property instead.
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        int returnCode = 0;
        try {
            do {
                restart = false;
                startAraqne(new StartOptions(args));
                felix.waitForStop(0);

                if (restart) {
                    // ensure all system libraries unloaded
                    for (int i = 0; i < 5; i++)
                        System.gc();
                }
            } while (restart);
        } catch (Throwable t) {
            t.printStackTrace();
            returnCode = -1;
        } finally {
            System.exit(returnCode);
        }
    }

    private static Araqne araqne;

    private static void startAraqne(StartOptions startOptions) throws Exception {
        araqne = new Araqne();
        try {
            Signal signal = new Signal("TERM");
            Signal.handle(signal, araqne);
        } catch (Exception e) {
            System.out.println("Signal handling is only supported on Sun JVM");
        }

        araqne.boot(startOptions);
    }

    public static boolean isServiceMode() {
        return serviceMode;
    }

    public static void stopAraqne() throws Exception {
        context.getBundle(0).stop();
        felix.waitForStop(0);
    }

    public static void windowsService(String[] args) throws Exception {
        String cmd = "start";
        if (args.length > 0) {
            cmd = args[0];
        }

        if ("start".equals(cmd)) {
            new ServiceRunner(args).start();
        } else {
            stopAraqne();
        }
    }

    private static class ServiceRunner extends Thread {
        private String[] args;

        public ServiceRunner(String[] args) {
            super("Windows Service Runner");
            serviceMode = true;
            this.args = args;
        }

        @Override
        public void run() {
            int returnCode = 0;
            try {
                do {
                    restart = false;
                    startAraqne(new StartOptions(args));
                    felix.waitForStop(0);

                    if (restart) {
                        // ensure all system libraries unloaded
                        for (int i = 0; i < 5; i++)
                            System.gc();
                    }
                } while (restart);
            } catch (Throwable t) {
                t.printStackTrace();
                returnCode = -1;
            } finally {
                System.exit(returnCode);
            }
        }
    }

    @Override
    public void handle(Signal signal) {
        try {
            context.getBundle(0).stop();
        } catch (BundleException e) {
            e.printStackTrace();
        }
    }

    /**
     * Boot felix framework up.
     * 
     * @param startOptions
     * 
     * @throws Exception
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public void boot(StartOptions startOptions) throws Exception {
        File jarPath = new File(URLDecoder
                .decode(Araqne.class.getProtectionDomain().getCodeSource().getLocation().getPath(), "utf-8"));
        File dir = jarPath.getParentFile();

        Environment.setAraqneSystemProperties(dir.getAbsolutePath());

        setLogger();

        List activators = new ArrayList();
        activators.add(this);

        Map configMap = new HashMap<String, String>();
        Logger logger = LoggerFactory.getLogger(Felix.class.getName());
        configMap.put(FelixConstants.LOG_LOGGER_PROP, logger);
        configMap.put(FelixConstants.LOG_LEVEL_PROP, "3"); // INFO
        configMap.put(FelixConstants.SYSTEMBUNDLE_ACTIVATORS_PROP, activators);
        configMap.put(Constants.FRAMEWORK_SYSTEMPACKAGES, getSystemPackages());
        configMap.put(Constants.FRAMEWORK_STORAGE,
                new File(System.getProperty("araqne.cache.dir")).getAbsolutePath());

        configMap.put(Constants.FRAMEWORK_BOOTDELEGATION,
                "org.eclipse.tptp.martini,com.jprofiler.*,com.jprofiler.agent.*");

        felix = new Felix(configMap);
        if (startOptions.isDeveloperMode()) {
            felix.init();
            BundleContext bundleContext = felix.getBundleContext();
            BundleManagerService manager = new BundleManagerService(bundleContext);
            BundleScript script = new BundleScript(context, manager);
            script.setScriptContext(new OutputOnlyScriptContext(logger));
            script.updateAll(new String[] { "force" });
            script.refresh(new String[0]);
            bundleContext.removeBundleListener(manager);
        }
        felix.start();
    }

    public static Framework getFramework() {
        return felix;
    }

    /**
     * Load log4j.properties file from working directory by default. If you need
     * to change log4j configuration file's location, set log4j.configuration
     * system property.
     * 
     * @throws IOException
     */
    private void setLogger() throws IOException {
        if (new File("log4j.properties").exists()) {
            System.setProperty("log4j.configuration", "file:log4j.properties");
        } else {
            setDefaultLogging();
        }

        logger = LoggerFactory.getLogger(Araqne.class.getName());
    }

    /**
     * Fetch all default packages provided by JavaSE 1.6 environment. All OSGi
     * bundles can use JavaSE packages naturally by doing this. Some OSGi and
     * logging related packages also included.
     * 
     * @return the whole concatenated list of system packages.
     * @throws FileNotFoundException
     */
    private String getSystemPackages() throws FileNotFoundException {
        StringBuffer buffer = new StringBuffer(4096);
        BufferedReader reader = new BufferedReader(
                new InputStreamReader(ClassLoader.getSystemResourceAsStream("system.packages")));
        String s = null;
        try {
            while ((s = reader.readLine()) != null) {
                buffer.append(s);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return buffer.toString();
    }

    /**
     * Bind telnet port.
     * 
     * @throws IOException
     */
    private void startTelnetServer() throws IOException {
        InetAddress address = getConsoleBindAddress();
        int port = getConsolePortNumber();
        InetSocketAddress bindSocketAddress = new InetSocketAddress(address, port);

        acceptor = new NioSocketAcceptor();
        acceptor.getFilterChain().addLast("protocol", new ProtocolCodecFilter(new TelnetCodecFactory()));
        acceptor.setHandler(new TelnetHandler(context));
        acceptor.setReuseAddress(true);
        acceptor.bind(bindSocketAddress);
        logger.info("Telnet " + bindSocketAddress + " opened.");
    }

    private void stopTelnetServer() {
        acceptor.unbind();
        acceptor.dispose();
    }

    /**
     * Return localhost address by default. Use araqne.bind.address system
     * property if you need to connect telnet server from remote host.
     * 
     * @return the bind address
     * @throws UnknownHostException
     */
    private InetAddress getConsoleBindAddress() throws UnknownHostException {
        String telnetAddress = System.getProperty("araqne.telnet.address");
        if (telnetAddress == null)
            return InetAddress.getByName("localhost");

        return InetAddress.getByName(telnetAddress);
    }

    /**
     * 
     * @return the telnet port number
     */
    private int getConsolePortNumber() {
        int consolePort = 7004;
        try {
            // @deprecated
            consolePort = Integer.parseInt((String) System.getProperty("araqne.port"));
        } catch (Exception e) {
            // ignore
        }
        try {
            consolePort = Integer.parseInt((String) System.getProperty("araqne.telnet.port"));
        } catch (Exception e) {
            // ignore
        }

        return consolePort;
    }

    /*************************************************
     * BundleActivator interfaces
     *************************************************/

    /**
     * Start system bundle.
     */
    @Override
    public void start(BundleContext context) throws Exception {
        Araqne.context = context;

        logger.info("Booting Araqne.");
        startLogging();

        prefsManager = new PreferencesManager();
        prefsManager.start(context);
        conf = new FileConfigService();
        auth = new DefaultAuthService(context, conf);
        cron = new CronServiceImpl(context, conf);
        tick = new TickServiceImpl();
        tick.start();
        banner = new DefaultBannerService();

        registerScripts();
        registerServices();
        startTelnetServer();
        startSshServer();
        logger.info("Araqne started.");
    }

    /**
     * Register default araqne scripts.
     * 
     * @see Araqne API documentation
     */
    private void registerScripts() {
        // config service should be registered first
        registerScriptFactory(new ConfScriptFactory(conf), "conf");

        registerScriptFactory(CoreScriptFactory.class, "core");
        registerScriptFactory(BundleScriptFactory.class, "bundle");
        registerScriptFactory(LoggerScriptFactory.class, "logger");
        registerScriptFactory(OsgiScriptFactory.class, "osgi");
        registerScriptFactory(PackageScriptFactory.class, "pkg");
        registerScriptFactory(HistoryScriptFactory.class, "history");
        registerScriptFactory(ThreadScriptFactory.class, "thread");
        registerScriptFactory(PerfScriptFactory.class, "perf");
        registerScriptFactory(KeyStoreScriptFactory.class, "keystore");
        registerScriptFactory(new AccountScriptFactory(context, conf), "account");
        registerScriptFactory(SunPerfScriptFactory.class, "sunperf");
        registerScriptFactory(new AuthScriptFactory(auth), "auth");
        registerScriptFactory(new CronScriptFactory(context, cron), "cron");
        registerScriptFactory(new TickScriptFactory(tick), "tick");
        registerScriptFactory(BatchScriptFactory.class, "batch");
    }

    /**
     * Register script factory to OSGi service registry.
     * 
     * @param scriptFactory
     *            the script factory
     * @param alias
     *            the script alias (e.g. logger is alias in "logger.list"
     *            command)
     */
    private void registerScriptFactory(Class<? extends ScriptFactory> scriptFactory, String alias) {
        try {
            registerScriptFactory(scriptFactory.newInstance(), alias);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    private void registerScriptFactory(ScriptFactory scriptFactory, String alias) {
        Dictionary<String, Object> props = new Hashtable<String, Object>();
        props.put("alias", alias);
        context.registerService(ScriptFactory.class.getName(), scriptFactory, props);
    }

    private void registerServices() {
        context.registerService(BannerService.class.getName(), banner, null);
        context.registerService(InstrumentationService.class.getName(), new InstrumentationServiceImpl(), null);
        context.registerService(CronService.class.getName(), cron, null);
        context.registerService(TickService.class.getName(), tick, null);
    }

    /**
     * Stop system bundle.
     */
    @Override
    public void stop(BundleContext context) throws Exception {
        cron.invalidate();
        tick.stop();

        stopSshServer();
        stopTelnetServer();

        prefsManager.stop(context);

        stopLogging();
    }

    /**
     * Register OSGi log service to OSGi service registry, and start logging
     * thread. Logging thread will log using log4j logger and pass all logs to
     * connected log monitor.
     */
    private void startLogging() {
        context.registerService(new String[] { LogService.class.getName(), LoggerControlService.class.getName() },
                new AraqneLogService(), null);

        AraqneLoggerFactory araqneLoggerFactory = (AraqneLoggerFactory) StaticLoggerBinder.getSingleton()
                .getLoggerFactory();
        araqneLoggerFactory.start();

        enforceLogLevel("httpclient.wire");
        enforceLogLevel("org.apache.commons.httpclient");
    }

    private void enforceLogLevel(String loggerName) {
        org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(loggerName);
        logger.setLevel(Level.INFO);
    }

    /**
     * Stop the logging thread.
     */
    private void stopLogging() {
        AraqneLoggerFactory araqneLoggerFactory = (AraqneLoggerFactory) StaticLoggerBinder.getSingleton()
                .getLoggerFactory();
        araqneLoggerFactory.stop();
        logCleaner.close();
    }

    private void setDefaultLogging() throws IOException {
        org.apache.log4j.Logger rootLogger = org.apache.log4j.Logger.getRootLogger();
        if (!rootLogger.getAllAppenders().hasMoreElements()) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");
            System.out.println(String.format(
                    "[%s]  INFO (Araqne) - Default logging enabled. "
                            + "Configure log4j.properties file for custom logging.",
                    dateFormat.format(new Date())));

            String logPath = new File(System.getProperty("araqne.log.dir"), "araqne.log").getAbsolutePath();
            rootLogger.setLevel(Level.DEBUG);
            PatternLayout layout = new PatternLayout("[%d] %5p (%c{1}) - %m%n");
            rootLogger.addAppender(new ConsoleAppender(layout));
            rootLogger.addAppender(new DailyRollingFileAppender(layout, logPath, ".yyyy-MM-dd"));
            // rootLogger.addAppender(new AraqneFileAppender(layout, logPath,
            // ".yyyy-MM-dd"));
        }

        logCleaner = new LogCleaner();
        logCleaner.setDaemon(true);
        logCleaner.start();
    }

    private void startSshServer() throws IOException {
        org.apache.log4j.Logger sshLogger = org.apache.log4j.Logger
                .getLogger("org.apache.sshd.server.session.ServerSession");
        sshLogger.setLevel(Level.WARN);

        String sshAddress = System.getProperty("araqne.ssh.address");

        int port = 7022;
        int timeout = 600000;
        try {
            port = Integer.parseInt((String) System.getProperty("araqne.ssh.port"));
        } catch (Exception e) {
            // ignore
        }

        try {
            timeout = Integer.parseInt((String) System.getProperty("araqne.ssh.timeout"));
        } catch (Exception e) {
            // ignore
        }

        sshd = SshServer.setUpDefaultServer();
        sshd.setHost(sshAddress);
        sshd.setPort(port);
        sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("hostkey.pem"));
        sshd.setShellFactory(new SshCommandFactory());
        sshd.setPasswordAuthenticator(new SshPasswordAuthenticator());

        List<NamedFactory<UserAuth>> userAuthFactories = new ArrayList<NamedFactory<UserAuth>>();
        userAuthFactories.add(new UserAuthPassword.Factory());
        sshd.setUserAuthFactories(userAuthFactories);

        List<NamedFactory<Command>> namedFactories = new ArrayList<NamedFactory<Command>>();
        namedFactories.add(new SftpSubsystem.Factory());
        sshd.setSubsystemFactories(namedFactories);
        sshd.setFileSystemFactory(new SshFileSystemFactory());

        sshd.getProperties().put(SshServer.IDLE_TIMEOUT, Integer.toString(timeout));

        // walk-around of SshShell thread leak problem
        List<NamedFactory<Channel>> namedChannelFactories = new ArrayList<NamedFactory<Channel>>();
        namedChannelFactories.add(new NamedFactory<Channel>() {
            @Override
            public Channel create() {
                return new ChannelSession() {
                    @Override
                    public CloseFuture close(boolean immediately) {
                        if (command != null) {
                            command.destroy();
                            command = null;
                        }
                        return super.close(immediately);
                    }
                };
            }

            @Override
            public String getName() {
                return "session";
            }
        });
        sshd.setChannelFactories(namedChannelFactories);

        sshd.start();
    }

    private void stopSshServer() {
        try {
            sshd.stop();
        } catch (InterruptedException e) {
        }
    }
}