/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, 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/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.enterprise.server;

import javax.enterprise.deploy.shared.ModuleType;

import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.List;

import com.sun.enterprise.admin.event.AdminEventListenerException;
import com.sun.enterprise.admin.event.AdminEventListenerRegistry;
import com.sun.enterprise.admin.event.BaseDeployEvent;
import com.sun.enterprise.admin.event.DeployEventListenerHelper;
import com.sun.enterprise.admin.event.ModuleDeployEvent;
import com.sun.enterprise.admin.event.ModuleDeployEventListener;
import com.sun.enterprise.config.ConfigException;
import com.sun.enterprise.config.ConfigBean;
import com.sun.enterprise.config.serverbeans.ApplicationHelper; 
import com.sun.enterprise.config.serverbeans.ApplicationRef;
import com.sun.enterprise.config.serverbeans.Server;
import com.sun.enterprise.config.serverbeans.ServerBeansFactory;
import com.sun.enterprise.instance.ExtensionModuleConfigManager;

import com.sun.enterprise.security.acl.RoleMapper;
import com.sun.enterprise.deployment.Application;

import com.sun.enterprise.util.i18n.StringManager;
import com.sun.enterprise.deployment.backend.Deployer;
import com.sun.enterprise.deployment.backend.DeployerFactory;
import com.sun.enterprise.deployment.backend.DeploymentRequest;
import com.sun.enterprise.deployment.backend.DeploymentCommand;
import com.sun.enterprise.deployment.backend.DeployableObjectType;
import com.sun.enterprise.deployment.backend.IASDeploymentException;
import com.sun.enterprise.deployment.util.XModuleType;
import com.sun.enterprise.deployment.pluggable.PluggableDeploymentInfo;
import com.sun.enterprise.deployment.interfaces.pluggable.ArchiveDeployer;
import com.sun.enterprise.deployment.phasing.ExtensionModuleDeployer;
import com.sun.enterprise.config.ConfigContext;
import com.sun.logging.LogDomains;

import com.sun.enterprise.management.StateManageable;
import javax.management.MBeanException;
import com.sun.enterprise.ManagementObjectManager;

/**
 * Manages all extension modules. It also acts as an listener for
 * the deployment events.
 *
 */
