/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2010-2012 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.datasource.deployer;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.ow2.jonas.Version;
import org.ow2.jonas.datasource.deployer.binding.DatasourceType;
import org.ow2.jonas.datasource.deployer.binding.Datasources;
import org.ow2.jonas.datasource.deployer.reader.DatasourceXmlReader;
import org.ow2.jonas.generators.raconfig.RAConfig;
import org.ow2.jonas.generators.raconfig.RAConfigException;
import org.ow2.jonas.lib.work.DeployerLog;
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.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.impl.deployer.AbsDeployerList;
import org.ow2.util.ee.deploy.impl.helper.DeployableHelper;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;
import org.ow2.util.plan.bindings.deploymentplan.maven2.Maven2Deployment;
import org.ow2.util.plan.bindings.exceptions.InvalidDeploymentException;
import org.ow2.util.plan.fetcher.api.IResourceFetcher;
import org.ow2.util.plan.fetcher.api.IResourceFetcherFactoryManager;
import org.ow2.util.plan.fetcher.api.exceptions.FetcherException;
import org.ow2.util.plan.fetcher.api.exceptions.FetcherFactoryException;
import org.ow2.util.plan.fetcher.api.exceptions.ResourceFetcherNotResolvedException;
import org.ow2.util.plan.fetcher.api.exceptions.ResourceNotFoundException;
import org.ow2.util.plan.repository.api.IRepositoryManager;
import org.ow2.util.url.URLUtils;

/**
 * This deployer will deploy Datasource module. Generating .RAR files and deploy
 * them.
 * @author Alexis RENOUX
 */
public class DatasourceDeployer extends AbsDeployerList<DatasourceDeployable> {

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

    /**
     * The Deployer Manager that will be used to deploy RAR files.
     */
    private IDeployerManager deployerManager = null;

    /**
     * The datasourceReader that will be used to read the datasource XML file.
     */
    private DatasourceXmlReader datasourceReader;

    /**
     * The default RAR creation path.
     */
    private static final String DEFAULT_ARCHIVE_PATH = "deployer" + File.separator +"datasources";

    /**
     * Working directory
     */
    protected File archiveFile = null;

    /**
     * The datasources that were deployed, and the generated RARs.
     */
    private Map<URI, List<IDeployable<?>>> datasourceDeployables = new HashMap<URI, List<IDeployable<?>>>();

    /**
     * Server Properties.
     */
    protected ServerProperties serverProps;

    /**
     * Reference to the {@link org.ow2.jonas.lib.work.DeployerLog} which is the class that manage the
     * accesses to the log file (to remove the jar).
     */
    protected DeployerLog deployerLog = null;

    /**
     * Internal JDBC RAR used to build datasource RAR
     */
    private String dmRar;

    /**
     * Resource fetcher factory
     */
    protected IResourceFetcherFactoryManager resourceFetcherFactoryManager;

    /**
     * Repository manager
     */
    protected IRepositoryManager repositoryManager;

    /**
     * Creates a new DatasourceDeployer.
     */
    public DatasourceDeployer() throws IOException {

    }

    /**
     * Resource service used by this deployer.
     */
    // private ResourceService resourceService = null;
    /**
     * Undeploy the given Datasource. (Not implemented yet)
     * @param datasourceDeployable the deployable to remove.
     * @throws org.ow2.util.ee.deploy.api.deployer.DeployerException if the Datasource is not undeployed.
     */
    protected void undeployDatasource(final DatasourceDeployable datasourceDeployable) throws DeployerException {
        logger.info("Undeploying {0}", datasourceDeployable);

        URI uri = getUri(datasourceDeployable);
        for (IDeployable<?> rar : datasourceDeployables.get(uri)) {
            // Undeploy the RAR file
            try {
                this.deployerManager.undeploy(rar);
            } catch (DeployerException e) {
                throw new DeployerException("Exception while undeploying deployable " + rar, e);
            } catch (UnsupportedDeployerException e) {
                throw new DeployerException("No deployer found for deployable " + rar, e);
            }
        }

        // Remove the entry deployment was succesfull.
        this.datasourceDeployables.remove(uri);

    }

