/**
 * 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: AddonDeployerImpl.java 21828 2011-10-23 22:48:27Z cazauxj $
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.addon.deploy.impl.deployer;

import org.osgi.framework.BundleContext;
import org.ow2.jonas.Version;
import org.ow2.jonas.addon.deploy.api.deployable.IAddonDeployable;
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.util.IAddonLogEntry;
import org.ow2.jonas.addon.deploy.impl.deployable.AddonDeployableImpl;
import org.ow2.jonas.addon.deploy.impl.util.AddonDeployerLog;
import org.ow2.jonas.addon.deploy.impl.util.AddonUtil;
import org.ow2.jonas.configuration.ConfigurationManager;
import org.ow2.jonas.lib.work.DeployerLogException;
import org.ow2.jonas.properties.ServerProperties;
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.impl.deployer.AbsDeployer;
import org.ow2.util.ee.deploy.impl.helper.UnpackDeployableHelper;
import org.ow2.util.file.FileUtils;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;
import org.ow2.util.plan.deploy.deployable.api.DeploymentPlanDeployable;
import org.ow2.util.url.URLUtils;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
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 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;

    /**
     * DeployerLog
     */
    private AddonDeployerLog deployerLog;

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

    /**
     * True if the server is starting with the clean option. Otherwise, false.
     */
    private boolean isCleanup = false;

    /*
     * The list of deployed addon names
     */
    private List<String> deployedAddonNames;

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

    /**
     * Configuration deployer
     */
    private ConfDeployerImpl confDeployer;

    /**
     * The configuration manager to set
     * Use to update the server properties
     */
    private ConfigurationManager configurationManager;

    /**
     * Default constructor
     * @param serverProperties Server properties
     */
    public AddonDeployerImpl(final ServerProperties serverProperties, final IDeployableHelper deployableHelper,
                             final AddonDeployerLog deployerLog, final IDeployerManager deployerManager,
                             final BundleContext bundleContext, final ConfigurationManager configurationManager) {
        this.serverProperties = serverProperties;
        this.deployableHelper = deployableHelper;
        this.deployerLog = deployerLog;
        this.deployerManager = deployerManager;
        this.addons = new HashMap<URL, IAddonDeployable>();
        this.isCleanup = Boolean.getBoolean("jonas.cache.clean");
        this.deployedAddonNames = new ArrayList<String>();
        this.bundleContext = bundleContext;
        this.configurationManager = configurationManager;
        this.confDeployer = new ConfDeployerImpl(this.bundleContext);

        //If some addons are removed from the deploy directories, the JOnAS configuration is retrieved
        checkLogs();
    }

    /**
     * If some addons are removed from the deploy directories, the JOnAS configuration is
     * retrieved
     */
    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>();

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

        for (IAddonLogEntry logEntry: logEntries) {
            if (!logEntry.getOriginal().exists()) {
                //an Addon has been remove 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(), logEntry.getCopy().getAbsolutePath());
                logEntriesToRemove.add(logEntry);
            } else {

                if (!logEntry.getCopy().exists()) {
                    //TODO JOnAS configuration is OK...but we need to unpack the addon in the work directory

                    //TODO: some troubles if the user remove some files from $JONAS_BASE/conf or from $JONAS_BASE/deploy
                }
            }
        }

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

        //retrieve JOnAS configuration
        retrieveJOnASConfiguration(addonsToUndeploy, true);
    }

    /**
     * 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 {

        File originalFile = null;
        try {
            originalFile = URLUtils.urlToFile(deployable.getArchive().getURL());
        } catch (ArchiveException e) {
            logger.error("Cannot get the deployable " + deployable.getShortName(), e);
        }

        boolean isAlreadyDeployed = (originalFile.exists() && this.deployerLog.getEntry(originalFile) != null);

        IAddonDeployable unpackedDeployable = null;

        //if it's already deployed, don't unpack it!
        if (isAlreadyDeployed) {
            // Get the unpacked deployable
            IArchive archive = ArchiveManager.getInstance().getArchive(this.deployerLog.getEntry(originalFile).getCopy());
            try {
                unpackedDeployable = IAddonDeployable.class.cast(this.deployableHelper.getDeployable(archive));
            } catch (DeployableHelperException e) {
                logger.error("Cannot get the deployable " + originalFile, e);
            }

             // display as a debug
            logger.debug("Deploying ''{0}''", unpackedDeployable);

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

            // display this line when Addon has been unpacked
            logger.info("Deploying ''{0}''", unpackedDeployable);
        }

        // 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
        AddonMetaData addonMetaData = AddonUtil.getAddonMetadata(AddonUtil.getAddonMetadataFile(addonArchive),
                unpackedFile.getAbsolutePath());

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

        //Check that everythings is OK
        checkAddonMetadata(addonMetaData);

        //Update the list of deployable (OSGi deployable, EJB3 deployable, EAR Deployable, etc...) of the unpacked deployable
        List<String> osgiDeployables = updateDeployables(unpackedFile.getAbsolutePath(), unpackedDeployable, addonMetaData);

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

        //if the addon is log
        if (this.deployerLog.getEntry(URLUtils.urlToFile(originalURL)) != null) {

            deploy(unpackedDeployable, addonMetaData, true);

            //TODO check if there is some troubles in work directories

            logger.debug("''{0}'' addon is already deployed", deployable.getShortName());

        } else {

            //deploy the addon
            deploy(unpackedDeployable, addonMetaData, false);

            //TODO: log the addon add the end (after deploy call). It will prevent some troubles when a delegate deploy operation
            //TODO failed (liked the OSGi deployer)
            //TODO: WARNING: currently to generate jonas.properties we are looking in in the addon log to find all properties fragments
            // Log the addon
            if (this.deployerLog != null) {
                try {
                    this.deployerLog.addEntry(addonMetaData.getName(), originalFile, unpackedFile, osgiDeployables);
                } catch (DeployerLogException e) {
                    logger.info("Cannot added a log entry to the addon logger");
                }
            }

            //add the name of the addon to the list of deployed addon names
            this.deployedAddonNames.add(addonMetaData.getName());

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

    /**
     * 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 Addon URL
        URL addonURL;
        try {
            addonURL = deployable.getArchive().getURL();
        } catch (ArchiveException e) {
            throw new DeployerException("Cannot get the URL on the Addon deployable '" + deployable + "'.", e);
        }

        // Check if this archive has been unpacked ?
        IAddonDeployable unpackedDeployable = null;
        if (addons.containsKey(addonURL)) {
            unpackedDeployable = addons.get(addonURL);
        } else {
            throw new DeployerException("Cannot get the URL of the unpacked Addon deployable '" + 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);
        }

        //get JOnAS Addon metadata
        AddonMetaData addonMetaData = AddonUtil.getAddonMetadata(AddonUtil.getAddonMetadataFile(unpackedDeployable.getArchive()),
                unpackedDeployableFile.getAbsolutePath());

        //remove the logEntry
        File originalFile = URLUtils.urlToFile(addonURL);
        IAddonLogEntry logEntry =  deployerLog.getEntry(originalFile);
        if (logEntry != null) {
            try {
                deployerLog.removeEntry(logEntry);
            } catch (DeployerLogException e) {
                logger.error("Cannot remove the LogEntry");
            }
        }

        //undeploy the addon
        undeploy(unpackedDeployable, addonMetaData);

        //remove the name of the addon to the list of deployed addon name
        this.deployedAddonNames.remove(addonMetaData.getName());

        // remove link original deployable -> unpacked deployable
        this.addons.remove(addonURL);

        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);
    }

    /**
     * Check that metadata file  is OK
     * @param addonMetaData The metadata of the addon to deploy
     * @throws DeployerException
     */
    private void checkAddonMetadata(final AddonMetaData addonMetaData) throws DeployerException {

        //check the name of the addon. If an addon with the same name have been previously deployed, throw a DeployerException
        if (this.deployedAddonNames.contains(addonMetaData.getName())) {
            throw new DeployerException("Cannot deploy " + addonMetaData.getName() + " addon. 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 deploy " + addonMetaData.getName() + " addon. 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 deploy " + addonMetaData.getName() + " addon. JVM version " +
                    jvmVersion + " doesn't match ");
        }
    }

    /**
     * Deploy the addon
     * @param unpackedDeployable The unpacked addon to deploy
     * @param addonMetaData Metadata of the addon to unpacked
     */
    private void deploy(final IAddonDeployable unpackedDeployable, final AddonMetaData addonMetaData,
                        final boolean isAlreadyDeployed) {

        if (isAlreadyDeployed) {
        //case 1: the addon is already deployed. We need to redeploy unpersistent deployables

            //sort the list of unpersistent deployables
            List<ISortableDeployable> unpersistentDeployables = unpackedDeployable.getDeployables();

            //deploy unpersistent deployables
            if (!unpersistentDeployables.isEmpty()) {
                deploySortableDeployables(unpersistentDeployables);
            }

        } else {
        //case 2: the addon is not deployed. We need to deploy all deployables (persistent & unpersistent)

            //deploy the configuration of the Addon
            this.confDeployer.deploy(unpackedDeployable, addonMetaData, this.serverProperties, this.deployerLog,
                    addonMetaData.getResource());

            //TODO: BinDeployerImpl deploy

            //TODO: AntDeployer deploy

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

            //TODO:manage case where the Maven2ResourceFetcher.resolveArtifact failed because of some troubles with a network connexion
            //TODO: have you this problem? I can't repeat this trouble now.
            //deploy deployables
            if (!allDeployables.isEmpty()) {
                deploySortableDeployables(allDeployables);
            }

            if (this.configurationManager != null) {

                //update the server properties
                this.configurationManager.updateServerProperties();

                if (addonMetaData.getAutostart()) {

                    //Create the service configuration for the given service. It'll start the service
                    String service = addonMetaData.getService();
                    try {
                        this.configurationManager.updateServiceConfiguration(service);
                    } catch (Exception e) {
                        logger.error("Cannot create the configuration for the service " + service, e);
                    }
                }
            }

        }
    }

    /**
     * Undeploy the addon
     * @param unpackedDeployable The unpacked addon to deploy
     * @param addonMetaData Metadata of the addon to unpacked
     */
    private void undeploy(final IAddonDeployable unpackedDeployable, final AddonMetaData addonMetaData) {

        //undeploy the configuration of the Addon
        this.confDeployer.undeploy(unpackedDeployable.getArchive(), addonMetaData, this.serverProperties, this.deployerLog);

        String service = addonMetaData.getService();

        if (this.configurationManager != null) {

            if (addonMetaData.getAutostart()) {

                //update the server properties
                this.configurationManager.updateServerProperties();

                //Delete the service configuration for the given service.
                try {
                    this.configurationManager.deleteServiceConfiguration(service);
                } catch (Exception e) {
                    logger.error("Cannot delete the configuration for the service " + service, e);
                }
            }
        }

        //TODO: BinDeployerImpl undeploy

        //TODO: AntDeployer undeploy

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

        //deploy deployables
        if (!allDeployables.isEmpty()) {
            undeploySortableDeployables(allDeployables);
        }
    }

    /**
     * Retrieve JOnAS configuration
     * @param addonsToUndeploy List of name of addon to undeploy
     * @param isJOnASStarting True if the server is starting. Otherwise, false.
     */
    public void retrieveJOnASConfiguration(final Map<String, String> addonsToUndeploy, final boolean isJOnASStarting) {

        //retrieve JOnAS configuration for configuration files
        this.confDeployer.retrieveJOnASConfiguration(this.serverProperties, this.deployerLog, addonsToUndeploy, isJOnASStarting);

        if (isJOnASStarting) {
            //update services configurations and the server properties
            for (Map.Entry<String, String> entry : addonsToUndeploy.entrySet()) {

                // Get the unpacked deployable
                IArchive archive = ArchiveManager.getInstance().getArchive(new File(entry.getValue()));
                IAddonDeployable unpackedDeployable = null;
                try {
                    unpackedDeployable = IAddonDeployable.class.cast(this.deployableHelper.getDeployable(archive));
                } catch (DeployableHelperException e) {
                    logger.error("Cannot get the deployable " + entry.getValue(), e);
                }

                File unpackedDeployableFile = null;
                try {
                    unpackedDeployableFile = URLUtils.urlToFile( unpackedDeployable.getArchive().getURL());
                } catch (ArchiveException e) {
                    logger.error("Cannot get the URL on the unpacked deployable '" + unpackedDeployable + "'.", e);
                }

                //get JOnAS Addon metadata
                AddonMetaData addonMetaData = AddonUtil.getAddonMetadata(AddonUtil.getAddonMetadataFile(unpackedDeployable.getArchive()),
                        unpackedDeployableFile.getAbsolutePath());

                //stop service
                String service = addonMetaData.getService();

                if (addonMetaData.getAutostart()) {

                    //update the server properties
                    this.configurationManager.updateServerProperties();

                    //Delete the service configuration for the given service.
                    try {
                        this.configurationManager.deleteServiceConfiguration(service);
                    } catch (Exception e) {
                        logger.error("Cannot delete the configuration for the service " + service, e);
                    }
                }
            }
        }

        //TODO: retrieve JOnAS configuration for binaries

        //TODO: retrieve JOnAS configuration for ANT tasks

        //uninstall bundles of undeployed addon
        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);
            List<ISortableDeployable> sortableDeployables = unpackedDeployable.getDeployables();

            //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
                undeploySortableDeployables(persistentSortableDeployable);

            }
        }

        //if the server is started with the clean option, we need to reinstall bundles in the felix cache
        if (this.isCleanup) {

            for (IAddonLogEntry addonLogEntry: this.deployerLog.getEntries()) {

                List<String> osgiDeployables = addonLogEntry.getOSGiDeployables();

                if (osgiDeployables != null) {
                    List<ISortableDeployable> sortableDeployables = new ArrayList<ISortableDeployable>();

                    for (String osgiDeployable: osgiDeployables) {
                        IDeployable deployable = AddonUtil.getDeployable(this.deployableHelper, new File(osgiDeployable));
                        sortableDeployables.add(AddonUtil.getSortableDeployable(deployable));
                    }

                    deploySortableDeployables(sortableDeployables);
                }
            }
        }
    }

    /**
     *  Update the list of deployables (OSGi deployable, EJB3 deployable, EAR Deployable, etc...)
     *  of the unpacked deployable & copy JOnAS deployment plan to $JONAS_BASE/repositories/url-internal
     * @param unpackedDeployablePath The path to the unpacked deployable
     * @param unpackedDeployable The unpacked deployable
     * @param addonMetaData Metadata of the addon
     * @return the list of OSGi deployable
     */
    public List<String> updateDeployables(final String unpackedDeployablePath, final IAddonDeployable unpackedDeployable,
                                          final AddonMetaData addonMetaData) {

        //we need to store the list of OSGi deployable for the JOnAS clean startup option
        List<String> osgiDeployables = new ArrayList<String>();

        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);
                //get the sortable deployable from the deployable
                ISortableDeployable sortableDeployable = AddonUtil.getSortableDeployable(deployable);

                if (deployable instanceof OSGiDeployable) {
                    unpackedDeployable.addDeployable(sortableDeployable);
                    osgiDeployables.add(file.getAbsolutePath());
                } else if (deployable instanceof DeploymentPlanDeployable) {

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

                        //TODO why not looking in $JONAS_BASE/repositories/url-internal?
                        //TODO copy of deployment plan in $JONAS_BASE/repositories/url-internal instead of $JONAS_ROOT/repositories/url-internal
                        //TODO Need to update the JOnAS kernel (see bootstrap/repositories and bootstrap/deploymentPlan)

                        unpackedDeployable.addDeployable(sortableDeployable);

                         //TODO maybe we sould remove this line (when new configuration files will be added to the JOnAS classpath):
                         //osgiDeployables.add(file.getAbsolutePath());
                        //TODO: the above line is commented. Right now JOnAS deployment plan are unpersistent

                    } else {
                        //case 2: unpersistent deployment plan (if it's an user deployment plan)
                        unpackedDeployable.addDeployable(sortableDeployable);
                    }
                } else if (!(deployable instanceof UnknownDeployable)) {
                    //cases: EARDeployable,EJB3Deployable, EJB21Deployable, WARDeployable, RARDeployable, FileDeployable
                    unpackedDeployable.addDeployable(sortableDeployable);
                }
            }
        }

        return osgiDeployables;
    }


    /**
     *  Simply Update the list of deployables (OSGi deployable, EJB3 deployable, EAR Deployable, etc...)
     *  of the unpacked deployable
     * @param unpackedDeployablePath The path to the unpacked deployable
     * @param unpackedDeployable The unpacked deployable
     */
    public void updateDeployables(final String unpackedDeployablePath, final IAddonDeployable unpackedDeployable) {

        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);
                //get the sortable deployable from the deployable
                ISortableDeployable sortableDeployable = AddonUtil.getSortableDeployable(deployable);

                if (!(deployable instanceof UnknownDeployable)) {
                    //cases: EARDeployable,EJB3Deployable, EJB21Deployable, WARDeployable, RARDeployable, FileDeployable
                    unpackedDeployable.addDeployable(sortableDeployable);
                }
            }
        }
    }

    /**
     * Deploy a deployable
     * @param deployable The deployable to deploy
     */
    private void deployADeployable(final IDeployable deployable) {
        try {
            if (!this.deployerManager.isDeployed(deployable)) {
                this.deployerManager.deploy(deployable);
            }
        } catch (DeployerException e) {
            logger.error("Cannot deploy the deployable " + deployable.getArchive().getName() + ".", e);
        } catch (UnsupportedDeployerException e) {
            logger.error("Cannot deploy the deployable " + deployable.getArchive().getName() + ".", e);
        }
    }

    /**
     * Undeploy a deployable
     * @param deployable The deployable to undeploy
     */
    private void undeployADeployable(final IDeployable deployable) {
        try {
            this.deployerManager.undeploy(deployable);
        } catch (DeployerException e) {
            logger.error("Cannot undeploy the deployable " + deployable.getArchive().getName() + ".", e);
        } catch (UnsupportedDeployerException e) {
            logger.error("Cannot undeploy the deployable " + deployable.getArchive().getName() + ".", e);
        }
    }

    /**
     * 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
     */
    private void deploySortableDeployables(final List<ISortableDeployable> sortableDeployables) {

        //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) {
                this.logger.error("Could not find if the deployable " + deployable.getShortName() + " is already deployed", e);
            }
        }

        //deploy all deployables
        try {
            this.deployerManager.deploy(deployables);
        } catch (DeployerException e) {
            for (IDeployable deployable: deployables) {
                logger.error("Cannot deploy the deployable " + deployable.getArchive().getName() + ".", e);
            }
        } catch (UnsupportedDeployerException e) {
            for (IDeployable deployable: deployables) {
                logger.error("Cannot deploy the deployable " + deployable.getArchive().getName() + ".", e);
            }
        }
    }

    /**
     * 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
     */
    private void 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
        try {
            this.deployerManager.undeploy(deployables);
        } catch (DeployerException e) {
            for (IDeployable deployable: deployables) {
                logger.error("Cannot deploy the deployable " + deployable.getArchive().getName() + ".", e);
            }
        } catch (UnsupportedDeployerException e) {
            for (IDeployable deployable: deployables) {
                logger.error("Cannot deploy the deployable " + deployable.getArchive().getName() + ".", e);
            }
        }
    }

    //TODO overidde the doDeploy method with a list of IDeployable as parameters, in order to fix errors dependencies between addons
}
