/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the License).  You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */

package com.sun.enterprise.server;

import java.io.File;
import java.io.FilenameFilter;
import java.lang.reflect.Method;

import java.net.MalformedURLException;
import java.net.URL;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.*;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.sun.appserv.server.util.ASURLClassLoader;
import com.sun.appserv.server.util.ClassLoaderChain;
import com.sun.enterprise.util.ASenvPropertyReader;
import com.sun.enterprise.util.SystemPropertyConstants;


/**
 * New Start up class for PE/RI. For AS9 this is set as the default 
 * <main-class> in processLauncher.xml. 
 * 
 * To disable the new classloader hierarchy the following needs to be done:
 * - Modify the system property '-Dcom.sun.aas.processName="as9-server"' to
 *   '-Dcom.sun.aas.processName="s1as8-server' in the startserv/stopserv scripts
 * - Modify system-classpath attribute in the relevant java-config to 
 *    if PE: "com.sun.aas.classloader.serverClassPath" element in processLauncher.xml
 *    if EE: "com.sun.aas.classloader.serverClassPath.ee" element in processLauncher.xml
 * 
 * @author Harsha RA, Sivakumar Thyagarajan
 */

public class PELaunch {
    
    //The new ClassLoader Hierarchy would be enabled only when this system 
    //property is set. 
    public static final String USE_NEW_CLASSLOADER_PROPERTY 
                                    = "com.sun.aas.useNewClassLoader";
    
    public static final String PROPERTIES_FILES 
                                    = "processLauncher.properties";

    //These properties are set by the ProcessLauncher in the new classloader
    //scheme. The classpath prefix and server classpath are prefixed to the
    //shared classloader chain. The classpath suffix is suffixed to the shared classloader
    //chain
    private  static final String CLASSPATH_PREFIX_PROPERTY 
                                    = "com.sun.aas.ClassPathPrefix";
    private  static final String CLASSPATH_SUFFIX_PROPERTY 
                                    = "com.sun.aas.ClassPathSuffix";
    private  static final String SERVER_CLASSPATH_PROPERTY 
                                    = "com.sun.aas.ServerClassPath";

    private static final String fileSeparator = File.separator;
    private static final String pathSeparator = File.pathSeparator;
    
    private static String installRoot = null;
    private static String javaHome =  null;

    private static List<String> _appserverClasspath = null;
    private static List<String> _sharedClasspath = null;
    private static List<String> _optionalClasspath = null;

    private static ASURLClassLoader _sharedClassLoader = null;
    private static ClassLoaderChain _optionalChain = null;
    private static ClassLoaderChain _asChain = null;
    
    private static final boolean  bDebug = new Boolean(
                    System.getProperty("com.sun.aas.useNewClassLoader.debug", "false")).booleanValue();


    //By default we set the new classloader hierarchy as the default 
    static {
        if (System.getProperty(USE_NEW_CLASSLOADER_PROPERTY) == null) {
            System.setProperty(USE_NEW_CLASSLOADER_PROPERTY, "true");
        }
    }
    
    public static ClassLoader getSharedChain(){
        return _sharedClassLoader;
    }

    public static ClassLoader getOptionalChain(){
        return _optionalChain;
    }
    
    public static ClassLoader getAppServerChain(){
        return _asChain;
    }

