/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007-2008 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.ear.internal;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.Policy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.management.ObjectName;
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.objectweb.util.monolog.api.BasicLevel;
import org.ow2.easybeans.api.EZBContainer;
import org.ow2.easybeans.api.EZBContainerException;
import org.ow2.easybeans.api.EZBServer;
import org.ow2.easybeans.deployment.InjectionHolder;
import org.ow2.easybeans.deployment.api.EZBInjectionHolder;
import org.ow2.easybeans.loader.EasyBeansClassLoader;
import org.ow2.easybeans.naming.context.ContextImpl;
import org.ow2.easybeans.persistence.PersistenceUnitManager;
import org.ow2.easybeans.persistence.api.EZBPersistenceUnitManager;
import org.ow2.easybeans.persistence.xml.PersistenceXmlFileAnalyzer;
import org.ow2.easybeans.persistence.xml.PersistenceXmlFileAnalyzerException;
import org.ow2.easybeans.resolver.ApplicationJNDIResolver;
import org.ow2.easybeans.resolver.api.EZBApplicationJNDIResolver;
import org.ow2.easybeans.resolver.api.EZBContainerJNDIResolver;
import org.ow2.jonas.Version;
import org.ow2.jonas.deployment.client.wrapper.ClientManagerWrapper;
import org.ow2.jonas.deployment.ear.EarDeploymentDesc;
import org.ow2.jonas.deployment.ear.EarDeploymentDescException;
import org.ow2.jonas.deployment.ear.wrapper.EarManagerWrapper;
import org.ow2.jonas.deployment.ejb.wrapper.EjbManagerWrapper;
import org.ow2.jonas.deployment.web.wrapper.WebManagerWrapper;
import org.ow2.jonas.ear.EarServiceException;
import org.ow2.jonas.ejb.easybeans.PrefixedNamingStrategy;
import org.ow2.jonas.ejb2.EJBService;
import org.ow2.jonas.generators.genbase.generator.Config;
import org.ow2.jonas.generators.wsgen.WsGen;
import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.lib.bootstrap.JProp;
import org.ow2.jonas.lib.bootstrap.LoaderManager;
import org.ow2.jonas.lib.bootstrap.loader.JClassLoader;
import org.ow2.jonas.lib.execution.ExecutionResult;
import org.ow2.jonas.lib.execution.IExecution;
import org.ow2.jonas.lib.execution.RunnableHelper;
import org.ow2.jonas.lib.management.javaee.J2eeObjectName;
import org.ow2.jonas.lib.naming.ComponentContext;
import org.ow2.jonas.lib.work.DeployerLog;
import org.ow2.jonas.properties.ServerProperties;
import org.ow2.jonas.resource.ResourceService;
import org.ow2.jonas.resource.ResourceServiceException;
import org.ow2.jonas.service.ServiceException;
import org.ow2.jonas.versioning.VersioningService;
import org.ow2.jonas.web.JWebContainerService;
import org.ow2.jonas.web.JWebContainerServiceException;
import org.ow2.jonas.ws.WebServicesService;
import org.ow2.util.archive.api.ArchiveException;
import org.ow2.util.archive.api.IArchive;
import org.ow2.util.archive.impl.ArchiveManager;
import org.ow2.util.ee.deploy.api.deployable.CARDeployable;
import org.ow2.util.ee.deploy.api.deployable.EARDeployable;
import org.ow2.util.ee.deploy.api.deployable.EJB21Deployable;
import org.ow2.util.ee.deploy.api.deployable.EJB3Deployable;
import org.ow2.util.ee.deploy.api.deployable.EJBDeployable;
import org.ow2.util.ee.deploy.api.deployable.IDeployable;
import org.ow2.util.ee.deploy.api.deployable.LibDeployable;
import org.ow2.util.ee.deploy.api.deployable.RARDeployable;
import org.ow2.util.ee.deploy.api.deployable.WARDeployable;
import org.ow2.util.ee.deploy.api.deployer.DeployerException;
import org.ow2.util.ee.deploy.api.deployer.IDeployer;
import org.ow2.util.ee.deploy.impl.helper.DeployableHelper;
import org.ow2.util.ee.deploy.impl.helper.UnpackDeployableHelper;
import org.ow2.util.file.FileUtils;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;
import org.ow2.util.url.URLUtils;

/**
 * This deployer will deploy EAR by using the other services.
 * @author Florent BENOIT
 *         Contributors:
 *              S. Ali Tokmen (fixes for EARs with spaces on Windows, versioning)
 */
public class EarDeployer implements IDeployer {

    /**
     * Folder to create in tmp folder.
     */
    public static final String DEFAULT_FOLDER = "JOnAS-Deployer";

    /**
     * Logger.
     */
    private Log logger = LogFactory.getLog(EarDeployer.class);

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

    /**
     * Reference to the JMX service.
     */
    private JmxService jmxService = null;

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

    /**
     * Reference to the EJB 2.1 service.
     */
    private EJBService ejb21Service = null;

    /**
     * Reference to the Web Container Service.
     */
    private JWebContainerService webContainerService = null;

    /**
     * Application Classloader that needs to be used for all EARs.
     */
    private ClassLoader appsClassLoader;

    /**
     * List of deployed ear.
     */
    private Map<URL, EARDeployable> ears = null;

    /**
     * Server properties.
     */
    private ServerProperties serverProperties = null;

    /**
     * Versioning service.
     */
    private VersioningService versioningService;

    /**
     * Work directory.
     */
    private static final String WORK_DIR = JProp.getWorkDir();

    /**
     * EAR Work directory.
     */
    private 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";

    /**
     * Build a new instance of the EAR deployer.
     */
    public EarDeployer() {
        this.ears = new HashMap<URL, EARDeployable>();
    }

    /**
     * Embedded server linked to this deployer.
     */
    private EZBServer embedded = null;

    /**
     *  Reference to the {@link DeployerLog} of the EAR service.
     */
    private DeployerLog deployerLog;

    /**
     * WSDL files are located in META-INF/wsdl directory .
     */
    private static final String WSDL_DIRECTORY = "META-INF/wsdl/";

    /**
     * @return the embedded instance used by this server.
     */
    public EZBServer getEmbedded() {
        return embedded;
    }

    /**
     * Receive Embedded instance for this deployer.
     * @param embedded the given instance of the embedded server.
     */
    public void setEmbedded(final EZBServer embedded) {
        this.embedded = embedded;
    }

    /**
     * Deploy a deployable. It can be an EJB jar, EAR, WAR, etc.
     * @param deployable a given deployable
     * @throws DeployerException if the deployment is not done.
     */
    public void deploy(final IDeployable<?> deployable) throws DeployerException {
        check(deployable);

        // Deploy the EAR Deployable
        if (deployable instanceof EARDeployable) {
            // Needs to unpack it before deploying it
            try {
                File folder = new File(WORK_APPS_DIR, getServerProperties().getServerName());
                File originalFile = URLUtils.urlToFile(deployable.getArchive().getURL());
                String archiveName = FileUtils.lastModifiedFileName(originalFile);
                EARDeployable earDeployable = UnpackDeployableHelper
                        .unpack((EARDeployable) deployable, folder, archiveName, false);
                deployEAR(earDeployable);
            } catch (Exception e) {
                throw new DeployerException("Cannot deploy archive for '" + deployable.getArchive() + "'", e);
            }
        }
    }

