/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2005 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.ear;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.Policy;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.modelmbean.ModelMBean;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.security.jacc.PolicyConfiguration;
import javax.security.jacc.PolicyConfigurationFactory;
import javax.security.jacc.PolicyContextException;

import org.apache.commons.modeler.ManagedBean;
import org.apache.commons.modeler.Registry;

import org.objectweb.jonas_client.deployment.api.ClientContainerDeploymentDesc;
import org.objectweb.jonas_client.deployment.api.ClientContainerDeploymentDescException;
import org.objectweb.jonas_client.deployment.lib.wrapper.ClientManagerWrapper;

import org.objectweb.jonas_ear.deployment.api.EarDeploymentDesc;
import org.objectweb.jonas_ear.deployment.api.EarDeploymentDescException;
import org.objectweb.jonas_ear.deployment.lib.wrapper.EarManagerWrapper;
import org.objectweb.jonas_ear.deployment.xml.Web;

import org.objectweb.jonas_ejb.deployment.lib.wrapper.EjbManagerWrapper;

import org.objectweb.jonas_lib.deployment.work.CleanerException;
import org.objectweb.jonas_lib.deployment.work.DeployerLog;
import org.objectweb.jonas_lib.deployment.work.DeployerLogException;
import org.objectweb.jonas_lib.deployment.work.EarCleanTask;
import org.objectweb.jonas_lib.deployment.work.EarFileManager;
import org.objectweb.jonas_lib.deployment.work.FileManagerException;
import org.objectweb.jonas_lib.deployment.work.WorkCleaner;
import org.objectweb.jonas_lib.files.CheckerException;
import org.objectweb.jonas_lib.files.WsGenChecker;
import org.objectweb.jonas_lib.loader.ClientClassLoader;
import org.objectweb.jonas_lib.loader.EjbJarClassLoader;

import org.objectweb.jonas_web.deployment.lib.wrapper.WebManagerWrapper;

import org.objectweb.jonas.client.AppClientModule;
import org.objectweb.jonas.common.JModule;
import org.objectweb.jonas.common.JProp;
import org.objectweb.jonas.common.Log;
import org.objectweb.jonas.container.EJBService;
import org.objectweb.jonas.container.EJBServiceImpl;
import org.objectweb.jonas.ear.lib.EarClassPathManager;
import org.objectweb.jonas.ear.lib.EarClassPathManagerException;
import org.objectweb.jonas.ear.lib.JarList;
import org.objectweb.jonas.ear.lib.JarListException;
import org.objectweb.jonas.jmx.J2eeObjectName;
import org.objectweb.jonas.jmx.JmxService;
import org.objectweb.jonas.jmx.JonasObjectName;
import org.objectweb.jonas.management.JonasMBeanTools;
import org.objectweb.jonas.naming.CompNamingContext;
import org.objectweb.jonas.resource.ResourceService;
import org.objectweb.jonas.resource.ResourceServiceException;
import org.objectweb.jonas.security.jacc.JPolicyUserRoleMapping;
import org.objectweb.jonas.server.JClassLoader;
import org.objectweb.jonas.server.LoaderManager;
import org.objectweb.jonas.service.AbsServiceImpl;
import org.objectweb.jonas.service.ServiceException;
import org.objectweb.jonas.service.ServiceManager;
import org.objectweb.jonas.web.JWebContainerService;
import org.objectweb.jonas.web.JWebContainerServiceException;
import org.objectweb.jonas.ws.AbsWebServicesServiceImpl;
import org.objectweb.jonas.ws.WSServiceException;
import org.objectweb.jonas.ws.WebServicesService;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

/**
 * JOnAS EAR Service Implementation class. This class provides an implementation
 * of the ear service.
 * @author Florent Benoit
 * @author Ludovic Bert Contributor(s): Adriana Danes: highlight configuration
 *         properties Eric Hardesty: added ability to include rar files in an
 *         ear Michel-Ange Anton : new JSR77 MBean
 */
public class EarServiceImpl extends AbsServiceImpl implements EarService, EarServiceImplMBean {

    /**
     * The name of the JONAS_BASE directory.
     */
    protected static final String JONAS_BASE = JProp.getJonasBase();

    /**
     * The name of the apps directory.
     */
    protected static final String APPS_DIR = JONAS_BASE + File.separator + "apps";

    /**
     * The name of the working directory.
     */
    protected static final String WORK_DIR = JProp.getWorkDir();

    /**
     * The name of the working apps directory.
     */
    protected static final String WORK_APPS_DIR = WORK_DIR + File.separator + "apps";
    
    /**
     * The name of the working apps directory.
     */
    protected static final String WORK_WEBAPPS_DIR = WORK_DIR + File.separator + "webapps";
    
    /**
     * The name of the property used in work directory for EAR webapps (in ear case).
     */
    protected static final String INEAR_WORK_WEBAPPS_DIR_SUFFIX = "ear";

    /**
     * Property for ear to deploy at runtime
     */
    protected static final String DESCRIPTORS = "jonas.service.ear.descriptors";

    /**
     * Directories property for autoload
     */
    protected static final String AUTOLOADDIR = "jonas.service.ear.autoloaddir";

    /**
     * Property for parsing with validation or not
     */
    protected static final String PARSINGWITHVALIDATION = "jonas.service.ear.parsingwithvalidation";

    /**
     * Property for the class of the EAR service
     */
    protected static final String CLASS = "jonas.service.ear.class";
    
    /**
     * JOnAS configuration properties : development mode
     */
    protected static final String DEVELOPMENT = "jonas.development";

    /**
     * Logger for this service.
     */
    private static Logger logger = null;

    /**
     * Reference to the cleaner
     */
    private static WorkCleaner workCleaner = null;

    /**
     * Reference to a MBean server.
     */
    private MBeanServer mbeanServer = null;

    /**
     * List of the ear names to load when starting the EarService
     */
    private Vector earNames = new Vector();

    /**
     * Reference to the Ejb service.
     */
    private EJBService ejbService = null;

    /**
     * Reference to the WebContainer service.
     */
    private JWebContainerService webContainerService = null;

    /**
     * Reference to the WebServices service.
     */
    private WebServicesService wsService = null;

    /**
     * Reference to the Resource service.
     */
    private ResourceService resourceService = null;

    /**
     * List of loaded ear
     */
    private Hashtable ears = null;

    /**
     * List of autoloaded directories.
     */
    private ArrayList autoloadDirectories = new ArrayList();

    /**
     * Reference on the DeployerLog which is the class that manage the
     * accesses to the log file (to remove the ear).
     */
    private DeployerLog earDeployerLog = null;

    /**
     * Application Classloader
     */
    private ClassLoader appsClassLoader;

