/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2004 Bull S.A.
 * Contact: jonas-team@objectweb.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.objectweb.jonas.server;


import java.io.File;
import java.util.Hashtable;
import java.util.Map;
import java.util.StringTokenizer;

import org.objectweb.jonas.common.JProp;

/**
 * This class create all the ClassLoader necessary for JOnAS.
 *
 * <code>
 *      system ($CLASSPATH)
 *         |
 *      commons
 *        /\
 *       /  \
 *      /    \
 *    apps   tools
 *      \
 *    catalina
 * </code>
 *
 * @author Guillaume Sauthier (initial developer)
 */
public class LoaderManager {

    /**
     * Singleton instance of LoaderManager
     */
    private static LoaderManager instance = null;

    /**
     * ClassLoader name for Commons libs
     */
    private static final String COMMONS = "loader.commons";

    /**
     * ClassLoader name for Tools libs
     */
    private static final String TOOLS   = "loader.tools";

    /**
     * ClassLoader name for Applications
     */
    private static final String APPS    = "loader.apps";

    /**
     * ClassLoader name for Catalina
     */
    private static final String CATALINA = "loader.catalina";

    /**
     * Catalina Bootstrap.jar (used to know if we're using external revision of Tomcat)
     */
    private static final String CATALINA_BOOTSTRAP_JAR = "bin" + File.separator + "bootstrap.jar";

    /**
     * Jetty org.mortbay.jetty.jar (used to know if we're using external revision of Jetty)
     */
    private static final String MORTBAY_JETTY_JAR = "lib" + File.separator + "org.mortbay.jetty.jar";

    /**
     * JProp instance
     */
    private JProp jp = null;

    /**
     * ClassLoaders
     */
    private Map loaders = null;

    /**
     * $JONAS_ROOT/lib
     */
    private static File jonasLib = null;

    /**
     * $JONAS_ROOT/lib/catalina
     */
    private static File catalinaLib = null;

    /**
     * $JONAS_ROOT/lib/jetty
     */
    private static File jettyLib = null;

    /**
     * $JONAS_BASE
     */
    private static String jonasBase = null;

    /**
     * $CATALINA_HOME
     */
    private static String catalinaHome = null;

    /**
     * $JETTY_HOME
     */
    private static String jettyHome = null;

    /**
     * true if JOnAS is launched with web container
     */
    private boolean hasWeb = false;

    /**
     * Use the default Classpath ?
     */
    private boolean useDefaultClassPath = false;

    /**
     * is the web container provided by Catalina
     */
    private boolean isCatalina = false;

    /**
     * ${jonas.root}/lib/commons
     */
    private File jonasCommonsDir;

    /**
     * ${jonas.root}/lib/tools
     */
    private File jonasToolsDir;

    /**
     * ${jonas.root}/lib/apps
     */
    private File jonasAppsDir;

    /**
     * Create a LoaderManager instance
     */
    private LoaderManager() {
        // Get the $JONAS_ROOT/lib directories
        String jonasRoot = System.getProperty("install.root");
        if (jonasRoot == null) {
            System.err.println("JONAS_ROOT is not set");
            System.exit(2);
        }

        jonasLib = new File(jonasRoot, "lib");
        jonasCommonsDir = new File(jonasLib, "commons");
        jonasToolsDir = new File(jonasLib, "tools");
        jonasAppsDir = new File(jonasLib, "apps");
        catalinaLib = new File(jonasLib, "catalina");
        jettyLib = new File(jonasLib, "jetty");

        // Init loaders map
        loaders = new Hashtable();
    }

    /**
     * @return Returns the unique instance of LoaderManager.
     */
    public static LoaderManager getInstance() {
        if (instance == null) {
            instance = new LoaderManager();
        }
        return instance;
    }

    /**
     * Initialize the LoaderManager with a JProp instance.
     *
     * @param props JProp used to configure JOnAS
     */
    public void init(JProp props) {
        jp = props;
        // Get $JONAS_BASE
        jonasBase = JProp.getJonasBase();

        useDefaultClassPath = jp.getValueAsBoolean("jonas.default.classloader", false);

        // find web services
        String[] services = props.getValueAsArray("jonas.services");
        if (services != null) {
            for (int i = 0; i < services.length; i++) {
                if (services[i].equals("web")) {
                    hasWeb = true;
                    String classname = props.getValue("jonas.service.web.class");
                    if (classname.matches(".*Catalina.*")) {
                        isCatalina = true;
                    }
                }
            }
        }

        catalinaHome = System.getProperty("catalina.home");
        jettyHome = System.getProperty("jetty.home");

        // Set in all case catalina.home and base to $JONAS_BASE
        // if library is found. Useful if a user want to run in
        // a same environment JOnAS/Tomcat and Tomcat with different
        // versions
        if (hasEmbeddedCatalina()) {
            System.setProperty("catalina.home", catalinaLib.getPath());
            System.setProperty("catalina.base", jonasBase);
            catalinaHome = catalinaLib.getPath();
        }

        // Set jetty.home to $JONAS_BASE if library is found
        // and if jetty.home isn't set.
        if (jettyHome == null) {
            if (hasEmbeddedJetty()) {
                System.setProperty("jetty.home", jettyLib.getPath());
                jettyHome = jettyLib.getPath();
            }
        }

    }

