/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007-2012 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.ArrayList;
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.InitialContext;
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.api.naming.EZBNamingStrategy;
import org.ow2.easybeans.deployment.api.EZBInjectionHolder;
import org.ow2.easybeans.persistence.api.EZBPersistenceUnitManager;
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.ejb2.EJBService;
import org.ow2.jonas.ejb3.IEasyBeansService;
import org.ow2.jonas.generators.genbase.generator.Config;
import org.ow2.jonas.generators.genclientstub.ClientStubGen;
import org.ow2.jonas.jmx.JmxService;
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.loader.FilteringClassLoader;
import org.ow2.jonas.lib.management.javaee.J2eeObjectName;
import org.ow2.jonas.lib.naming.ComponentContext;
import org.ow2.jonas.lib.security.mapping.JPolicyUserRoleMapping;
import org.ow2.jonas.lib.work.DeployerLog;
import org.ow2.jonas.management.ServiceManager;
import org.ow2.jonas.multitenant.MultitenantService;
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.jaxrpc.IJAXRPCService;
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.impl.deployer.AbsDeployer;
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)
 * @author Mohammed Boukada (multitenant)
 */
public class EarDeployer extends AbsDeployer<EARDeployable> {

    /**
     * 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 JAX-RPC service.
     */
    private IJAXRPCService jaxrpcService = 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 EJB 3.0 service (optional).
     */
    private IEasyBeansService ejb3Service = 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;

    /**
     * Versioning service.
     */
    private MultitenantService multitenantService;

    /**
     * Service manager.
     */
    private ServiceManager serviceManager;

    /**
     * EAR Work directory.
     */
    private String appsWorkDirectory = null;

    /**
     * EAR WebApps Work directory.
     */
    private String webappsWorkDirectory = null;

    /**
     * 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";

    /**
     * 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/";

    /**
     * Enable call to GenClientStub.
     */
    private boolean genClientStubEnabled = false;

    /**
     * Use a child classLoader for EJB3.
     */
    private boolean useEJB3ChildClassloader = false;

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

    /**
     * @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;
    }

    /**
     * Enable GenClientStub.
     */
    public void enableGenClientStub() {
        this.genClientStubEnabled = true;
    }

    /**
     * Enable the use of a child classloader for EJB3s.
     */
    public void useEJB3ChildClassloader() {
        this.useEJB3ChildClassloader = true;
    }

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

        // Do nothing if not enabled
        if (!genClientStubEnabled) {
            return deployable;
        }

        // Ugly but who knows?...
        try {
            Thread.sleep(150);
        } catch (InterruptedException e1) {
            logger.error("Cannot sleep in  GenClientStub");
        }
        // Execute GenClientStub in an execution block
        IExecution<EARDeployable> exec = new IExecution<EARDeployable>() {
            public EARDeployable execute() throws Exception {
                EARDeployable earDeployable = deployable;

                ClientStubGen stubGen = new ClientStubGen();
                Config config = new Config();
                // Ensure path is correct (no %20 for spaces)
                String inputName = URLUtils.urlToFile(deployable.getArchive().getURL()).getPath();
                config.setInputname(inputName);
                config.setOut(new File(getServerProperties().getWorkDirectory(), "genclientstub"));

                try {
                    String path = stubGen.execute(config, deployable);
                    // Check if a new archive has been created
                    if (stubGen.isInputModified()) {
                        IArchive archive = ArchiveManager.getInstance().getArchive(new File(path));
                        // Build a new deployable with the new archive
                        EARDeployable newDeployable = EARDeployable.class.cast(DeployableHelper.getDeployable(archive));
                        File folder = new File(appsWorkDirectory, getServerProperties().getServerName());
                        String archiveName = URLUtils.urlToFile(deployable.getArchive().getURL()).getName();
                        earDeployable = UnpackDeployableHelper.unpack(newDeployable, folder, archiveName, false);
                        earDeployable.setOriginalDeployable(deployable.getOriginalDeployable());
                    }
                } catch (Throwable e) {
                    logger.error("Cannot execute GenClientStub on archive '" + deployable.getArchive() + "'", e);
                } finally {
                    FileUtils.delete(config.getOut());
                }
                try {
                    Thread.sleep(150);
                } catch (InterruptedException e1) {
                    logger.error("Cannot sleep in  GenClientStub");
                }
                return earDeployable;
            }
        };