    /**
     * Deploy the given Datasource.
     * @param deployable the deployable to add.
     * @throws org.ow2.util.ee.deploy.api.deployer.DeployerException if the Datasource is not deployed.
     */
    protected void deployDatasource(final IDeployable<DatasourceDeployable> deployable) throws DeployerException {
        logger.debug("Request to deploy {0} received", deployable);

        if (deployable == null) {
            throw new DeployerException("Null deployable");
        }

        if (!(DatasourceDeployable.class.isAssignableFrom(deployable.getClass()))) {
            throw new DeployerException("Bad deployable type " + deployable.getClass());
        }

        logger.info("Deploying datasource {0}", deployable);

        DatasourceDeployable datasourceDeployable = DatasourceDeployable.class.cast(deployable);

        datasourceReader = new DatasourceXmlReader();
        if (datasourceReader == null) {
            throw new DeployerException("Deployment aborted - The DatasourceReader is null");
        }

        File xmlfile = this.getFile(datasourceDeployable);
        Datasources datasources = null;

        // Create java objects from XML file
        try {
            datasources = datasourceReader.extractDataSources(xmlfile);
        } catch (Exception e) {
            throw new DeployerException("Error while parsing file " + xmlfile + " - Deployment aborted", e);
        }

        if (datasources == null) {
            throw new DeployerException("The deployable " + deployable + " resolved in null datasource - Deployment aborted");
        }

        datasourceDeployable.setAttachedData(datasources);
        List<IDeployable<?>> generatedResourceAdapters = new ArrayList<IDeployable<?>>();

        for (DatasourceType ds : datasources.getDatasources()) {
            DatasourceType dsInitialized = datasourceReader.initDataSource(ds);
            // Get the Properties java object from datasourceType
            Properties properties = datasourceReader.getProperties(dsInitialized);
            String fileIn = null;
            String fileOut = null;
            String fileName = "";
            try {
                fileName = "ds-" + dsInitialized.getDatasourceConfiguration().getName().replace('/', '_') + "-";
                fileOut = File.createTempFile(fileName, ".rar", archiveFile).getAbsolutePath();
            } catch (IOException e1) {
                String msg = "Error while creating temp file <" + fileName + ".rar> in directory <" + archiveFile.getPath() + ">";
                throw new DeployerException(msg, e1);
            }

            // Generate .rar (only DM are supported)
            if (dmRar == null) {
                if (resourceFetcherFactoryManager != null) {
                    Maven2Deployment deployment = new Maven2Deployment();
                    deployment.setGroupId("org.ow2.jonas");
                    deployment.setArtifactId("jonas-jca-jdbc-dm");
                    deployment.setVersion(Version.getNumber());
                    deployment.setType("rar");

                    try {
                        IResourceFetcher fetcher = this.resourceFetcherFactoryManager.getResourceFetcher(deployment);
                        fetcher.setRepositoryManager(repositoryManager);
                        fetcher.setDeployment(deployment);
                        fetcher.resolve();
                        setDmRar(fetcher.getResource().getPath());
                    } catch (FetcherException e) {
                        e.printStackTrace();
                    } catch (ResourceNotFoundException e) {
                        e.printStackTrace();
                    } catch (InvalidDeploymentException e) {
                        e.printStackTrace();
                    } catch (FetcherFactoryException e) {
                        e.printStackTrace();
                    } catch (ResourceFetcherNotResolvedException e) {
                        e.printStackTrace();
                    }
                } else {
                    throw new DeployerException("Resource fetcher factory service was not found. Cannot resolve " +
                            "the artifact org.ow2.jonas:jonas-jca-jdbc-dm:" + Version.getNumber() + ":rar");
                }
            }

            fileIn = dmRar;

            logger.info("Generate RAR {0} -> {1}", fileIn, fileOut);

            try {
                RAConfig.generateRars(properties, "JOnASJDBC_DM", fileIn, fileOut);
            } catch (RAConfigException e) {
                logger.error("Failure to deploy datasource {0}", ds);
                throw new DeployerException(e);
            }

            /*
             * Deploy .rar 1- Create IArchive 2- Create Deployable 3- Deploy
             */

            // Create an IArchive
            File generatedFile = new File(fileOut);

            if (!generatedFile.exists()) {
                logger.error("RAR was not generated with RAConfig- {0}", fileOut);
                throw new DeployerException("RAR was not generated <" + fileOut + ">");
            }

            IArchive archiveRar = ArchiveManager.getInstance().getArchive(generatedFile);
            if (archiveRar == null) {
                throw new DeployerException("Null archive for file " + fileOut);
            }

            // Create a Deployable
            IDeployable<?> rardeployable = null;
            try {
                rardeployable = DeployableHelper.getDeployable(archiveRar);
            } catch (DeployableHelperException e) {
                throw new DeployerException(e);
            }
            if (rardeployable == null) {
                throw new DeployerException("Null deployable for archive " + archiveRar);
            }

            // Deploy
            try {
                this.deployerManager.deploy(rardeployable);
            } catch (DeployerException e) {
                throw new DeployerException("Exception while deploying deployable " + rardeployable, e);
            } catch (UnsupportedDeployerException e) {
                throw new DeployerException("No deployer found for deployable " + rardeployable, e);
            }

            // Here deployment was successful.
            generatedResourceAdapters.add(rardeployable);

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

        this.datasourceDeployables.put(xmlfile.toURI(), generatedResourceAdapters);

    }

    /**
     * Allows to get a File from the given DataSource Deployable.
     * @param datasourceDeployable the given DataSource deployable.
     * @return a File object of this deployable
     * @throws org.ow2.util.ee.deploy.api.deployer.DeployerException if the File can't be obtained.
     */
    protected File getFile(final DatasourceDeployable datasourceDeployable) throws DeployerException {
        // Get URL
        URL datasourceURL = null;
        try {
            datasourceURL = datasourceDeployable.getArchive().getURL();
        } catch (ArchiveException e) {
            throw new DeployerException("Cannot get URL from Datasource deployable '" + datasourceDeployable + "'.", e);
        }

        // Get File
        return URLUtils.urlToFile(datasourceURL);
    }

    /**
     * {@inheritDoc}
     */
    public void deploy(final IDeployable<DatasourceDeployable> deployable) throws DeployerException {
        check(deployable);

        // Deploy the Datasource Deployable

        if (DatasourceDeployable.class.isAssignableFrom(deployable.getClass())) {
            deployDatasource(deployable);
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean isDeployed(final IDeployable<DatasourceDeployable> deployable) throws DeployerException {
        if (this.supports(deployable)) {
            return (this.datasourceDeployables.get(getUri(deployable)) != null);
        } else {
            return false;
        }

    }

    /**
     * Extract the URI of the given Deployable
     * @param deployable the given DataSource deployable.
     * @return the URI identifying this deployable
     * @throws DeployerException if the URI can't be obtained.
     */
    protected URI getUri(final IDeployable<?> deployable) {
        // Get URI
        try {
            return deployable.getArchive().getURL().toURI();
        } catch (Exception e) {
            // Should never happen
            throw new RuntimeException("Cannot get URI from Datasource deployable '" + deployable + "'.", e);
        }
    }

    /**
     * check if the RAR is currently deployed
     */
    public boolean isDeployed(final String rarFileName) {

        return datasourceDeployables.containsValue(rarFileName);

    }

    /**
     * {@inheritDoc}
     */
    public boolean supports(final IDeployable<?> deployable) {
        return (DatasourceDeployable.class.isAssignableFrom(deployable.getClass()));
    }

    /**
     * {@inheritDoc}
     */
    public void undeploy(final IDeployable<DatasourceDeployable> deployable) throws DeployerException {
        if (!this.supports(deployable)) {
            throw new DeployerException("Cannot undeploy non-datasource deployable " + deployable + ".");
        }
        if (!this.isDeployed(deployable)) {
            throw new DeployerException("Cannot undeploy datasource deployable " + deployable + " - it is not deployed.");
        }
        undeployDatasource(DatasourceDeployable.class.cast(deployable));
    }

    /**
     * Check if the given deployable is deployable or not.
     * @param deployable the deployable to check.
     * @throws org.ow2.util.ee.deploy.api.deployer.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.");
        }
    }

    /**
     * {@inheritDoc}
     */
    public IDeployerManager getDeployerManager() {
        return deployerManager;
    }

    /**
     * {@inheritDoc}
     */
    public void setDeployerManager(final IDeployerManager deployerManager) {
        this.deployerManager = deployerManager;
    }

    /**
     * Set the Server Properties.
     * @param props {@link org.ow2.jonas.properties.ServerProperties} instance
     */
    public void setServerProperties(final ServerProperties props) {
        this.serverProps = props;
    }

    /**
     * Create working directory for the deployer.
     */
    protected void initWorkingDirectory() {
        if (archiveFile == null) {
            archiveFile = new File(serverProps.getWorkDirectory() + File.separator + DEFAULT_ARCHIVE_PATH + File.separator + serverProps.getServerName());
            archiveFile.mkdirs();
        }
    }

    /**
     * @return Get path to the DM rar
     */
    public String getDmRar() {
        return dmRar;
    }

    /**
     *  set the DM RAR path
     * @param dmRar path to rar file
     */
    public void setDmRar(final String dmRar) {
        this.dmRar = dmRar;
    }

}