    public static List<String> getSharedClasspath() {
        if(_sharedClasspath!=null)
            return _sharedClasspath;
        //PE/EE Shared jars        
        String asLib = installRoot + fileSeparator + "lib" + fileSeparator;        
        String sharedJarsList = System.getProperty("com.sun.aas.classloader.sharedChainJars");
        if (isEE()) {
            String eeSharedJarsList = System.getProperty("com.sun.aas.classloader.sharedChainJars.ee");
            sharedJarsList  += ("," + eeSharedJarsList);
        }
        logFine("shared jar list " + sharedJarsList);
        
        List<String> shr = getLibraryList(asLib, sharedJarsList);
        
        //Computing classpath prefix,suffix and server classpath
        String prefixString = System.getProperty(CLASSPATH_PREFIX_PROPERTY);
        logFine(" prefixString " + prefixString );
        String[] classpathPrefix = null;
        if (prefixString != null) classpathPrefix = prefixString.split("" +File.pathSeparatorChar);
        
        String suffixString = System.getProperty(CLASSPATH_SUFFIX_PROPERTY);
        logFine(" suffixString " + suffixString);
        String[] classpathSuffix = null;
        if (suffixString != null)  classpathSuffix = suffixString.split("" +File.pathSeparatorChar);
        
        String serverClassPathString = System.getProperty(SERVER_CLASSPATH_PROPERTY);
        logFine(" serverClassPathString " + serverClassPathString);
        String[] serverClassPath = null;
        if (serverClassPathString != null)  serverClassPath = serverClassPathString.split("" +File.pathSeparatorChar);
        
        //Creating final shared chain list.
        List<String> sharedChainList = new ArrayList<String>();
        if (classpathPrefix != null) sharedChainList.addAll(Arrays.asList(classpathPrefix));
        sharedChainList.addAll(shr);
        if (serverClassPath != null) sharedChainList.addAll(Arrays.asList(serverClassPath));
        if (classpathSuffix != null) sharedChainList.addAll(Arrays.asList(classpathSuffix));
        
        List manifestaddonJars = getManifestAddonJars();
        if(manifestaddonJars.size() != 0)
            sharedChainList.addAll(manifestaddonJars);
        _sharedClasspath=sharedChainList;
        return _sharedClasspath;
    }

    
    public static List getAppServerClasspath() {
        return _appserverClasspath;
    }
    
    public static List<String> getOptionalClasspath() {
        return _optionalClasspath;
    }
      
    /**
     * ServerClassPath was earlier provided as "server-classpath" attribute
     * in the "java-config" element of domain.xml and this is used by 
     * BaseManager Only.
     */
    public static List getServerClasspath() {
        String asLib = installRoot + fileSeparator + "lib" + fileSeparator;
        String serverJarsList = System.getProperty("com.sun.aas.classloader.serverClassPath");

        if (isEE()) {
            String eeServerJarsList = System.getProperty("com.sun.aas.classloader.serverClassPath.ee");
            serverJarsList  += ("," + eeServerJarsList);
        }
        logFine("serverClassPathJarsList " + serverJarsList);
        
        List<String> serverClasspathList = getLibraryList(asLib, serverJarsList);
        return serverClasspathList;
    }
    
