/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007-2009 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.launcher.felix;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.net.URL;
import java.rmi.RMISecurityManager;
import java.util.Map;

import javax.naming.Context;

import org.apache.felix.main.AutoProcessor;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.launch.FrameworkFactory;
import org.ow2.jonas.launcher.felix.util.IOUtils;
import org.ow2.jonas.launcher.felix.util.JOnASUtils;

/**
 * JOnAS Running on OSGi/Felix.
 * @author Guillaume Sauthier
 */
public class JOnAS {

    /**
     * OSGi Framework.
     */
    private Framework framework;

    /**
     * Property for the security manager.
     */
    private static final String SECURITY_MANAGER = "jonas.security.manager";

    /**
     * Creates a new configured framework instance.
     * @param forceCleanUp true if cache directory must be deleted.
     * @throws Exception cannot create the internal gateway.
     */
    public JOnAS(final boolean forceCleanUp) throws Exception {
        
        // RMI Security Manager
        boolean useSecurityManager = new Boolean(JOnASUtils.getServerProperty(SECURITY_MANAGER, "true").trim()).booleanValue();

        if (useSecurityManager) {
            if (System.getSecurityManager() == null) {
                System.setSecurityManager(new RMISecurityManager());
            }
        }

        // Init required System properties
        initializeSystemProperties();

        // Get a new provider
        IConfigurationProvider provider = getConfigurationProvider();

        // Retrieve the configuration
        Map<?, ?> configProps = provider.getConfiguration();

        // Delete the cache directory if requested
        if (forceCleanUp) {
            IOUtils.deleteDir(new File((String) configProps.get(Constants.FRAMEWORK_STORAGE)));
        }

        framework = getFrameworkFactory().newFramework(configProps);

        System.out.println("\nWelcome to OW2 JOnAS (Running on " + framework.getClass().getSimpleName() + " v" + framework.getVersion() + ").");
        System.out.println("-----------------------------------------------\n");


        framework.init();
        AutoProcessor.process(configProps, framework.getBundleContext());

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                try {
                    // Only stop if the system is still running
                    if (framework.getState() == Bundle.ACTIVE) {
                        framework.stop();
                    }
                } catch (Exception e) {
                    e.printStackTrace(System.err);
                }
            }
        });
    }

    /**
     * Start JOnAS on Felix.
     * @throws Exception Thrown if the start fails
     */
    public void start() throws Exception {
        // Start it !
        framework.start();

        startTransientBundles();

        // Wait for framework to stop to exit the VM.
        framework.waitForStop(0);
        System.exit(0);
    }

    /**
     * Stop JOnAS.
     * @throws Exception Thrown if the stop fails
     */
    public void stop() throws Exception {
        // Only stop if the system is still running
        if (framework.getState() == Bundle.ACTIVE) {
            framework.stop();
        }
    }

    /**
     * @return a new {@link IConfigurationProvider}.
     */
    protected IConfigurationProvider getConfigurationProvider() {
        // can be sub-classed
        return new DefaultConfigurationProvider();
    }

    /**
     * Start a new JOnAS.
     * @param args not used
     * @throws Exception if something failed
     */
    public static void main(final String[] args) throws Exception {
        JOnAS jonas = new JOnAS(Boolean.getBoolean("jonas.cache.clean"));
        jonas.start();
    }

    /**
     * Initialize required System properties (using reasonable default values
     * when needed).
     */
    protected void initializeSystemProperties() {
        // Set the current date to calculate the server startup time
        System.setProperty("jonas.start.date", Long.toString(System.currentTimeMillis()));

        // Sets the Wrappers that monolog will remove in order to print the
        // right caller
        System.setProperty("monolog.wrappers", "mx4j.log.CommonsLogger,mx4j.log.Logger,java.util.logging.Logger,"
                + "org.apache.commons.logging.impl.Jdk14Logger,"
                + "org.apache.juli.logging.impl.Jdk14Logger,"
                + "org.ow2.util.log.JDKLogger,org.apache.juli.logging.DirectJDKLog,"
                + "org.ow2.carol.util.configuration.TraceCarol,org.slf4j.impl.JCLLoggerAdapter");

        // Corba/JacORB Properties
        System.setProperty("org.omg.CORBA.ORBClass", "org.jacorb.orb.ORB");
        System.setProperty("org.omg.CORBA.ORBSingletonClass", "org.jacorb.orb.ORBSingleton");
        System.setProperty("org.omg.PortableInterceptor.ORBInitializerClass.standard_init",
                "org.jacorb.orb.standardInterceptors.IORInterceptorInitializer");
        // Corba/Carol Properties
        System.setProperty("javax.rmi.CORBA.PortableRemoteObjectClass", "org.ow2.carol.rmi.multi.MultiPRODelegate");
        System.setProperty("javax.rmi.CORBA.UtilClass", "org.ow2.carol.util.delegate.UtilDelegateImpl");

        // Naming properties
        System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.ow2.carol.jndi.intercept.spi.InterceptorInitialContextFactory");

        // Security Properties
        setDefaultPropertyIfNotSet("java.security.policy", IOUtils.getSystemPath(JOnASUtils.getJOnASBase(), "conf/java.policy"));
        setDefaultPropertyIfNotSet("java.security.auth.login.config", IOUtils.getSystemPath(JOnASUtils.getJOnASBase(),
                "conf/jaas.config"));

    }

    private FrameworkFactory getFrameworkFactory() throws Exception {
        URL url = getClass().getClassLoader().getResource("META-INF/services/org.osgi.framework.launch.FrameworkFactory");
        if (url != null) {
            BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
            try {
                for (String s = br.readLine(); s != null; s = br.readLine()) {
                    s = s.trim();
                    // Try to load first non-empty, non-commented line.
                    if ((s.length() > 0) && (s.charAt(0) != '#')) {
                        return (FrameworkFactory) Class.forName(s).newInstance();
                    }
                }
            } finally {
                if (br != null) {
                    br.close();
                }
            }
        }
        throw new Exception("Could not find framework factory.");
    }

    /**
     * If the property was already set, do not change its value, otherwise, use
     * the default.
     * @param key property name
     * @param def default property value
     */
    protected void setDefaultPropertyIfNotSet(final String key, final String def) {
        System.setProperty(key, System.getProperty(key, def));
    }

    /**
     * The bundle start operation is transient for TUI and GUI bundles.
     * @throws BundleException If bundles startup fails
     */
    protected void startTransientBundles() throws BundleException {
        Bundle[] bundles = framework.getBundleContext().getBundles();
        for (Bundle bundle : bundles) {
            // OSGi R3 bundle may not have the Bundle-SymbolicName manifest header
            String symbolicName = bundle.getSymbolicName();
            if (symbolicName != null) {

                if ("org.apache.felix.shell.tui".equals(symbolicName)) {
                    // Use Text UI ?
                    if (Boolean.getBoolean("jonas.felix.tui.enabled")) {
                        startTransient(bundle);
                    }
                }
                
                if (symbolicName.startsWith("org.apache.felix.shell.gui")) {
                    // Use GUI ?
                    if (Boolean.getBoolean("jonas.felix.gui.enabled")) {
                        startTransient(bundle);
                    }
                }
            }
        }
    }

    /**
     * The bundle start operation is transient for the given bundle.
     * @param bundle the given bundle.
     * @throws BundleException If bundle startup fails
     */
    protected void startTransient(final Bundle bundle) throws BundleException {
        bundle.start(Bundle.START_TRANSIENT);
    }

    /**
     * @return the Framework instance used by this launcher.
     */
    public Framework getFramework() {
        return framework;
    }

}
