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

package org.ow2.jonas.ejb.easybeans;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
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.deployment.EasyBeansDeployableInfo;
import org.ow2.easybeans.deployment.api.EZBDeployableInfo;
import org.ow2.easybeans.loader.EasyBeansClassLoader;
import org.ow2.easybeans.resolver.api.EZBApplicationJNDIResolver;
import org.ow2.easybeans.resolver.api.EZBContainerJNDIResolver;
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<EJB3Deployable> {

    /**
     * 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<EJB3Deployable> deployable) throws DeployerException {
        EJB3Deployable unpackedDeployable = null;

        URL initialURL = null;
        File initialFile = null;
        
        
        EZBDeployableInfo deployableInfo = (EZBDeployableInfo) deployable.getExtension(EasyBeansDeployableInfo.class);
        boolean unpack = true;
        ClassLoader ejb3ClassLoader = null;
        
        EZBApplicationJNDIResolver applicationJNDIResolver = null;
        if (deployableInfo != null) {
            if (deployableInfo.hasToBeUnpacked() != null) {
                unpack = deployableInfo.hasToBeUnpacked().booleanValue();
            }
            ejb3ClassLoader = deployableInfo.getClassLoader();
            applicationJNDIResolver = deployableInfo.getApplicationJNDIResolver();
        }
        
        // Unpack or not ?
        if (unpack) {
            // 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);
            }
        } else {
            unpackedDeployable = (EJB3Deployable) deployable;
        }

        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) {
            // Search naming strategies
            List<EZBNamingStrategy> namingStrategies = container.getConfiguration().getNamingStrategies();
            logger.info("Naming Strategies = ''{0}''", namingStrategies);
            
            // Wrap all Naming strategies
            List<EZBNamingStrategy> newNamingStrategies = new ArrayList<EZBNamingStrategy>();
            for (EZBNamingStrategy oldNamingStrategy : namingStrategies) {
                newNamingStrategies.add(new PrefixedNamingStrategy(prefix, oldNamingStrategy));
            }
            container.getConfiguration().setNamingStrategies(newNamingStrategies);
        }
        URL[] arrayURLs = {getURL(unpackedDeployable)};

        if (ejb3ClassLoader == null) {
        
            // Filtering classloader
            FilteringClassLoader filteringClassLoader = createFilteringClassLoader(deployable);

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

        // Set the classloader that needs to be used
        container.setClassLoader(ejb3ClassLoader);
        
        if (applicationJNDIResolver != null) {
            EZBContainerJNDIResolver containerJNDIResolver = container.getConfiguration().getContainerJNDIResolver();
            containerJNDIResolver.setApplicationJNDIResolver(applicationJNDIResolver);
            // Add child on application JNDI Resolver
            applicationJNDIResolver.addContainerJNDIResolver(containerJNDIResolver);
        }
        

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

}