    /**
     ** Get all Non-manifest jars under lib/addons directory and add it to sharedClassPath.
     */
    private static List getManifestAddonJars() {
        List<String> manifestJars = new ArrayList<String>();
        JarFile file = null;
        try {
        String addonDir = installRoot + fileSeparator + "lib" + fileSeparator + "addons";
        File libAddonDirectory = new File(addonDir);
        if(!libAddonDirectory.isDirectory())
            return manifestJars;
        
        File[] fileArray = libAddonDirectory.listFiles();
        for(int i = 0;i<fileArray.length;i++) {
                String addonJar = fileArray[i].getName();
                String jarExtension = "";
                int dotLastIndex = addonJar.lastIndexOf(".");
                if(dotLastIndex != -1)
                    jarExtension = addonJar.substring(dotLastIndex + 1);
                if(jarExtension.equalsIgnoreCase("jar")) {
                    manifestJars.add(fileArray[i].getAbsolutePath()); 
                    file = new JarFile(fileArray[i].getAbsolutePath());
                    Manifest mf = file.getManifest();
                    Attributes attributes = null;
                    if(mf != null) {
                        attributes = mf.getMainAttributes();
                        if(attributes != null) {
                            String classPath = attributes.getValue(Attributes.Name.CLASS_PATH);
                            if(classPath != null && !classPath.trim().equals("")) {
                                StringTokenizer stoken = new StringTokenizer(classPath);
                                while(stoken.hasMoreTokens()) {
                                    String classPathJar = addonDir + fileSeparator + stoken.nextElement();
                                    manifestJars.add(classPathJar);
                                }
                            }
                            //Logger.getAnonymousLogger().log(Level.FINE, "Main Class "+mainClass); 
                        }
                        file.close();
                    }
                }
        }
        }catch(Exception e) {
            e.printStackTrace(System.err);
        }finally {
            try {
            if(file != null)
                file.close();
            }catch(Exception ex) {
                ex.printStackTrace(System.err);
            }
        }
        //Logger.getAnonymousLogger().log(Level.INFO, "nonManifestJars list: "+nonManifestJars);
        return manifestJars;
    }
    /**
     * Entry point into the application server
     */
    public static void main(String[] args) {
        try{
            Class peMainClass = null;
            
            if(Boolean.getBoolean(USE_NEW_CLASSLOADER_PROPERTY)){
                ASenvPropertyReader reader = new ASenvPropertyReader(
                    System.getProperty(SystemPropertyConstants.CONFIG_ROOT_PROPERTY),
                    false);
                reader.setSystemProperties();
                installRoot = System.getProperty(SystemPropertyConstants.INSTALL_ROOT_PROPERTY);
                javaHome = System.getProperty(SystemPropertyConstants.JAVA_ROOT_PROPERTY);
                
                setupClassloaders();
                //Use the new classloader hierarchy
                peMainClass = _asChain.loadClass(
                                "com.sun.enterprise.server.PEMain", true);
                Thread.currentThread().setContextClassLoader(_asChain);            
            } else {
                peMainClass = Class.forName("com.sun.enterprise.server.PEMain");
            }
            
            Class[] argTypes = new Class[]{String[].class};
            Method m = peMainClass.getMethod("main", argTypes);           
            Object[] argListForInvokedMain = new Object[]{args};
            m.invoke(null, argListForInvokedMain);
        } catch(Exception e) {
            e.printStackTrace(System.err);
            System.exit(1);
        }
    }

    /*
     * Sets up all internal classloader chains 
     */
    private static void setupClassloaders(){
        prepareAppServerJars();
        prepareOptionalOverrideableJars();
        appendOtherJarsToSharedChain();
        setupSharedChain();
        setupOptionalOverrideableChain();
        setupAppServerChain();
        
        List<String> cp = getSharedClasspath();
        //Seed the classpath string with the system classpath
        //to support users setting "env-classpath-ignored" to false.
        StringBuilder classpath = new StringBuilder(System.getProperty("java.class.path"));
        for(String s:cp){
            classpath.append(s);
            classpath.append(pathSeparator);
        }
        System.setProperty("java.class.path", classpath.toString());
    }

