/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2011 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.addon.deploy.impl.deployer;

import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.ow2.jonas.Version;
import org.ow2.jonas.addon.deploy.api.deployable.IAddonDeployable;
import org.ow2.jonas.addon.deploy.api.deployable.IAddonMetadata;
import org.ow2.jonas.addon.deploy.api.deployable.ISortableDeployable;
import org.ow2.jonas.addon.deploy.api.deployer.IAddonDeployer;
import org.ow2.jonas.addon.deploy.api.deployer.IAddonDeployerLog;
import org.ow2.jonas.addon.deploy.api.deployer.IConfDeployer;
import org.ow2.jonas.addon.deploy.api.util.IAddonLogEntry;
import org.ow2.jonas.addon.deploy.impl.deployable.AddonDeployableImpl;
import org.ow2.jonas.addon.deploy.impl.deployable.AddonProvidesRequirements;
import org.ow2.jonas.addon.deploy.impl.util.AddonUtil;
import org.ow2.jonas.lib.work.LogEntryImpl;
import org.ow2.jonas.management.ServiceManager;
import org.ow2.jonas.multitenant.MultitenantService;
import org.ow2.jonas.properties.ServerProperties;
import org.ow2.jonas.properties.ServiceProperties;
import org.ow2.jonas.workcleaner.DeployerLogException;
import org.ow2.jonas.workcleaner.IDeployerLog;
import org.ow2.jonas.workcleaner.LogEntry;
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.IDeployable;
import org.ow2.util.ee.deploy.api.deployable.OSGiDeployable;
import org.ow2.util.ee.deploy.api.deployable.UnknownDeployable;
import org.ow2.util.ee.deploy.api.deployer.DeployerException;
import org.ow2.util.ee.deploy.api.deployer.IDeployerManager;
import org.ow2.util.ee.deploy.api.deployer.UnsupportedDeployerException;
import org.ow2.util.ee.deploy.api.helper.DeployableHelperException;
import org.ow2.util.ee.deploy.api.helper.IDeployableHelper;
import org.ow2.util.ee.deploy.api.report.IDeploymentReport;
import org.ow2.util.ee.deploy.impl.deployer.AbsDeployer;
import org.ow2.util.ee.deploy.impl.helper.UnpackDeployableHelper;
import org.ow2.util.ee.deploy.impl.report.DeploymentReport;
import org.ow2.util.file.FileUtils;
import org.ow2.util.file.FileUtilsException;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;
import org.ow2.util.plan.bindings.exceptions.InvalidRepositoryException;
import org.ow2.util.plan.bindings.repository.Repository;
import org.ow2.util.plan.bindings.repository.RepositoryKind;
import org.ow2.util.plan.deploy.deployable.api.DeploymentPlanDeployable;
import org.ow2.util.plan.repository.api.IRepositoryManager;
import org.ow2.util.plan.repository.api.RepositoryIdCollisionException;
import org.ow2.util.url.URLUtils;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

/**
 * Represents an Addon Deployer
 * @author Jeremy Cazaux
 */
public class AddonDeployerImpl extends AbsDeployer<IAddonDeployable> implements IAddonDeployer {

    /**
     * The property name to register a new ServiceProperties component as an OSGi service
     */
    public final String SERVICE_REGISTRATION_SERVICE_PROPERTY = "service";

    /**
     * The XSD for the addon metadata file
     */
    public static final String XSD_RESOURCE = "/META-INF/jonas-addon-1.0.xsd";

    /**
     * The clear cache system property of JOnAS
     */
    public static final String JONAS_CLEAN_CACHE_PROPERTY = "jonas.cache.clean";

    /**
     * Max waiting time in milliseconds (1 second)
     */
    public static final long MAX_WAITING_TIME = 1000L;

    /**
     * the length of time to sleep in milliseconds (0,05 second)
     */
    public static final long SLEEPING_TIME = 50;

    /**
     * The logger
     */
    private static Log logger = LogFactory.getLog(AddonDeployerImpl.class);

    /**
     * Server properties
     */
    private ServerProperties serverProperties;

    /**
     * The DeployableHelper which analyze an archive and build the associated Deployable object
     */
    private IDeployableHelper deployableHelper;

    /**
     * List of deployed addons.
     */
    private Map<URL, IAddonDeployable> addons = null;

    /**
     * Addons log
     */
    private IAddonDeployerLog<IAddonLogEntry> deployerLog;

    /**
     * Log for the work cleaner service
     */
    private IDeployerLog workCleanerLog;

    /**
     * The deployer manager
     */
    private IDeployerManager deployerManager;

    /**
     * OSGi bundle context
     */
    private BundleContext bundleContext;

    /**
     * Configuration deployer
     */
    private IConfDeployer confDeployer;

    /**
     * The service manager.
     * Used to start / stop JOnAS services and to register the service for the JMX management
     */
    private ServiceManager serviceManager;

    /**
     * True if the adon deployer is initialized
     */
    private boolean isInitialized;

    /**
     * List of OSGi service registration of ServiceProperties instance
     */
    private List<ServiceRegistration> serviceRegistrations;

    /**
     * Multitenant service
     */
    private MultitenantService multitenantService;

    /**
     * Repository manager
     */
    private IRepositoryManager repositoryManager;

    /**
     * List of repositories of each addons
     */
    private Map<IAddonDeployable, Repository> repositories;

    /**
     * Id of the latest installed addon
     */
    private long addonId = 0;

    /**
     * Default constructor
     * @param serverProperties Server properties
     */
    public AddonDeployerImpl(final ServerProperties serverProperties, final IDeployableHelper deployableHelper,
                             final IAddonDeployerLog<IAddonLogEntry> deployerLog, final IDeployerManager deployerManager,
                             final BundleContext bundleContext, final ServiceManager serviceManager,
                             final IDeployerLog<LogEntry> workCleanerLog, final IRepositoryManager repositoryManager,
                             final IConfDeployer confDeployer) {
        this.serverProperties = serverProperties;
        this.deployableHelper = deployableHelper;
        this.deployerLog = deployerLog;
        this.workCleanerLog = workCleanerLog;
        this.deployerManager = deployerManager;
        this.addons = new LinkedHashMap<URL, IAddonDeployable>();
        this.bundleContext = bundleContext;
        this.serviceManager = serviceManager;
        this.confDeployer = confDeployer;
        this.confDeployer.setAddonDeployer(this);
        this.repositoryManager = repositoryManager;
        this.repositories = new HashMap<IAddonDeployable, Repository>();
        this.isInitialized = false;
        this.serviceRegistrations = new ArrayList<ServiceRegistration>();

        //Warning: it shouldn't be registered later (check logs could deploy embedded addons)
        this.deployerManager.register(this);

        //Retrieve the configuration of the server:
        // - if some addons are removed from the deploy directories, the JOnAS configuration is retrieved
        // - install all addons which have been previously installed from an OSGi command.
        // - start them if their previous state was equals to ACTIVE
        checkLogs();
    }

    /**
     * Retrieve the configuration of the server:
     *   - if some addons have been removed from the deploy directories, the JOnAS configuration is
     * retrieved
     *   - install all addons which have been previously installed from an OSGi command.
     *   - start them if their previous state was equals to ACTIVE
     */
    private void checkLogs() {

        //Map between the name of the addon to undeploy and the path to the deployable of the addon to undeploy
        Map<String, String> addonsToUndeploy = new HashMap<String, String>();

        //Map between a deployable to install and the state of the deployable on the previous server execution
        Map<File, Integer> addonsToInstall = new HashMap<File, Integer>();

        //get log entries
        Vector<IAddonLogEntry> logEntries = this.deployerLog.getEntries();
        Vector<IAddonLogEntry> logEntriesToRemove = new Vector<IAddonLogEntry>();

        for (IAddonLogEntry logEntry: logEntries) {

            File orginalFile = logEntry.getOriginal();
            File unpackedFile = logEntry.getCopy();
            int state = logEntry.getState();
            boolean isInstalledFromACommand = logEntry.isInstalledFromACommand();

            if (orginalFile == null || !orginalFile.exists()) {
                //an Addon has been removed from deploy directory when the server was not running
                //we need to retrieve JOnAS Configuration (for configurations files, binaries, deployable, ANT tasks, etc...)
                addonsToUndeploy.put(logEntry.getName(), unpackedFile.getAbsolutePath());
                logEntriesToRemove.add(logEntry);
            } else if (isInstalledFromACommand) {
                addonsToInstall.put(orginalFile, state);
            }
        }

        //retrieve JOnAS configuration
        retrieveJOnASConfiguration(addonsToUndeploy);

        //remove log entries for each addon to undeploy
        for (IAddonLogEntry logEntry: logEntriesToRemove) {
            try {
                this.deployerLog.removeEntry(logEntry);
                this.logger.info("''{0}'' Addon Deployable is now undeployed", logEntry.getOriginal().getAbsolutePath());
            } catch (DeployerLogException e) {
                this.logger.error("Cannot remove log entry " + logEntry.getName() + "." , e);
            }
        }



        //Going to install all addons which have been previously installed from an OSGi command.
        //Start them if their previous state was equals to ACTIVE

        //list of addon to start
        List<IDeployable<IAddonDeployable>> addonsToStart = new ArrayList<IDeployable<IAddonDeployable>>();

        //get the list of addon deployable
        for (Map.Entry<File, Integer> entry: addonsToInstall.entrySet()) {
            File file = entry.getKey();
            Integer state = entry.getValue();
            IArchive archive = ArchiveManager.getInstance().getArchive(file);
            IAddonDeployable deployable = null;
            try {
                deployable = IAddonDeployable.class.cast(this.deployableHelper.getDeployable(archive));
            } catch (DeployableHelperException e) {
                logger.error("Could get the deployable instance of the archive " + file.getAbsolutePath(), e);
            }
            if (deployable != null) {

                IDeployable<IAddonDeployable> installed = null;
                try {
                    installed = install(deployable, true);
                } catch (DeployerException e) {
                    logger.error("Could not install addon deployable " + deployable.getShortName(), e);
                }

                if (installed != null && IAddonDeployable.ACTIVE == state) {
                    //add the deployable to the list of addon to start
                    addonsToStart.add(installed);
                }
            }
        }

        if (!addonsToStart.isEmpty()) {
            //find <provides, requirements> property of each deployable
            List<AddonProvidesRequirements> addonProvidesRequirementsList = getAddonProvidesRequirementsList(addonsToStart);

            //order the list of deployables according to requirements/provides dependencies
            AddonUtil.sort(addonProvidesRequirementsList);

            //then start the list of installed addon in the right order
            for (AddonProvidesRequirements addonProvidesRequirements: addonProvidesRequirementsList) {
                IAddonDeployable addon = IAddonDeployable.class.cast(addonProvidesRequirements.getDeployable());
                try {
                    start(addon.getMetadata().getName());
                } catch (DeployerException e) {
                    logger.error("Could not start addon " + addon.getMetadata().getName(), e);
                }
            }
        }
    }

