/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007-2008 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.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.server.Embedded;
import org.ow2.jonas.lib.bootstrap.JProp;
import org.ow2.jonas.lib.util.Env;
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.api.deployer.IDeployer;
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 implements IDeployer {

    /**
     * Logger.
     */
    private Log logger = LogFactory.getLog(EasyBeansDeployer.class);
    /**
     * Link to the Embedded instance of EasyBeans.
     */
    private Embedded easybeansServer = null;

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

    /**
     * EJB3 Work directory.
     */
    private static final String WORK_EJB3S_DIR = JProp.getWorkDir() + File.separator + "ejb3s";

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

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

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

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

        // Deploy the EJB3 Deployable
        if (deployable instanceof EJB3Deployable) {
            EJB3Deployable ejb3Deployable = (EJB3Deployable) deployable;

            // Unpack EJB3 Deployable before deploying it on Windows platform to avoid file lock
            if (Env.isOsWindows()) {
                File folder = new File(WORK_EJB3S_DIR, getServerProperties().getServerName());
                folder.mkdirs();
                try {
                    ejb3Deployable = UnpackDeployableHelper.unpack((EJB3Deployable) deployable, folder,
                            FileUtils.lastModifiedFileName(URLUtils.urlToFile(deployable.getArchive().getURL())));
                } 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);
                }
            }

            deployEJB3(ejb3Deployable);
        }
    }

    /**
     * Deploy the given deployable.
     * @param ejb3Deployable the EJB3 deployable.
     * @throws DeployerException if the EJB3 is not deployed.
     */
    protected void deployEJB3(final EJB3Deployable ejb3Deployable) throws DeployerException {
        // Set the original deployable
        EJB3Deployable originalDeployable = ejb3Deployable.getOriginalDeployable();
        if (originalDeployable == null) {
            originalDeployable = ejb3Deployable;
        }

        logger.info("Deploying ''{0}''...", originalDeployable);
        EZBContainer container = easybeansServer.createContainer(ejb3Deployable.getArchive());

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

        if (prefix != null) {
            container.getConfiguration().setNamingStrategy(
                    new PrefixedNamingStrategy(prefix, container.getConfiguration().getNamingStrategy()));
        }

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

        // Keep the link original deployable -> unpacked deployable for undeployment
        try {
            URL initialURL = originalDeployable.getArchive().getURL();
            ejb3s.put(initialURL, ejb3Deployable);
        } catch (ArchiveException e) {
            throw new DeployerException("Cannot get  the url of the initial deployable for the EJB3 Module '"
                    + originalDeployable + "'.", e);
        }

        if (prefix != null) {
            versioningService.createJNDIBindingMBeans(originalDeployable.getModuleName(), versionID, prefix);
        }

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

    /**
     * Undeploy the given EJB3.
     * @param ejb3Deployable the deployable to remove.
     * @throws DeployerException if the EJB3 is not deployed.
     */
    protected void undeployEJB3(final EJB3Deployable ejb3Deployable) throws DeployerException {
        // Set the original deployable
        EJB3Deployable originalDeployable = ejb3Deployable.getOriginalDeployable();
        if (originalDeployable == null) {
            originalDeployable = ejb3Deployable;
        }

        // Get EJB3 URL
        URL ejb3URL = null;
        try {
            ejb3URL = originalDeployable.getArchive().getURL();
        } catch (ArchiveException e) {
            throw new DeployerException("Cannot get the URL on the EJB3 deployable '" + originalDeployable + "'.", e);
        }

        IArchive archive = null;

        // Try to retrieve the unpacked deployable
        EJB3Deployable unpackedDeployable = ejb3Deployable.getUnpackedDeployable();
        if (unpackedDeployable != null) {
            archive = unpackedDeployable.getArchive();
        } else if (ejb3s.containsKey(ejb3URL)) {
            // No unpacked deployable found, look for in the map of deployed EJB3
            archive = ejb3s.get(ejb3URL).getArchive();
        } else {
            archive = ejb3Deployable.getArchive();
        }

        logger.info("Undeploying ''{0}''...", originalDeployable);
        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 '" + originalDeployable
                    + "' as this container is not deployed");
        } else {
            ejb3s.remove(ejb3URL);
            logger
                    .debug(
                            "Found a matching container ''{0}'' for the archive ''{1}''",
                            foundContainer, originalDeployable.getArchive());
        }

        // Stop the container
        try {
            foundContainer.stop();
        } catch (Exception e) {
            throw new DeployerException("Cannot undeploy the deployable '" + originalDeployable + "'", e);
        }
        if (versioningService != null && versioningService.isVersioningEnabled()) {
            versioningService.garbageCollectJNDIBindingMBeans();
        }
        logger.info("''{0}'' EJB3 Deployable is now undeployed", originalDeployable);
    }

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

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

        // Deploy the EJB3 Deployable
        if (deployable instanceof EJB3Deployable) {
            undeployEJB3((EJB3Deployable) deployable);
        }
    }

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

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

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