    /**
     * Init the EAR service.
     * @param ctx the configuration of the Ear service.
     * @throws ServiceException if the initialization failed.
     */
    protected void doInit(Context ctx) throws ServiceException {

        //we init the logger
        logger = Log.getLogger(Log.JONAS_EAR_PREFIX);

        // get apps ClassLoader
        try {
            LoaderManager lm = LoaderManager.getInstance();
            appsClassLoader = lm.getAppsLoader();
        } catch (Exception e) {
            logger.log(BasicLevel.ERROR, "Cannot get the Applications ClassLoader from EAR Container Service: " + e);
            throw new ServiceException("Cannot get the Applications ClassLoader from EAR Container Service", e);
        }

        ServiceManager sm  = null;

        try {
            sm = ServiceManager.getInstance();
        } catch (Exception e) {
            String err = "Cannot get ServiceManager instance";
            logger.log(BasicLevel.ERROR, err);
            throw new ServiceException(err, e);
        }

        // Get the JMX Server via JMX Service
        try {
            mbeanServer = ((JmxService) sm.getJmxService()).getJmxServer();
        } catch (ServiceException e) {
            // the JMX service may not be started
            mbeanServer = null;
        }

        // Get the EJB service
        try {
            ejbService = (EJBService) sm.getEjbService();
        } catch (ServiceException e) {
            //not started
            ejbService = null;
        }

        // Get the Web Container service
        try {
            webContainerService = (JWebContainerService) sm.getWebContainerService();
        } catch (ServiceException e) {
            //not started
            webContainerService = null;
        }

        // Get the WebServices service
        try {
            wsService = (WebServicesService) sm.getWebServicesService();
        } catch (ServiceException e) {
            //not started
            wsService = null;
        }

        // Get the Resource service
        try {
            resourceService = (ResourceService) sm.getResourceService();
        } catch (ServiceException e) {
            //not started
            resourceService = null;
        }

        // Set the XML parsing mode to no validation
        String parsingMode = "false";
        try {
            parsingMode = (String) ctx.lookup(PARSINGWITHVALIDATION);
        } catch (NamingException e) {
            /*
             * No problem if there is no value for 'parsingwithvalidation'
             * (false by default)
             */
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "No XML parsing validation property, use default");
            }
        }
        EarManagerWrapper.setParsingWithValidation("true".equalsIgnoreCase(parsingMode));
        if ("false".equalsIgnoreCase(parsingMode)) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Ear XML parsing without validation");
            }
        } else {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Ear XML parsing with validation");
            }
        }

        // Init the ear names to be loaded when starting
        String descsValue = null;
        try {
            descsValue = (String) ctx.lookup(DESCRIPTORS);
        } catch (NamingException e) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "No property DESCRIPTORS");
            }
            // No problem if there is no value for 'descriptors'
            //(no ear to load)
        }
        if (descsValue != null) {
            StringTokenizer st = new StringTokenizer(descsValue, ",");
            while (st.hasMoreTokens()) {
                String fileName = st.nextToken().trim();
                earNames.add(fileName);
            }
        }

        // Add the ears of the jonas.service.ear.autoloaddir property
        String dirValue = null;
        ArrayList autoDirs = new ArrayList();
        try {
            dirValue = (String) ctx.lookup(AUTOLOADDIR);
        } catch (NamingException e) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "No AUTOLOADDIR property");
            }
            // No problem if there is no value for 'autoloaddir'
            //(no ear to load)
        }
        if (dirValue != null) {
            StringTokenizer st = new StringTokenizer(dirValue, ",");
            while (st.hasMoreTokens()) {
                String dirName = st.nextToken().trim();
                addEars(dirName);
                autoDirs.add(dirName);
            }
        }
        // Build autoload directories
        File oFile;
        Iterator it = autoDirs.iterator();
        while (it.hasNext()) {
            String dirName = (String) it.next();
            try {
                oFile = new File(APPS_DIR, dirName);
                if (!oFile.exists()) {
                    oFile = new File(dirName);
                }
                if (oFile.exists()) {
                    autoloadDirectories.add(oFile.getCanonicalPath());
                }
            } catch (Exception e) {
                String err = "Error when trying to verify Application EAR autoload directory : " + dirName;
                logger.log(BasicLevel.ERROR, err, e);
            }
        }

        //create hashtable of the ears ( Url of an ear ---> Ear structure)
        ears = new Hashtable();

        // Init the rar deployment desc manager.
        //rarDDManager = RarDeploymentDescManager.getInstance();
    }

    /**
     * Stop the EAR service.
     * @throws ServiceException if the stop failed.
     */
    protected void doStop() throws ServiceException {

        //undeploy the loaded ears files
        URL earFileName = null;
        //For each ear file, we delegate the operation to the
        //unDeployEar method
        for (Enumeration earEntries = ears.keys(); earEntries.hasMoreElements();) {
            earFileName = (URL) earEntries.nextElement();
            //Try to undeploy the ear
            try {
                Context ctx = new CompNamingContext(earFileName.getFile());
                ctx.rebind("filename", earFileName);
                unDeployEar(ctx);
            } catch (Exception e) {
                //We don't stop the process of undeploying the ears.
                //We only display an error in the logger.
                String err = "Error when undeploying the ear :" + earFileName;
                logger.log(BasicLevel.ERROR, err, e);
            }
        }

        if (mbeanServer != null) {
            try {
                // unregister EarService MBean : EarServiceImplMBean
                mbeanServer.unregisterMBean(JonasObjectName.earService());
            } catch (Exception e) {
                logger.log(BasicLevel.ERROR, "Cannot stop the EarService", e);
            }
        }

        //The service is stopped
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "EarService stopped");
        }

    }

    /**
     * Deploy an EAR file with sending JAR file to the EJB container and WAR
     * file to the WEB container and RAR file to the resource service.
     * @param ctx the context which contains the configuration in order to
     *        deploy an EAR.
     * @return The ObjectName of the J2EE Application MBean associated to the
     *         deployed EAR the Application Container
     * @throws EarServiceException if the deployment of the EAR failed.
     */
    public String deployEar(Context ctx) throws EarServiceException {

        //We have one of the ejb or web services launched
        if ((webContainerService == null) && (ejbService == null)) {
            throw new EarServiceException(
                    "The ear service requires that at least the service ejb or web is launched for deploying an ear file.");
        }

        //We get the fileName
        // Get the file name and check if file exists
        String fileName;
        try {
            fileName = (String) ctx.lookup("filename");
        } catch (NamingException e) {
            throw new EarServiceException("Error during performing lookup a fileName", e);
        }

        File f = null;
        try {
            //We search the file
            f = new File(fileName).getCanonicalFile();

            if (!f.exists()) {
                boolean found = false;
                String earFileName = null;
                if (fileName.toLowerCase().endsWith(".ear")) {
                    // In case of the name is a ear file name, check also in
                    // the JONAS_BASE/apps directory
                    earFileName = APPS_DIR + File.separator + fileName;
                    f = new File(earFileName).getCanonicalFile();
                    found = f.exists();
                }
                if (found) {
                    fileName = earFileName;
                } else {
                    String err = "deployEar: The file " + fileName
                            + " was not found neither in the current directory nor in the " + APPS_DIR + " directory";
                    logger.log(BasicLevel.ERROR, err);
                    throw new EarServiceException(err);
                }
            }
        } catch (IOException e) {
            String err = "Error when trying to get the canonical file from " + fileName;
            logger.log(BasicLevel.ERROR, err + " " + e.getMessage());
            throw new EarServiceException(err, e);
        }

        // Apply WsGen if needed
        // Before creating URLs, ...
        // -------------------------------
        try {
            ObjectName j2eeServer = J2eeObjectName.J2EEServer(this.getDomainName(), this.getJonasServerName());
            WsGenChecker wsgc = new WsGenChecker(this.mbeanServer, fileName, j2eeServer);
            wsgc.checkWsGen();
        } catch (CheckerException e) {
            throw new EarServiceException("Cannot apply WsGen on the ear : " + fileName, e);
        } catch (Exception ue) {
            throw new EarServiceException("Cannot apply WsGen on the ear (unexpected exception) : " + fileName, ue);
        }

        //--------------------------------

        //Make the url for the specified fileName and the earRootUrl
        URL[] earUrl = new URL[1];
        URL earRootUrl = null;

        try {
            earUrl[0] = f.toURL();
            earRootUrl = (new File(WORK_APPS_DIR + File.separator + getJonasServerName())).toURL();
        } catch (MalformedURLException e) {
            String err = "Invalid ear file name '" + fileName;
            logger.log(BasicLevel.ERROR, err + "': " + e.getMessage());
            throw new EarServiceException(err, e);
        }

        //Check if the ear is already deployed or not
        if (ears.get(earUrl[0]) != null) {
            String err = "The ear file : " + f.getName() + " is already deployed ('" + earUrl[0].getFile()
                    + "'). You must undeploy the application " + "before a new deployment.";
            logger.log(BasicLevel.ERROR, err);
            throw new EarServiceException(err);
        }

        //Create classLoader
        //parent classloader is the current classloader
        URLClassLoader loaderCls = new URLClassLoader(earUrl, appsClassLoader);

        EarDeploymentDesc earDD = null;
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Getting the deployment descriptor of the file" + f.getName());
        }
        try {
            earDD = EarManagerWrapper.getDeploymentDesc(earUrl[0].getFile(), loaderCls);
        } catch (EarDeploymentDescException e) {
            String err = "Error in the Deployment descriptor '" + fileName + "': " + e;
            logger.log(BasicLevel.ERROR, err);
            throw new EarServiceException(err, e);
        }

        //We get the tags from the Deployment descriptor
        Web[] webTags = earDD.getWebTags();
        String[] ejbTags = earDD.getEjbTags();
        String[] connectorTags = earDD.getConnectorTags();
        String[] clientTags = earDD.getClientTags();
        String[] altDDClients = earDD.getAltDDClients();
        String[] altDDEjbs = earDD.getAltDDEjbs();
        String[] altDDWebs = earDD.getAltDDWebs();
        String[] altDDConnectors = earDD.getAltDDConnectors();
        String[] securityRoles = earDD.getSecurityRolesNames();

        //Check if all modules are inside the EAR file
        //no relatives mode like ../../file1.jar

        File fEar = new File(earUrl[0].getFile());
        File tmpFile = null;

        try {
            File fCanonicEar = fEar.getCanonicalFile();
            for (int i = 0; i < ejbTags.length; i++) {
                tmpFile = new File(fEar, ejbTags[i]);
                tmpFile = tmpFile.getCanonicalFile();
                if (!tmpFile.getPath().startsWith(fCanonicEar.getPath())) {
                    String err = "Error : The ejb-jar file " + ejbTags[i] + " is not inside the ear file " + fEar;
                    logger.log(BasicLevel.ERROR, err);
                    throw new EarServiceException(err);
                }
            }

            for (int i = 0; i < webTags.length; i++) {
                tmpFile = new File(fEar, webTags[i].getWebUri());
                tmpFile = tmpFile.getCanonicalFile();
                if (!tmpFile.getPath().startsWith(fCanonicEar.getPath())) {
                    String err = "Error : The war file " + webTags[i].getWebUri() + " is not inside the ear file "
                            + fEar;
                    logger.log(BasicLevel.ERROR, err);
                    throw new EarServiceException(err);
                }
            }

            for (int i = 0; i < connectorTags.length; i++) {
                tmpFile = new File(fEar, connectorTags[i]);
                tmpFile = tmpFile.getCanonicalFile();
                if (!tmpFile.getPath().startsWith(fCanonicEar.getPath())) {
                    String err = "Error : The rar file " + connectorTags[i] + " is not inside the ear file " + fEar;
                    logger.log(BasicLevel.ERROR, err);
                    throw new EarServiceException(err);
                }
            }


            for (int i = 0; i < clientTags.length; i++) {
                tmpFile = new File(fEar, clientTags[i]);
                tmpFile = tmpFile.getCanonicalFile();

                if (!tmpFile.getPath().startsWith(fCanonicEar.getPath())) {
                    String err = "Error : The client jar file " + clientTags[i] + " is not inside the ear file " + fEar;
                    throw new EarServiceException(err);
                }
            }
        } catch (IOException ioe) {
            String err = "Error while trying to get the canonical file of " + tmpFile;
            logger.log(BasicLevel.ERROR, err + " : " + ioe.getMessage());
            throw new EarServiceException(err, ioe);
        }

        //Changing array into JarList
        JarList ejbsList = new JarList(ejbTags);
        JarList warsList = new JarList(webTags);
        JarList connectorsList = new JarList(connectorTags);
        JarList clientsList = new JarList(clientTags);

        //Unpack the ear file and get the unpacked dir
        URL dirUnpackURL = null;
        try {
            dirUnpackURL = EarFileManager.unpackEar(earUrl[0], earRootUrl);
        } catch (FileManagerException e) {
            String err = "Error while unpacking the file '" + earUrl[0] + "'";
            logger.log(BasicLevel.ERROR, err + " : " + e.getMessage());
            throw new EarServiceException(err, e);
        }

        //the file is unpacked. Log it only if the ear is a file and not a
        // directory
        if (new File(earUrl[0].getFile()).isFile()) {
            try {
                earDeployerLog.addEntry(new File(earUrl[0].getFile()), new File(dirUnpackURL.getFile()));
            } catch (DeployerLogException e) {
                String err = "Error while adding the " + earUrl[0] + " entry in the log file";
                logger.log(BasicLevel.ERROR, err + " : " + e.getMessage());
                throw new EarServiceException(err, e);
            }
        }

        //We have successfully unpacked the ear file, now we analyze manifest
        // Class-path:
        EarClassPathManager earCPManager = null;
        try {
            earCPManager = new EarClassPathManager(ejbsList, warsList, dirUnpackURL);
        } catch (EarClassPathManagerException e) {
            String err = "Error while creating the Ear class path manager of the ear : '" + earUrl[0] + "'";
            logger.log(BasicLevel.ERROR, err + " : " + e.getMessage());
            throw new EarServiceException(err, e);
        }

        URL[] classpathURLs = null;
        //Get the urls of the ear class path manager
        try {
            classpathURLs = earCPManager.getResolvedClassPath();
        } catch (EarClassPathManagerException e) {
            String err = "Error while trying to resolve the classpath of the ejbjars and wars of the ear : '"
                    + earUrl[0] + "'";
            logger.log(BasicLevel.ERROR, err + " : " + e.getMessage());
            throw new EarServiceException(err, e);
        }

        /*
         * We have the urls to load at our level We can now create our
         * classLoader
         */
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Creating the EAR classLoader");
        }
        JClassLoader earClassLoader = new JClassLoader(earUrl[0].toExternalForm() , new URL[0], appsClassLoader);

        //Extract the urls of the jarList
        URL[] jarUrls = null;
        URL[] warUrls = null;
        URL[] connectorUrls = null;
        URL[] clientUrls = null;
        try {
            jarUrls = ejbsList.getURLs(dirUnpackURL.toExternalForm());
            warUrls = warsList.getURLs(dirUnpackURL.toExternalForm());
            connectorUrls = connectorsList.getURLs(dirUnpackURL.toExternalForm());
            clientUrls = clientsList.getURLs(dirUnpackURL.toExternalForm());
        } catch (JarListException e) {
            String err = "Error while geting the Urls from jarlist of the ear : '" + earUrl[0] + "'";
            logger.log(BasicLevel.ERROR, err + " : " + e.getMessage());
            throw new EarServiceException(err, e);
        }

        //Extract context roots for wars
        //They are in webTags. Dump into warsContextRoots
        String[] warsContextRoots = new String[webTags.length];
        String ctxRoot = null;
        for (int i = 0; i < webTags.length; i++) {
            ctxRoot = webTags[i].getContextRoot();
            if (ctxRoot != null) {
                warsContextRoots[i] = ctxRoot;
            }
        }

        //Fill Alt-DD for Ejbs and Wars and Rars
        String altdd = null;
        File fAltDD = null;

        //Transorm the array altDDWebs into an array with the absolute URL to
        // the file
        URL[] warsAltDDs = new URL[altDDWebs.length];
        for (int i = 0; i < altDDWebs.length; i++) {
            if (altDDWebs[i] != null) {
                altdd = altDDWebs[i];
                if (altdd != null) {
                    try {
                        fAltDD = new File(new URL(dirUnpackURL.toExternalForm() + File.separator + altdd).getFile());
                        warsAltDDs[i] = fAltDD.getCanonicalFile().toURL();
                    } catch (MalformedURLException e) {
                        String err = "Can't build URL for alt-dd '" + altdd;
                        logger.log(BasicLevel.ERROR, err + "': " + e.getMessage());
                        throw new EarServiceException(err, e);
                    } catch (IOException ioe) {
                        String err = "Can't get canonicalFile() for the file '" + fAltDD;
                        logger.log(BasicLevel.ERROR, err + "': " + ioe.getMessage());
                        throw new EarServiceException(err, ioe);
                    }
                }
            }
        }

        URL[] ejbsAltDDs = new URL[altDDEjbs.length];
        for (int i = 0; i < altDDEjbs.length; i++) {
            if (altDDEjbs[i] != null) {
                altdd = altDDEjbs[i];
                if (altdd != null) {
                    try {
                        fAltDD = new File(new URL(dirUnpackURL.toExternalForm() + File.separator + altdd).getFile());
                        ejbsAltDDs[i] = fAltDD.getCanonicalFile().toURL();
                    } catch (MalformedURLException e) {
                        String err = "Can't build URL for alt-dd '" + altdd;
                        logger.log(BasicLevel.ERROR, err + "': " + e.getMessage());
                        throw new EarServiceException(err, e);
                    } catch (IOException ioe) {
                        String err = "Can't get canonicalFile() for the file '" + fAltDD;
                        logger.log(BasicLevel.ERROR, err + "': " + ioe.getMessage());
                        throw new EarServiceException(err, ioe);
                    }
                }
            }
        }

        URL[] connectorsAltDDs = new URL[altDDConnectors.length];
        for (int i = 0; i < altDDConnectors.length; i++) {
            if (altDDConnectors[i] != null) {
                altdd = altDDConnectors[i];
                if (altdd != null) {
                    try {
                        fAltDD = new File(new URL(dirUnpackURL.toExternalForm() + File.separator + altdd).getFile());
                        connectorsAltDDs[i] = fAltDD.getCanonicalFile().toURL();
                    } catch (MalformedURLException e) {
                        String err = "Can't build URL for alt-dd '" + altdd;
                        logger.log(BasicLevel.ERROR, err + "': " + e.getMessage());
                        throw new EarServiceException(err, e);
                    } catch (IOException ioe) {
                        String err = "Can't get canonicalFile() for the file '" + fAltDD;
                        logger.log(BasicLevel.ERROR, err + "': " + ioe.getMessage());
                        throw new EarServiceException(err, ioe);
                    }
                }
            }
        }

        URL[] clientAltDDs = new URL[altDDClients.length];
        for (int i = 0; i < altDDClients.length; i++) {
            if (altDDClients[i] != null) {
                altdd = altDDClients[i];
                if (altdd != null) {
                    try {
                        fAltDD = new File(new URL(dirUnpackURL.toExternalForm() + File.separator + altdd).getFile());
                        clientAltDDs[i] = fAltDD.getCanonicalFile().toURL();
                    } catch (MalformedURLException e) {
                        String err = "Can't build URL for alt-dd '" + altdd;
                        logger.log(BasicLevel.ERROR, err + "': " + e.getMessage());
                        throw new EarServiceException(err, e);
                    } catch (IOException ioe) {
                        String err = "Can't get canonicalFile() for the file '" + fAltDD;
                        logger.log(BasicLevel.ERROR, err + "': " + ioe.getMessage());
                        throw new EarServiceException(err, ioe);
                    }
                }
            }
        }
        //Extract roles name from the securityRole array
        String[] roleNames = new String[securityRoles.length];
        String affRoleNames = "";
        for (int i = 0; i < securityRoles.length; i++) {
            roleNames[i] = securityRoles[i];
            affRoleNames += roleNames[i] + ";";
        }

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "role names = " + affRoleNames);
        }

        /**
         * Now this is the deployment step with sending
         * + rars to the rar service
         * + jars to the ejb service
         * + wars to the web container service
         * + jars and wars to web services service
         */

        /**
         * Ejb ClassLoader is needed for WebServices deployment so We create it
         * in advance ...
         */
        // Set the list of the ejb-jar, war and clients that can do an ejb-link in this
        // ear application. This array can be empty.
        // The alternate urls are given
        EjbManagerWrapper.setAvailableEjbJarsAndAltDDs(earClassLoader, jarUrls, ejbsAltDDs);
        WebManagerWrapper.setAltDD(earClassLoader, warUrls, warsAltDDs);
        ClientManagerWrapper.setAltDD(earClassLoader, clientUrls, clientAltDDs);

        // Construct the ejb classloader for all the ejb-jars of the same
        // ear application. Because there is one ejb classloader for all
        // the ejb-jars of the same ear application.
        URL[] urls = new URL[jarUrls.length + classpathURLs.length];
        System.arraycopy(jarUrls, 0, urls, 0, jarUrls.length);
        System.arraycopy(classpathURLs, 0, urls, jarUrls.length, classpathURLs.length);

        // Set the list of the rars that can be referenced in this
        // ear application. This array can be empty.
        // The alternate urls are given

        //rarDDManager.setAvailableRarsAndAltDDs(earClassLoader, connectorUrls,
        // connectorsAltDDs);

        //Only if the connectorUrls is not empty
        //And if the service is started
        if (resourceService != null && (connectorUrls.length > 0)) {
            try {
                CompNamingContext contctx = null;
                try {
                    contctx = new CompNamingContext(dirUnpackURL.getFile());
                    contctx.rebind("earUrl", earUrl[0]);
                    contctx.rebind("urls", connectorUrls);
                    contctx.rebind("earClassLoader", earClassLoader);
                    contctx.rebind("altDDs", connectorsAltDDs);
                } catch (NamingException e) {
                    String err = "Can not bind params for the resource service, Can't deploy rars ";
                    throw new ResourceServiceException(err, e);
                }
                resourceService.deployRars(contctx);
            } catch (ServiceException e) {
                String err = "Error during the deployment of the rars files of the Ear file " + fileName;
                logger.log(BasicLevel.ERROR, err + "': " + e.getMessage());
                //remove DDesc
                //resourceService.removeCache(earClassLoader);
                throw new EarServiceException(err, e);
            }
        }

        // Check GenIC has to be done after RAR deployment because
        // RAR resources (classes, ...) are added to the Application ClassLoader
        // only at deploy time.

        /**
         * Check that GenIC was applied on ejb-jar prior to sending them to EJB service.
         * As classloader is defined in this service, classes after will not be taken
         * into account. So GenIC should be called before creating Classloader.
         */
        if (jarUrls.length > 0 && ejbService != null) {

            URL[] compilationURLs = null;
            // if we have some resources, we have to use them in GenIC compilation
            if (resourceService != null) {
                // get the resources from the ear classloader and apps classloader

                // content of the ear loader (application wide resources)
                URL[] myApplicationJars = earClassLoader.getURLs();
                // content of the apps loader (system wide resources)
                URL[] appsJars = ((URLClassLoader) earClassLoader.getParent()).getURLs();

                // merge the 3 Set of URLs
                compilationURLs = new URL[myApplicationJars.length + appsJars.length + urls.length];

                System.arraycopy(urls, 0, compilationURLs, 0, urls.length);
                System.arraycopy(appsJars, 0, compilationURLs, urls.length, appsJars.length);
                System.arraycopy(myApplicationJars, 0, compilationURLs, urls.length + appsJars.length, myApplicationJars.length);

            }

            // Check GenIC :
            for (int i = 0; i < jarUrls.length; i++) {
                ejbService.checkGenIC(jarUrls[i].getFile(), compilationURLs);
            }
        }

        URLClassLoader ejbClassLoader;
        try {
            ejbClassLoader = new EjbJarClassLoader(urls, earClassLoader);
        } catch (IOException ioe) {
            String err = "Cannot Create EJB ClassLoader for EAR '" + fileName + "'";
            logger.log(BasicLevel.ERROR, err + " : " + ioe.getMessage());
            throw new EarServiceException(err, ioe);
        }

        // Only if the WebServicesService is started
        if (wsService != null) {
            try {
                Hashtable ctxRoots = new Hashtable();
                for (int w = 0; w < webTags.length; w++) {
                    ctxRoots.put(warUrls[w], webTags[w].getContextRoot());
                }
                CompNamingContext contctx = null;
                try {
                    contctx = new CompNamingContext(dirUnpackURL.getFile());
                    contctx.rebind("unpackDir", dirUnpackURL.toExternalForm());
                    contctx.rebind("jarUrls", jarUrls);
                    contctx.rebind("warUrls", warUrls);
                    contctx.rebind("earURL", earUrl[0]);
                    contctx.rebind("ejbClassLoader", ejbClassLoader);
                    contctx.rebind("earClassLoader", earClassLoader);
                    contctx.rebind("warCtxRootMapping", ctxRoots);
                } catch (NamingException e) {
                    String err = "Cannot bind params for the WebServices service, Can't deploy Web Services Endpoints ";
                    throw new WSServiceException(err, e);
                }
                wsService.deployWebServices(contctx);
            } catch (ServiceException se) {
                String err = "Error during the deployment of the WebServices of the Ear file " + fileName;
                logger.log(BasicLevel.ERROR, err + " : " + se.getMessage());
                wsService.removeCache(earClassLoader);
                try {
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "Undeploying Rars of the ear " + fileName);
                    }
                    if (resourceService != null && connectorUrls.length > 0) {
                        // remove Resource
                        resourceService.unDeployRars(connectorUrls, earUrl[0]);
                    }
                } catch (ServiceException se2) {
                    err = "Error during the undeployment of the rars files of the Ear file " + fileName;
                    logger.log(BasicLevel.ERROR, err + "': " + se2.getMessage());
                }

                throw new EarServiceException(err, se);
            }
        }

        // ejbClassLoader created above

        //Only if the jarUrls is not empty
        //And if the service is started
        if (ejbService != null && (jarUrls.length > 0)) {
            try {
                CompNamingContext contctx = null;
                try {
                    contctx = new CompNamingContext(dirUnpackURL.getFile());
                    contctx.rebind("earRootUrl", dirUnpackURL);
                    contctx.rebind("earUrl", earUrl[0]);
                    contctx.rebind("jarURLs", jarUrls);
                    contctx.rebind("earClassLoader", earClassLoader);
                    contctx.rebind("ejbClassLoader", ejbClassLoader);
                    contctx.rebind("roleNames", roleNames);
                } catch (NamingException e) {
                    String err = "Can not bind params for the ejb service, Can't deploy jars ";
                    throw new EarServiceException(err, e);
                }
                ejbService.deployJars(contctx);
            } catch (ServiceException e) {
                String err = "Error during the deployment of the jars files of the Ear file " + fileName;
                logger.log(BasicLevel.ERROR, err + "': " + e.getMessage());
                //remove DDesc
                ejbService.removeCache(earClassLoader);
                // Remove the deployed rars
                if (resourceService != null && connectorUrls.length > 0) {
                    // remove Resource
                    resourceService.unDeployRars(connectorUrls, earUrl[0]);
                }
                throw new EarServiceException(err, e);
            }
        }

        /*
         * We have deploy the ejb-jar files if the service is active And we have
         * the ejbClassLoader
         */

        // Link policy context of EJBs and Webs components
        linkPolicyObjects(earDD.getUserToRoleMapping(), jarUrls, warUrls, warsContextRoots);
        // commit ejb policy context (make them available for checking security)
        commitEJBPolicyObjects(jarUrls);

        /*
         * We're going to deploy wars Parent ClassLoader for web container is
         * ejbclassloader if ejbService is active, and a classloader with the
         * visibility of jar file if ejbservice is not active.
         */

        ClassLoader parentWarClassLoader = ejbClassLoader;

        //Only if the warUrls is not empty
        //And if the service is started
        if (webContainerService != null && (warUrls.length > 0)) {
            try {
                CompNamingContext contctx = null;
                try {
                    contctx = new CompNamingContext(dirUnpackURL.getFile());
                    contctx.rebind("earURL", earUrl[0]);
                    contctx.rebind("urls", warUrls);
                    contctx.rebind("parentClassLoader", parentWarClassLoader);
                    contctx.rebind("earClassLoader", earClassLoader);
                    contctx.rebind("altDDs", warsAltDDs);
                    contctx.rebind("contextRoots", warsContextRoots);
                } catch (NamingException e) {
                    String err = "Can not bind params for the web container service, Can't deploy wars ";
                    throw new EarServiceException(err, e);
                }
                webContainerService.deployWars(contctx);
                
                // Add a log entry corresponding to the directory where the wars of this ear are unpacked in order to
                // delete it when the ear is undeployed
                // Note: in production mode, the unpacking of war archives is not logged by the cleaner because the
                // directory name doesn't contain a timestamp
                try {
                    // Get the execution mode
                    String development = JProp.getInstance().getValue(DEVELOPMENT, "false");
                    
                    // Only in development mode
                    if ("true".equals(development)) {
                        String earFileName = new File(dirUnpackURL.getFile()).getName();  
                        File unpackedWarFile = new File(WORK_WEBAPPS_DIR, getJonasServerName() + File.separator
                                + INEAR_WORK_WEBAPPS_DIR_SUFFIX + File.separator + earFileName);
                        earDeployerLog.addEntry(new File(earUrl[0].getFile()), unpackedWarFile);
                    }    
                } catch (Exception e) {
                    logger.log(BasicLevel.WARN, "Error when adding the log entry for unpacked wars directory.");
                }

            } catch (JWebContainerServiceException e) {
                String err = "Error during the deployment of the wars file of the Ear file " + fileName;
                logger.log(BasicLevel.ERROR, err + "': " + e.getMessage());
                //remove DDesc
                webContainerService.removeCache(earClassLoader);
                err = "Undeploy the jars loaded from this ear";
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, err);
                }
                try {
                    if (ejbService != null && (jarUrls.length > 0)) {
                        //remove DDesc
                        ejbService.removeCache(earClassLoader);
                        //remove the ejbs
                        ejbService.unDeployJars(jarUrls);
                    }
                    // Remove the deployed rars
                    if (resourceService != null && connectorUrls.length > 0) {
                        // remove Resource
                        resourceService.unDeployRars(connectorUrls, earUrl[0]);
                    }
                } catch (ServiceException se) {
                    err = "Error during the undeployment of the jars file of the Ear file " + fileName;
                    logger.log(BasicLevel.ERROR, err + "': " + se.getMessage());
                    throw new EarServiceException(err, e);
                }

                throw new EarServiceException(err, e);
            }
        }

        // Commit JACC policy configuration objects of WebApp components.
        commitWebBPolicyObjects(warUrls, warsContextRoots);

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "We store the rars/wars/jars associated to the url :" + earUrl[0]);
        }

        // Complete the Deployment of the WebServices
        // Only if the WebServicesService is started
        if (wsService != null) {
            try {
                CompNamingContext contctx = null;
                try {
                    contctx = new CompNamingContext(earRootUrl.getFile());
                    contctx.rebind(AbsWebServicesServiceImpl.CLASSLOADER_CTX_PARAM, earClassLoader);
                    ObjectName name = J2eeObjectName.J2EEApplication(getDomainName(), getJonasServerName(), buildJ2eeApplicationName(earUrl[0]));
                    contctx.rebind(WebServicesService.PARENT_OBJECTNAME_CTX_PARAM , name);
                    contctx.rebind(WebServicesService.ISINEAR_CTX_PARAM, Boolean.TRUE);

                } catch (NamingException e) {
                    String err = "Can not bind params for the WebServices service, "
                            + "can't complete deployment of Web Services Endpoints";
                    throw new JWebContainerServiceException(err, e);
                }
                wsService.completeWSDeployment(contctx);
            } catch (ServiceException se) {
                String err = "Error during the deployment of the WebServices of the Ear file '" + earRootUrl + "'";
                logger.log(BasicLevel.ERROR, err + " : " + se.getMessage());
                try {
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "Undeploy the WebApps loaded from this ear");
                    }
                    if (webContainerService != null && (warUrls.length > 0)) {
                        //remove DDesc
                        webContainerService.removeCache(earClassLoader);
                        //remove the webapps
                        webContainerService.unDeployWars(warUrls);
                    }
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "Undeploy the EjbJars loaded from this ear");
                    }
                    if (ejbService != null && (jarUrls.length > 0)) {
                        //remove DDesc
                        ejbService.removeCache(earClassLoader);
                        //remove the ejbs
                        ejbService.unDeployJars(jarUrls);
                    }
                    // Remove the deployed rars
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "Undeploy the Resources loaded from this ear");
                    }
                    if (resourceService != null && connectorUrls.length > 0) {
                        // remove Resource
                        resourceService.unDeployRars(connectorUrls, earUrl[0]);
                    }
                } catch (ServiceException se2) {
                    err = "Error during the undeployment of the webapps/ejbjars/rars file of the Ear file " + fileName;
                    logger.log(BasicLevel.ERROR, err + "': " + se2.getMessage());
                }
                throw new JWebContainerServiceException(err, se);
            }
        }


        // Management support
        // ------------------

        // Build the 'name' used to construct the J2EEApplication ObjectName
        String sJ2EEApplicationName = buildJ2eeApplicationName(earUrl[0]);

        // Create AppClientModule MBeans first
        AppClientModule[] appClientModules = null;
        ObjectName[] onAppClientModules = null;
        if (clientUrls.length > 0) {
            appClientModules = new AppClientModule[clientUrls.length];
            onAppClientModules = new ObjectName[clientUrls.length];
        }

        String moduleName = null;
        String moduleFileName = null;
        String moduleDD = null;
        String jonasModuleDD = null;
        ClientContainerDeploymentDesc clientContainerDD = null;
        for (int i = 0; i < clientUrls.length; i++) {
            ClassLoader moduleCL = null;
            try {
                moduleCL = new ClientClassLoader(clientUrls[i], ejbClassLoader);
            } catch (IOException ioe) {
                // fail case :
                // we use the ejbjar classloader, but the descriptor loading will fail
                moduleCL = ejbClassLoader;
                // TODO Find a better Exception handling ?
            }
            moduleName =  EJBServiceImpl.buildEJBModuleName(clientUrls[i]);
            moduleFileName = clientUrls[i].getPath();
            try {
                clientContainerDD = ClientManagerWrapper.getDeploymentDesc(clientUrls[i], moduleCL, earClassLoader);
                moduleDD = clientContainerDD.getXmlContent();
                jonasModuleDD = clientContainerDD.getJOnASXmlContent();
            } catch (ClientContainerDeploymentDescException e) {
                // make debug trace
                String err = "Cannot read the deployment descriptors ': " + clientUrls[i] + "'" + e.toString();
                logger.log(BasicLevel.ERROR, err, e);
            }
            onAppClientModules[i] = J2eeObjectName.getAppClientModule(getDomainName(), getJonasServerName(), sJ2EEApplicationName, moduleName);
            appClientModules[i] = new AppClientModule(onAppClientModules[i].toString()
                    , moduleFileName
                    , moduleDD
                    , jonasModuleDD);
        }
        URL clientUrl = null;
        try {
            if (mbeanServer != null) {
                Registry oRegistry = JonasMBeanTools.getRegistry();
                ManagedBean oManaged = null;
                ModelMBean oMBean = null;
                // Register AppClientModule MBeans
                if (appClientModules != null) {
                    oManaged = oRegistry.findManagedBean("AppClientModule");
                    for (int i = 0; i < appClientModules.length; i++) {
                        clientUrl = clientUrls[i];
                        oMBean = oManaged.createMBean(appClientModules[i]);
                        mbeanServer.registerMBean(oMBean, onAppClientModules[i]);
                    }
                }
            }
        } catch (Exception eare) {
            logger.log(BasicLevel.ERROR, "Can not register the MBean for the client jar " + clientUrl, eare);
        }

        // Remove the Deployment descriptors
        if (ejbService != null) {
            ejbService.removeCache(earClassLoader);
        }

        if (webContainerService != null) {
            webContainerService.removeCache(earClassLoader);
        }

        if (wsService != null) {
            wsService.removeCache(earClassLoader);
        }

        // Create J2EEApplication MBeans
        ObjectName onEar = J2eeObjectName.J2EEApplication(getDomainName(), getJonasServerName(), sJ2EEApplicationName);
        // Store the ejb-jar, war and the rar urls associated with the ear URL
        // in case of success deploy
        Ear ear = new Ear(onEar.toString(), sJ2EEApplicationName, dirUnpackURL.getFile(), earUrl[0], earDD.getXmlContent(), jarUrls, warUrls,
                connectorUrls);

        try {
            if (mbeanServer != null) {
                Registry oRegistry = JonasMBeanTools.getRegistry();
                ManagedBean oManaged = oRegistry.findManagedBean("J2EEApplication");
                ModelMBean oMBean = oManaged.createMBean(ear);
                // Register Ear MBean (all the J2EEModule MBeans are already registered
                mbeanServer.registerMBean(oMBean, onEar);
            }
        } catch (Exception eare) {
            logger.log(BasicLevel.ERROR, "Can not register the MBean for the ear " + earUrl[0], eare);
        }


        ears.put(earUrl[0], ear);

        logger.log(BasicLevel.INFO, "Ear " + earUrl[0] + " available.");

        //set the timestamp dir
        File dirUnpackFile = new File(dirUnpackURL.getFile());
        dirUnpackFile.setLastModified((new Date()).getTime());

        // return ObjectName of the J2EEApplication MBean
        return onEar.toString();
    }

    /**
     * Deploy an ear with its given fileName
     * @param fileName file to deploy (ear)
     * @return The ObjectName of the J2EE Application MBean associated to the
     *         deployed EAR
     * @throws Exception if the deployment of the EAR failed.
     * @see org.objectweb.jonas.ear.EarService#deployEar(java.lang.String)
     */
    public String deployEar(String fileName) throws Exception {
        return deployEarMBean(fileName);
    }

    /**
     * Deploy an EAR by delegating the operation to the deployEar() method. This
     * is used by jonasAdmin management application.
     * @param fileName the fileName of the ear which must be be deployed.
     * @return The ObjectName of the J2EE Application MBean associated to the
     *         deployed EAR
     * @throws Exception if the deployment of the EAR failed.
     */
    public String deployEarMBean(String fileName) throws Exception {
        Context ctx = null;
        String mbeanOn = null;
        try {
            ctx = new CompNamingContext(fileName);
            ctx.rebind("filename", fileName);
        } catch (NamingException e) {
            String err = "Error when deploying the ear file ";
            logger.log(BasicLevel.ERROR, err, e);
            throw e;
        }
        try {
            mbeanOn = deployEar(ctx);
        } catch (ServiceException e) {
            logger.log(BasicLevel.ERROR, "Cannot deploy file '" + fileName + "'");
            throw new Exception("Cannot deploy file '" + fileName + "'", e);
        }
        return mbeanOn;
    }

    /**
     * Start the EAR service.
     * @throws ServiceException if the startup failed.
     */
    protected void doStart() throws ServiceException {

        //url
        URL earApps = null;
        try {
            earApps = new File(WORK_APPS_DIR + File.separator + getJonasServerName()).toURL();
        } catch (MalformedURLException mue) {
            throw new ServiceException("Error when trying to get the URL of the jonasroot/apps directory", mue);
        }

        File fLog = new File(earApps.getFile() + File.separator + getJonasServerName() + ".log");
        if (!fLog.exists()) {
            try {
                //create
                fLog.getParentFile().mkdirs();
                fLog.createNewFile();
            } catch (IOException e) {
                throw new ServiceException("cannot create the log file" + fLog, e);
            }
        }

        //get the logger
        try {
            earDeployerLog = new DeployerLog(fLog);
        } catch (DeployerLogException e) {
            throw new ServiceException("Can not get an EarDeployerLog", e);
        }

        // create the ear deployment work file control task
        EarCleanTask earCleanTask = new EarCleanTask(earApps, earDeployerLog);

        //get the cleaner ref
        workCleaner = WorkCleaner.getInstance();

        // add the ear deployment work file control task
        try {
            workCleaner.registerTask(earCleanTask);
        } catch (CleanerException ce) {
            throw new ServiceException("Cannot register the EAR clean task", ce);
        }

        // force the cleaning now
        workCleaner.executeTasks();

        // Deploy all ear which are in descriptors section
        Iterator it = earNames.iterator();
        while (it.hasNext()) {
            String fileName = (String) it.next();
            Context contctx = null;
            try {
                contctx = new CompNamingContext(fileName);
                contctx.rebind("filename", fileName);
            } catch (NamingException e) {
                throw new ServiceException("Cannot start the EarService", e);
            }
            try {
                deployEar(contctx);
            } catch (EarServiceException ese) {
                String err = "Cannot deploy the file '" + fileName + "' : " + ese.getMessage();
                logger.log(BasicLevel.WARN, err);
            } catch (Exception ue) {
                String err = "Cannot deploy the file (unexpected exception) '" + fileName + "' : " + ue.getMessage();
                logger.log(BasicLevel.WARN, err);
            }
        }

        try {
            // Register EarService MBean : EarServiceImplMBean
            if (mbeanServer != null) {
                mbeanServer.registerMBean(this, JonasObjectName.earService());
            }
        } catch (InstanceAlreadyExistsException iae) {
            throw new ServiceException("Cannot start the EarService Already Exists", iae);
        } catch (MBeanRegistrationException mbre) {
            throw new ServiceException("Cannot start the EarService (MBean registration error)", mbre);
        } catch (NotCompliantMBeanException ncmbe) {
            throw new ServiceException("Cannot start the EarService (MBean Not compliant error)", ncmbe);
        }
    }

    /**
     * Undeploy an EAR by sending the request to the EJB container and to the
     * WEB container and the Resource service.
     * @param ctx the context which contains the configuration in order to
     *        undeploy an EAR.
     * @throws EarServiceException if the undeployment of the EAR failed.
     */
    public void unDeployEar(Context ctx) throws EarServiceException {

        //Get the ear url param which we want to undeploy
        URL earUrl = null;
        try {
            earUrl = (URL) ctx.lookup("filename");
        } catch (NamingException e) {
            throw new EarServiceException("Trying to remove the ear file but there is no filename specified", e);
        }

        //This ear is in loaded EARs files ?
        Ear ear = (Ear) ears.get(earUrl);

        //Not loaded
        if (ear == null) {
            throw new EarServiceException("Trying to remove the ear file " + earUrl.getFile()
                    + " but this file was not found in the loaded Ear files");
        }

        //undeploy wars if the web container service is started
        URL[] warsToUndeploy = ear.getWars();
        if (webContainerService != null && (warsToUndeploy.length > 0)) {
            webContainerService.unDeployWars(warsToUndeploy);
        }

        //undeploy jars if the ejb service is started
        URL[] jarsToUndeploy = ear.getEjbJars();
        if (ejbService != null && (jarsToUndeploy.length > 0)) {
            ejbService.unDeployJars(jarsToUndeploy);
        }

        //undeploy rars if the resource service is started
        URL[] rarsToUndeploy = ear.getRars();
        if (resourceService != null && (rarsToUndeploy.length > 0)) {
            resourceService.unDeployRars(rarsToUndeploy, earUrl);
        }

        File f = new File(earUrl.getFile());
        //Undeploy successful, we remove the ear

        //Only if there is no Exception !
        ears.remove(earUrl);
        //unregister mbean
        if (mbeanServer != null) {
            // Build the ObjectName if the ear's MBea
            //String sJ2EEApplicationName = buildJ2eeApplicationName(earUrl);
            //ObjectName onEar =
            // J2eeObjectName.J2EEApplication(getDomainName(),
            // getJonasServerName(), sJ2EEApplicationName);
            // Un-regsiter the MBean
            try {
                // unregister J2EEApplication
                ObjectName onJ2EEApplication = ObjectName.getInstance(ear.getObjectName());
                String appName = onJ2EEApplication.getKeyProperty("name");
                String domainName = onJ2EEApplication.getDomain();
                String serverName = onJ2EEApplication.getKeyProperty("J2EEServer");
                mbeanServer.unregisterMBean(onJ2EEApplication);
                // ubregister the AppClientModules, if any
                ObjectName onAppClientModules = J2eeObjectName.getAppClientModules(domainName, serverName, appName);
                List onList = J2eeObjectName.queryObjectNames(onAppClientModules);
                for (int i = 0; i < onList.size(); i++) {
                    mbeanServer.unregisterMBean((ObjectName) onList.get(i));
                }
            } catch (Exception e) {
                logger.log(BasicLevel.ERROR, "Cannot remove the MBean for the ear " + earUrl.getFile(), e);
            }
        }

        logger.log(BasicLevel.INFO, "Ear " + f.getName() + " no longer available.");

    }

    /**
     * Undeploy an EAR by delegating the operation to the unDeployEar() method.
     * This is used for JMX management.
     * @param fileName the fileName of the ear which must be be undeployed.
     * @throws Exception if the undeployment of the EAR failed.
     * @see org.objectweb.jonas.ear.EarService#unDeployEar(java.lang.String)
     */
    public void unDeployEar(String fileName) throws Exception {
        unDeployEarMBean(fileName);
    }

    /**
     * Undeploy an EAR by delegating the operation to the unDeployEar() method.
     * This is used for JMX management.
     * @param fileName the fileName of the ear which must be be undeployed.
     * @throws Exception if the undeployment of the EAR failed.
     */
    public void unDeployEarMBean(String fileName) throws Exception {

        /*
         * We have only the name of the file, not its associated path, so we
         * look in the current directory and in the ear applications directory
         */

        //We check if the url exists in the hashtable
        //compare: First in the current dir, after in the apps directory
        URL url = null;
        URL earUrl = null;
        boolean found = false;
        try {
            Enumeration e = ears.keys();
            url = new File(fileName).getCanonicalFile().toURL();
            while (e.hasMoreElements() && !found) {
                earUrl = (URL) e.nextElement();
                if (earUrl.equals(url)) {
                    //We have found
                    found = true;
                }
            }

            if (fileName.toLowerCase().endsWith(".ear") && !found) {
                // In case of the name is an ear check also in
                // the JONAS_BASE/apps directory
                String earFileName = APPS_DIR + File.separator + fileName;
                e = ears.keys();
                url = new File(earFileName).getCanonicalFile().toURL();
                while (e.hasMoreElements() && !found) {
                    earUrl = (URL) e.nextElement();
                    if (earUrl.equals(url)) {
                        //We have found
                        found = true;
                    }
                }
            }
            if (!found) {
                String err = "Cannot undeploy the ear '" + fileName + "', it is not deployed.";
                logger.log(BasicLevel.ERROR, err);
                throw new Exception(err);
            }
        } catch (MalformedURLException mue) {
            String err = "Error when trying to get the url from" + fileName;
            logger.log(BasicLevel.ERROR, err);
            throw new Exception(err, mue);
        } catch (IOException ioe) {
            String err = "Error when trying to get the canonical file from " + fileName;
            logger.log(BasicLevel.ERROR, err + ioe.getMessage());
            throw new Exception(err, ioe);
        }

        //We've got the file, now we bind the params
        Context ctx = null;
        try {
            ctx = new CompNamingContext(fileName);
            //The fileName is an url
            ctx.rebind("filename", url);
        } catch (NamingException e) {
            String err = "Error when binding parameters";
            logger.log(BasicLevel.ERROR, err + e.getMessage());
            throw new Exception(err, e);
        }

        //Call the function
        try {
            unDeployEar(ctx);
        } catch (EarServiceException e) {
            throw new Exception(e);
        }

    }

    /**
     * @return current number of ears deployed in the JOnAS server
     */
    public Integer getCurrentNumberOfEars() {
        return new Integer(ears.size());
    }

    /**
     * Return the list of installed Applications. The EAR files or the
     * directories with expanded Applications are searched in JONAS_BASE/apps
     * and all Applications directories 'autoload'.
     * @return The list of EAR files or the directories with expanded
     *         Applications found
     * @throws Exception if the list can't be retrieved
     */
    public List getInstalledEars() throws Exception {
        // get EAR files found in JONAS_BASE/apps
        ArrayList al = JModule.getInstalledContainersInDir(APPS_DIR, JModule.EAR_EXTENSION, JModule.EAR_CHILD_DIR,
                JModule.EAR_CONFIRM_FILE);
        // get EAR files found in all autoload directories
        Iterator it = autoloadDirectories.iterator();
        while (it.hasNext()) {
            String name = (String) it.next();
            al.addAll(JModule.getInstalledContainersInDir(name, JModule.EAR_EXTENSION, JModule.EAR_CHILD_DIR,
                    JModule.EAR_CONFIRM_FILE));
        }
        return al;
    }

    /**
     * This method is added temporarily. It will disapear when Ears will have
     * their associated MBeans (when Ears will become manageable)
     * @return the names of the ears currently deployed in the JOnAS server
     */
    public Set getEarNames() {
        HashSet names = new HashSet();
        URL earUrl = null;
        for (Enumeration e = ears.keys(); e.hasMoreElements();) {
            earUrl = (URL) e.nextElement();
            names.add(earUrl.getFile());
        }
        return names;
    }

    /**
     * Add recursively the ears of the specified directory. If the dir has a
     * relative path, this path is relative from where the Application Server is
     * launched. If the dir is not found it will be searched in
     * $JONAS_BASE/apps/ directory.
     * @param dirPath the path to the directory containing the ears to load.
     */
    private void addEars(String dirPath) {
        boolean found = false;

        // Look the directory relative to the $JONAS_BASE/apps directory
        File    dir = new File(APPS_DIR + File.separator + dirPath);
        found = dir.isDirectory();

        if (found) {
            addEarsFrom(dir);
        } else {
            String err = "Warning: Cannot load dir: '" + dirPath + "' ";
            err += "is not a directory or directory doesn't exist";
            logger.log(BasicLevel.WARN, err);
        }
    }

    /**
     * Add the ears of the specified directory.
     * @param dir the directory from which the ears are loaded.
     * @throws EarServiceException if the argument is not a directory
     */
    private void addEarsFrom(File dir) throws EarServiceException {
        try {
            if (dir.isDirectory()) {
                File[] files = dir.listFiles();
                for (int i = 0; i < files.length; i++) {
                    if (files[i].getPath().toLowerCase().endsWith(".ear")) {
                        earNames.add(files[i].getCanonicalPath());
                    } else {
                        if (files[i].isDirectory()) {
                            addEarsFrom(files[i]);
                        }
                    }
                }
            } else {
                String err = "Cannot load dir: '" + dir.getPath();
                err += "' is not a directory";
                logger.log(BasicLevel.ERROR, err);
                throw new EarServiceException(err);
            }
        } catch (IOException e) {
            String err = "Invalid file name '" + dir.getPath();
            logger.log(BasicLevel.ERROR, err);
            throw new EarServiceException(err, e);
        }
    }

    /**
     * Test if the specified filename is already deployed or not. This method
     * is a management method provided by the EarServerice MBean.
     * @param fileName the name of the ear file.
     * @return true if the ear is deployed, else false.
     */
    public boolean isEarLoaded(String fileName) {

        URL url = null;
        boolean isLoaded = false;
        try {
            // Absolute filename
            try {
                url = new File(fileName).getCanonicalFile().toURL();
                //Check if the ear is already deployed or not
                if (ears.get(url) != null) {
                    isLoaded = true;
                } else {
                    // Not found force to test in relative Apps directory
                    url = null;
                }
            } catch (Exception e) {
                url = null;
            }
            // Relative filename
            if (url == null) {
                url = new File(APPS_DIR + File.separator + fileName).getCanonicalFile().toURL();
                //Check if the ear is already deployed or not
                if (ears.get(url) != null) {
                    isLoaded = true;
                }
            }
        } catch (Exception e) {
            String err = "Can not found if the ear is deployed or not";
            logger.log(BasicLevel.ERROR, err);
            return false;
        }

        return isLoaded;
    }

    /**
     * Test if the specified filename is already deployed or not. This
     * method is defined in the EarService interface.
     * @param fileName the name of the ear file.
     * @return true if the ear is deployed, else false.
     */
    public Boolean isEarDeployed(String fileName) {
        return new Boolean(isEarLoaded(fileName));
    }

    /**
     * Test if the specified unpack name is already deployed or not. This
     * method is defined in the EarService interface.
     * @param unpackName the name of the ear file.
     * @return true if the ear is deployed, else false.
     */
    public boolean isEarDeployedByUnpackName(String unpackName) {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "entering for unpackName= " + unpackName);
        }
        // for each ear loaded
        Enumeration lc = ears.elements();
        while (lc.hasMoreElements()) {
            Ear ear = (Ear) lc.nextElement();

            // get unpack name of the ear
            String deployedUnpackName = new File(ear.getUnpackName()).getName();
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "deployedUnpackName=" + deployedUnpackName);
            }

            if (deployedUnpackName.equals(unpackName)) {
                return true;
            }
            // else, go to the next loop
        }
        // not found
        return false;
    }


    /**
     * Return the list of all loaded Applications.
     * @return The list of deployed Applications
     */
    public List getDeployedEars() {
        ArrayList al = new ArrayList();
        Ear oEar;
        URL oURL;
        for (Enumeration enumEars = ears.elements(); enumEars.hasMoreElements();) {
            oEar = (Ear) enumEars.nextElement();
            oURL = oEar.getEarUrl();
            al.add(oURL.getFile());
        }
        return al;
    }

    /**
     * Return the list of installed Applications ready to deploy.
     * @return The list of deployable Applications
     * @throws Exception if the list can't be built
     */
    public List getDeployableEars() throws Exception {
        List al = getInstalledEars();
        al.removeAll(getDeployedEars());
        return al;
    }

    /**
     * Return the list of "autoload" directories for applications.
     * @return The list of all "autoload" directories
     */
    public List getAutoloadDirectories() {
        ArrayList al = new ArrayList();
        Iterator it = autoloadDirectories.iterator();
        while (it.hasNext()) {
            String fileName = (String) it.next();
            try {
                al.add(new File(fileName).toURL().getPath());
            } catch (Exception e) {
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Can't get autoload directories : " + e.getMessage());
                }
            }
        }
        return al;
    }

    /**
     * Return the Apps directory.
     * @return The Apps directory
     */
    public String getAppsDirectory() {
        String sRet = null;
        try {
            sRet = (new File(APPS_DIR)).toURL().getPath();
        } catch (Exception e) {
            throw new RuntimeException("Cannot get the APPS directory", e);
        }
        return sRet;
    }

    /**
     * Build the J2EEApplication name.
     * @param pUrl The URL of Ear file
     * @return The J2EEApplication name
     */
    public static String buildJ2eeApplicationName(URL pUrl) {
        String sName = null;
        try {
            sName = new File(pUrl.getFile()).getName();
            if ("file".equals(pUrl.getProtocol())) {
                sName = buildJ2eeApplicationName(sName);
            }
        } catch (NullPointerException e) {
            if (logger != null) {
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Can't build j2ee application", e);
                }
            }
        }
        return sName;
    }

    /**
     * Build the J2EEApplication name.
     * @param pFilename The name of Ear file
     * @return The J2EEApplication name
     */
    public static String buildJ2eeApplicationName(String pFilename) {
        String sName = null;
        try {
            sName = new File(pFilename).getName();
            int iPos = sName.lastIndexOf('.');
            if (iPos > -1) {
                sName = sName.substring(0, iPos);
            }
        } catch (NullPointerException e) {
            if (logger != null) {
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Can't build j2ee application : " + e.getMessage());
                }
            }
        }
        return sName;
    }

    /**
     * @return policy configuration factory
     */
    private PolicyConfigurationFactory getPolicyConfigurationFactory() {
        PolicyConfigurationFactory pcFactory = null;
        try {
            pcFactory = PolicyConfigurationFactory.getPolicyConfigurationFactory();
        } catch (Exception cnfe) {
            String err = "Cannot retrieve current policy configuration factory";
            logger.log(BasicLevel.ERROR, err + "': " + cnfe.getMessage());
            throw new EarServiceException(err, cnfe);
        }
        return pcFactory;
    }

    /**
     * Add context-id for given jar urls to a given List.
     * @param jarUrls list of EJB-Jars
     * @param contextIDs the list of context-id.
     */
    private void addEjbContextIdToList(URL[] jarUrls, List contextIDs) {
        // Get contextID of EJB
        if (ejbService != null) {
            for (int u = 0; u < jarUrls.length; u++) {
                contextIDs.add(ejbService.getContainer(jarUrls[u].getFile()).getContextId());
            }
        }
    }

    /**
     * Add context-id for given web urls to a given List.
     * Also, reset the context-id associated to it.
     * @param jarUrls list of EJB-Jars
     * @param contextIDs the list of context-id.
     * @param contextRoots the name of the context-root of the web applications.
     * @param resetPolicyConfiguration reset or not the associated policy configuration.
     * @throws EarServiceException if policy context cannot be get.
     */
    private void addWebBContextIdToList(URL[] warUrls, String[] contextRoots, List contextIDs, final boolean resetPolicyConfiguration) throws EarServiceException {
        if (warUrls != null) {
            for (int u = 0; u < warUrls.length; u++) {
                // build context ID of war
                String ctxId = warUrls[u].getFile() + contextRoots[u];
                // reset the policy configuration associated to this context ID.
                if (resetPolicyConfiguration) {
                    try {
                        getPolicyConfigurationFactory().getPolicyConfiguration(ctxId, true);
                    } catch (PolicyContextException pce) {
                        String err = "Cannot retrieve a policy configuration";
                        logger.log(BasicLevel.ERROR, err + "': " + pce.getMessage());
                        throw new EarServiceException(err, pce);
                    }
                }
                contextIDs.add(ctxId);
            }
        }
    }


    /**
     * Link policy configuration objects of EJB and Web Component
     * @param userToRoleMapping mapping for user-to-role
     * @param jarUrls list of jars which have been deployed
     * @param warUrls list of wars which have been deployed
     * @param contextRoots list of context-root elements for each war url
     * @throws EarServiceException if the policy objects can't be linked
     */
    private void linkPolicyObjects(Map userToRoleMapping, URL[] jarUrls, URL[] warUrls, String[] contextRoots) throws EarServiceException {
        List ctxIDs = new LinkedList();
        // Get contextID of EJB
        addEjbContextIdToList(jarUrls, ctxIDs);

        // Now for WebApp
        addWebBContextIdToList(warUrls, contextRoots, ctxIDs, true);

        try {
            // Now link the policy configuration objects
            for (Iterator itCtxId = ctxIDs.iterator(); itCtxId.hasNext();) {
                String toBeLinkedCtxId = (String) itCtxId.next();
                PolicyConfiguration toBeLinkedPC = getPolicyConfigurationFactory().getPolicyConfiguration(toBeLinkedCtxId, false);
                for (Iterator linkCId = ctxIDs.iterator(); linkCId.hasNext();) {
                    String linkedCtxId = (String) linkCId.next();
                    if (!toBeLinkedCtxId.equals(linkedCtxId)) {
                        PolicyConfiguration linkedPC = getPolicyConfigurationFactory().getPolicyConfiguration(linkedCtxId, false);
                        toBeLinkedPC.linkConfiguration(linkedPC);
                    }
                }
            }
        } catch (PolicyContextException pce) {
            String err = "Cannot retrieve a policy configuration";
            logger.log(BasicLevel.ERROR, err + "': " + pce.getMessage());
            throw new EarServiceException(err, pce);
        }

        // Do user-to-role mapping
        if (userToRoleMapping != null) {
            for (Iterator itCtxId = ctxIDs.iterator(); itCtxId.hasNext();) {
                String contextId = (String) itCtxId.next();
                for (Iterator itMapping = userToRoleMapping.keySet().iterator(); itMapping.hasNext();) {
                    String principalName = (String) itMapping.next();
                    List roles = (List) userToRoleMapping.get(principalName);
                    String[] roleNames = (String[]) roles.toArray(new String[roles.size()]);
                    JPolicyUserRoleMapping.addUserToRoleMapping(contextId, principalName, roleNames);
                }
            }

        }
    }

    /**
     * Commit policy configuration objects of EJB Component
     * @param jarUrls list of jars which have been deployed
     * @throws EarServiceException if the policy objects can't be committed
     */
    private void commitEJBPolicyObjects(final URL[] jarUrls) {
        List ctxIDs = new LinkedList();
        addEjbContextIdToList(jarUrls, ctxIDs);
        commitPolicyObjects(ctxIDs);
    }


    /**
     * Commit policy configuration objects of Web Component
    * @param warUrls list of wars which have been deployed
     * @param contextRoots list of context-root elements for each war url
     * @throws EarServiceException if the policy objects can't be committed
     */
    private void commitWebBPolicyObjects(final URL[] warUrls, final String[] contextRoots) {
        List ctxIDs = new LinkedList();
        addWebBContextIdToList(warUrls, contextRoots, ctxIDs, false);
        commitPolicyObjects(ctxIDs);

    }

    /**
     * Commit policy context IDs of the given list.
     * @param ctxIDs list of context ID to commit.
     */
    private void commitPolicyObjects(final List ctxIDs) {
        String ctxId = null;
        try {
            // commit the policy configuration objects
            for (Iterator itCtxId = ctxIDs.iterator(); itCtxId.hasNext();) {
                ctxId = (String) itCtxId.next();
                PolicyConfiguration pc = getPolicyConfigurationFactory().getPolicyConfiguration(ctxId, false);
                pc.commit();
            }
        } catch (PolicyContextException pce) {
            String err = "Cannot commit policy configuration with Id '" + ctxId + "'";
            logger.log(BasicLevel.ERROR, err + "': " + pce.getMessage());
            throw new EarServiceException(err, pce);
        }

        // refresh policy
        Policy.getPolicy().refresh();
    }


}