    /**
     * Retrieve JOnAS configuration
     * @param addonName Name of the addon to undeploy
     * @param unpackedFile Path of the unpacked file
     */
    private void retrieveJOnASConfiguration(final String addonName, final String unpackedFile) {
        if (addonName != null && unpackedFile != null) {
            Map<String, String> addonsToUndeploy = new HashMap<String, String>();
            addonsToUndeploy.put(addonName, unpackedFile);
            retrieveJOnASConfiguration(addonsToUndeploy);
        }
    }

    /**
     * Retrieve JOnAS configuration when the server is starting
     * @param addonsToUndeploy List of name of addon to undeploy
     */
    private void retrieveJOnASConfiguration(final Map<String, String> addonsToUndeploy) {

        //retrieve JOnAS configuration for configuration files
        for (String name: addonsToUndeploy.keySet()) {
            try {
                this.confDeployer.uninstall(name);
            } catch (DeployerException e) {
                logger.error("Could not uninstall the configuration of the addon " + name, e);
            }
        }

        //TODO: retrieve JOnAS configuration for binaries

        //TODO: retrieve JOnAS configuration for ANT tasks

        if (!Boolean.getBoolean(JONAS_CLEAN_CACHE_PROPERTY)) {
            //uninstall bundles of undeployed addons
            for (Map.Entry<String, String> entry : addonsToUndeploy.entrySet()) {

                //get persistent deployables
                String unpackedDeployablePath = entry.getValue();
                IAddonDeployable unpackedDeployable = (IAddonDeployable) AddonUtil.getDeployable(this.deployableHelper,
                        new File(unpackedDeployablePath));
                updateDeployables(unpackedDeployablePath, unpackedDeployable, false);
                //we assume that unknown deployables are not persistent
                List<ISortableDeployable> sortableDeployables = unpackedDeployable.getKnownDeployables();

                //undeploy persistent deployables (OSGi deployable, DeploymentPlan deployable)
                if (sortableDeployables != null) {

                    List<ISortableDeployable> persistentSortableDeployable = new ArrayList<ISortableDeployable>();

                    for (ISortableDeployable sortableDeployable: sortableDeployables) {
                        IDeployable deployable = sortableDeployable.getDeployable();
                        if (deployable instanceof OSGiDeployable /*|| deployable instanceof DeploymentPlanDeployable*/) {
                            persistentSortableDeployable.add(sortableDeployable);
                        }
                    }

                    //undeploy persistent deployable
                    List<IDeploymentReport> deploymentReports = undeploySortableDeployables(persistentSortableDeployable);

                    int count = 0;
                    for (IDeploymentReport deploymentReport: deploymentReports) {
                        if (!deploymentReport.isDeploymentOk()) {
                            logger.error("The deployable " + deploymentReport.getDeployable().getShortName() +
                                    " could not be undeployed.", deploymentReport.getException());
                            count++;
                        }
                    }
                    if (count > 0) {
                        logger.error("The addon " + entry.getKey() + " cannot be undeployed");
                    }
                }
            }
        }
    }