    /**
     * SPI for Verifier to use when it runs in appserver mode. Returns the server classpath
     * for an application. This as now includes all libraries in installRoot and 
     * server-classpath.
     */ 
    public static List<String> getServerClassPath(String configDir, 
                                                  String domainDir){
        ASenvPropertyReader reader = new ASenvPropertyReader(configDir,false);
        reader.setSystemProperties();
        installRoot = System.getProperty(SystemPropertyConstants.INSTALL_ROOT_PROPERTY);
        javaHome = System.getProperty(SystemPropertyConstants.JAVA_ROOT_PROPERTY);

        //Add all libraries in as-install-dir/lib
        String asLib = installRoot + fileSeparator ;        
        List<String >serverClassPathList = new ArrayList<String>();
        File[] fls= getAllLibrariesInLib(asLib); 
        
        for (File element : fls) {
            serverClassPathList.add(element.getAbsolutePath()); 
        }
        
        //add server-classpath
        String mqlib = System.getProperty(SystemPropertyConstants.IMQ_LIB_PROPERTY);
        String antlib = System.getProperty(SystemPropertyConstants.ANT_LIB_PROPERTY);
        String jdmklib = System.getProperty(SystemPropertyConstants.JDMK_HOME_PROPERTY);
        String hadbRoot = System.getProperty(SystemPropertyConstants.HADB_ROOT_PROPERTY);
        
        String[] peServerClassPath = {installRoot + "/lib/install/applications/jmsra/imqjmsra.jar",
                        mqlib + "/jaxm-api.jar" , mqlib + "/fscontext.jar",
                        mqlib + "/imqbroker.jar", mqlib + "/imqjmx.jar",
                        mqlib + "/imqxm.jar",
                        antlib + "/ant.jar", jdmklib + "/lib/jdmkrt.jar"} ;
        String[] eeServerClassPath = {hadbRoot + "/lib/hadbjdbc4.jar",
                        jdmklib + "/lib/jdmkrt.jar",hadbRoot + "/lib/dbstate.jar",
                        hadbRoot + "/lib/hadbm.jar", hadbRoot + "/lib/hadbmgt.jar"} ;
        for (String element : peServerClassPath) {
            File f = new File(element);
            if(f.exists()) {
                serverClassPathList.add(f.getAbsolutePath());
            }
        }
        for (String element : eeServerClassPath) {
            File f = new File(element);
            if(f.exists()) {
                serverClassPathList.add(f.getAbsolutePath());
            }
        }
        
        logFine("Server Classpath for verifier " + serverClassPathList + "\n");
        Logger.getAnonymousLogger().log(Level.FINE, "Server Classpath for verifier " + serverClassPathList);
        return serverClassPathList;
    }
    
    /**
     * The Shared chain is the child of SystemClassLoader in the AS classloader 
     * hierarchy and houses all immutable, platform classes. [eg j2ee.jar] 
     *
     */
    private static void setupSharedChain(){
        List<String> sharedChainJarList = getSharedClasspath();
        logFine("shared classpath jars : " + sharedChainJarList + "\n");
        URL[] urls = getURLList(sharedChainJarList);
        logFine(" SharedChain URL List " + urls);
         _sharedClassLoader = new ASURLClassLoader(urls, 
                         ClassLoader.getSystemClassLoader());
        _sharedClassLoader.setName("Shared ClassLoader Chain");
    }

    
    /**
     * The optional overrideable chain is "suffixed" to the chain of 
     * dependencies of an application. It consists of libraries that are by 
     * default provided to an application when an application has not specified
     * an explicit dependency. This chain consists of all AS provided libraries 
     * that could be overridden by an application. 
     */
    private static void setupOptionalOverrideableChain(){
         
        _optionalChain = new ClassLoaderChain(_sharedClassLoader);
        _optionalChain.setName("optionalChain");
        
        URL[] urls = getURLList(_optionalClasspath);
        //Parent set to Shared Chain
        ASURLClassLoader optionalJarsLoader = new ASURLClassLoader(urls, 
                                                                _sharedClassLoader);
        _optionalChain.addToList(optionalJarsLoader);

    }
    
    private static void prepareOptionalOverrideableJars(){
        String asLib = installRoot + fileSeparator + "lib" + fileSeparator;
        String optionalJarsString = System.getProperty("com.sun.aas.classloader.optionalOverrideableChain");
        if (isEE()) {
            String eeOptionalJarsList = System.getProperty("com.sun.aas.classloader.optionalOverrideableChain.ee");
            optionalJarsString  += ("," + eeOptionalJarsList);
        }
        
        logFine(" optionalOverrideableChain" + optionalJarsString );
        
        _optionalClasspath = getLibraryList(asLib, optionalJarsString);
        logFine("Optional overrideable chain classpath : " + _optionalClasspath + "\n");
    }    
    