        // Execute
        ExecutionResult<EARDeployable> result = null;
        try {
            result = RunnableHelper.execute(LoaderManager.getInstance().getExternalLoader(), exec);
        } catch (Exception e) {
            throw new DeployerException("Cannot get the 'external' ClassLoader", e);
        }

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

        return result.getResult();

    }

    /**
     * Apply WSGen of the given archive.
     * @param deployable the deployable to use
     * @throws DeployerException if WSGen cannot be applied.
     */
    private void applyWSGenIfNeeded(final EARDeployable deployable) throws DeployerException {
        // JAX-RPC service started ?
        if (jaxrpcService == null) {
            logger.debug("The JAX-RPC service is not present, no need to call WSGen");
            return;
        }

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

        // Check version in manifest
        String jonasVersionWsGen = deployable.getArchive().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;
        }

        try {
            jaxrpcService.applyWSGen(deployable);
        } catch (Exception e) {
            throw new DeployerException(e);
        }
    }

    /**
     * Deploy the given deployable.
     * @param deployable the EAR deployable.
     * @throws DeployerException if the EAR is not deployed.
     */
    @Override
    public void doDeploy(final IDeployable<EARDeployable> deployable) throws DeployerException {

        long startTime = System.currentTimeMillis();

        // Warning: all URLs in this method are unescaped (meaning the there may be some '%20' inside them)
        // Convenience File variables are provided (originalFile, unpackedFile) that are unescaped (with real ' ')
        // Avoid (unless you know what you're doing) to call File.getURL() as it is buggy, prefer using
        // URLUtils to convert between File and URL when necessary.

        EARDeployable unpackedDeployable = null;
        File originalFile = null;

        // Deploy the EAR Deployable. Needs to unpack it before deploying it
        try {
            File folder = new File(appsWorkDirectory, getServerProperties().getServerName());
            originalFile = URLUtils.urlToFile(deployable.getArchive().getURL());
            String archiveName = FileUtils.lastModifiedFileName(originalFile);

            // Needs to know if files needs to be updated or not
            boolean keepArchives = false;
            // Override files if file to deploy is newer than the unpacked directory (and if already exists)
            File unpackedFolder = new File(folder, archiveName);
            if (unpackedFolder.exists()) {
                if (originalFile.lastModified() <= unpackedFolder.lastModified()) {
                    // Need to keep the data
                    keepArchives = true;
                }
            }
            unpackedDeployable = UnpackDeployableHelper.unpack(EARDeployable.class.cast(deployable), folder, archiveName, keepArchives);

        } catch (Exception e) {
            throw new DeployerException("Cannot deploy archive for '" + deployable.getArchive() + "'", e);
        }

        // display this line when EAR has been unpacked so that details about the EAR are available.
        logger.info("Deploying ''{0}''", unpackedDeployable);

        // Registers the required deployers by starting their associated services
        serviceManager.startRequiredServices(unpackedDeployable);

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

        // Apply WsGen if needed
        applyWSGenIfNeeded(unpackedDeployable);

        // Apply GenClientStub if needed
        unpackedDeployable = applyGenClientStubIfNeeded(unpackedDeployable);

        // Get URL of this Deployable
        URL unpackedURL;
        File unpackedFile;
        try {
            unpackedURL = earArchive.getURL();
            unpackedFile = URLUtils.urlToFile(unpackedURL);
        } 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 = deployable.getArchive().getURL();
            // The file is unpacked, so log it
            if (deployerLog != null) {
                deployerLog.addEntry(originalFile, unpackedFile);
            }
        } catch (Exception e) {
            throw new DeployerException(
                    "Cannot get the url of the initial deployable for the EAR Module '" + deployable + "'.", e);
        }

        // Create classLoader for loading the Deployment descriptor
        // parent classloader is the current classloader
        URLClassLoader loaderCls = new URLClassLoader(new URL[] {unpackedURL}, 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);
        }

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

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

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

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

        // bind of tenantId
        String tenantId = null;
        if (isMultitenantEnabled()){
            tenantId = multitenantService.getTenantIdFromContext();
            if (tenantId == null || (tenantId != null && tenantId.equals(""))){
                // it's not an addon, try to retrieve tenant-id id from jonas-application.xml
                tenantId = earDD.getTenantId();
                if (tenantId == null || (tenantId != null && tenantId.equals(""))){
                    // If the tenantId is not set in jonas-wep-app
                    // use the default tenant id (=0)
                    tenantId = multitenantService.getDefaultTenantID();
                }
            }
            if (tenantId != null){
                multitenantService.addTenantIdDeployableInfo(unpackedDeployable, tenantId);
            }else{
                logger.debug("Fail to bind object tenantId ''{0}''", tenantId);
            }
        }

        // TODO: Classpath manager

        // Create the filtering ClassLoader at the EAR level
        FilteringClassLoader filteringClassLoader = createFilteringClassLoader(earArchive);

        /*
        * 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(unpackedURL.toExternalForm(), new URL[0], filteringClassLoader);

        // 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 : unpackedDeployable.getEJBDeployables()) {
            try {
                URL ejbURL = ejb.getArchive().getURL();
                urlsEJB.add(ejbURL);
                urlsAltDDEJB.add(unpackedDeployable.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 : unpackedDeployable.getWARDeployables()) {
            try {
                URL warURL = war.getArchive().getURL();
                urlsWAR.add(warURL);
                urlslAtDDWAR.add(unpackedDeployable.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 && getServerProperties().isDevelopment()) {
                try {
                    File unpackedWarFile = new File(new File(new File(webappsWorkDirectory, getServerProperties().getServerName()),
                                                             INEAR_WORK_WEBAPPS_DIR_SUFFIX),
                                                    unpackedFile.getName());
                    deployerLog.addEntry(originalFile, unpackedWarFile);
                } catch (Exception e) {
                    logger.warn("Error when adding the log entry for unpacked wars directory.");
                }
            }
        }

        // TODO This block is never used, normal ?
        // ------------------------------------------------------
        List<URL> urlsRAR = new LinkedList<URL>();
        List<URL> urlsAtDDRAR = new LinkedList<URL>();
        for (RARDeployable rar : unpackedDeployable.getRARDeployables()) {
            try {
                URL rarURL = rar.getArchive().getURL();
                urlsRAR.add(rarURL);
                urlsAtDDRAR.add(unpackedDeployable.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 : unpackedDeployable.getCARDeployables()) {
            try {
                URL carURL = car.getArchive().getURL();
                urlsClient.add(carURL);
                urlsAtDDClient.add(unpackedDeployable.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(unpackedDeployable, unpackedURL, earClassLoader);

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

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

        // Create array of URLs with EJBs + Libraries
        List<URL> urls = new LinkedList<URL>();
        for (EJBDeployable<?> ejb : ejbs) {
            try {
                urls.add(ejb.getArchive().getURL());
            } catch (Exception e) {
                throw new DeployerException("Cannot get the URL for the Archive '" + ejb.getArchive() + "'.", e);
            }
        }
        for (LibDeployable lib : libs) {
            try {
                urls.add(lib.getArchive().getURL());
            } 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 = null;

        // Filtering ClassLoader for all the EjbJars + Libraries
        // TODO If this is always transparent, maybe we could simply remove it ?
        FilteringClassLoader filteringClassLoaderEAR = new FilteringClassLoader(earClassLoader);
        filteringClassLoaderEAR.setTransparent(true); // the loader for EJBs is transparent
        filteringClassLoaderEAR.start();

        if (ejb3Service != null) {
            ejbClassLoader = ejb3Service.buildByteCodeEnhancementClassLoader(arrayURLs, filteringClassLoaderEAR);
        } else {
            ejbClassLoader = new URLClassLoader(arrayURLs, filteringClassLoaderEAR);
        }

        // 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 :
        if (ejb21Service != null) {
            for (EJB21Deployable ejb : unpackedDeployable.getEJB21Deployables()) {
                URL ejbURL = null;
                try {
                    ejbURL = ejb.getArchive().getURL();
                } catch (ArchiveException e) {
                    throw new DeployerException("Cannot get the URL on the deployable '" + ejb + "'", e);
                }

                if (ejb21Service == null) {
                    logger.warn("Unable to call checkGenIC on the EJB ''{0}'' because the EJB 2.1 service is not available",
                                ejbURL);
                    return;
                } else {
                    logger.debug("Calling GenIC on the EJB ''{0}'' with compilation URL ''{1}''.",
                                 ejbURL,
                                 Arrays.asList(compilationURLs));
                    ejb21Service.checkGenIC(URLUtils.urlToFile(ejbURL).getPath(), compilationURLs);
                }

            }
        }

        EZBInjectionHolder ejbInjectionHolder = null;
        if (getEmbedded() == null) {
            if (!ejb3s.isEmpty()) {
                logger.warn("There are EJB 3 files in the EAR ''{0}'' but the EJB 3 service is not available", deployable);
            }
        } else {

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

            // Analyze libraries to detect persistence archive
            EZBPersistenceUnitManager persistenceUnitManager = null;
            try {
                persistenceUnitManager = ejb3Service.getPersistenceUnitManager(unpackedDeployable, ejbClassLoader);
            } catch (org.ow2.easybeans.persistence.api.PersistenceXmlFileAnalyzerException e) {
                throw new DeployerException("Unable to get Persistence unit Manager on '" + unpackedDeployable.getArchive()
                        + "'.", e);
            }
            
            // Add eclipselink properties to enable multitenant
            if (isMultitenantEnabled() && multitenantService.isMultitenant(unpackedDeployable)){
                if (persistenceUnitManager != null){
                    multitenantService.updatePersistenceUnitManager(persistenceUnitManager, tenantId);
                } else {
                    logger.debug("Persistence unit manager is null !");
                }
            }

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

            // Create containers for each EJB3 deployable
            String prefix = null;
            if (versioningService != null && versioningService.isVersioningEnabled()) {
                prefix = versioningService.getPrefix(unpackedDeployable);
            }

            if (isMultitenantEnabled() && multitenantService.isMultitenant(unpackedDeployable)) {
                if (prefix == null){
                    prefix = tenantId + "/";
                } else {
                    prefix.concat(tenantId + "/");
                }
            }

            String javaEEApplicationName = unpackedDeployable.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 '" + deployable
                            + "'.");
                }

                // Create a container for the given archive
                EZBContainer container = getEmbedded().createContainer(ejb);

                if (prefix != null) {
                    // Search naming strategies
                    List<EZBNamingStrategy> namingStrategies = container.getConfiguration().getNamingStrategies();

                    // Wrap all Naming strategies
                    List<EZBNamingStrategy> newNamingStrategies = new ArrayList<EZBNamingStrategy>();

                    for (EZBNamingStrategy oldNamingStrategy : namingStrategies) {
                        newNamingStrategies.add(ejb3Service.getNamingStrategy(prefix, oldNamingStrategy));
                    }
                    container.getConfiguration().setNamingStrategies(newNamingStrategies);
                }
                container.getConfiguration().setApplicationName(javaEEApplicationName);

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

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

            // Create Resolver for EAR
            EZBApplicationJNDIResolver applicationJNDIResolver = ejb3Service.buildApplicationJNDIResolver();

            // Configure containers
            for (EZBContainer container : containers) {
                // Assign classloader for EJB3
                ClassLoader ejb3ClassLoader = ejbClassLoader;

                // We create a child classloader that will be used for the bytecode enhancement
                if (useEJB3ChildClassloader) {
                    ejb3ClassLoader = ejb3Service.buildByteCodeEnhancementClassLoader(new URL[0], ejbClassLoader);
                }

                // Set the classloader that needs to be used
                container.setClassLoader(ejb3ClassLoader);

                // 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
            ejbInjectionHolder = ejb3Service.buildInjectionHolder(persistenceUnitManager, applicationJNDIResolver);

            // Start containers
            for (EZBContainer container : containers) {
                Object oldTenantContext = null;
                if (isMultitenantEnabled()) {
                    oldTenantContext = multitenantService.getTenantContext();
                }
                try {
                    if (isMultitenantEnabled()) {
                        multitenantService.setTenantIdInContext(tenantId);
                    }
                    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);
                } finally {
                    if (isMultitenantEnabled()) {
                        multitenantService.setTenantContext(oldTenantContext);
                    }
                }

                // add eclipselink properties to enable multitenant
                if (isMultitenantEnabled() && multitenantService.isMultitenant(unpackedDeployable)){
                    if (container.getPersistenceUnitManager() != null){
                        multitenantService.updatePersistenceUnitManager(container.getPersistenceUnitManager(), tenantId);
                    } else {
                        logger.debug("Persistence unit manager is null !");
                    }
                }
            }

            if (isMultitenantEnabled() && multitenantService.isMultitenant(unpackedDeployable)
                && tenantId != null && containers.size() > 0) {
                Object oldTenantContext = multitenantService.getTenantContext();
                try {
                    multitenantService.setTenantIdInContext(tenantId);
                    multitenantService.createJNDIBindingMBeans(unpackedDeployable, prefix);
                } finally {
                    multitenantService.setTenantContext(oldTenantContext);
                }
            }

            if (versioningService !=null && versioningService.isVersioningEnabled()
                && prefix != null && containers.size() > 0) {
                versioningService.createJNDIBindingMBeans(unpackedDeployable);
            }

        }

        // Deploy Web Services
        deployWebServices(unpackedDeployable, unpackedURL, earClassLoader, ejbClassLoader, urlsEJB, urlsWAR);

        // Deploy EJB 2.1
        deployEJB21s(unpackedDeployable, unpackedURL, earClassLoader, ejbClassLoader, roleNames);

        // Link policy context of EJBs and Webs components
        linkPolicyObjects(unpackedDeployable, earDD);

        // Commit EJB Policy objects
        commitEJBPolicyObjects(unpackedDeployable);

        // Deploy Web App
        // ejbInjectionHolder is null is the ejb3 service is not started
        deployWARs(unpackedDeployable, unpackedURL, earClassLoader, ejbClassLoader, ejbInjectionHolder);

        // Commit Web policy objects
        commitWebBPolicyObjects(unpackedDeployable);

        // Complete deployment of Web Services
        completeWebServicesDeployment(unpackedURL, unpackedDeployable, earClassLoader);

        // Create EAR object
        String xmlDD = null;
        if (earDD != null) {
            xmlDD = earDD.getXmlContent();
        }
        EARModule earModule = new EARModule(unpackedDeployable, xmlDD);
        earModule.setClassLoader(earClassLoader);
        earModule.setFilteringClassLoader(filteringClassLoader);

        // Register MBean
        Object oldTenantContext = null;
        if (isMultitenantEnabled()) {
            oldTenantContext = multitenantService.getTenantContext();
        }
        try {
            if (isMultitenantEnabled()) {
                multitenantService.setTenantIdInContext(tenantId);
            }
            jmxService.registerMBean(earModule);
        } catch (Exception e) {
            throw new DeployerException("Cannot register the MBean for the EAR Module of EARDeployable '" + deployable + "'.",
                    e);
        } finally {
            if (isMultitenantEnabled()) {
                multitenantService.setTenantContext(oldTenantContext);
            }
        }

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

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

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

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

        logger.info("''{0}'' EAR Deployable has been deployed ({1} ms)", deployable.getShortName(), System.currentTimeMillis()
                - startTime);

    }

    /**
     * Creates a {@code FilteringClassLoader} for this EAR.
     * @param earArchive the EAR archive
     * @return a Filtering ClassLoader configured from {@code META-INF/classloader-filtering.xml} (if any).
     */
    private FilteringClassLoader createFilteringClassLoader(final IArchive earArchive) {
        // This one is sitting between the common applications loader and this application loader
        // It will typically filter resources visible to resource adapters (*.rar) declared in this EAR
        // But it will also filter resources for all child ClassLoaders (EjbJars + lib and then WebApps)
        FilteringClassLoader filteringClassLoader = new FilteringClassLoader(appsClassLoader);
        // Configure the loader with the provided filter definition file (if there is one)
        try {
            String name = "META-INF/" + FilteringClassLoader.CLASSLOADER_FILTERING_FILE;
            URL filteringDefinitionUrl = earArchive.getResource(name);
            if (filteringDefinitionUrl != null) {
                filteringClassLoader.setDefinitionUrl(filteringDefinitionUrl);
            }
        } catch (ArchiveException ae) {
            // Ignored
            logger.debug("Cannot get classloader-filtering.xml file from the EAR archive {0}.", earArchive, ae);
        }
        // Finally start the loader
        filteringClassLoader.start();
        return filteringClassLoader;
    }

    /**
     * Undeploy the given EAR.
     * @param deployable the deployable to remove.
     * @throws DeployerException if the EAR is not undeployed.
     */
    @Override
    public void doUndeploy(final IDeployable<EARDeployable> deployable) throws DeployerException {
        logger.info("Undeploying {0}", deployable.getShortName());

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

        // Check if this archive has been unpacked ?
        EARDeployable unpackedDeployable = null;
        if (ears.containsKey(earURL)) {
            unpackedDeployable = ears.get(earURL);
        } else {
            throw new DeployerException("Cannot get the URL of the unpacked EAR deployable '" + deployable + "'.");
        }

        // Undeploy wars (by sending URL of these wars)
        List<WARDeployable> warDeployables = unpackedDeployable.getWARDeployables();
        if (warDeployables != null && webContainerService != null) {
            List<URL> urls = new LinkedList<URL>();
            for (WARDeployable warDeployable : warDeployables) {
                try {
                    // Archive URLs are alreay escaped
                    // The WebContainer expects escaped URL
                    urls.add(warDeployable.getArchive().getURL());
                } 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 = unpackedDeployable.getEJB21Deployables();
        if (ejb21Deployables != null && ejb21Service != null) {
            List<URL> urls = new LinkedList<URL>();
            for (EJB21Deployable ejbDeployable : ejb21Deployables) {
                try {
                    // Archive URLs are alreay escaped
                    // The EJBContainer expects escaped URL
                    urls.add(ejbDeployable.getArchive().getURL());
                } 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(unpackedDeployable);

        // Undeploy Resource Adapters (by sending URL of these jars)
        List<RARDeployable> rarDeployables = unpackedDeployable.getRARDeployables();
        if (rarDeployables != null && resourceService != null) {
            List<URL> urls = new LinkedList<URL>();
            for (RARDeployable rarDeployable : rarDeployables) {
                try {
                    // Archive URLs are alreay escaped
                    // The EJBContainer expects escaped URL
                    urls.add(rarDeployable.getArchive().getURL());
                } 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(unpackedDeployable, "");

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

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

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

    }

    /**
     * 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()) {
            if (getEmbedded() == null) {
                logger.warn("No EJB3 service, but there are EJB3s in the given EAR archive '"
                        + earDeployable.getOriginalDeployable() + "'.");
                return;
            }
            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
            Object oldContext = null;
            if (isMultitenantEnabled()) {
                oldContext = multitenantService.getTenantContext();
            }
            try {
                if (isMultitenantEnabled()) {
                    multitenantService.setTenantIdInContext(multitenantService.getTenantIdDeployableInfo(workingDeployable));
                }
                container.stop();
            } finally {
                if (isMultitenantEnabled()) {
                    multitenantService.setTenantContext(oldContext);
                }
            }
            // remove it
            getEmbedded().removeContainer(container);
        }
    }

    /**
     * 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.
     */
    @Override
    public boolean supports(final IDeployable<?> deployable) {
        return EARDeployable.class.isAssignableFrom(deployable.getClass());
    }

    /**
     * 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 ComponentContext(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
                try {
                    // Store escaped URLs
                    urls.add(warDeployable.getArchive().getURL());
                } catch (Exception e) {
                    throw new DeployerException("Cannot get the URL for the archive '" + warDeployable.getArchive() + "'", e);
                }

                // Context-root
                ctxRoots.add(warDeployable.getContextRoot());

                // TenantId info
                if (isMultitenantEnabled()) {
                    multitenantService.addTenantIdDeployableInfo(warDeployable, multitenantService.getTenantIdDeployableInfo(earDeployable));
                }
            }

            try {
                ctx.rebind("warDeployables", wars);
            } catch (NamingException e) {
                throw new DeployerException("Cannot add the WAR deployables '" + wars + "'", e);
            }

            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 {
                if (ejbInjectionHolder != null) {
                    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 Classloader
                    URL wsdlDirectory = ejb21Deployable.getArchive().getResource(WSDL_DIRECTORY);
                    if (wsdlDirectory != null) {

                        // Search addURL method
                        Method addURLMethod = null;
                        Class<?> classToSearch = ejbClassLoader.getClass();
                        while (addURLMethod == null && classToSearch != null) {
                            try {
                                addURLMethod = classToSearch.getDeclaredMethod("addURL", URL.class);
                            } catch (NoSuchMethodException e) {
                                logger.debug("Method addURL not found on class ''{0}''.", classToSearch.getName());
                            }
                            classToSearch = classToSearch.getSuperclass();
                        }
                        if (addURLMethod == null) {
                            logger.warn("Unable to find addURL method on the classloaders");
                        } else {
                            boolean accessible = addURLMethod.isAccessible();
                            try {
                                addURLMethod.setAccessible(true);
                                addURLMethod.invoke(ejbClassLoader, wsdlDirectory);
                            } catch (Exception e) {
                                logger.warn("Cannot add URL for META-INF/wsdl directory to the Classloader", e);
                            } finally {
                                addURLMethod.setAccessible(accessible);
                            }
                        }
                    }
                } catch (ArchiveException e) {
                    throw new DeployerException("Cannot get the URL for the archive '" + ejb21Deployable.getArchive() + "'", e);
                }
            }

            // Deploy EJB 2.1 on JOnAS service
            Context ctx = new ComponentContext(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 ComponentContext(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 (jaxrpcService == null) {
            return;
        }

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

        try {
            File earFile = URLUtils.urlToFile(earURL);
            ctx.rebind(IJAXRPCService.UNPACK_DIRECTORY_CTX_PARAM, earFile);
        } 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 {
            jaxrpcService.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 JAX-RPC Service is started
        if (jaxrpcService != null) {
            try {
                ComponentContext contctx = null;
                try {
                    contctx = new ComponentContext(earURL.toExternalForm());
                    contctx.rebind(IJAXRPCService.CLASSLOADER_CTX_PARAM, earClassLoader);

                    String javaEEApplicationName = earDeployable.getModuleName();

                    ObjectName name = J2eeObjectName.J2EEApplication(getServerProperties().getDomainName(),
                            getServerProperties().getServerName(), javaEEApplicationName);
                    contctx.rebind(IJAXRPCService.PARENT_OBJECTNAME_CTX_PARAM, name);
                    contctx.rebind(IJAXRPCService.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);
                }
                jaxrpcService.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
     * @param earDD The EAR deployment descriptor
     * @throws DeployerException If the policy objects can't be linked
     */
    private void linkPolicyObjects(final EARDeployable earDeployable, final EarDeploymentDesc earDD) 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);
        }

        // Do user-to-role mapping
        if (earDD != null) {
            Map userToRoleMapping = earDD.getUserToRoleMapping();
            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);
                    }
                }
            }

        }

    }

    /**
     * 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 contextRoot = warDeployable.getContextRoot();
                if (versioningService != null && versioningService.isVersioningEnabled()) {
                    String versionID = versioningService.getVersionID(earDeployable);

                    if (versionID != null && !contextRoot.contains(versionID)) {
                        contextRoot += versionID;
                    }
                }

                if (isMultitenantEnabled() && multitenantService.isMultitenant(earDeployable)) {
                    String tenantId = multitenantService.getTenantIdDeployableInfo(earDeployable);

                    if (tenantId != null && !contextRoot.contains(tenantId)) {
                        contextRoot = tenantId + '/' + contextRoot;
                    }
                }

                String ctxId = warURL.getFile() + contextRoot;
                // 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 JAX-RPC service.
     * @param jaxrpcService JAX-RPC service
     */
    public void setJAXRPCService(final IJAXRPCService jaxrpcService) {
        this.jaxrpcService = jaxrpcService;
    }

    /**
     * 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;
    }

    /**
     * @param service the EJB3 Service to be injected.
     */
    public void setEasyBeansService(final IEasyBeansService service) {
        this.ejb3Service = service;
    }

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

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

    /**
     * @param multitenantService The multitenant service to set.
     */
    public void setMultitenantService(final MultitenantService multitenantService) {
        this.multitenantService = multitenantService;
    }

    /**
     * Sets the multitenant service to null.
     */
    public void unsetMultitenantService() {
        this.multitenantService = null;
    }

    /**
     * @return The multitenant service.
     */
    public MultitenantService getMultitenantService() {
        return this.multitenantService;
    }

    /**
     * @return Whether multitenant is enabled.
     */
    public boolean isMultitenantEnabled() {
        return (multitenantService != null);
    }

    /**
     * Sets the service manager to null.
     */
    public void unsetServiceManager() {
        this.serviceManager = null;
    }

    /**
     * Sets the service manager.
     */
    public void setServiceManager(final ServiceManager serviceManager) {
       this.serviceManager = serviceManager;
    }

    /**
     * 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;
    }

    /**
     * Sets the working directory for Apps.
     * @param appsWorkDirectory the given directory
     */
    public void setAppsWorkDirectory(final String appsWorkDirectory) {
        this.appsWorkDirectory = appsWorkDirectory;
    }

    /**
     * Sets the working directory for webapps.
     * @param webappsWorkDirectory the given directory
     */
    public void setWebappsWorkDirectory(final String webappsWorkDirectory) {
        this.webappsWorkDirectory = webappsWorkDirectory;
    }
}