    /**
     * @return Returns true if using embedded catalina classes.
     */
    private boolean hasEmbeddedCatalina() {
        return new File(catalinaLib, CATALINA_BOOTSTRAP_JAR).exists();
    }

    /**
     * @return Returns true if using embedded jetty classes.
     */
    private boolean hasEmbeddedJetty() {
        return new File(jettyLib, MORTBAY_JETTY_JAR).exists();
    }

    /**
     * @return Returns the Commons ClassLoader
     * @throws Exception When ClassLoader cannot be created.
     */
    public JClassLoader getCommonsLoader() throws Exception {
        JClassLoader commons = getLoader(COMMONS);
        if (commons == null) {
            commons = createCommonsClassLoader();
            setLoader(COMMONS, commons);
        }
        return commons;
    }

    /**
     * @return returns the Tools ClassLoader
     * @throws Exception When ClassLoader cannot be created
     */
    public JClassLoader getToolsLoader() throws Exception {
        JClassLoader tools = getLoader(TOOLS);
        if (tools == null) {
            tools = createToolsClassLoader();
            setLoader(TOOLS, tools);
        }
        return tools;
    }

    /**
     * @return Returns the Application ClassLoader
     * @throws Exception When ClassLoader cannot be created
     */
    public JClassLoader getAppsLoader() throws Exception {
        JClassLoader apps = getLoader(APPS);
        if (apps == null) {
            apps = createAppsClassLoader();
            setLoader(APPS, apps);
        }
        return apps;
    }

    /**
     * @return Returns the Catalina ClassLoader
     * @throws Exception When ClassLoader cannot be created.
     */
    public JClassLoader getCatalinaLoader() throws Exception {
        JClassLoader catalina = getLoader(CATALINA);
        if (catalina == null) {
            catalina = createCatalinaClassLoader();
            setLoader(CATALINA, catalina);
        }
        return catalina;
    }

    /**
     * Return an array containing all the resources declared in the classpath
     * environment variable.
     *
     * @param xtracp XTRA classpath
     *
     * @return a list containing all the resources declared in the classpath
     * environment variable.
     *
     * @throws Exception if a resource cannot be found.
     */
    private static JURLs getClassPath(boolean xtracp) throws Exception {
        String cpprop = "java.class.path";
        if (xtracp) {
            cpprop = "jonas.classpath";
        }
        String cpValue = System.getProperty(cpprop);
        //System.out.println("classpath=" + cpValue); // DEBUG
        String sep = File.pathSeparator;
        JURLs jurls = new JURLs();
        StringTokenizer tokenizer = new StringTokenizer(cpValue, sep);
        while (tokenizer.hasMoreTokens()) {
            File file = new File(tokenizer.nextToken());
            if (file.isDirectory()) {
                jurls.addDir(file);
            } else {
                // TODO check for ${jonas.root}/lib/bootstrap
                // do not re-add bootstrap.jar inside classloader
                if (!file.getName().equals("ow_jonas_bootstrap.jar")) {
                    jurls.add(file);
                }
            }
        }
        return jurls;
    }

    /**
     * Returns a JClassLoader.
     *
     * @param name name of the loader
     *
     * @return Returns the JClassLoader
     */
    private JClassLoader getLoader(String name) {
        return (JClassLoader) loaders.get(name);
    }

    /**
     * Add a couple (name, ClassLoader).
     *
     * @param name ClassLoader name
     * @param loader the ClassLoader
     */
    private void setLoader(String name, JClassLoader loader) {
        loaders.put(name, loader);
    }