    /**
     * Apply WSGen of the given archive.
     * @param archive the archive to check
     * @param deployable the deployable to use
     * @throws DeployerException if WSGen cannot be applied.
     * @return the modified deployable or the original deployable if WSGen has not been launched.
     */
    protected EARDeployable applyWSGenIfNeeded(final IArchive archive, final EARDeployable deployable) throws DeployerException {

        // WS service present ?
        if (wsService == null) {
            logger.debug("The WS service is not present, no need to call WSGen");
            return deployable;
        }

        // Auto WsGen enabled ?
        if (!wsService.isAutoWsGenEngaged()) {
            logger.debug("Automatic WsGen is not enabled, no need to call WSGen");
            return deployable;
        }

        // Check version in manifest
        String jonasVersionWsGen = archive.getMetadata().get("WsGen-JOnAS-Version");
        if (Version.getNumber().equals(jonasVersionWsGen)) {
            // no changes, just continue the normal deployment process
            logger.debug(BasicLevel.DEBUG, "Manifest version up-to-date, no need to call WSGen");
            return deployable;
        }


        // Execute WSGen in an execution block
        IExecution<EARDeployable> exec = new IExecution<EARDeployable>() {
            public EARDeployable execute() throws Exception {
                EARDeployable earDeployable = deployable;

                WsGen wsGen = new WsGen();
                Config config = new Config();
                config.setInputname(URLUtils.urlToFile(deployable.getArchive().getURL()).getPath());

                // Create temporary output directory
                config.setOut(new File(JProp.getWorkDir(), "wsgen"));
                try {
                    String path = wsGen.execute(config, deployable);
                    // Check if a new archive has been created
                    if (!path.equals(URLUtils.urlToFile(deployable.getArchive().getURL()).getPath())) {
                        IArchive archive = ArchiveManager.getInstance().getArchive(new File(path));
                        // Build a new deployable with the new archive
                        EARDeployable newDeployable = (EARDeployable) DeployableHelper.getDeployable(archive);
                        File folder = new File(WORK_APPS_DIR, getServerProperties().getServerName());
                        String archiveName = URLUtils.urlToFile(deployable.getArchive().getURL()).getName();
                        earDeployable = UnpackDeployableHelper.unpack(newDeployable, folder, archiveName, false);
                        earDeployable.setOriginalDeployable(deployable.getOriginalDeployable());
                    }
                } catch (Exception e) {
                    throw new DeployerException("Cannot execute WSGen on archive '" + archive + "'", e);
                } finally {
                    FileUtils.delete(config.getOut());
                }

                return earDeployable;
            }
        };

        // Execute
        ExecutionResult<EARDeployable> result = null;
        try {
            result = RunnableHelper.execute(LoaderManager.getInstance().getExternalLoader(), exec);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // Throw a DeployerException if needed
        if (result.hasException()) {
            throw new DeployerException(result.getException().getMessage(), result.getException());
        }

        return result.getResult();

    }

    /**
     * Deploy the given deployable.
     * @param deployable the EAR deployable.
     * @throws DeployerException if the EAR is not deployed.
     */
    protected void deployEAR(final EARDeployable deployable) throws DeployerException {

        // Archive
        IArchive earArchive = deployable.getArchive();

        // Apply WsGen if needed
        EARDeployable earDeployable = applyWSGenIfNeeded(earArchive, deployable);

        // Get URL of this Deployable
        URL earURL;
        try {
            earURL = earArchive.getURL();
            if ("file".equals(earURL.getProtocol())) {
                // If file, make sure it is escaped correctly
                File file = URLUtils.urlToFile(earURL);
                earURL = file.toURL();
            }
        } catch (Exception e) {
            throw new DeployerException("Cannot get URL from archive '" + earArchive + "'", e);
        }

        // Get URL of the initial deployable for the EAR Module
        URL originalURL;
        try {
            originalURL = earDeployable.getOriginalDeployable().getArchive().getURL();
            if ("file".equals(originalURL.getProtocol())) {
                // If file, make sure it is escaped correctly
                File file = URLUtils.urlToFile(originalURL);
                originalURL = file.toURL();
            }
            // The file is unpacked, so log it
            if (deployerLog != null) {
                deployerLog.addEntry(new File(originalURL.getFile()), new File(earURL.getFile()));
            }
        } catch (Exception e) {
            throw new DeployerException("Cannot get the url of the initial deployable for the EAR Module '"
                    + earDeployable.getOriginalDeployable() + "'.", e);
        }

        // Create classLoader for loading the Deployment descriptor
        // parent classloader is the current classloader
        URLClassLoader loaderCls = new URLClassLoader(new URL[] {earURL}, appsClassLoader);

        // Deployment descriptor
        EarDeploymentDesc earDD = null;

        // Entry application.xml ?
        URL applicationXML = null;
        try {
            applicationXML = earArchive.getResource("META-INF/application.xml");
        } catch (ArchiveException e) {
           throw new DeployerException("Cannot get resource META-INF/application.xml", e);
        }

        if (applicationXML != null) {
            // yes, then parse the Deployment Descriptor

            try {
                earDD = EarManagerWrapper.getDeploymentDesc(earDeployable, loaderCls);
            } catch (EarDeploymentDescException e) {
                String err = "Error in the Deployment descriptor '" + earDeployable.getOriginalDeployable() + "': " + e;
                logger.error(err);
                throw new EarServiceException(err, e);
            }
        }

        // security roles
        String[] securityRoles = new String[0];
        if (earDD != null) {
            securityRoles = earDD.getSecurityRolesNames();
        }


        // TODO: Classpath manager

        /*
         * We have the urls to load at our level We can now create our
         * classLoader
         */
        logger.debug("Creating the EAR classLoader");
        JClassLoader earClassLoader = new JClassLoader(earURL.toExternalForm(), new URL[0], appsClassLoader);

        // 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] + ";";
        }

        logger.debug("role names = ''{0}''", 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
         */

        // Get the URLs of EJB, WEB and Clients
        List<URL> urlsEJB = new LinkedList<URL>();
        List<URL> urlsAltDDEJB = new LinkedList<URL>();
        for (EJBDeployable<?> ejb : earDeployable.getEJBDeployables()) {
            try {
                URL ejbURL = ejb.getArchive().getURL();
                if ("file".equals(ejbURL.getProtocol())) {
                    // If file, make sure it is escaped correctly
                    File file = URLUtils.urlToFile(ejbURL);
                    ejbURL = file.toURL();
                }
                urlsEJB.add(ejbURL);
                urlsAltDDEJB.add(earDeployable.getAltDDURL(ejb));
            } catch (Exception e) {
                throw new DeployerException("Cannot get the URL for the archive '" + ejb.getArchive() + "'", e);
            }
        }
        List<URL> urlsWAR = new LinkedList<URL>();
        List<URL> urlslAtDDWAR = new LinkedList<URL>();
        for (WARDeployable war : earDeployable.getWARDeployables()) {
            try {
                URL warURL = war.getArchive().getURL();
                if ("file".equals(warURL.getProtocol())) {
                    // If file, make sure it is escaped correctly
                    File file = URLUtils.urlToFile(warURL);
                    warURL = file.toURL();
                }
                urlsWAR.add(warURL);
                urlslAtDDWAR.add(earDeployable.getAltDDURL(war));
            } catch (Exception e) {
                throw new DeployerException("Cannot get the URL for the archive '" + war.getArchive() + "'", e);
            }
        }

        // 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
        if (urlsWAR.size() > 0) {
            if (deployerLog != null) {
                try {
                    File unpackedWarFile = new File(WORK_WEBAPPS_DIR, getServerProperties().getServerName() + File.separator
                            + INEAR_WORK_WEBAPPS_DIR_SUFFIX + File.separator + earDeployable.getModuleName());
                    deployerLog.addEntry(new File(originalURL.getFile()), unpackedWarFile);
                } catch (Exception e) {
                    logger.warn("Error when adding the log entry for unpacked wars directory.");
                }
            }
        }

        List<URL> urlsRAR = new LinkedList<URL>();
        List<URL> urlsAtDDRAR = new LinkedList<URL>();
        for (RARDeployable rar : earDeployable.getRARDeployables()) {
            try {
                URL rarURL = rar.getArchive().getURL();
                if ("file".equals(rarURL.getProtocol())) {
                    // If file, make sure it is escaped correctly
                    File file = URLUtils.urlToFile(rarURL);
                    rarURL = file.toURL();
                }
                urlsRAR.add(rarURL);
                urlsAtDDRAR.add(earDeployable.getAltDDURL(rar));
            } catch (Exception e) {
                throw new DeployerException("Cannot get the URL for the archive '" + rar.getArchive() + "'", e);
            }
        }

        List<URL> urlsClient = new LinkedList<URL>();
        List<URL> urlsAtDDClient = new LinkedList<URL>();
        for (CARDeployable car : earDeployable.getCARDeployables()) {
            try {
                URL carURL = car.getArchive().getURL();
                if ("file".equals(carURL.getProtocol())) {
                    // If file, make sure it is escaped correctly
                    File file = URLUtils.urlToFile(carURL);
                    carURL = file.toURL();
                }
                urlsClient.add(carURL);
                urlsAtDDClient.add(earDeployable.getAltDDURL(car));
            } catch (Exception e) {
                throw new DeployerException("Cannot get the URL for the archive '" + car.getArchive() + "'", e);
            }
        }

        /**
         * 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
        // TODO: use ALT-DD !!

        EjbManagerWrapper.setAvailableEjbJarsAndAltDDs(earClassLoader,
                urlsEJB.toArray(new URL[urlsEJB.size()]),
                urlsAltDDEJB.toArray(new URL[urlsAltDDEJB.size()]));

        WebManagerWrapper.setAltDD(earClassLoader,
                urlsWAR.toArray(new URL[urlsWAR.size()]),
                urlslAtDDWAR.toArray(new URL[urlslAtDDWAR.size()]));

        ClientManagerWrapper.setAltDD(earClassLoader,
                urlsClient.toArray(new URL[urlsClient.size()]),
                urlsAtDDClient.toArray(new URL[urlsAtDDClient.size()]));


        // Deploy the RAR files of the EAR (if any)
        deployRARs(earDeployable, earURL, earClassLoader);

        // deploy EJB3s
        // Get EJBs of this EAR
        List<EJB3Deployable> ejb3s = earDeployable.getEJB3Deployables();
        List<EJBDeployable<?>> ejbs = earDeployable.getEJBDeployables();

        // Get libraries of this EAR
        List<LibDeployable> libs = earDeployable.getLibDeployables();

        // Create array of URLs with EJBs + Libraries
        List<URL> urls = new LinkedList<URL>();
        for (EJBDeployable<?> ejb : ejbs) {
            try {
                URL ejbURL = ejb.getArchive().getURL();
                if ("file".equals(ejbURL.getProtocol())) {
                    // If file, make sure it is escaped correctly
                    File file = URLUtils.urlToFile(ejbURL);
                    ejbURL = file.toURL();
                }
                urls.add(ejbURL);
            } catch (Exception e) {
                throw new DeployerException("Cannot get the URL for the Archive '" + ejb.getArchive() + "'.", e);
            }
        }
        for (LibDeployable lib : libs) {
            try {
                URL libURL = lib.getArchive().getURL();
                if ("file".equals(libURL.getProtocol())) {
                    // If file, make sure it is escaped correctly
                    File file = URLUtils.urlToFile(libURL);
                    libURL = file.toURL();
                }
                urls.add(libURL);
            } catch (Exception e) {
                throw new DeployerException("Cannot get the URL for the Archive '" + lib.getArchive() + "'.", e);

            }
        }

        // Create classloader with these URLs
        URL[] arrayURLs = urls.toArray(new URL[urls.size()]);

        // Child of the EAR classloader with RARs
        ClassLoader ejbClassLoader = new EasyBeansClassLoader(arrayURLs, earClassLoader);

        // Get Extra libraries
        List<IArchive> libArchives = getLibArchives(earDeployable);

        // Analyze libraries to detect persistence archive
        EZBPersistenceUnitManager persistenceUnitManager = getPersistenceUnitManager(earDeployable, ejbClassLoader);

        // Reset context ID of EJBs
        addEjbContextIdToList(earDeployable, new LinkedList<String>(), true);

        // Create containers for each EJB3 deployable
        String prefix = null;
        String versionID = null;
        if (versioningService != null && versioningService.isVersioningEnabled()) {
            prefix = versioningService.getPrefix(earDeployable);
            versionID = versioningService.getVersionID(earDeployable);
        }
        String javaEEApplicationName = earDeployable.getModuleName();
        List<EZBContainer> containers = new LinkedList<EZBContainer>();
        for (EJB3Deployable ejb : ejb3s) {
            if (getEmbedded() == null) {
                throw new DeployerException("No EJB3 service, but there are EJB3s in the given EAR archive '"
                        + earDeployable.getArchive() + "'.");
            }

            // Create a container for the given archive
            EZBContainer container = getEmbedded().createContainer(ejb.getArchive());
            if (prefix != null) {
                container.getConfiguration().setNamingStrategy(
                        new PrefixedNamingStrategy(prefix, container.getConfiguration().getNamingStrategy()));
            }
            container.setApplicationName(javaEEApplicationName);

            // Add the metadata
            container.setExtraArchives(libArchives);

            // Add the container
            containers.add(container);
        }

        // Create Resolver for EAR
        EZBApplicationJNDIResolver applicationJNDIResolver = new ApplicationJNDIResolver();

        // Configure containers
        for (EZBContainer container : containers) {
            // Set the classloader that needs to be used
            container.setClassLoader(ejbClassLoader);
            // Add persistence context found
            container.setPersistenceUnitManager(persistenceUnitManager);

            // set parent JNDI Resolver
            EZBContainerJNDIResolver containerJNDIResolver = container.getConfiguration().getContainerJNDIResolver();
            containerJNDIResolver.setApplicationJNDIResolver(applicationJNDIResolver);

            // Add child on application JNDI Resolver
            applicationJNDIResolver.addContainerJNDIResolver(containerJNDIResolver);

            // Resolve container
            try {
                container.resolve();
            } catch (EZBContainerException e) {
                throw new DeployerException("Cannot resolve the container '" + container.getArchive() + "'.", e);
            }


        }

        // Create EasyBeans injection Holder
        InjectionHolder ejbInjectionHolder = new InjectionHolder();
        ejbInjectionHolder.setPersistenceUnitManager(persistenceUnitManager);
        ejbInjectionHolder.setJNDIResolver(applicationJNDIResolver);

        // Start containers
        for (EZBContainer container : containers) {
            try {
                container.start();
            } catch (Exception e) {
                logger.error("Cannot start container {0}", container.getName(), e);
                // stop it
                try {
                    container.stop();
                    getEmbedded().removeContainer(container);
                } catch (Exception se) {
                    logger.error("Cannot stop failing container {0}", container.getName(), se);
                }
                // rethrow it
                throw new DeployerException("Container '" + container.getName() + "' has failed", e);
            }
        }
        if (prefix != null && containers.size() > 0) {
            versioningService.createJNDIBindingMBeans(earDeployable.getModuleName(), versionID, prefix);
        }

        // Deploy EJB 2.1
        deployEJB21s(earDeployable, earURL, earClassLoader, ejbClassLoader, roleNames);

        // Deploy Web Services
        deployWebServices(earDeployable, earURL, earClassLoader, ejbClassLoader, urlsEJB, urlsWAR);

        // Link policy context of EJBs and Webs components
        linkPolicyObjects(earDeployable);

        // Commit EJB Policy objects
        commitEJBPolicyObjects(earDeployable);

        // Deploy Web App
        deployWARs(earDeployable, earURL, earClassLoader, ejbClassLoader, ejbInjectionHolder);

        // Commit Web policy objects
        commitWebBPolicyObjects(earDeployable);

        // Complete deployment of Web Services
        completeWebServicesDeployment(earURL, earDeployable, earClassLoader);

        // Create EAR object
        //TODO: get Deployment desc data
        EARModule earModule = new EARModule(earDeployable, "");

        // Register MBean
        try {
            jmxService.registerMBean(earModule);
        } catch (Exception e) {
            throw new DeployerException("Cannot register the MBean for the EAR Module of EARDeployable '"
                    + earDeployable.getOriginalDeployable() + "'.", e);
        }

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

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

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

        // keep the link original deployable -> unpacked deployable for undeployment
        ears.put(originalURL, earDeployable);

        logger.info("''{0}'' EAR Deployable is now deployed", earDeployable.getOriginalDeployable());

    }

    /**
     * Gets the persistence unit manager for the given EAR and classloader.
     * @param earDeployable the ear deployable
     * @param appClassLoader the classloader used as deployable
     * @return the given persistence unit manager
     */
    protected EZBPersistenceUnitManager getPersistenceUnitManager(final EARDeployable earDeployable,
            final ClassLoader appClassLoader) {
        // Analyze libraries to detect persistence archive (only once for now
        // and for all libraries)
        // Get libraries of this EAR
        List<LibDeployable> libs = earDeployable.getLibDeployables();
        PersistenceUnitManager persistenceUnitManager = null;
        for (LibDeployable lib : libs) {
            PersistenceUnitManager builtPersistenceUnitManager = null;
            try {
                builtPersistenceUnitManager = PersistenceXmlFileAnalyzer.analyzePersistenceXmlFile(lib.getArchive(),
                        appClassLoader);
            } catch (PersistenceXmlFileAnalyzerException e) {
                throw new IllegalStateException("Failure when analyzing the persistence.xml file", e);
            }

            // Existing manager and new manager found
            if (persistenceUnitManager != null) {
                if (builtPersistenceUnitManager != null) {
                    // Add the persistence unit infos to the existing
                    // persistence unit manager
                    persistenceUnitManager.addExtraPersistenceUnitInfos(builtPersistenceUnitManager.getPersistenceUnitInfos());
                }
            } else {
                // New persistence manager use the built manager
                persistenceUnitManager = builtPersistenceUnitManager;
            }
        }
        return persistenceUnitManager;
    }

    /**
     * Undeploy the given EAR.
     * @param tmpEARDeployable the deployable to remove.
     * @throws DeployerException if the EAR is not undeployed.
     */
    protected void undeployEAR(final EARDeployable tmpEARDeployable) throws DeployerException {
        logger.info("Undeploying {0}", tmpEARDeployable);

        // From which deployable get the containers deployed
        EARDeployable earDeployable = tmpEARDeployable;

        //  get EAR URL
        URL earURL;
        try {
            earURL = earDeployable.getArchive().getURL();
        } catch (ArchiveException e) {
            throw new DeployerException("Cannot get the URL on the EAR deployable '" + earDeployable + "'.", e);
        }

        // Check if this archive has been unpacked ?
        EARDeployable unpackedDeployable = earDeployable.getUnpackedDeployable();
        if (unpackedDeployable != null) {
            earDeployable = unpackedDeployable;
        } else {
            if (ears.containsKey(earURL)) {
                earDeployable = ears.get(earURL);
            }
        }

        // Undeploy wars (by sending URL of these wars)
        List<WARDeployable> warDeployables = earDeployable.getWARDeployables();
        if (warDeployables != null && webContainerService != null) {
            List<URL> urls = new LinkedList<URL>();
            for (WARDeployable warDeployable : warDeployables) {
                try {
                    // Escape the URL since warDeployables have escaped URLs
                    urls.add(URLUtils.urlToFile(warDeployable.getArchive().getURL()).toURL());
                } catch (Exception e) {
                    logger.error("Cannot get the URL from the Deployable ''{0}''", warDeployable, e);
                }
            }

            URL[] warURLs = urls.toArray(new URL[urls.size()]);
            webContainerService.unDeployWars(warURLs);
        }

        // Undeploy EJB 2.1 jars (by sending URL of these jars)
        List<EJB21Deployable> ejb21Deployables = earDeployable.getEJB21Deployables();
        if (ejb21Deployables != null && ejb21Service != null) {
            List<URL> urls = new LinkedList<URL>();
            for (EJB21Deployable ejbDeployable : ejb21Deployables) {
                try {
                    // Escape the URL since ejb21Deployables have escaped URLs
                    urls.add(URLUtils.urlToFile(ejbDeployable.getArchive().getURL()).toURL());
                } catch (Exception e) {
                    logger.error("Cannot get the URL from the Deployable ''{0}''", ejbDeployable, e);
                }
            }

            // Call undeploy by using the array of URLs
            URL[] ejbJarURLs = urls.toArray(new URL[urls.size()]);
            ejb21Service.unDeployJars(ejbJarURLs);
        }

        // Undeploy EJB3s
        undeployEJB3FromEAR(earDeployable);

        // Undeploy Resource Adapters (by sending URL of these jars)
        List<RARDeployable> rarDeployables = earDeployable.getRARDeployables();
        if (rarDeployables != null && resourceService != null) {
            List<URL> urls = new LinkedList<URL>();
            for (RARDeployable rarDeployable : rarDeployables) {
                try {
                    // Escape the URL since rarDeployables have escaped URLs
                    urls.add(URLUtils.urlToFile(rarDeployable.getArchive().getURL()).toURL());
                } catch (Exception e) {
                    logger.error("Cannot get the URL from the Deployable ''{0}''", rarDeployable, e);
                }
            }

            // Call undeploy by using the array of URLs
            URL[] rarURLs = urls.toArray(new URL[urls.size()]);
            resourceService.unDeployRars(rarURLs, earURL);

        }

        EARModule earModule = new EARModule(earDeployable, "");

        // Unregister MBean
        try {
            jmxService.unregisterMBean(earModule);
            if (versioningService != null && versioningService.isVersioningEnabled()) {
                versioningService.garbageCollectJNDIBindingMBeans();
            }
        } catch (Exception e) {
            throw new DeployerException("Cannot unregister the MBean for the EAR Module of EARDeployable '"
                    + earDeployable + "'.", e);
        }

        // remove link original deployable -> unpacked deployable
        ears.remove(earURL);

        logger.info("''{0}'' EAR Deployable is now undeployed", tmpEARDeployable);

    }

    /**
     * Undeploy EJB3s of an EAR (called by the undeploy method).
     * @param earDeployable a given EAR deployable
     * @throws DeployerException if the deployment is not done.
     */
    protected void undeployEJB3FromEAR(final EARDeployable earDeployable) throws DeployerException {
        // From which deployable get the containers deployed
        EARDeployable workingDeployable = earDeployable;

        // Check if this archive has been unpacked ?
        EARDeployable unpackedDeployable = earDeployable.getUnpackedDeployable();
        if (unpackedDeployable != null) {
            workingDeployable = unpackedDeployable;
        }

        // Get Containers of this deployable
        List<EZBContainer> containers = new LinkedList<EZBContainer>();
        for (EJB3Deployable ejb3 : workingDeployable.getEJB3Deployables()) {
            EZBContainer container = getEmbedded().findContainer(ejb3.getArchive());
            // not found
            if (container == null) {
                logger.warn("No container found for the archive ''{0}'', creation has maybe failed", ejb3.getArchive());
                continue;
            }
            // found, add it
            containers.add(container);
        }

        // Remove all these containers
        for (EZBContainer container : containers) {
            // stop it
            container.stop();

            // remove it
            getEmbedded().removeContainer(container);
        }
    }

    /**
     * Check if the given deployable is deployable or not.
     * @param deployable the deployable to check.
     * @throws DeployerException if the deployable is not supported.
     */
    private void check(final IDeployable<?> deployable) throws DeployerException {
        if (!supports(deployable)) {
            throw new DeployerException("The deployment of the deployable'" + deployable
                    + "' is not supported by this deployer.");
        }
    }

    /**
     * Undeploy the given deployable. It can be an EJB jar, EAR, WAR, etc.
     * @param deployable a given deployable to undeploy
     * @throws DeployerException if the undeploy operation fails.
     */
    public void undeploy(final IDeployable<?> deployable) throws DeployerException {
        check(deployable);

        // Undeploy the EAR Deployable
        if (deployable instanceof EARDeployable) {
            undeployEAR((EARDeployable) deployable);
        }
    }

    /**
     * Checks if the given deployable is deployed or not.
     * @param deployable test if a given deployable is already deployed.
     * @return true if the deployable is deployed.
     * @throws DeployerException if the undeploy operation fails.
     */
    public boolean isDeployed(final IDeployable<?> deployable) throws DeployerException {
        throw new UnsupportedOperationException("IsDeployed not yet supported");
    }

    /**
     * Checks if the given deployable is supported by the Deployer.
     * @param deployable the deployable to be checked
     * @return true if it is supported, else false.
     */
    public boolean supports(final IDeployable<?> deployable) {
        return (deployable instanceof EARDeployable);
    }

    /**
     * Deploy the WAR files present in the given EAR.
     * @param earDeployable the EAR containing the WARs
     * @param earURL the EAR URL
     * @param earClassLoader the EAR classloader
     * @param parentClassLoader the parent classloader (EJB) to use
     * @param ejbInjectionHolder the given ejb injection holder (including persistence unit manager, etc).
     * @throws DeployerException if the wars are not deployed.
     */
    protected void deployWARs(final EARDeployable earDeployable, final URL earURL, final ClassLoader earClassLoader,
            final ClassLoader parentClassLoader, final EZBInjectionHolder ejbInjectionHolder) throws DeployerException {
        // First, try to see if there are .war in this EAR
        List<WARDeployable> wars = earDeployable.getWARDeployables();

        if (wars.size() > 0) {
            if (webContainerService == null) {
                logger.warn("There are WAR files in the EAR ''{0}'' but the 'web' service is not available", earDeployable);
                return;
            }

            // Build context for sending parameters
            Context ctx = new ContextImpl(earURL.toExternalForm());
            try {
                ctx.rebind("earDeployable", earDeployable);
            } catch (NamingException e) {
                throw new DeployerException("Cannot add the EAR deployable parameter '" + earDeployable + "'", e);
            }
            try {
                ctx.rebind("earURL", earURL);
            } catch (NamingException e) {
                throw new DeployerException("Cannot add the EAR URL parameter '" + earURL + "'", e);
            }
            // Get URLS of the wars and context-root
            List<URL> urls = new LinkedList<URL>();
            List<String> ctxRoots = new LinkedList<String>();
            for (WARDeployable warDeployable : wars) {
                // URL
                URL url = null;
                try {
                    url = warDeployable.getArchive().getURL();
                    if ("file".equals(url.getProtocol())) {
                        // If file, make sure it is escaped correctly
                        File file = URLUtils.urlToFile(url);
                        url = file.toURL();
                    }
                } catch (Exception e) {
                    throw new DeployerException("Cannot get the URL for the archive '" + warDeployable.getArchive() + "'", e);
                }
                urls.add(url);

                // Context-root
                ctxRoots.add(warDeployable.getContextRoot());
            }
            try {
                ctx.rebind("urls", urls.toArray(new URL[urls.size()]));
            } catch (NamingException e) {
                throw new DeployerException("Cannot add the urls parameter '" + urls + "'", e);
            }

            // Bind the parent classloader of the web application
            try {
                ctx.rebind("parentClassLoader", parentClassLoader);
            } catch (NamingException e) {
                throw new DeployerException("Cannot add the parentClassLoader parameter '" + parentClassLoader + "'", e);
            }

            // Bind the earClassLoader of the web application
            try {
                ctx.rebind("earClassLoader", earClassLoader);
            } catch (NamingException e) {
                throw new DeployerException("Cannot add the earClassLoader parameter '" + earClassLoader + "'", e);
            }

            // No alt-dd yet, give an empty array
            try {
                ctx.rebind("altDDs", new URL[urls.size()]);
            } catch (NamingException e) {
                throw new DeployerException("Cannot add the altDDs parameter.'", e);
            }

            // Build context roots
            try {
                ctx.rebind("contextRoots", ctxRoots.toArray(new String[ctxRoots.size()]));
            } catch (NamingException e) {
                throw new DeployerException("Cannot add the contextRoots parameter '" + urls + "'", e);
            }

            // Bind EJB Injection holder
            try {
                ctx.rebind(EZBInjectionHolder.class.getName(), ejbInjectionHolder);
            } catch (NamingException e) {
                throw new DeployerException("Cannot add the ejb injection holder parameter '" + ejbInjectionHolder + "'", e);
            }

            try {
                webContainerService.deployWars(ctx);
            } catch (JWebContainerServiceException e) {
                throw new DeployerException("Cannot deploy the WARs.'", e);
            }
        }

    }

    /**
     * Deploy the EJB 2.1 of the given EAR.
     * @param earDeployable the EAR that contains the EJB files
     * @param earURL the URL of the EAR
     * @param earClassLoader the classloader of the EAR
     * @param ejbClassLoader the given EJB ClassLoader
     * @param roleNames the name of the roles to use for security
     * @throws DeployerException if the EJB 2.1 filse can't be deployed
     */
    protected void deployEJB21s(final EARDeployable earDeployable, final URL earURL, final URLClassLoader earClassLoader,
            final ClassLoader ejbClassLoader, final String[] roleNames) throws DeployerException {
        // First, try to see if there are EJB 2.1 in this EAR
        List<EJB21Deployable> ejbs = earDeployable.getEJB21Deployables();
        if (ejbs.size() > 0) {
            if (ejb21Service == null) {
                logger.warn("There are EJB 2.1 files in the EAR ''{0}'' but the EJB 2.1 service is not available", earDeployable);
                return;
            }
            // Build list of EJB URLs
            List<URL> urls = new LinkedList<URL>();
            for (EJB21Deployable ejb21Deployable : ejbs) {
                try {
                    urls.add(ejb21Deployable.getArchive().getURL());

                    // If the directory META-INF/wsdl is present in the jar archive,
                    // add it to the list of URLs of the EasyBeansClassloader
                    URL wsdlDirectory = ejb21Deployable.getArchive().getResource(WSDL_DIRECTORY);
                    if (wsdlDirectory != null) {
                        try {
                            Method addURLMethod = ejbClassLoader.getClass().getSuperclass()
                                    .getDeclaredMethod("addURL", URL.class);
                            addURLMethod.setAccessible(true);
                            addURLMethod.invoke(ejbClassLoader, wsdlDirectory);
                        } catch (Exception e) {
                            logger.warn("Cannot add URL for META-INF/wsdl directory to the EasyBeansClassloader", e);
                        }
                    }
                } catch (ArchiveException e) {
                    throw new DeployerException("Cannot get the URL for the archive '" + ejb21Deployable.getArchive() + "'", e);
                }
            }

            // Get classpath of the EJBs
            URL[] compilationURLs = null;

            // 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.size()];
            System.arraycopy(urls.toArray(new URL[urls.size()]), 0, compilationURLs, 0, urls.size());
            System.arraycopy(appsJars, 0, compilationURLs, urls.size(), appsJars.length);
            System.arraycopy(myApplicationJars, 0, compilationURLs, urls.size() + appsJars.length, myApplicationJars.length);

            // Call automatic GenIC on the URLs of the EJB 2.1
            // Check GenIC :
            for (EJB21Deployable ejb : ejbs) {
                URL ejbURL = null;
                try {
                    ejbURL = ejb.getArchive().getURL();
                } catch (ArchiveException e) {
                    throw new DeployerException("Cannot get the URL on the deployable '" + ejb + "'", e);
                }
                logger.debug("Calling GenIC on the EJB ''{0}'' with compilation URL ''{1}''.", ejbURL, Arrays
                        .asList(compilationURLs));
                ejb21Service.checkGenIC(URLUtils.urlToFile(ejbURL).getPath(), compilationURLs);
            }

            // Deploy EJB 2.1 on JOnAS service
            Context ctx = new ContextImpl(earURL.toExternalForm());
            try {
                URL earRootUrl = earURL;
                EARDeployable originalEarDeployable = earDeployable;
                while (originalEarDeployable != null) {
                    earRootUrl = originalEarDeployable.getArchive().getURL();
                    originalEarDeployable = originalEarDeployable.getOriginalDeployable();
                }
                ctx.rebind("earUrl", earURL);
                ctx.rebind("earRootUrl", earRootUrl);
                ctx.rebind("j2eeApplicationName", earDeployable.getModuleName());
            } catch (Exception e) {
                throw new DeployerException("Cannot add the EAR URL parameter '" + earURL + "'", e);
            }

            try {
                ctx.rebind("jarURLs", urls.toArray(new URL[urls.size()]));
            } catch (NamingException e) {
                throw new DeployerException("Cannot add the urls parameter '" + urls + "'", e);
            }
            try {
                ctx.rebind("earClassLoader", earClassLoader);
            } catch (NamingException e) {
                throw new DeployerException("Cannot add the earClassLoader parameter '" + earClassLoader + "'", e);
            }

            // Bind the EJB classloader
            try {
                ctx.rebind("ejbClassLoader", ejbClassLoader);
            } catch (NamingException e) {
                throw new DeployerException("Cannot add the ejbClassLoader parameter '" + ejbClassLoader + "'", e);
            }

            // Role names
            try {
                ctx.rebind("roleNames", roleNames);
            } catch (NamingException e) {
                throw new DeployerException("Cannot add the roleNames parameter.", e);
            }

            // Deploy
            try {
                ejb21Service.deployJars(ctx);
            } catch (ServiceException e) {
                throw new DeployerException("Cannot deploy the EJB 2.1.", e);
            }
        }

    }

    /**
     * Deploy the RARs of the given EAR.
     * @param earDeployable the EAR that contains the war files
     * @param earURL the URL of the EAR
     * @param earClassLoader the classloader of the EAR
     * @throws DeployerException if the RARs file can't be deployed
     */
    protected void deployRARs(final EARDeployable earDeployable, final URL earURL, final ClassLoader earClassLoader)
            throws DeployerException {
        // First, try to see if there are .rar in this EAR
        List<RARDeployable> rars = earDeployable.getRARDeployables();
        if (rars.size() > 0) {
            if (resourceService == null) {
                logger.warn("There are RAR files in the EAR ''{0}'' but the resource service is not available", earDeployable);
                return;
            }

            Context ctx = new ContextImpl(earURL.toExternalForm());
            try {
                ctx.rebind("earUrl", earURL);
            } catch (NamingException e) {
                throw new DeployerException("Cannot add the EAR URL parameter '" + earURL + "'", e);
            }
            List<URL> urls = new LinkedList<URL>();
            for (RARDeployable rarDeployable : rars) {
                try {
                    urls.add(rarDeployable.getArchive().getURL());
                } catch (ArchiveException e) {
                    throw new DeployerException("Cannot get the URL for the archive '" + rarDeployable.getArchive() + "'", e);
                }
            }
            try {
                ctx.rebind("urls", urls.toArray(new URL[urls.size()]));
            } catch (NamingException e) {
                throw new DeployerException("Cannot add the urls parameter '" + urls + "'", e);
            }
            try {
                ctx.rebind("earClassLoader", earClassLoader);
            } catch (NamingException e) {
                throw new DeployerException("Cannot add the earClassLoader parameter '" + earClassLoader + "'", e);
            }
            try {
                ctx.rebind("altDDs", new URL[urls.size()]);
            } catch (NamingException e) {
                throw new DeployerException("Cannot add the altDDs parameter.'", e);
            }

            try {
                resourceService.deployRars(ctx);
            } catch (ResourceServiceException e) {
                throw new DeployerException("Cannot deploy the RARs.'", e);
            }
        }

    }

    /**
     * Deploy the Web Services of the given EAR.
     * @param earDeployable the EAR that contains the Web Services
     * @param earURL the URL of the EAR
     * @param earClassLoader the classloader of the EAR
     * @param ejbClassLoader the classloader of the EJBs
     * @param urlsEJB List of the EJBs of the EAR
     * @param urlsWAR List of the WARs of the EAR
     * @throws DeployerException if the Web Services can't be deployed
     */
    protected void deployWebServices(final EARDeployable earDeployable, final URL earURL, final ClassLoader earClassLoader,
            final ClassLoader ejbClassLoader, final List<URL> urlsEJB, final List<URL> urlsWAR) throws DeployerException {

        if (wsService == null) {
            return;
        }

        // Build context for sending parameters
        Context ctx = new ContextImpl(earURL.toExternalForm());

        try {
            ctx.rebind("unpackDir", earURL.toExternalForm());
        } catch (NamingException e) {
            throw new DeployerException("Cannot add the 'unpackDir' parameter '", e);
        }

        try {
            ctx.rebind("jarUrls", urlsEJB.toArray(new URL[urlsEJB.size()]));
        } catch (NamingException e) {
            throw new DeployerException("Cannot add the 'jarUrls' parameter '", e);
        }

        URL[] warURLs = urlsWAR.toArray(new URL[urlsWAR.size()]);

        try {
            ctx.rebind("warUrls", warURLs);
        } catch (NamingException e) {
            throw new DeployerException("Cannot add the 'warUrls' parameter '", e);
        }

        try {
            Hashtable<URL, String> ctxRoots = new Hashtable<URL, String>();
            List<WARDeployable> wars = earDeployable.getWARDeployables();
            for (WARDeployable deployable : wars) {
                ctxRoots.put(deployable.getArchive().getURL(), deployable.getContextRoot());
            }

            ctx.rebind("warCtxRootMapping", ctxRoots);
        } catch (Exception e) {
            throw new DeployerException("Cannot add the 'warCtxRootMapping' parameter '", e);
        }


        try {
            ctx.rebind("earURL", earURL);
        } catch (NamingException e) {
            throw new DeployerException("Cannot add the 'earURL' parameter '" + earURL + "'", e);
        }

        try {
            ctx.rebind("earDeployable", earDeployable);
        } catch (NamingException e) {
            throw new DeployerException("Cannot add the 'earDeployable' parameter '" + earDeployable + "'", e);
        }

        // Bind the parent classloader of the web application
        try {
            ctx.rebind("ejbClassLoader", ejbClassLoader);
        } catch (NamingException e) {
            throw new DeployerException("Cannot add the ejbClassLoader parameter '" + ejbClassLoader + "'", e);
        }

        // Bind the earClassLoader of the web application
        try {
            ctx.rebind("earClassLoader", earClassLoader);
        } catch (NamingException e) {
            throw new DeployerException("Cannot add the earClassLoader parameter '" + earClassLoader + "'", e);
        }

        try {
            wsService.deployWebServices(ctx);
        } catch (JWebContainerServiceException e) {
            throw new DeployerException("Cannot deploy the Web Services.", e);
        }

    }

    /**
     * Complete the Deployment of the WebServices.
     * @param earURL The URL of the EAR
     * @param earDeployable the deployable of the EAR
     * @param earClassLoader The classloader of the EAR
     * @throws DeployerException Thrown if the Web Services can't be deployed
     */
    protected void completeWebServicesDeployment(final URL earURL, final EARDeployable earDeployable,
            final ClassLoader earClassLoader) throws DeployerException {
        // Only if the WebServicesService is started
        if (wsService != null) {
            try {
                ComponentContext contctx = null;
                try {
                    contctx = new ComponentContext(earURL.toExternalForm());
                    contctx.rebind(WebServicesService.CLASSLOADER_CTX_PARAM, earClassLoader);

                    String javaEEApplicationName = earDeployable.getModuleName();

                    ObjectName name = J2eeObjectName.J2EEApplication(getServerProperties().getDomainName(), getServerProperties()
                            .getServerName(), javaEEApplicationName);
                    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 (Exception e) {
                throw new DeployerException("Cannot complete Web Services deployment.", e);
            }
        }
    }

    /**
     * Link policy configuration objects of EJB and Web Component.
     * @param earDeployable the EAR that contains the EJB files
     * @throws DeployerException if the policy objects can't be linked
     */
    private void linkPolicyObjects(final EARDeployable earDeployable) throws DeployerException {

        // Add context ID of EJB and Web components
        List<String> ctxIDs = new LinkedList<String>();
        // Get contextID of EJB
        addEjbContextIdToList(earDeployable, ctxIDs, false);

        // Now for WebApp
        addWebBContextIdToList(earDeployable, ctxIDs, true);

        try {
            // Now link the policy configuration objects
            for (Iterator<String> itCtxId = ctxIDs.iterator(); itCtxId.hasNext();) {
                String toBeLinkedCtxId = itCtxId.next();
                PolicyConfiguration toBeLinkedPC = getPolicyConfigurationFactory().getPolicyConfiguration(toBeLinkedCtxId, false);
                for (Iterator<String> linkCId = ctxIDs.iterator(); linkCId.hasNext();) {
                    String linkedCtxId = linkCId.next();
                    if (!toBeLinkedCtxId.equals(linkedCtxId)) {
                        PolicyConfiguration linkedPC = getPolicyConfigurationFactory().getPolicyConfiguration(linkedCtxId, false);
                        toBeLinkedPC.linkConfiguration(linkedPC);
                    }
                }
            }
        } catch (PolicyContextException pce) {
            throw new DeployerException("Cannot retrieve a policy configuration", pce);
        }

    }

    /**
     * Add context-id for given jar urls to a given List.
     * @param earDeployable the EAR deployable to analyze
     * @param contextIDs the list of context-id.
     * @param resetPolicyConfiguration reset or not the associated policy
     *        configuration.
     * @throws DeployerException if the EAR can't be analyzed
     */
    private void addEjbContextIdToList(final EARDeployable earDeployable, final List<String> contextIDs,
            final boolean resetPolicyConfiguration) throws DeployerException {
        // Extract URLs of EJB from the EAR
        List<EJBDeployable<?>> ejbDeployables = earDeployable.getEJBDeployables();
        List<URL> urls = new LinkedList<URL>();
        if (ejbDeployables != null) {
            for (EJBDeployable<?> ejbDeployable : ejbDeployables) {
                try {
                    urls.add(ejbDeployable.getArchive().getURL());
                } catch (ArchiveException e) {
                    throw new DeployerException("Cannot get URL on the deployable '" + ejbDeployable + "'.", e);
                }
            }
        }

        URL[] jarUrls = urls.toArray(new URL[urls.size()]);

        // Get contextID of EJB
        for (int u = 0; u < jarUrls.length; u++) {
            String ctxId = jarUrls[u].getPath();
            // reset the policy configuration associated to this context ID.
            if (resetPolicyConfiguration) {
                try {
                    getPolicyConfigurationFactory().getPolicyConfiguration(ctxId, true);
                } catch (PolicyContextException pce) {
                    throw new DeployerException("Cannot retrieve a policy configuration", pce);
                }
            }
            contextIDs.add(ctxId);
        }
    }

    /**
     * Add context-id for given web urls to a given List. Also, reset the
     * context-id associated to it. *
     * @param earDeployable the EAR deployable to analyze
     * @param contextIDs the list of context-id.
     * @param resetPolicyConfiguration reset or not the associated policy
     *        configuration.
     * @throws DeployerException if policy context cannot be get.
     */
    private void addWebBContextIdToList(final EARDeployable earDeployable, final List<String> contextIDs,
            final boolean resetPolicyConfiguration) throws DeployerException {

        // Extract URLs of Web from the EAR
        List<WARDeployable> warDeployables = earDeployable.getWARDeployables();
        if (warDeployables != null) {
            for (WARDeployable warDeployable : warDeployables) {
                URL warURL = null;
                try {
                    warURL = warDeployable.getArchive().getURL();
                } catch (ArchiveException e) {
                    throw new DeployerException("Cannot get URL on the deployable '" + warDeployable + "'.", e);
                }

                // build context ID of war
                String ctxId = warURL.getFile() + warDeployable.getContextRoot();
                // reset the policy configuration associated to this context ID.
                if (resetPolicyConfiguration) {
                    try {
                        getPolicyConfigurationFactory().getPolicyConfiguration(ctxId, true);
                    } catch (PolicyContextException pce) {
                        throw new DeployerException("Cannot retrieve a policy configuration", pce);
                    }
                }
                contextIDs.add(ctxId);
            }
        }
    }

    /**
     * Commit policy configuration objects of EJB Component.
     * @param earDeployable the EAR to analyze
     * @throws DeployerException if the policy objects can't be committed
     */
    private void commitEJBPolicyObjects(final EARDeployable earDeployable) throws DeployerException {
        List<String> ctxIDs = new LinkedList<String>();
        addEjbContextIdToList(earDeployable, ctxIDs, false);
        commitPolicyObjects(ctxIDs);
    }

    /**
     * Commit policy configuration objects of Web Component.
     * @param earDeployable the EAR to analyze
     * @throws DeployerException if the policy objects can't be committed
     */
    private void commitWebBPolicyObjects(final EARDeployable earDeployable) throws DeployerException {
        List<String> ctxIDs = new LinkedList<String>();
        addWebBContextIdToList(earDeployable, ctxIDs, false);
        commitPolicyObjects(ctxIDs);
    }

    /**
     * @return policy configuration factory
     * @throws DeployerException if the policy configuration factory cannot be
     *         obtain
     */
    private PolicyConfigurationFactory getPolicyConfigurationFactory() throws DeployerException {
        PolicyConfigurationFactory pcFactory = null;
        try {
            pcFactory = PolicyConfigurationFactory.getPolicyConfigurationFactory();
        } catch (Exception cnfe) {
            throw new DeployerException("Cannot retrieve current policy configuration factory", cnfe);
        }
        return pcFactory;
    }

    /**
     * Commit policy context IDs of the given list.
     * @param ctxIDs list of context ID to commit.
     * @throws DeployerException if the policy objects cannot be committed.
     */
    private void commitPolicyObjects(final List<String> ctxIDs) throws DeployerException {
        String ctxId = null;
        try {
            // commit the policy configuration objects
            for (Iterator<String> itCtxId = ctxIDs.iterator(); itCtxId.hasNext();) {
                ctxId = itCtxId.next();
                PolicyConfiguration pc = getPolicyConfigurationFactory().getPolicyConfiguration(ctxId, false);
                pc.commit();
            }
        } catch (PolicyContextException pce) {
            throw new DeployerException("Cannot commit policy configuration with Id '" + ctxId + "'", pce);
        }

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

    /**
     * Sets the WS service.
     * @param wsService WS service
     */
    public void setWsService(final WebServicesService wsService) {
        this.wsService = wsService;
    }

    /**
     * Sets the RAR service.
     * @param resourceService RAR service.
     */
    public void setResourceService(final ResourceService resourceService) {
        this.resourceService = resourceService;
    }

    /**
     * Sets the EJB 2.1 service.
     * @param ejb21Service the EJB 2.1 service.
     */
    public void setEjb21Service(final EJBService ejb21Service) {
        this.ejb21Service = ejb21Service;
    }

    /**
     * Sets the JMX service.
     * @param jmxService the JMX service.
     */
    public void setJMXService(final JmxService jmxService) {
        this.jmxService = jmxService;
    }

    /**
     * Sets the WEB container service.
     * @param webContainerService the web container service.
     */
    public void setWebContainerService(final JWebContainerService webContainerService) {
        this.webContainerService = webContainerService;
    }

    /**
     * Sets the classloader to use for all deployed applications.
     * @param appsClassLoader the given classloader.
     */
    public void setAppsClassLoader(final ClassLoader appsClassLoader) {
        this.appsClassLoader = appsClassLoader;
    }

    /**
     * Gets Archives of the libraries of this EAR.
     * @param earDeployable the given EAR deployable.
     * @return list of archives
     */
    protected List<IArchive> getLibArchives(final EARDeployable earDeployable) {

        // Build list
        List<IArchive> libArchives = new LinkedList<IArchive>();

        // Get data of all libraries
        for (LibDeployable lib : earDeployable.getLibDeployables()) {
            libArchives.add(lib.getArchive());
        }

        return libArchives;
    }

    /**
     * Returns the server properties.
     * @return the server properties
     */
    public ServerProperties getServerProperties() {
        return serverProperties;
    }

    /**
     * Sets the server properties.
     * @param serverProperties the given server properties
     */
    public void setServerProperties(final ServerProperties serverProperties) {
        this.serverProperties = serverProperties;
    }

    /**
     * @param versioningService  The versioning service to set.
     */
    public void setVersioningService(final VersioningService versioningService) {
        this.versioningService = versioningService;
    }

    /**
     * Sets the versioning service to null.
     */
    public void unsetVersioningService() {
        this.versioningService = null;
    }

    /**
     * @return The versioning service.
     */
    public VersioningService getVersioningService() {
        return this.versioningService;
    }

    /**
     * Set the DeployerLog of the EarDeployer.
     * @param deployerLog the DeployerLog to use
     */
    public void setDeployerLog(final DeployerLog deployerLog) {
        this.deployerLog = deployerLog;
    }

    /**
     * Returns a Map containing all deployed EARs.
     * @return a Map containing all deployed EARs.
     */
    public Map<URL, EARDeployable> getEars() {
        return ears;
    }
}