    /**
     * @param originalURL The original {@link URL} of an addon
     * @return its parent if exist
     */
    private IAddonDeployable getParent(final URL originalURL) {
        IAddonDeployable parent = null;

        //get the name of the original deployable
        String name = new File(originalURL.getFile()).getName();

        //get the list of installed addons in the reverse order
        List<IAddonDeployable> addons = new ArrayList<IAddonDeployable>(getAddons());
        Collections.reverse(addons);

        //check if an installed addon is the parent of the addon associated to the originalURL
        for (IAddonDeployable addon: addons) {
            URL url = null;
            try {
                url = addon.getOriginalDeployable().getArchive().getURL();
            } catch (ArchiveException e) {
                logger.error("Could not get the url of the deployable " + addon, e);
            }
            if (url != null) {
                File file = new File(new File(url.getFile(), IAddonDeployable.DEPLOY_DIRECTORY), name);
                if (file.exists()) {
                    return addon;
                }
            }
        }

        return parent;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IDeployable<IAddonDeployable> install(final IDeployable<IAddonDeployable> deployable,
                                                  final boolean installFromACommand) throws DeployerException {
        File originalFile = null;
        try {
            originalFile = URLUtils.urlToFile(deployable.getArchive().getURL());
        } catch (ArchiveException e) {
            throw new DeployerException("Cannot get the deployable " + deployable.getShortName(), e);
        }

        IAddonLogEntry logEntry = this.deployerLog.getEntry(originalFile);
        boolean isAlreadyInstalled = (originalFile.exists() && logEntry != null);

        IAddonDeployable unpackedDeployable = null;
        String lastModifiedOriginalFileName = null;
        try {
            lastModifiedOriginalFileName = FileUtils.lastModifiedFileName(originalFile);
        } catch (FileUtilsException e) {
            throw new DeployerException("Cannot get the last modified file name of " + originalFile.getAbsolutePath(), e);
        }

        if (logEntry != null) {
            String unpackedFileName =  logEntry.getCopy().getName();

            //if it's a cold update, the addon need to be redeployed
            if (!lastModifiedOriginalFileName.equals(unpackedFileName)) {
                logger.info("Deployable ''{0}'' has been updated.", deployable);

                //get the log entry
                IAddonLogEntry addonLogEntry = this.deployerLog.getEntry(originalFile);

                // Get the unpacked deployable
                IArchive archive = ArchiveManager.getInstance().getArchive(addonLogEntry.getCopy());
                try {
                    unpackedDeployable = IAddonDeployable.class.cast(this.deployableHelper.getDeployable(archive));
                    unpackedDeployable.setOriginalDeployable(IAddonDeployable.class.cast(deployable));
                } catch (DeployableHelperException e) {
                    throw new DeployerException("Cannot get the deployable " + originalFile, e);
                }

                //get JOnAS Addon metadata
                IAddonMetadata addonMetadata = null;
                try {
                    addonMetadata = AddonUtil.getAddonMetadata(AddonUtil.getAddonMetadataFile(unpackedDeployable.getArchive()),
                            logEntry.getCopy().getAbsolutePath());
                    unpackedDeployable.setMetadata(addonMetadata);
                } catch (Exception e) {
                    //do nothing...the addon need to be undeployed
                    logger.warn("Addon metadata are incorrect", e);
                }

                //get the name of the addon to uninstall
                String name = addonLogEntry.getName();

                //retrieve JOnAS configuration
                retrieveJOnASConfiguration(name, addonLogEntry.getCopy().getAbsolutePath());

                //remove the logEntry
                try {
                    this.deployerLog.removeEntry(addonLogEntry);
                } catch (DeployerLogException e) {
                    logger.error("Cannot remove log entry of the file " + originalFile.getAbsolutePath(), e);
                }

                isAlreadyInstalled = false;
            }
        }

        //if it's already installed, don't unpack it!
        if (isAlreadyInstalled) {
            File unpackedFile = logEntry.getCopy();

            //an unpacked deployable has been removed from the work directory. We need to unpack it again
            if (unpackedFile == null|| !unpackedFile.exists()) {
                //unpacked the addon
                File folder = new File(AddonUtil.getAddonsWorkDirectory(this.serverProperties));
                try {
                    unpackedDeployable = UnpackDeployableHelper.unpack(
                            IAddonDeployable.class.cast(deployable), folder, lastModifiedOriginalFileName, false,
                            this.deployableHelper);
                } catch (Exception e) {
                    throw new DeployerException("Cannot unpacked archive for '" + deployable.getArchive() + "'", e);
                }
            } else {
                // Get the unpacked deployable
                IArchive archive = ArchiveManager.getInstance().getArchive(this.deployerLog.getEntry(originalFile).getCopy());
                try {
                    unpackedDeployable = IAddonDeployable.class.cast(this.deployableHelper.getDeployable(archive));
                    unpackedDeployable.setOriginalDeployable(IAddonDeployable.class.cast(deployable));
                } catch (DeployableHelperException e) {
                    throw new DeployerException("Cannot get the deployable " + originalFile, e);
                }
            }

        } else {
            logger.info("Installing ''{0}''", deployable.getShortName());

            //unpacking the addon
            unpackedDeployable = null;
            File folder = new File(AddonUtil.getAddonsWorkDirectory(this.serverProperties));
            try {
                originalFile = URLUtils.urlToFile(deployable.getArchive().getURL());
                unpackedDeployable = UnpackDeployableHelper.unpack(IAddonDeployable.class.cast(deployable), folder,
                        lastModifiedOriginalFileName, false, this.deployableHelper);
            } catch (Exception e) {
                throw new DeployerException("Cannot install archive for '" + deployable.getArchive() + "'", e);
            }
        }

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

        //get the unpackedFile
        File unpackedFile;
        try {
            unpackedFile = URLUtils.urlToFile(addonArchive.getURL());
        } catch (Exception e) {
            throw new DeployerException("Cannot get URL from archive '" + addonArchive + "'", e);
        }

        //get the metadata of the addon
        File addonMetadataFile;
        if (isAlreadyInstalled) {
            //get metadata from $JB/conf/addons/{addon.name} directory
            addonMetadataFile = new File(AddonUtil.getAddonDirectoryPath(logEntry.getName()), IAddonDeployable.METADATA_FILENAME);
        } else {
            //get metadata from the unpacked deployable
            addonMetadataFile = AddonUtil.getAddonMetadataFile(addonArchive);
        }

        //check the validity of the metadata file
        validate(addonMetadataFile);

        IAddonMetadata addonMetadata;
        try {
            addonMetadata = AddonUtil.getAddonMetadata(addonMetadataFile, unpackedFile.getAbsolutePath());
        } catch (Exception e) {
            throw new DeployerException("Addon metadata are incorrect", e);
        }

        //set deployable metadata
        unpackedDeployable.setMetadata(addonMetadata);

        //check addon metadata (except requirements)
        checkAddonMetadata(unpackedDeployable);

        //update the list of deployable of the addon
        updateDeployables(unpackedFile.getAbsolutePath(), unpackedDeployable, false);

        //install configuration files
        if (!isAlreadyInstalled) {
            try {
                this.confDeployer.install(unpackedDeployable);
            } catch (DeployerException e) {
                throw new DeployerException("Could not install configuration files of the deployable " + unpackedDeployable.getShortName(), e);
            }

            //TODO: BinDeployerImpl install

            //TODO: AntDeployer install
        }

        //get the original URL
        URL originalURL;
        try {
            originalURL = deployable.getArchive().getURL();
        } catch (Exception e) {
            throw new DeployerException("Cannot get the url of the deployable '" + deployable.getShortName(), e);
        }

        // Register repository dir if exists
        String repositoryDir = AddonUtil.getAddonRepositoryWorkDirectory(AddonUtil.getAddonWorkDirectory(unpackedDeployable));
        File repositoryFile = new File(repositoryDir);
        if (repositoryFile.exists() && repositoryFile.isDirectory()) {
            Repository repository = new Repository();
            repository.setId(unpackedDeployable.toString());
            repository.setType(RepositoryKind.MAVEN_2);
            repository.setUrl("file:" + repositoryFile.getPath());

            try {
                this.repositoryManager.addRepository(repository);
                this.repositories.put(unpackedDeployable, repository);
                logger.debug("The repository '" + repository + "' was added");
            } catch (RepositoryIdCollisionException e) {
                throw new DeployerException("Repository '" + repository + "' already added");
            } catch (InvalidRepositoryException e) {
                throw new DeployerException("'" + repository + "' is an invalid repository");
            }
        }

        //check if this addon has a parent
        IAddonDeployable parent = getParent(originalURL);
        if (parent != null)  {
            (AddonDeployableImpl.class.cast(unpackedDeployable)).setParent(parent);
        }

        //update the state of the addon
        unpackedDeployable.updateState(IAddonDeployable.INSTALLED);

        //set the addon id
        this.addonId++;
        (AddonDeployableImpl.class.cast(unpackedDeployable)).setAddonId(this.addonId);

        // log the addon into the addon log
        if (this.deployerLog != null && this.deployerLog.getEntry(URLUtils.urlToFile(originalURL)) == null) {
            try {
                this.deployerLog.addEntry(addonMetadata.getName(), unpackedDeployable.getState(), installFromACommand,
                        originalFile, unpackedFile);
            } catch (DeployerLogException e) {
                logger.info("Cannot added a log entry to the addon logger");
            }
        }

        //log the addon into the workcleaner log file
        try {
            this.workCleanerLog.addEntry(new LogEntryImpl(originalFile, unpackedFile));
        } catch (DeployerLogException e) {
            logger.info("Cannot added a log entry to the addon work cleaner logger");
        }

        //add the addon to the deployed list
        this.addons.put(originalURL, unpackedDeployable);

        if (!isAlreadyInstalled) {
            logger.info("Deployable ''{0}'' has been installed.", unpackedDeployable);
        }

        return unpackedDeployable;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void start(final String addonName) throws DeployerException {

        //retrieve the addon
        if (addonName == null) {
            throw new DeployerException("The name of the addon to start could not be null");
        }
        IAddonDeployable addon = getAddon(addonName);

        //check that the addon to start is installed in the addon system
        if (addon == null) {
            throw new DeployerException("The addon " + addonName + " could not be retrieved");
        }

        //check the state of the addon
        int state = addon.getState();
        if (IAddonDeployable.INSTALLED == state || IAddonDeployable.RESOLVED == state) {

            //update the state of the addon
            addon.updateState(IAddonDeployable.STARTING);

            boolean ok = false;
            try {
                //check requirements
                checkRequirements(addon, false);
                ok = true;
            } finally {
                if (!ok) {
                    //if there is a least one requirement which is not satisfied, the addon go back in the INSTALLED state
                    addon.updateState(IAddonDeployable.INSTALLED);

                    //update the state of the addon in the deployer log
                    try {
                        this.deployerLog.updateEntry(addonName, addon.getState());
                    } catch (DeployerLogException e) {
                        logger.error("Could not update the log entry of the addon " +addonName);
                    }
                }
            }

            ok = false;
            try {

                //add access to configurations files of the addon through the OSGi registry
                this.confDeployer.start(addon.getMetadata().getName());

                //deploy all deployables of the addon
                deployEmbeddedDeployables(addon);

                ok = true;
                logger.info("''{0}'' addon has been started", addon.getShortName());
            } finally {

                //update the state of the deployable
                if (ok) {

                    addon.updateState(IAddonDeployable.ACTIVE);

                    //check if requirements of addons with an INSTALLED state are no more unsatisfied
                    List<IAddonDeployable> addons = getAddons(IAddonDeployable.INSTALLED);
                    for (IAddonDeployable deployable: addons) {
                        if (!deployable.equals(addon)) {
                            try {
                                checkRequirements(deployable);
                                deployable.updateState(IAddonDeployable.RESOLVED);
                                try {
                                    this.deployerLog.updateEntry(deployable.getMetadata().getName(), deployable.getState());
                                } catch (DeployerLogException e) {
                                    logger.error("Could not update the log entry of the addon " + deployable.getMetadata().getName(), e);
                                }
                            } catch (Exception e) {
                                //do nothing
                            }
                        }
                    }

                } else {
                    //at least one embedded deployable could not be deployed

                    try {
                        //check requirements without including scan of embedded addons
                        checkRequirements(addon);
                        addon.updateState(IAddonDeployable.RESOLVED);
                    } catch (Exception e) {
                        //the deployment of at least one of the required embedded addon has failed
                        addon.updateState(IAddonDeployable.INSTALLED);
                    }
                }

                //update the state of the addon in the deployer log
                try {
                    this.deployerLog.updateEntry(addonName, addon.getState());
                } catch (DeployerLogException e) {
                    logger.error("Could not update the log entry of the addon " +addonName, e);
                }
            }
        } else {
            throw new DeployerException("Could not start an addon that does not have an INSTALLED or RESOLVED state");
        }
    }

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

        //first, install the addon
        IAddonDeployable addon = IAddonDeployable.class.cast(install(deployable, false));

        // get addon metadata
        IAddonMetadata metadata = addon.getMetadata();

        //then, start it if autostart property has been set to true
        if (metadata.getAutostart()) {
            start(metadata.getName());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void stop(final String addonName) throws DeployerException {

        //retrieve the addon
        if (addonName == null) {
            throw new DeployerException("The name of the addon to stop could not be null");
        }
        IAddonDeployable addon = getAddon(addonName);

        //check that the addon has been installed
        if (addon == null) {
            throw new DeployerException("The addon " + addonName + " could not be retrieved");
        }

        //check if the deployable is an embedded addon
        IAddonDeployable parent = addon.getParent();
        if (parent != null && parent.getState() == IAddonDeployable.ACTIVE) {
            throw new DeployerException("The addon " + addonName + " is an embedded addon." +
                    " To stop it, you should stop its parent (ID " + parent.getAddonId() + ")");
        }

        //check the state of the addon
        int state = addon.getState();

        //stop active addon only
        if (IAddonDeployable.ACTIVE == state) {

            try {

                //update the state of the addon
                addon.updateState(IAddonDeployable.STOPPING);

                //for each addon with an ACTIVE state, if at least one of its requirements is no more satisfied then stop it
                List<IAddonDeployable> addons = getAddons(IAddonDeployable.ACTIVE);
                for (IAddonDeployable deployable: addons) {
                    if (!deployable.equals(addon)) {
                        try {
                            checkRequirements(deployable, addon, true);
                        } catch (Exception error) {

                            logger.warn("Stopping addon " + deployable.getMetadata().getName() + " ...", error);

                            try {
                                //stop the addon
                                stop(deployable.getMetadata().getName());

                                logger.warn("Addon " + deployable + " has been stopped.");

                            } catch (Exception ex) {
                                logger.error("Could not stop addon " + deployable.getMetadata().getName(), ex);
                            } finally {
                                //update its state
                                deployable.updateState(IAddonDeployable.INSTALLED);

                                //update the state of the addon in the deployer log
                                try {
                                    this.deployerLog.updateEntry(deployable.getMetadata().getName(), deployable.getState());
                                } catch (DeployerLogException ex) {
                                    logger.error("Could not update the log entry of the addon " +deployable.getMetadata().getName(), ex);
                                }
                            }
                        }
                    }
                }

                try {
                    //Remove access to the configuration of an Addon
                    this.confDeployer.stop(addonName);
                } catch (DeployerException e) {
                    logger.error("Could not remove access to the configuration of the addon " + addonName, e);
                }

                //undeploy all deployable embedded in the addon
                undeployEmbeddedDeployables(addon);

            } finally {

                //update the state of the addon
                addon.updateState(IAddonDeployable.RESOLVED);

                //update the state of the addon in the deployer log
                try {
                    this.deployerLog.updateEntry(addonName, addon.getState());
                } catch (DeployerLogException e) {
                    logger.error("Could not update the log entry of the addon " +addonName, e);
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void uninstall(final String addonName) throws DeployerException {
        uninstall(addonName, false);
    }

    /**
     * @param addonName The name of the addon to uninstall
     * @param force If set to true, we'll try to uninstall the addon even if its state is ACTIVE
     * @throws DeployerException if the addon could not be uninstalled
     */
    public void uninstall(final String addonName, final boolean force) throws DeployerException {

        //retrieve the addon
        if (addonName == null) {
            throw new DeployerException("The name of the addon to uninstall could not be null");
        }
        IAddonDeployable addon = getAddon(addonName);

        //check that the addon to uninstall is installed on the addon system
        if (addon == null) {
            throw new DeployerException("The addon " + addonName + " could not be retrieved");
        }

        if (!force) {
            //check the state of the addon
            int state = addon.getState();
            if (IAddonDeployable.ACTIVE == state) {
                throw new DeployerException("Could not uninstall an addon which have an ACTIVE state. Please stop the addon before and try again.");
            } else if (IAddonDeployable.STARTING == state) {
                throw new DeployerException("Could not uninstall an addon which have an STARTING state. Please stop the addon before and try again.");
            } else if (IAddonDeployable.STOPPING == state) {
                throw new DeployerException("Could not uninstall an addon which have an STOPPING state. Please stop the addon before and try again.");
            }
        }

        try {
            //uninstall configuration files
            this.confDeployer.uninstall(addonName);
        } catch (DeployerException e) {
            logger.error("Configuration files of the addon " + addonName + " could not be uninstalled.", e);
        }

        //TODO: BinDeployerImpl uninstall

        //TODO: AntDeployer uninstall

        // get Addon URL
        URL originalAddonURL;
        try {
            originalAddonURL = addon.getOriginalDeployable().getArchive().getURL();
        } catch (ArchiveException e) {
            throw new DeployerException("Cannot get the URL on the Addon deployable '" + addon + "'.", e);
        }

        //remove the logEntry
        File originalFile = URLUtils.urlToFile(originalAddonURL);
        IAddonLogEntry logEntry =  this.deployerLog.getEntry(originalFile);
        if (logEntry != null) {
            try {
                this.deployerLog.removeEntry(logEntry);
            } catch (DeployerLogException e) {
                logger.error("Cannot remove the adddon log entry", e);
            }
        } else {
            logger.error("Cannot retrieve the addon log entry to remove");
        }

        // remove the repository
        Repository repository = this.repositories.get(addon);
        if (repository != null) {
            this.repositoryManager.removeRepository(repository);
            logger.debug("The repository '" + repository + "' was removed");
        }

        //remove the addon from the deployed list
        this.addons.remove(originalAddonURL);

        //update the state of the deployable
        addon.updateState(IAddonDeployable.UNINSTALLED);
    }

    /**
     * Undeploy the given Addon.
     * @param deployable the deployable to remove.
     * @throws DeployerException if the Addon is not deployed.
     */
    @Override
    public void doUndeploy(final IDeployable<IAddonDeployable> deployable) throws DeployerException {

        logger.info("Undeploying {0}", deployable.getShortName());

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

        String addonName = null;

        try {

            //get metadata of the Addon
            IAddonDeployable unpackedDeployable = null;
            if (this.addons.containsKey(addonURL)) {
                unpackedDeployable = this.addons.get(addonURL);
                if (unpackedDeployable == null) {
                    throw new DeployerException("Cannot get the unpacked deployable " + deployable.getShortName() + " with URL '"
                            + addonURL.getPath() + "'.");
                }
            } else {
                throw new DeployerException("Cannot get the unpacked deployable " + deployable.getShortName() + " with URL '"
                        + addonURL.getPath() + "'.");
            }

            IAddonMetadata metadata = unpackedDeployable.getMetadata();
            if (metadata == null) {
                //retrieve metadata from the unpacked deployable
                File unpackedDeployableFile = null;
                try {
                    unpackedDeployableFile = URLUtils.urlToFile( unpackedDeployable.getArchive().getURL());
                } catch (ArchiveException e) {
                    throw new DeployerException("Cannot get the URL on the unpacked deployable '" + unpackedDeployable + "'.", e);
                }
                try {
                    unpackedDeployable.setMetadata(AddonUtil.getAddonMetadata(AddonUtil.getAddonMetadataFile(unpackedDeployable.getArchive()),
                            unpackedDeployableFile.getAbsolutePath()));
                } catch (Exception e) {
                    logger.warn("Cannot get metadata of the addon " + unpackedDeployable.getShortName(), e);
                }
                metadata = unpackedDeployable.getMetadata();
                if (metadata == null) {
                    throw new DeployerException("Could not retrieve metadata of the addon " + deployable.getShortName());
                }
            }

            // get the name of the Addon
            addonName = metadata.getName();

            //stop the addon
            stop(addonName);
        } finally {
            //uninstall the addon
            if (addonName != null) {
                uninstall(addonName, true);
            } else {
                throw new DeployerException("Could not uninstall addon " + deployable.getShortName() +
                        ". The name of the addon could not be retrieved");
            }
        }

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

    /**
     * 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 AddonDeployableImpl.class.isInstance(deployable);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<IAddonDeployable> getAddons() {
        return new ArrayList<IAddonDeployable>(this.addons.values());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IAddonDeployable getAddon(long addonId) {
        List<IAddonDeployable> addons = getAddons();
        for (IAddonDeployable addon: addons) {
            if (addon.getAddonId() == addonId) {
                return addon;
            }
        }
        return null;
    }

    /**                                               *
     * @return the list of name of deployed addons
     */
    private List<String> getDeployed() {
        List<String> deployed = new ArrayList<String>();
        Collection<IAddonDeployable> addons = this.addons.values();
        if (addons != null) {
            for (IAddonDeployable addon: addons) {
                IAddonMetadata addonMetadata = addon.getMetadata();
                if (addonMetadata != null) {
                    String name = addonMetadata.getName();
                    if (name != null) {
                        deployed.add(name);
                    }
                }
            }
        }
        return deployed;
    }

    /**
     * @param state The state, used to filter the list of deployed addon
     * @return the list of addon which are matching the given state
     */
    private List<IAddonDeployable> getAddons(int state) {
        List<IAddonDeployable> list = new ArrayList<IAddonDeployable>();
        Collection<IAddonDeployable> addons = this.addons.values();
        if (addons != null) {
            for (IAddonDeployable addon: addons) {
                if (addon.getState() == state) {
                    list.add(addon);
                }
            }
        }
        return list;
    }

    /**
     * Check that metadata file  is OK
     * @param deployable The {@link IAddonDeployable}
     * @throws DeployerException
     */
    private void checkAddonMetadata(final IAddonDeployable deployable) throws DeployerException {
        IAddonMetadata addonMetadata = deployable.getMetadata();

        //check the name of the addon.
        if (addonMetadata.getName() == null || addonMetadata.getName().isEmpty()) {
            throw new DeployerException("Addon name not found");
        }
        //If an addon with the same name have been previously deployed, throw a DeployerException
        if (getDeployed().contains(addonMetadata.getName())) {
            throw new DeployerException("Cannot install addon " + addonMetadata.getName() + ". An Addon with the same " +
            "name is already deployed.");
        }

        // if JOnASVersion doesn't match, throw a DeployerException
        String JOnASVersion = Version.getNumber();
        if (!addonMetadata.isJOnASVersionSupported(JOnASVersion)){
            throw new DeployerException("Cannot install addon " + addonMetadata.getName() + ". JOnAS version " +
                    JOnASVersion + " doesn't match ");
        }

        //check if the jvm version match.
        String jvmVersion = System.getProperty("java.version");
        if (!addonMetadata.isJvmVersionSupported(jvmVersion)) {
            throw new DeployerException("Cannot install addon " + addonMetadata.getName() + ". JVM version " +
                    jvmVersion + " doesn't match ");
        }
    }

    /**
     * Try to resolve all unresolved Addons
     */
    @Override
    public void resolve() {
        List<IAddonDeployable> addons = getAddons(IAddonDeployable.INSTALLED);
        for (IAddonDeployable addon: addons) {
            try {
                checkRequirements(addon);

                //update the state of the addon
                addon.updateState(IAddonDeployable.RESOLVED);
                try {
                    this.deployerLog.updateEntry(addon.getMetadata().getName(), addon.getState());
                } catch (DeployerLogException e) {
                    logger.error("Could not update the log entry of the addon " + addon.getMetadata().getName(), e);
                }
            } catch (DeployerException e) {
                logger.error("The addon " + addon.getMetadata().getName() + " could not be resolved.", e);
            }
        }
    }

    /**
     * Check requirements of an Addon
     * @param deployable The {@link IAddonDeployable}
     * @throws DeployerException if a requirement could not be satisfied
     */
    public void checkRequirements(final IAddonDeployable deployable) throws DeployerException {
        checkRequirements(deployable, (IAddonDeployable) null, true);
    }


    /**
     * Check requirements of an Addon
     * @param deployable The {@link IAddonDeployable}
     * @throws DeployerException if a requirement could not be satisfied
     */
    public void checkRequirements(final IAddonDeployable deployable, final boolean excludeEmbeddedAddonsScan) throws DeployerException {
        checkRequirements(deployable, (IAddonDeployable) null, excludeEmbeddedAddonsScan);
    }

    /**
     * Check requirements of an Addon
     * @param deployable The {@link IAddonDeployable}
     * @param excluded Capabilities provide by the given addon will be excluded from the resolver context
     * @param excludeEmbeddedAddonsScan Set to true if embedded addons will be excluded from the resolution context
     * @throws DeployerException if a requirement could not be satisfied
     */
    private void checkRequirements(final IAddonDeployable deployable, final IAddonDeployable excluded,
                                   final boolean excludeEmbeddedAddonsScan) throws DeployerException {
        List<IAddonDeployable> list = new ArrayList<IAddonDeployable>();
        list.add(excluded);
        checkRequirements(deployable, list, excludeEmbeddedAddonsScan);
    }

    /**
     * Check requirements of an Addon
     * @param deployable The {@link IAddonDeployable}
     * @param excluded Capabilities provide by the given list of addon will be excluded from the resolver context
     * @param excludeEmbeddedAddonsScan Set to true if embedded addons will be excluded from the resolution context
     * @throws DeployerException if a requirement could not be satisfied
     */
    private void checkRequirements(final IAddonDeployable deployable, final List<IAddonDeployable> excluded,
                                   final boolean excludeEmbeddedAddonsScan) throws DeployerException {
        IAddonMetadata addonMetadata = deployable.getMetadata();
        if (addonMetadata != null) {
            List<String> requirements = addonMetadata.getRequirements();
            List<String> provides = addonMetadata.getProvides();
            if (requirements != null) {
                for (String requirement: requirements) {
                    checkRequirement(requirement, provides, deployable.getArchive(), excluded, excludeEmbeddedAddonsScan);
                }
            }
        } else {
            throw new DeployerException("Could not retrieve metadata of deployable "+ deployable.getShortName());
        }
    }

    /**
     * Check a requirement of an addon
     * @param requirement A requirement of an addon
     * @param provides Capabilities provide by the addon (but not provide by embedded addons)
     * @param addonArchive The {@link IArchive} associated to the addon
     * @param excluded Capabilities provide by the given list of addon will be excluded from the resolver context
     * @param excludeEmbeddedAddonsScan Set to true if embedded addons will be excluded from the resolution context
     * @throws DeployerException
     */
    private void checkRequirement(final String requirement, final List<String> provides, final IArchive addonArchive,
                                  final List<IAddonDeployable> excluded, final boolean excludeEmbeddedAddonsScan) throws DeployerException {
        //check capabilities provide by the addon (but not by embedded addons)
        boolean found = AddonUtil.checkRequirement(requirement, provides);

        if (!found) {

            if (!excludeEmbeddedAddonsScan) {
                //check capabilities provide by embedded addons
                found = AddonUtil.checkRequirement(requirement, getCapabilitiesProvideByEmbeddedAddons(addonArchive, excluded));
            }

            if (!found) {
                //check capabilities provide by deployed addons
                List<String> capabilities = getCapabilities(excluded);
                found = AddonUtil.checkRequirement(requirement, capabilities);

                if (!found) {
                    throw new DeployerException("Missing requirement " + requirement);
                }
            }
        }
    }

    /**
     * @param excluded Capabilities provide by the given list of addon will be excluded from the resolver context
     * @return the list of properties provide provide by deployed addons with an ACTIVE state
     */
    private List<String> getCapabilities(final List<IAddonDeployable> excluded) {
        List<String> capabilities = new ArrayList<String>();
        Collection<IAddonDeployable> addons = this.addons.values();
        if (addons != null) {
            for (IAddonDeployable addon: addons) {

                if (excluded == null || !excluded.contains(addon)) {
                    int state = addon.getState();
                    if (state == IAddonDeployable.ACTIVE) {
                        capabilities.addAll(addon.getCapabilities());
                    }
                }
            }
        }
        return capabilities;
    }

    /*
    * @param addonArchive The {@link IArchive} associated to an addon
    * @param excluded Capabilities provide by the given list of addon will be excluded from the resolver context
    * @return the list of provides properties by embedded addons
    * @throws DeployerException
    */
    private final List<String> getCapabilitiesProvideByEmbeddedAddons(final IArchive addonArchive,
                                                                      final List<IAddonDeployable> excluded) throws DeployerException {
        List<String> provides = new ArrayList<String>();
        List<IArchive> embeddedAddons = getEmbeddedAddons(addonArchive);
        if (embeddedAddons != null) {
            for (IArchive embeddedAddon: embeddedAddons) {
                if (excluded != null && !excluded.contains(embeddedAddon)) {
                    provides.addAll(AddonUtil.getProvidesRequirements(AddonUtil.getAddonMetadataInpustream(embeddedAddon)).getKey());
                    provides.addAll(getCapabilitiesProvideByEmbeddedAddons(embeddedAddon, excluded));
                }
            }
        }
        return provides;
    }

    /**
     * @param addonArchive The {@link IArchive} associated to the deployable
     * @return the list of embedded addons as a list of {@link IArchive}
     */
    public final List<IArchive> getEmbeddedAddons(final IArchive addonArchive) throws DeployerException{
        //get the file associated to the given archive
        URL url;
        try {
            url = addonArchive.getURL();
        } catch (ArchiveException e) {
            throw new DeployerException("Cannot get the url of the archive " + addonArchive.getName(), e);
        }
        File addonFile = URLUtils.urlToFile(url);

        //compute deploy dir
        File deployDir = new File(addonFile, IAddonDeployable.DEPLOY_DIRECTORY);

        //scan deployable in order to find embedded addons
        List<IArchive> embedddedAddons = new ArrayList<IArchive>();
        if (deployDir.exists()) {
            File files[] = deployDir.listFiles();
            if (files != null) {
                for (File file: files) {
                    IArchive archive = ArchiveManager.getInstance().getArchive(file);
                    IDeployable deployable = null;
                    try {
                        deployable = this.deployableHelper.getDeployable(archive);
                    } catch (DeployableHelperException e) {
                        logger.error("Cannot get the deployable of the archive " + archive.getName(), e);
                    }
                    if (supports(deployable)) {
                        embedddedAddons.add(archive);
                    }
                }
            }
        }
        return embedddedAddons;
    }

    /**
     * Deploy all deployables embedded in the addon  and start optional JOnAS service
     * @param unpackedDeployable The unpacked {@link IAddonDeployable}
     */
    private void deployEmbeddedDeployables(final IAddonDeployable unpackedDeployable) throws DeployerException {

        //sort the list of all deployables (persistent and unpersistent)
        List<ISortableDeployable> knownDeployables = new ArrayList<ISortableDeployable>();
        knownDeployables.addAll(unpackedDeployable.getKnownDeployables());

        //list of deployables which couldn't be deployed because of unsupport deployer
        List<IDeployable> deployablesWithUnsupportDeployer = new ArrayList<IDeployable>();

        //get addon metadata
        IAddonMetadata addonMetadata = unpackedDeployable.getMetadata();

        //deploy only deployables which are not deployed yet
        if (!knownDeployables.isEmpty()) {
            Object oldTenantContext = null;
            if (isMultitenantEnabled()) {
                oldTenantContext = this.multitenantService.getTenantContext();
            }
            try {
                if (isMultitenantEnabled()) {
                    this.multitenantService.setTenantIdAndInstanceNameInContext(addonMetadata.getTenantId(), addonMetadata.getInstance());
                }
                //list of deployables which cannot be deployed
                List<IDeploymentReport> deploymentReports  = deploySortableDeployables(knownDeployables);
                int count = 0;
                for (IDeploymentReport deploymentReport: deploymentReports) {
                    if (!deploymentReport.isDeploymentOk()) {
                        if (UnsupportedDeployerException.class.isInstance(deploymentReport.getException())) {
                            deployablesWithUnsupportDeployer.add(deploymentReport.getDeployable());
                        } else {
                            logger.error("The deployable " + deploymentReport.getDeployable().getShortName() +
                                    " could not be deployed.", deploymentReport.getException());
                            count++;
                        }
                    }
                }
                if (count > 0) {
                    throw new DeployerException("The addon " + addonMetadata.getName() + " cannot be deployed");
                }
            } finally {
                if (isMultitenantEnabled()) {
                    this.multitenantService.setTenantContext(oldTenantContext);
                }
            }
        }

        //start JOnAS services
        String service = addonMetadata.getService();
        if (service != null) {
            //if it's a JOnAS service, register the ServiceProperties component
            Dictionary<String, String> dictionary = new Hashtable<String, String>();
            dictionary.put(SERVICE_REGISTRATION_SERVICE_PROPERTY, service);
            this.serviceRegistrations.add(this.bundleContext.registerService(ServiceProperties.class.getName(),
                    addonMetadata.getServiceProperties(), dictionary));

            //Create the service configuration for the given service. It'll start the service
            try {
                this.serviceManager.startService(service, false);
            } catch (Exception e) {
                throw new DeployerException("Cannot create the configuration for the service " + service, e);
            }
        }

        //check if unknown deployables are still unknown
        List<ISortableDeployable> unknownDeployables = unpackedDeployable.getUnknownDeployables();
        List<ISortableDeployable> deployablesToDeploy = new ArrayList<ISortableDeployable>();
        for (ISortableDeployable unknownSortableDeployable: unknownDeployables) {
            IDeployable unknownDeployable = unknownSortableDeployable.getDeployable();
            IDeployable deployable;

            //wait some time in order to fix a synchronisation issue
            boolean ok = false;
            long maxWaitTime = System.currentTimeMillis() + MAX_WAITING_TIME;
            while (!ok && System.currentTimeMillis() < maxWaitTime) {
                try {
                    deployable = this.deployableHelper.getDeployable(unknownDeployable.getArchive());
                    if (!UnknownDeployable.class.isInstance(deployable)) {
                        ok = true;
                    } else {
                        try {
                            Thread.sleep(SLEEPING_TIME);
                        } catch (InterruptedException e) {
                            //do nothing
                        }
                    }
                } catch (DeployableHelperException e) {
                    logger.error("Cannot get a deployable for the archive ''{0}''", unknownDeployable.getArchive(), e);
                }
            }

            try {
                deployable = this.deployableHelper.getDeployable(unknownDeployable.getArchive());
                if (UnknownDeployable.class.isInstance(deployable)) {
                    logger.error("Unknown deployable " + deployable.getShortName() + ". It will not be deployed.");
                } else {
                    //the deployable is no more an unknown deployable

                    //update the deployable embedded in the addon deployable
                    unpackedDeployable.removeDeployable(unknownSortableDeployable);
                    unpackedDeployable.addDeployable(AddonUtil.getSortableDeployable(deployable));
                    //add the deployable to the list of deployables to deploy
                    deployablesToDeploy.add(AddonUtil.getSortableDeployable(deployable));
                }
            } catch (DeployableHelperException e) {
                logger.error("Cannot get a deployable for the archive ''{0}''", unknownDeployable.getArchive(), e);
            }
        }

        //deploy deployables which are no more unknown deployables
        if (!deployablesToDeploy.isEmpty()) {
            Object oldTenantContext = null;
            if (isMultitenantEnabled()) {
                oldTenantContext = this.multitenantService.getTenantContext();
            }
            try {
                if (isMultitenantEnabled()) {
                    this.multitenantService.setTenantIdAndInstanceNameInContext(addonMetadata.getTenantId(), addonMetadata.getInstance());
                }
                //list of deployables which cannot be deployed
                List<IDeploymentReport> deploymentReports  = deploySortableDeployables(deployablesToDeploy);
                int count = 0;
                for (IDeploymentReport deploymentReport: deploymentReports) {
                    if (!deploymentReport.isDeploymentOk()) {
                        if (UnsupportedDeployerException.class.isInstance(deploymentReport.getException())) {
                            deployablesWithUnsupportDeployer.add(deploymentReport.getDeployable());
                        } else {
                            logger.error("The deployable " + deploymentReport.getDeployable().getShortName() +
                                    " could not be deployed.", deploymentReport.getException());
                            count++;
                        }
                    }
                }
                if (count > 0) {
                    throw new DeployerException("The addon " + addonMetadata.getName() + " cannot be deployed");
                }
            } finally {
                if (isMultitenantEnabled()) {
                    this.multitenantService.setTenantContext(oldTenantContext);
                }
            }
        }

        //attempt to deploy deployables with no associated deployer
        deployablesToDeploy = new ArrayList<ISortableDeployable>();
        for (IDeployable deployable: deployablesWithUnsupportDeployer) {
            deployablesToDeploy.add(AddonUtil.getSortableDeployable(deployable));
        }

        //wait some time in order to fix synchronisation issues
        boolean ok = false;
        long maxWaitTime = System.currentTimeMillis() + MAX_WAITING_TIME;
        while (!ok && System.currentTimeMillis() < maxWaitTime) {
            int count = 0;
            List<IDeploymentReport> deploymentReports = deploySortableDeployables(deployablesToDeploy);
            for (IDeploymentReport deploymentReport: deploymentReports) {
                if (!deploymentReport.isDeploymentOk()) {
                    if (!UnsupportedDeployerException.class.isInstance(deploymentReport.getException())) {
                        logger.error("The deployable " + deploymentReport.getDeployable().getShortName() +
                                " could not be deployed.", deploymentReport.getException());
                        ISortableDeployable sortableDeployable = AddonUtil.getSortableDeployable(deploymentReport.getDeployable());
                        if (sortableDeployable != null) {
                            deployablesToDeploy.remove(sortableDeployable);
                        }
                        count++;
                    } else {
                        //we'll try to redeploy deployable with unsupport deployer in the next iteration
                    }
                } else {
                    ISortableDeployable deployed = AddonUtil.getSortableDeployable(deployablesToDeploy, deploymentReport.getDeployable());
                    if (deployed != null) {
                        deployablesToDeploy.remove(deployed);
                    }
                    ok = true;
                }
            }
            if (count > 0) {
                throw new DeployerException("The addon " + addonMetadata.getName() + " cannot be deployed");
            }
            if (!ok) {
                try {
                    Thread.sleep(SLEEPING_TIME);
                } catch (InterruptedException e) {
                    //do nothing
                }
            }
        }

        //manage deployable with an unsupport deployer
        if (deployablesToDeploy.size() > 0) {
            for (ISortableDeployable sortableDeployable: deployablesToDeploy) {
                IDeployable deployable = sortableDeployable.getDeployable();
                logger.error("The deployable " + deployable.getShortName() +  " could not be deployed.",
                        new UnsupportedDeployerException("No deployer was found for the deployable " + deployable.getShortName()));
            }
            throw new DeployerException("The addon " + addonMetadata.getName() + " cannot be deployed");
        }
    }

    /**
     * Undeploy all deployables embedded in the addon. Stop optional service
     * @param unpackedDeployable The unpacked addon to deploy
     */
    private void undeployEmbeddedDeployables(final IAddonDeployable unpackedDeployable)  throws DeployerException {

        //manage unknown deployables
        List<ISortableDeployable> unknownDeployables = unpackedDeployable.getUnknownDeployables();
        for (ISortableDeployable unknownDeployable: unknownDeployables) {
            logger.error("Unknown deployable " + unknownDeployable.getDeployable().getShortName() + " .It will not be undeployed");
        }

        //sort the list (in the reverse order) of all deployables (persistent and unpersistent)
        List<ISortableDeployable> knownDeployables = new ArrayList<ISortableDeployable>();
        knownDeployables.addAll(unpackedDeployable.getKnownDeployables());

        //get addon metadata
        IAddonMetadata addonMetadata = unpackedDeployable.getMetadata();

        int count = 0;

        //undeploy known deployables
        if (!knownDeployables.isEmpty()) {
            Object oldTenantContext = null;
            List<IDeploymentReport> deploymentReports;

            if (isMultitenantEnabled()) {
                oldTenantContext = multitenantService.getTenantContext();
            }
            try {
                if (isMultitenantEnabled()) {
                    multitenantService.setTenantIdAndInstanceNameInContext(addonMetadata.getTenantId(), addonMetadata.getInstance());
                }
                deploymentReports = undeploySortableDeployables(knownDeployables);
            } finally {
                if (isMultitenantEnabled()) {
                    multitenantService.setTenantContext(oldTenantContext);
                }
            }

            for (IDeploymentReport deploymentReport: deploymentReports) {
                if (!deploymentReport.isDeploymentOk()) {
                    logger.error("The deployable " + deploymentReport.getDeployable().getShortName() +
                            " could not be undeployed.", deploymentReport.getException());
                    count++;
                }
            }
        }

        String service = addonMetadata.getService();

        if (service != null) {

            //Delete the service configuration for the given service.
            try {
                this.serviceManager.stopService(service);
            } catch (Exception e) {
                throw new DeployerException("Cannot delete the configuration for the service " + service, e);
            }

            //unregister the ServiceProperties
            ServiceRegistration serviceRegistration = AddonUtil.getServiceRegistration(this.serviceRegistrations,
                    SERVICE_REGISTRATION_SERVICE_PROPERTY, addonMetadata.getService());
            if (serviceRegistration != null) {
                serviceRegistration.unregister();
                this.serviceRegistrations.remove(serviceRegistration);
            }
        }


        //the unpdeloyment operation of the addon fails if there is at least one deployment operation which has failed.
        if (count > 0) {
            throw new DeployerException("The addon " + addonMetadata.getName() + " cannot be undeployed");
        }
    }

    /**
     *  Update the list of deployables (OSGi deployable, EJB3 deployable, EAR Deployable, etc...)
     *  of the unpacked deployable. Could copy JOnAS deployment plans to $JONAS_BASE/repositories/url-internal
     * @param unpackedDeployablePath The path to the unpacked deployable
     * @param unpackedDeployable The unpacked deployable
     * @param copyJOnASPlans Set to true if we need to copy JOnAS plans in $JONAS_BASE/repositories/url-internal
     */
    public void updateDeployables(final String unpackedDeployablePath, final IAddonDeployable unpackedDeployable,
                                  final boolean copyJOnASPlans) {

        final File addonDeployWorkDirectory = new File(AddonUtil.getAddonDeployWorkDirectory(unpackedDeployablePath));

        if (addonDeployWorkDirectory.isDirectory()) {

            for (File file: addonDeployWorkDirectory.listFiles()) {

                //get the deployable from the file
                IDeployable deployable = AddonUtil.getDeployable(this.deployableHelper, file);
                //add embedded deployable to the addon deployable
                unpackedDeployable.addDeployable(AddonUtil.getSortableDeployable(deployable));

                if (copyJOnASPlans && deployable instanceof DeploymentPlanDeployable) {

                    IAddonMetadata addonMetadata = unpackedDeployable.getMetadata();

                    //2 cases:
                    // - persistent deployment plan (if it's a JOnAS deployment plan)
                    // - unpersistent deployment plan (if it's an user deployment plan
                    String service = addonMetadata.getService();

                    //case 1: persistent deployment plan (if it's a JOnAS deployment plan)
                    if (service != null) {

                        //get the JOnAS url-internal directory
                        File urlInternalDirectory = new File(AddonUtil.JONAS_ROOT_URL_INTERNAL_DIRECTORY);
                        if (!urlInternalDirectory.exists()) {
                            urlInternalDirectory.getParentFile().mkdirs();
                        }

                        String implementation = addonMetadata.getImplementation();
                        if (implementation == null) {
                            //it's a default deployment plan
                            //we need to copy this default deployment plan in $JONAS_BASE/repositories/url-internal
                            final String defaultDeploymentPlanName = AddonUtil.getDefaultDeploymentPlan(service);
                            File defaultDeploymentPlan = new File(addonDeployWorkDirectory.getAbsolutePath(),
                                    defaultDeploymentPlanName);

                            if (defaultDeploymentPlan.exists()) {
                                AddonUtil.copyFile(defaultDeploymentPlan,  new File(urlInternalDirectory.getAbsolutePath(),
                                        defaultDeploymentPlanName));
                            }

                        } else {
                            //it's a deployment plan for an implementation of an abstract service
                            //we need to copy the abstract deployment plan and it's implementation in $JONAS_BASE/repositories/url-internal
                            final String jonasAbstractDeploymentPlanName = AddonUtil.getAbstractDeploymentPlan(service);
                            final String jonasDeploymentPlanImplName = AddonUtil.getImplDeploymentPlan(service, implementation);
                            File jonasAbstractDeploymentPlan = new File(addonDeployWorkDirectory.getAbsolutePath(),
                                    jonasAbstractDeploymentPlanName);

                            if (jonasAbstractDeploymentPlan.exists()) {
                                AddonUtil.copyFile(jonasAbstractDeploymentPlan,  new File(urlInternalDirectory.getAbsolutePath(),
                                        jonasAbstractDeploymentPlanName));
                            }

                            File jonasDeploymentPlanImpl = new File(addonDeployWorkDirectory.getAbsolutePath(),
                                    jonasDeploymentPlanImplName);
                            if (jonasDeploymentPlanImpl.exists()) {
                                AddonUtil.copyFile(jonasDeploymentPlanImpl, new File(urlInternalDirectory.getAbsolutePath(),
                                        jonasDeploymentPlanImplName));
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Sort a multi type list of sortables deployables and deploy each element which is not yet deployed
     * @param sortableDeployables The list of sortable deployables to deploy
     * @return the list of {@link IDeploymentReport}
     */
    private List<IDeploymentReport> deploySortableDeployables(final List<ISortableDeployable> sortableDeployables) {
        List<IDeploymentReport> deploymentReports = new ArrayList<IDeploymentReport>();

        //sort deployables
        AddonUtil.sortSortableDeployable(sortableDeployables);

        //get deployables which are not yet deployed
        List<IDeployable<?>> deployables = new ArrayList<IDeployable<?>>();
        for (ISortableDeployable sortableDeployable: sortableDeployables) {
            IDeployable deployable = sortableDeployable.getDeployable();
            try {
                if (!this.deployerManager.isDeployed(deployable)) {
                    deployables.add(sortableDeployable.getDeployable());
                }
            } catch (DeployerException e) {
                this.logger.error("Could not find if the deployable " + deployable.getShortName() + " is already deployed", e);
            } catch (UnsupportedDeployerException e) {
                DeploymentReport deploymentReport = new DeploymentReport();
                deploymentReport.setDeploymentOk(false);
                deploymentReport.setException(e);
                deploymentReport.setDeployable(sortableDeployable.getDeployable());
                deploymentReports.add(deploymentReport);
            }
        }

        //deploy all deployables
        List<IDeploymentReport> reports = this.deployerManager.deploy(deployables);
        if (reports != null) {
            deploymentReports.addAll(reports);
        }
        return deploymentReports;
    }

    /**
     * Sort a multi type list of sortables deployables in the reverse order and undeploy each element
     * @param sortableDeployables The list of sortable deployables to undeploy
     * @return the list of {@link IDeploymentReport}
     */
    private List<IDeploymentReport> undeploySortableDeployables(final List<ISortableDeployable> sortableDeployables) {

        //sort deployables in the reverse order
        AddonUtil.sortSortableDeployable(sortableDeployables);
        Collections.reverse(sortableDeployables);

        //get deployables
        List<IDeployable<?>> deployables = new ArrayList<IDeployable<?>>();
        for (ISortableDeployable sortableDeployable: sortableDeployables) {
            deployables.add(sortableDeployable.getDeployable());
        }

        //undeploy all deployables
        return this.deployerManager.undeploy(deployables);
    }

    /**
     * {@inheritDoc}
     */
    public boolean isAddonDeployedByWorkName(final String unpackName) {
        //if the addon deployer isn't initialize yet, we shouldn't use the addon clean task
        if (!isInitialized()) {
            return true;
        }

        for (IAddonDeployable addonDeployable : this.addons.values()) {
            try {
                File unpackedFile = URLUtils.urlToFile(addonDeployable.getArchive().getURL());
                if (unpackName.equals(unpackedFile.getName())) {
                    return true;
                }
            } catch (ArchiveException e) {
                logger.debug("Cannot retrieve the name of the unpacked addon {0}", unpackName);
            }
        }
        return false;
    }

    /**
     * @return True is the addon deployer is initialized
     */
    private boolean isInitialized() {

        if (this.isInitialized == false) {
            int deployerLogSize = this.deployerLog.getEntries().size();
            this.isInitialized = (deployerLogSize == 0 || deployerLogSize == this.addons.size());
        }

        return this.isInitialized;
    }

    /**
     * Allow to validate an addon Metadata file
     * @param addonMetadataFile The addon metadata file to validate
     * @throws DeployerException {@link DeployerException}
     * @return true if the node has been valide. Otherwise, false.
     */
    private void validate(File addonMetadataFile) throws DeployerException {
        //First create a Validator from the XSD file
        InputStream xsdInputStream = getClass().getResourceAsStream(XSD_RESOURCE);

        if (xsdInputStream != null) {
            SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
            Schema schema = null;
            try {
                schema = factory.newSchema(new StreamSource(xsdInputStream));
            } catch (SAXException e) {
                throw new DeployerException("Cannot create a new Schema from the resource " + XSD_RESOURCE, e);
            }
            Validator validator = schema.newValidator();

            //then get the XML resource
            DocumentBuilderFactory xmlFactory = DocumentBuilderFactory.newInstance();
            xmlFactory.setNamespaceAware(true);
            DocumentBuilder builder = null;
            try {
                builder = xmlFactory.newDocumentBuilder();
            } catch (ParserConfigurationException e) {
                throw new DeployerException("Cannot create a new DocumentBuilder", e);
            }
            Document document = null;
            try {
                document = builder.parse(addonMetadataFile);
            } catch (SAXException e) {
                throw new DeployerException("Cannot parse the file " + addonMetadataFile.getAbsolutePath(), e);
            } catch (IOException e) {
                throw new DeployerException("Cannot parse the file " + addonMetadataFile.getAbsolutePath(), e);
            }

            //finally try to validate the XML file
            try {
                validator.validate(new DOMSource(document));
            } catch (SAXException e) {
                throw new DeployerException("Cannot validate the file " + addonMetadataFile.getAbsolutePath() + " from the XSD " +
                        "resource " + XSD_RESOURCE, e);
            } catch (IOException e) {
                throw new DeployerException("Cannot validate the Node " + addonMetadataFile.getAbsolutePath() + " from the XSD " +
                        "resource " + XSD_RESOURCE, e);
            }

        } else {
            throw new DeployerException("Cannot get the inputstream of the resource " + XSD_RESOURCE);
        }

    }

    /**
     * Whether Multitenant service is enabled
     * @return
     */
    protected boolean isMultitenantEnabled() {
        return (this.multitenantService != null);
    }

    /**
     * Set Multitenant service impl
     * @param multitenantService
     */
    public void setMultitenantService(MultitenantService multitenantService) {
        this.multitenantService = multitenantService;
    }

    /**
     * Unset Multitenant service impl
     */
    public void unsetMultitenantService() {
        this.multitenantService = null;
    }

    /**
     * {@inheritDoc}
     */
    public IAddonDeployable getAddon(final String addonName) {
        String name = null;
        if (this.addons != null && addonName != null) {
            for (IAddonDeployable addon: this.addons.values()) {
                IAddonMetadata metadata = addon.getMetadata();
                if (metadata != null) {
                    name = metadata.getName();
                    if (addonName.equals(name)) {
                        return addon;
                    }
                }
            }
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<IDeploymentReport> doDeploy(final List<IDeployable<IAddonDeployable>> deployables) {
        List<IDeploymentReport> deploymentReports = new ArrayList<IDeploymentReport>();
        if (deployables != null) {

            //first install all addons
            List<IDeployable<IAddonDeployable>> addons = new ArrayList<IDeployable<IAddonDeployable>>();
            for (IDeployable<IAddonDeployable> deployable: deployables) {
                try {
                    addons.add(install(deployable, false));
                } catch (DeployerException e) {
                    DeploymentReport deploymentReport = new DeploymentReport();
                    deploymentReport.setDeployable(deployable);
                    deploymentReport.setDeploymentOk(false);
                    deploymentReport.setException(e);
                    deploymentReports.add(deploymentReport);
                }
            }

            if (!addons.isEmpty()) {

                //find <provides, requirements> property of each deployable
                List<AddonProvidesRequirements> addonProvidesRequirementsList = getAddonProvidesRequirementsList(addons);

                //order the list of deployables according to requirements/provides dependencies
                AddonUtil.sort(addonProvidesRequirementsList);

                //then start the list of installed addon
                for (AddonProvidesRequirements addonProvidesRequirements: addonProvidesRequirementsList) {
                    IAddonDeployable addon = IAddonDeployable.class.cast(addonProvidesRequirements.getDeployable());
                    DeploymentReport deploymentReport = new DeploymentReport();
                    deploymentReport.setDeployable(addon.getOriginalDeployable());
                    try {
                        start(addon.getMetadata().getName());
                        deploymentReport.setDeploymentOk(true);
                    } catch (DeployerException e) {
                        deploymentReport.setDeploymentOk(false);
                        deploymentReport.setException(e);
                    }
                    deploymentReports.add(deploymentReport);
                }
            }
        }
        return deploymentReports;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<IDeploymentReport> doUndeploy(final List<IDeployable<IAddonDeployable>> deployables) {
        List<IDeploymentReport> deploymentReports = new ArrayList<IDeploymentReport>();
        if (deployables != null) {

            //get the list of unpacked deployables
            List<IDeployable<IAddonDeployable>> unpackedDeployables = new ArrayList<IDeployable<IAddonDeployable>>();
            for (IDeployable deployable: deployables) {
                IDeployable unpackedDeployable = deployable.getUnpackedDeployable();
                if (unpackedDeployable == null) {
                    // get Addon URL
                    URL addonURL;
                    try {
                        addonURL = deployable.getArchive().getURL();
                        //get the unpacked deployable
                        if (this.addons.containsKey(addonURL)) {
                            unpackedDeployable = this.addons.get(addonURL);
                        } else {
                            DeploymentReport deploymentReport = new DeploymentReport();
                            deploymentReport.setDeployable(deployable);
                            deploymentReport.setDeploymentOk(false);
                            deploymentReport.setException(new DeployerException("Cannot get the unpacked deployable " + deployable.getShortName()
                                    + " with URL '" + addonURL.getPath() + "'.Deployable " + deployable.getShortName() + " will not be undeployed."));
                            deploymentReports.add(deploymentReport);
                        }
                    } catch (ArchiveException e) {
                        DeploymentReport deploymentReport = new DeploymentReport();
                        deploymentReport.setDeployable(deployable);
                        deploymentReport.setDeploymentOk(false);
                        deploymentReport.setException(new DeployerException("Cannot get the URL on the Addon deployable '" + deployable +
                                ".Deployable " + deployable.getShortName() + " will not be undeployed.", e));
                        deploymentReports.add(deploymentReport);
                    }
                }
                if (unpackedDeployable != null) {
                    if (deployable.getUnpackedDeployable() == null) {
                        deployable.setUnpackedDeployable(unpackedDeployable);
                    }
                    if (unpackedDeployable.getOriginalDeployable() == null) {
                        unpackedDeployable.setOriginalDeployable(deployable);
                    }
                    unpackedDeployables.add(unpackedDeployable);
                }
            }

            //find <provides, requirements> property of each unpacked deployable
            List<AddonProvidesRequirements> addonProvidesRequirementsList = getAddonProvidesRequirementsList(unpackedDeployables);

            //order the list of unpacked deployables
            AddonUtil.sort(addonProvidesRequirementsList);
            Collections.reverse(addonProvidesRequirementsList);

            //undeploy the list of unpacked deployables
            for (AddonProvidesRequirements addon: addonProvidesRequirementsList) {
                IDeployable unpackedDeployable = addon.getDeployable();
                IDeployable deployable = unpackedDeployable.getOriginalDeployable();
                DeploymentReport deploymentReport = new DeploymentReport();
                deploymentReport.setDeployable(deployable);
                try {
                    doUndeploy(deployable);
                    deploymentReport.setDeploymentOk(true);
                } catch (DeployerException e) {
                    deploymentReport.setDeploymentOk(false);
                    deploymentReport.setException(e);
                }
                deploymentReports.add(deploymentReport);
            }
        }
        return deploymentReports;
    }

    /**
     * find <provides, requirements> property for each deployable
     * @param deployables The list of {@link IDeployable}
     * @return the {@link AddonProvidesRequirements}
     */
    private List<AddonProvidesRequirements> getAddonProvidesRequirementsList(final List<IDeployable<IAddonDeployable>> deployables) {
        List<AddonProvidesRequirements> addonProvidesRequirementsList = new ArrayList<AddonProvidesRequirements>();
        if (deployables != null) {
            List<String> provides;
            List<String> requirements;

            //get <provides, requirements> for each deployable
            for (IDeployable deployable: deployables) {
                provides = null;
                requirements = null;

                Map.Entry<List<String>, List<String>> providesRequirements = getProvidesRequirements(deployable);
                if (providesRequirements != null) {
                    provides = providesRequirements.getKey();
                    requirements = providesRequirements.getValue();
                }
                addonProvidesRequirementsList.add(new AddonProvidesRequirements(deployable, provides, requirements));
            }
        }
        return addonProvidesRequirementsList;
    }

    /**
     * @param deployable The {@link IDeployable}
     * @return the list of provides and requirements properties of the addon (including properties of embedded addons)
     */
    private Map.Entry<List<String>, List<String>> getProvidesRequirements(IDeployable<IAddonDeployable> deployable) {
        IArchive archive = deployable.getArchive();

        //get provides and requirements of the addon deployable
        Map.Entry<List<String>, List<String>> entry = null;
        try {
            entry = AddonUtil.getProvidesRequirements(AddonUtil.getAddonMetadataInpustream(archive));
        } catch (DeployerException e) {
            logger.error("Cannot get the couple of <provides, requirements> of the deployable " + deployable.getShortName(), e);
        }
        List<String> provides = new ArrayList<String>();
        List<String> requirements = new ArrayList<String>();
        if (entry != null) {
            if (entry.getKey() != null) {
                provides.addAll(entry.getKey());
            }
            if (entry.getValue() != null) {
                requirements.addAll(entry.getValue());
            }
        }

        //get provides and requirements of embedded addons
        List<IArchive> embeddedArchives = null;
        try {
            embeddedArchives = getEmbeddedAddons(archive);
        } catch (DeployerException e) {
            logger.error("Cannot get the list of embedded deployable of the deployable " + deployable.getShortName(), e);
        }
        if (embeddedArchives != null) {
            for (IArchive embeddedArchive: embeddedArchives) {
                Map.Entry<List<String>, List<String>> providesRequirements = getProvidesRequirements(new AddonDeployableImpl(embeddedArchive));
                if (providesRequirements != null) {
                    if (providesRequirements.getKey() != null) {
                        provides.addAll(providesRequirements.getKey());
                    }
                    if (providesRequirements.getValue() != null) {
                        requirements.addAll(providesRequirements.getValue());
                    }
                }
            }
        }

        return new AbstractMap.SimpleEntry<List<String>, List<String>>(provides, requirements);
    }

    //TODO check if there is some troubles in work directories
    //TODO some troubles when the user remove the work directory (or files of the work directory) when the server is running
}