    /**
     * Constructs the Commons ClassLoader.
     *
     * @return Returns the Commons ClassLoader.
     * @throws Exception When ClassLoader cannot be created from CLASSPATH or resources cannot be added to URLs List.
     */
    private JClassLoader createCommonsClassLoader() throws Exception {

        JURLs jurls = new JURLs();

        // add classpath in loader
        jurls.merge(getClassPath(useDefaultClassPath));

        // Load $JONAS_ROOT/lib/commons/**/*.jar
        jurls.add(jonasCommonsDir, ".jar");

        // Load $JONAS_BASE/lib/commons
        File jonasBaseLibCommons = new File(jonasBase, "lib" + File.separator + "commons");
        if (jonasBaseLibCommons.exists()) {
            jurls.add(jonasBaseLibCommons, ".jar");
        }

        // add jonas.base/lib/ext only if jonas.base != jonas.root
        // jonas.base/lib/ext is added before jonas.root/lib/ext
        if (!System.getProperty("install.root").toLowerCase().equals(jonasBase.toLowerCase())) {
            File jonasBaseLibExt = new File(jonasBase + File.separator + "lib" + File.separator + "ext");
            if (jonasBaseLibExt.exists()) {
                jurls.add(jonasBaseLibExt, ".jar");
            }
        }

        // add jonas.root/lib/ext
        jurls.add(new File(jonasLib, "ext"), ".jar");

        if (hasWeb) {
            // resources for catalina have been put in apps ClassLoader

            // add resources for jetty if required
            if (jettyHome != null && !isCatalina) {
                File jettyLibDir = new File(jettyHome, "lib");
                File jettyExtDir = new File(jettyHome, "ext");
                File jettyExtraDir = new File(new File(jettyHome, "extra"), "lib");
                jurls.add(jettyLibDir, ".jar");
                jurls.add(jettyExtDir, ".jar");
                jurls.add(jettyExtraDir, "jsr77.jar");
                if (!hasEmbeddedJetty()) {
                    // load ${jonas.root}/jetty/lib/ow_jonas_jetty.jar
                    File jonasJetty5Glue = new File(new File(jettyLib, "lib"), "ow_jonas_jetty.jar");
                    jurls.add(jonasJetty5Glue);
                }
            }
            // else user has not set the webcontainer HOME var
        }

        return new JClassLoader("Commons", jurls.toURLs());
    }

    /**
     * Constructs the Tools ClassLoader using Commons ClassLoader as parent.
     *
     * @return Returns the Tools ClassLoader
     *
     * @throws Exception When resources cannot be added to URL list.
     */
    private JClassLoader createToolsClassLoader() throws Exception {
        JURLs jurls = new JURLs();

        jurls.add(jonasToolsDir, ".jar");

        // Load $JONAS_BASE/lib/toolds
        File jonasBaseLibTools = new File(jonasBase, "lib" + File.separator + "tools");
        if (jonasBaseLibTools.exists()) {
            jurls.add(jonasBaseLibTools, ".jar");
        }

        return new JClassLoader("Tools", jurls.toURLs(), getCommonsLoader());
    }

    /**
     * Constructs the Application ClassLoader with Commons ClassLoader as parent.
     * At this time, this is an empty ClassLoader
     *
     * @return the Application ClassLoader
     *
     * @throws Exception When resources cannot be added to URL list.
     */
    private JClassLoader createAppsClassLoader() throws Exception {
        JURLs jurls = new JURLs();

        // ${jonas.root}/lib/apps
        jurls.add(jonasAppsDir, ".jar");

        // Load $JONAS_BASE/lib/apps
        File jonasBaseLibApps = new File(jonasBase, "lib" + File.separator + "apps");
        if (jonasBaseLibApps.exists()) {
            jurls.add(jonasBaseLibApps, ".jar");
        }

        if (hasWeb) {
            // add resources for catalina if required
            if (catalinaHome != null && isCatalina) {
                File catalinaBinDir = new File(catalinaHome, "bin");
                File catalinaCommonLibDir = new File(catalinaHome, "common" + File.separator + "lib");
                File catalinaSharedLibDir = new File(catalinaHome, "shared" + File.separator + "lib");
                jurls.add(new File(catalinaBinDir, "bootstrap.jar"));
                jurls.add(catalinaCommonLibDir, ".jar");
                jurls.add(catalinaSharedLibDir, ".jar");
            }
        }
        return new JClassLoader("Apps", jurls.toURLs(), getCommonsLoader());
    }

    /**
     * Constructs the Catalina ClassLoader with Applications ClassLoader as parent.
     *
     * @return the Catalina ClassLoader
     *
     * @throws Exception When resources cannot be added to URL list.
     */
    private JClassLoader createCatalinaClassLoader() throws Exception {
        JURLs jurls = new JURLs();

        File jonasTomcat55Glue = new File(catalinaLib, "server" + File.separator + "lib" + File.separator + "ow_jonas_tomcat55.jar");

        // ${jonas.root}/lib/catalina/server/lib/ow_jonas_tomcat55.jar
        if (jonasTomcat55Glue.exists()) {
            jurls.add(jonasTomcat55Glue);
        }

        // ${catalina.home}/server/lib/*jar
        File catalinaServerLibDir = new File(catalinaHome, "server" + File.separator + "lib");
        jurls.add(catalinaServerLibDir, ".jar");


        return new JClassLoader("Catalina", jurls.toURLs(), getAppsLoader());
    }
}