public class ExtensionModuleManager extends AbstractManager
        implements ModuleDeployEventListener { 

    /** logger to log core messages */
    static Logger _logger = LogDomains.getLogger(LogDomains.CORE_LOGGER);

    /** local string manager */
    private static StringManager localStrings = 
                StringManager.getManager(ExtensionModuleManager.class);

    private ModuleType moduleType = null;
    private ExtensionModuleDeployer  extModuleDeployer = null;

    /**
     * This manager is responsible for loading all extension modules 
     * in an instance. It creates ExtensionModuleLoader for each deployed 
     * extension module.
     * ExtensionModuleLoader is responsible for further loading extension 
     * containers for each module.
     *
     * <p>
     * It is assumed that the next two params are set by the startup Code.
     *
     * @param extConfigManager         encapsulates application related info
     * @param commonclassLoader  the top level class loader. For now this 
     *                           can be the MainThread's ClassLoader.
     */
    ExtensionModuleManager(ExtensionModuleConfigManager extConfigManager,
            ClassLoader parentClassLoader, ModuleType mType) {

        super(parentClassLoader, extConfigManager);
        moduleType = mType;
        AdminEventListenerRegistry.addModuleDeployEventListener(this);
     }

    /**
     * Returns a loader for extension module. 
     *
     * @param    moduleId    registration name of the extension module
     * 
     * @return   a loader for the extension module
     */
    protected AbstractLoader getLoader(String moduleId) {
        ExtensionModuleLoader loader =  new ExtensionModuleLoader(moduleId, 
            this.parentClassLoader, 
            (ExtensionModuleConfigManager)configManager, moduleType);
        return loader;
    }

    private void holdRequest() { }

    private void holdRequest(String appID) { }

    private boolean moduleDeployed(boolean jsr77, String moduleName,
		ConfigContext dynamicConfigContext)
    {
        boolean result = false;
        boolean loadJSR77 = 
            (jsr77 || loadJSR77(moduleName, ManagementObjectManager.J2EE_TYPE_EXT_MODULE));

        AbstractLoader modLoader = null;
        try {

            modLoader = getLoader(moduleName);
	    modLoader.setConfigContext(dynamicConfigContext);

            // create root mBean for this module
            if (loadJSR77) {
                try {
                    modLoader.createRootMBean();
                } catch (MBeanException mbe) {
                    _logger.log(Level.WARNING,
                        "core.error_while_creating_jsr77_root_mbean",mbe);
                }
            }

            if (isEnabled(dynamicConfigContext, moduleName)) {

                if (_logger.isLoggable(Level.FINEST)) {
                   _logger.log(Level.FINEST,
                           "Loading enabled extension module: " + moduleName);
                }

                //Check to see if the app is already loaded.  If yes, do not reload.
                if (id2loader.get(moduleName) != null) {
                    return true;
                }

                // set jsr77 state STARTING
                try {
                    modLoader.setState(StateManageable.STARTING_STATE);
                } catch (MBeanException mbe) {
                    _logger.log(Level.WARNING,
                            "core.error_while_setting_jsr77_state",mbe);
                }

                boolean retSts = modLoader.doLoad(false);

                if (retSts) {
                    this.id2loader.put(moduleName, modLoader);

                    // set jsr77 state to RUNNING
                    try {
                        modLoader.setState(StateManageable.RUNNING_STATE);
                    } catch (MBeanException mbe) {
                        _logger.log(Level.WARNING,
                            "core.error_while_setting_jsr77_state",mbe);
                    }

                    result = true;
                } else {
                    if (loadJSR77) {
                        // delete root mBean for this module
                        try {
                            modLoader.deleteRootMBean();
                        } catch (MBeanException mbe) {
                            _logger.log(Level.WARNING,
                            "core.error_while_deleting_jsr77_root_mbean",mbe);
                        }
                    } else {
                        // set jsr77 state FAILED
                        try {
                            modLoader.setState(StateManageable.FAILED_STATE);
                        } catch (MBeanException mbe) {
                            _logger.log(Level.WARNING,
                                "core.error_while_setting_jsr77_state",mbe);
                        }
                    }

                    _logger.log(Level.WARNING,
                                "core.error_while_loading_extension_module");
                }
            } else {
                _logger.log(Level.INFO, "core.extension_module_disabled", moduleName);
                //setting result to false since module was disabled
                result = false;
            }
        } finally {
	    //To ensure that the config context is not reused...
	    if (modLoader != null) {
		modLoader.setConfigContext(null);
	    }
	}
        return result;
    }

    /**
     * Unloads the given extension module.
     * This removes this extension module from reload monitor list.
     * This gets called when the extension module is undeployed.
     *
     * @param    moduleName    name of the extension module
     * @return   true if module was unloaded successfully
     *
     * @throws   ConfigException  if an error while reading data from config
     */
    private boolean moduleUnDeployed(boolean jsr77, String moduleName) {

        ExtensionModuleLoader modLoader = 
            (ExtensionModuleLoader) this.id2loader.remove(moduleName);

        // module is not in the registry - must have been undeployed 
        if (modLoader == null) {
            // now lets check if its in the configContext
            try {
                ConfigBean app = ApplicationHelper.findApplication(
                          configManager.getConfigContext(), moduleName);

                Server server = ServerBeansFactory.getServerBean(
                                   configManager.getConfigContext());
                ApplicationRef appRef = server.getApplicationRefByRef(moduleName);
                if (appRef == null) {
                    return true;
                } else {
                    modLoader = (ExtensionModuleLoader)getLoader(moduleName);
                }
                
            } catch (ConfigException ce) {
                _logger.log(Level.INFO, 
                        "ConfigException thrown", moduleName);  
                return false;
             }
        }

        // set jsr77 state to STOPPING
        try {
            modLoader.setState(StateManageable.STOPPING_STATE);
        } catch (MBeanException mbe) {
            _logger.log(Level.WARNING,
                "core.error_while_setting_jsr77_state",mbe);
        }

        boolean undeployed = modLoader.unload(false);

        if (undeployed) {
            String msg = localStrings.getString(
                "core.extension_module_unload_successful",moduleName);
            _logger.log(Level.INFO, msg);
            if (jsr77) {
                // delete jsr77 mBean for this ejbModule
                try {
                    modLoader.deleteRootMBean();
                } catch (MBeanException mbe) {
                    _logger.log(Level.WARNING,
                        "core.error_while_deleting_jsr77_root_mbean",mbe);
                }
            }
        } else {
             String msg = localStrings.getString(
                "core.extension_module_not_unloaded",moduleName);
            _logger.log(Level.INFO, msg);                        
                // set jsr77 state to FAILED
                try {
                    modLoader.setState(StateManageable.FAILED_STATE);
                } catch (MBeanException mbe) {
                    _logger.log(Level.WARNING,
                        "core.error_while_setting_jsr77_state",mbe);
                }
        }
        //Clean up objects in the modLoader
        modLoader.done();
        return undeployed;
    }

    public boolean moduleEnabled(String moduleName, 
                ConfigContext dynamicConfigContext) {
        
        boolean enabled = false;
        if(isEnabled(dynamicConfigContext, moduleName)) {
            ExtensionModuleLoader modLoader =
                    (ExtensionModuleLoader)getLoader(moduleName);
            if (modLoader != null ) {
                modLoader.setIsEnableDisable(true);
                enabled = modLoader.load(false);
            }
            return enabled;         
        } else {
            return enabled;
        }
    }
    
    public boolean moduleDisabled(String moduleName,
        ConfigContext dynamicConfigContext) {
        boolean disabled = false;
        if(isEnabled(dynamicConfigContext, moduleName)){
            ExtensionModuleLoader modLoader =
                (ExtensionModuleLoader)getLoader(moduleName);
            if (modLoader != null ) {
                modLoader.setIsEnableDisable(true);
                disabled = modLoader.unload(false);
            }
            return disabled;   
        } else {
            return false;
        }
        
    }
    
    // ---- START OF ModuleDeployEventListener METHODS ------------------------

    /**
     * Invoked when an extension module is deployed.
     */
    public synchronized void moduleDeployed(ModuleDeployEvent event) 
        throws AdminEventListenerException {

        boolean jsr77 = false;

        extModuleDeployer = PluggableDeploymentInfo.getExtensionModuleDeployer(moduleType);
        if (moduleType.equals(XModuleType.getModuleType(event.getModuleType())) &&
                extModuleDeployer.getExtensionLoader() != null ) {
            
            DeployEventListenerHelper.getDeployEventListenerHelper().synchronize(event);

            String modID = event.getModuleName();

            if (_logger.isLoggable(Level.FINEST)) {
                _logger.log(Level.FINEST,
                            "Deploying Extension Module: " + modID);
            }

            try {
                // refreshes the config context with the context from this event
                this.configManager.refreshConfigContext(
                                        event.getConfigContext() );

                String action = event.getAction();

                // set jsr77 flag
                if ((action.equals(BaseDeployEvent.DEPLOY)) ||
                     (action.equals(BaseDeployEvent.REDEPLOY))) {
                    jsr77 = true;
                }

                if (isEnabled(event.getConfigContext(), modID) && 
                      !moduleDeployed(jsr77, modID, event.getConfigContext())) {
                          
                    String msg = localStrings.getString(
                        "extensionmodulesmgr.extensionmodule_deployed_failed", 
                        modID);
                    registerException(event, msg);
                }

                if (!isEnabled(event.getConfigContext(), modID)) {
                    createRootMBeanForDisabledApplication(modID);
                }
            } catch (ConfigException ce) {
                throw new AdminEventListenerException(ce.getMessage()); 
            }
        }
    }

   /**
    * Invoked when an extension module is undeployed.
    */
    public synchronized void moduleUndeployed(ModuleDeployEvent event) 
            throws AdminEventListenerException {

        boolean jsr77 = false;
        String action = event.getAction();
        if ((action.equals(BaseDeployEvent.UNDEPLOY)) ||
            (action.equals(BaseDeployEvent.REDEPLOY))) {
            jsr77 = true;
        }

        extModuleDeployer = PluggableDeploymentInfo.getExtensionModuleDeployer(moduleType);
        try {
            if (moduleType.equals(XModuleType.getModuleType(event.getModuleType())) &&
                    extModuleDeployer.getExtensionLoader() != null ) {

                String modID = event.getModuleName();

                if (_logger.isLoggable(Level.FINE)) {
                    _logger.log(Level.FINE,
                                "Undeploying Extension Module: " + modID);
                }

                // refreshes the config context with the context from this event
                this.configManager.refreshConfigContext(
                                event.getOldConfigContext() );

                boolean undeployed = moduleUnDeployed(jsr77, modID);

                if (!undeployed) {
                    String msg = localStrings.getString(
                        "extensionmodulesmgr.extensionmodule_undeployed_failed", 
                        modID);
                    registerException(event, msg);
                }
            }
        } catch (ConfigException ce) {
            throw new AdminEventListenerException(ce.getMessage()); 
        }
    }

   /**
    * Invoked when an extension module is redeployed.
    */
    public synchronized void moduleRedeployed(ModuleDeployEvent event) 
            throws AdminEventListenerException {
        
        extModuleDeployer = PluggableDeploymentInfo.getExtensionModuleDeployer(moduleType);
        if (moduleType.equals(XModuleType.getModuleType(event.getModuleType())) &&
                extModuleDeployer.getExtensionLoader() != null) {

            String modID = event.getModuleName();

                        if (_logger.isLoggable(Level.FINE)) {
                    _logger.log(Level.FINE,
                            "Redeploying Extension Module: " + modID);
            }

            moduleUndeployed(event);
            moduleDeployed(event);
        }
    }

   /**
    * Invoked when an extension module is enabled.
    */
    public synchronized void moduleEnabled(ModuleDeployEvent event) 
            throws AdminEventListenerException {
        extModuleDeployer = PluggableDeploymentInfo.getExtensionModuleDeployer(moduleType);
        if (moduleType.equals(XModuleType.getModuleType(event.getModuleType())) &&
                extModuleDeployer.getExtensionLoader() != null) {

            String modID = event.getModuleName();

            if (_logger.isLoggable(Level.FINE)) {
                _logger.log(Level.FINE, "Enabling Extension Module: " + modID);
            }
            moduleEnabled(modID, event.getConfigContext());
        }
    }

   /**
    * Invoked when an extension module is disabled.
    */
    public synchronized void moduleDisabled(ModuleDeployEvent event) 
            throws AdminEventListenerException {
        
        extModuleDeployer = PluggableDeploymentInfo.getExtensionModuleDeployer(moduleType);
        if (moduleType.equals(XModuleType.getModuleType(event.getModuleType())) &&
                extModuleDeployer.getExtensionLoader() != null) {

            String modID = event.getModuleName();

                        if (_logger.isLoggable(Level.FINE)) {
                    _logger.log(Level.FINE,
                            "Disabling Extension Module: " + modID);
            }

            moduleDisabled(modID, event.getConfigContext());
        }
    }
    
    
/**
     * Invoked when a  reference is created from a
     * server instance (or cluster) to a particular module.
     *
     * @throws AdminEventListenerException when the listener is unable to
     *         process the event.
     */
    public void moduleReferenceAdded(ModuleDeployEvent event)
            throws AdminEventListenerException {
                
    }

    /**
     * Invoked when a reference is removed from a
     * server instance (or cluster) to a particular module.
     *
     * @throws AdminEventListenerException when the listener is unable to
     *         process the event.
     */
    public void moduleReferenceRemoved(ModuleDeployEvent event)
            throws AdminEventListenerException {
                
    }
        
    

    // ---- END OF ModuleDeployEventListener METHODS ------------------------

    /**
     * @return the module type this class is managing
     */
    public ModuleType getModuleType() {
        return moduleType;
    }

}
