/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007-2009 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: EasyBeansDeployer.java 20374 2010-09-27 15:00:15Z sauthieg $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.ejb.easybeans;

import java.io.File;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import org.ow2.easybeans.api.EZBContainer;
import org.ow2.easybeans.api.EZBContainerException;
import org.ow2.easybeans.api.naming.EZBNamingStrategy;
import org.ow2.easybeans.loader.EasyBeansClassLoader;
import org.ow2.easybeans.server.Embedded;
import org.ow2.jonas.lib.loader.FilteringClassLoader;
import org.ow2.jonas.lib.work.DeployerLog;
import org.ow2.jonas.properties.ServerProperties;
import org.ow2.jonas.versioning.VersioningService;
import org.ow2.util.archive.api.ArchiveException;
import org.ow2.util.archive.api.IArchive;
import org.ow2.util.ee.deploy.api.deployable.EJB3Deployable;
import org.ow2.util.ee.deploy.api.deployable.IDeployable;
import org.ow2.util.ee.deploy.api.deployer.DeployerException;
import org.ow2.util.ee.deploy.impl.deployer.AbsDeployer;
import org.ow2.util.ee.deploy.impl.helper.UnpackDeployableHelper;
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.url.URLUtils;

/**
 * This deployer will deploy EJB3 on EasyBeans.
 * @author Florent BENOIT Contributors: S. Ali Tokmen (versioning)
 */
public class EasyBeansDeployer extends AbsDeployer {

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

    /**
     * Link to the Embedded instance of EasyBeans.
     */
    private Embedded easybeansServer = null;

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

    /**
     * List of deployed ejb3.
     */
    private Map<URL, EJB3Deployable> ejb3s = null;

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

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

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

    /**
     * EJB3 work directory.
     */
    private String workDirectory = null;

    /**
     * Build a new instance of the EasyBeans deployer.
     */
    public EasyBeansDeployer() {
        this.ejb3s = new HashMap<URL, EJB3Deployable>();
    }