    /**
     * The application server chain is composed of jars/libraries that are used
     * and visible to application server classes only. The optional overrideable
     * chain is also a part of this chain. The Shared ClassLoader chain is set
     * as the parent of the application server chain.
     */
    private static void setupAppServerChain(){
        URL[] urls = getURLList(_appserverClasspath);
        
        //parent set to Shared Chain
        _asChain = new ClassLoaderChain(_sharedClassLoader);
        _asChain.setName("ASChain");
        
        ASURLClassLoader asimplloader = new ASURLClassLoader(urls, _asChain);
        asimplloader.setName("asimpl");
        _asChain.addToList(asimplloader);
        _asChain.addToList(_optionalChain);        
    }

    private static void prepareAppServerJars(){
        //AS only jars
        String asLib = installRoot + fileSeparator + "lib";
        String appserverJarsStr = System.getProperty("com.sun.aas.classloader.appserverChainJars");
        if (isEE()) {
            String eeAppserverJarsList = System.getProperty("com.sun.aas.classloader.appserverChainJars.ee");
            appserverJarsStr  += ("," + eeAppserverJarsList);
        }
        logFine("appserverJarsString " + appserverJarsStr );
        
        _appserverClasspath = getLibraryList(asLib, appserverJarsStr);
        logFine("Application server classpath : " + _appserverClasspath + "\n");
    }
    
    /**
     * Determines if the AS process is running in EE. 
     * XXX: to refactor this to use the common implementation.
     */
    private static boolean isEE() {
        boolean isEE = false;
        final String eepffc = "com.sun.enterprise.ee.server.pluggable.EEPluggableFeatureImpl";
        final String pn = "com.sun.appserv.pluggable.features";
        final String pv = System.getProperty(pn);
        if (eepffc.equals(pv)) {
            isEE = true;
        }
        return ( isEE );
    }
    
    private static List<String> getLibraryList(String libraryRoot, String librariesString) {
        String[] libraries = librariesString.split(",");
        List<String> shr = new ArrayList<String>();
        for(String library: libraries){
            library = library.trim();
            File file = new File(library);
            if (!file.isAbsolute()) {
                shr.add(libraryRoot+library);
            } else {
                shr.add(library);
            }
        }
        return shr;
    }
    
    private static URL[] getURLList(List<String> librariesList) {
        int i=0;
        String [] sharedJars = librariesList.toArray(new String[] {});
        URL [] urls = new URL[sharedJars.length];
        for(String s:sharedJars){
            try{
                URL url = (new File(s)).toURI().toURL();
                urls[i++] = url;
            }catch(MalformedURLException e){
                Logger.getAnonymousLogger().warning(e.getMessage());
                Logger.getAnonymousLogger().log(Level.WARNING, "Exception while" +
                                                                                                 "setting up shared chain", e);
            }
        }
        return urls;
    }
    
    private static void logFine(String s) {
        if(bDebug) {
            System.err.println(s);
        }
    }
    
    private static void appendOtherJarsToSharedChain(){
        List<String> list = new ArrayList<String>();
        list.addAll(getSharedClasspath());
        list.addAll(getAppServerClasspath());
        list.addAll(getOptionalClasspath());
        File[] files = getAllLibrariesInLib(installRoot);
        for(File file:files){
            try{
                if(!list.contains(file.getCanonicalPath())){
                    _sharedClasspath.add(file.getCanonicalPath());
                }
            }catch(java.io.IOException ioe){
                System.err.println("Error getting "+file.getAbsolutePath()+" "+ioe.getMessage());
            }
        }    
}

    private static File[] getAllLibrariesInLib(String asLib) {
        File installLib = new File(asLib,"lib");
        File [] files = installLib.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                if(name.endsWith(".jar") || name.endsWith(".zip")) {
                    // Exclude j2ee.jar, it is not needed in server runtime
                    // It is present for backward compatibility with AS 8.x
                    if (name.equals("j2ee.jar")) {
                        return false;
                    }
                    return true;
                }
                return false;
            }
        });
        return files;
    }
}