    /**
     * Deploy the given deployable.
     * @param deployable the EJB3 deployable.
     * @throws DeployerException if the EJB3 is not deployed.
     */
    @Override
    public void doDeploy(final IDeployable<?> deployable) throws DeployerException {
        EJB3Deployable unpackedDeployable = null;

        URL initialURL = null;
        File initialFile = null;

        // Unpack EJB3 Deployable before deploying it to avoid file lock (specially on Windows platform)
        File folder = new File(workDirectory, getServerProperties().getServerName());
        folder.mkdirs();
        try {
            initialURL = deployable.getArchive().getURL();
            initialFile = URLUtils.urlToFile(initialURL);
            unpackedDeployable = UnpackDeployableHelper.unpack(EJB3Deployable.class.cast(deployable),
                                                               folder,
                                                               FileUtils.lastModifiedFileName(initialFile));
        } catch (ArchiveException e) {
            throw new DeployerException("Cannot get archive while deploying EJB3Deployable", e);
        } catch (FileUtilsException e) {
            throw new DeployerException("Cannot get file last modified date while deploying EJB3Deployable", e);
        }

        try {
            // The file is unpacked, so log it
            if (deployerLog != null) {
                File unpackedFile = URLUtils.urlToFile(unpackedDeployable.getArchive().getURL());
                deployerLog.addEntry(initialFile, unpackedFile);
            }
        } catch (Exception e) {
            throw new DeployerException("Cannot get  the url of the initial deployable for the EJB3 Module '" + deployable
                    + "'.", e);
        }

        logger.info("Deploying ''{0}''...", deployable.getShortName());
        EZBContainer container = easybeansServer.createContainer(unpackedDeployable);

        String prefix = null;
        if (versioningService != null && versioningService.isVersioningEnabled()) {
            prefix = versioningService.getPrefix(deployable);
        }

        if (prefix != null) {
            EZBNamingStrategy strategy = new PrefixedNamingStrategy(prefix,
                                                                    container.getConfiguration().getNamingStrategy());
            container.getConfiguration().setNamingStrategy(strategy);
        }
        URL[] arrayURLs = {getURL(unpackedDeployable)};

        // Filtering classloader
        FilteringClassLoader filteringClassLoader = createFilteringClassLoader(deployable);

        // Child of the appClassLoader
        ClassLoader ejb3ClassLoader = new EasyBeansClassLoader(arrayURLs, filteringClassLoader);

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

        try {
            container.start();
        } catch (EZBContainerException e) {
            easybeansServer.removeContainer(container);
            throw new DeployerException("Cannot deploy the given EJB '" + deployable + "'.", e);
        }

        // Keep the link original deployable -> unpacked deployable for undeployment
        ejb3s.put(initialURL, unpackedDeployable);

        if (prefix != null) {
            versioningService.createJNDIBindingMBeans(deployable);
        }

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

    /**
     * Creates a {@code FilteringClassLoader} for this EjbJar.
     * @param deployable the EjbJar {@code Deployable} File
     * @return a Filtering ClassLoader configured from {@code META-INF/classloader-filtering.xml} (if any).
     */
    private FilteringClassLoader createFilteringClassLoader(final IDeployable<?> deployable) {
        FilteringClassLoader filteringClassLoader = new FilteringClassLoader(appsClassLoader);
        // Add any module filtering definition we may find
        IArchive archive = deployable.getArchive();
        try {
            String name = "META-INF/" + FilteringClassLoader.CLASSLOADER_FILTERING_FILE;
            URL resource = archive.getResource(name);
            if (resource != null) {
                filteringClassLoader.setDefinitionUrl(resource);
            }
        } catch (ArchiveException ae) {
            // Can be safely ignored
            logger.debug("Cannot get classloader-filtering.xml file from the EJB3 archive {0}.", archive, ae);
        } finally {
            archive.close();
        }

        filteringClassLoader.start();
        return filteringClassLoader;
    }

    /**
     * Undeploy the given EJB3.
     * @param deployable the deployable to remove.
     * @throws DeployerException if the EJB3 is not deployed.
     */
    @Override
    public void doUndeploy(final IDeployable<?> deployable) throws DeployerException {
        // Get initialURL EJB3 URL
        URL initialURL = null;
        try {
            initialURL = deployable.getArchive().getURL();
        } catch (ArchiveException e) {
            throw new DeployerException("Cannot get the URL of the EJB3 deployable '" + deployable + "'.", e);
        }

        IArchive archive = null;

        // Try to retrieve the unpacked deployable
        if (ejb3s.containsKey(initialURL)) {
            // No unpacked deployable found, look for in the map of deployed EJB3
            archive = ejb3s.get(initialURL).getArchive();
        } else {
            throw new DeployerException("Cannot get the URL of the unpacked EJB3 deployable '" + deployable + "'.");
        }

        logger.info("Undeploying ''{0}''...", deployable.getShortName());
        Map<String, EZBContainer> containers = easybeansServer.getContainers();
        EZBContainer foundContainer = null;

        // Search a matching archive
        for (EZBContainer container : containers.values()) {
            IArchive containerArchive = container.getArchive();
            if (archive.equals(containerArchive)) {
                foundContainer = container;
                break;
            }
        }

        // Not found
        if (foundContainer == null) {
            throw new DeployerException("Cannot undeploy the deployable '" + deployable + "' as this container is not deployed");
        } else {
            ejb3s.remove(initialURL);
            logger.debug("Found a matching container ''{0}'' for the archive ''{1}''", foundContainer, deployable.getArchive());
        }

        // Stop the container
        try {
            foundContainer.stop();
            easybeansServer.removeContainer(foundContainer);
        } catch (Exception e) {
            throw new DeployerException("Cannot undeploy the deployable '" + deployable + "'", e);
        }
        if (versioningService != null && versioningService.isVersioningEnabled()) {
            versioningService.garbageCollectJNDIBindingMBeans();
        }
        logger.info("''{0}'' EJB3 Deployable 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 EJB3Deployable.class.isInstance(deployable);
    }

    /**
     * Returns a Map containing all deployed EJB3s.
     * @return a Map containing all deployed EJB3s.
     */
    public Map<URL, EJB3Deployable> getEJB3s() {
        return ejb3s;
    }

    /**
     * Sets the EasyBeans embedded instance.
     * @param easybeansServer the EasyBeans instance.
     */
    public void setEmbedded(final Embedded easybeansServer) {
        this.easybeansServer = easybeansServer;
    }

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

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

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

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

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

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

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

    /**
     * Sets the working directory for EJB3s.
     * @param workDirectory the given directory
     */
    public void setWorkDirectory(final String workDirectory) {
        this.workDirectory = workDirectory;
    }

}
