/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2005 Bull S.A.
 * 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 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.lib.wsgen;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.ReflectionException;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.jonas.Version;
import org.ow2.jonas.lib.bootstrap.JProp;
import org.ow2.jonas.lib.util.JonasObjectName;
import org.ow2.jonas.lib.util.Log;
import org.ow2.util.file.FileUtils;
import org.ow2.util.file.FileUtilsException;

/**
 * Check if a file has been WsGen-ized.
 * If not, apply WsGen on it and return the new archive name.
 *
 * @author Guillaume Sauthier
 */
public class WsGenChecker {

    /**
     * Property name for WsGen engagement or not
     */
    private static final String AUTO_WSGEN_ENGAGED = "jonas.service.ws.auto-wsgen.engaged";

    /**
     * logger
     */
    private static Logger logger = Log.getLogger("org.ow2.jonas.ws");

    /**
     * name of the file to be checked
     */
    private String filename = null;

    /**
     * MbeanServer used to execute MBean's operations
     */
    private MBeanServer mbeanServer = null;

    /**
     * J2EEServer ObjectName
     */
    private ObjectName j2eeServerON;

    /**
     * 'true' if the Auto WsGen mode is engaged
     */
    private boolean engaged = true;

    /**
     * Creates a new Chceker for WsGen
     * @param ms MBeanServer to use
     * @param filename file to be checked
     * @param j2eeServerON J2EEServer ObjectName
     */
    public WsGenChecker(String filename, ObjectName j2eeServerON, MBeanServer jmxServer) {
        mbeanServer = jmxServer;
        this.filename = filename;
        this.j2eeServerON = j2eeServerON;
        try {
            // is 'ws' service deployed ?
            String[] services = JProp.getInstance().getValueAsArray("jonas.services");
            boolean wsEnabled = false;
            for (int i = 0; i < services.length; i++) {
                if ("ws".equals(services[i])) {
                    wsEnabled = true;
                }
            }

            // engage only if ws service is deployed
            this.engaged = wsEnabled && JProp.getInstance().getValue(AUTO_WSGEN_ENGAGED, "true").trim().equalsIgnoreCase("true");

        } catch (Exception e) {
            this.engaged = true;
        }
    }


    /**
     * Check that WsGen have been applied on the given file
     * If it was not done, it run WsGen against the file.
     * @param domainName the domain name
     * @return Returns the path to the modified archive
     * @throws CheckerException FIXME
     */
    public String checkWsGen(String domainName) throws CheckerException {
        if (!engaged) {
            // user don't want Automatic WsGen
            // just Quit
            return filename;
        }

        String jonasVersionWsGen = getAttributeInManifest(filename, "WsGen-JOnAS-Version");

        if ((jonasVersionWsGen == null) || (!Version.getNumber().equals(jonasVersionWsGen))) {
            // The archive need WsGen

            // Print a little explanation before calling WsGen
            if (jonasVersionWsGen == null) {
                logger.log(BasicLevel.DEBUG, "JOnAS version not found into '" + filename
                        + "' manifest file. Trying to generate WebServices classes...");
            } else if (!Version.getNumber().equals(jonasVersionWsGen)) {
                logger.log(BasicLevel.DEBUG, "Different JOnAS version found for '" + filename + "' (archive version : '"
                        + jonasVersionWsGen + "' / JOnAS version : '" + Version.getNumber() + "'). Trying to re-generate WebServices classes...");
            }

            File module = new File(filename);
            Boolean unpacked = Boolean.FALSE;
            if (module.isDirectory()) {
                unpacked = Boolean.TRUE;
            }
            Object[] param = {filename, unpacked};
            String[] signature = {"java.lang.String", "java.lang.Boolean"};

            // Invoke wsgenLocalFile
            String path = null;
            try {
                path = (String) mbeanServer.invoke(j2eeServerON, "wsgenLocalFile", param, signature);
            } catch (InstanceNotFoundException e) {
                // no J2EEserver MBean !?
                // Should not occurs
                logger.log(BasicLevel.WARN, "Cannot find J2EEServer MBean", e);
            } catch (MBeanException e) {
                // wsgenLocalFile thrown an Exception
                logger.log(BasicLevel.WARN, "WsGen throw an Exception.", e.getTargetException());
            } catch (ReflectionException e) {
                // cannot invoke mbean operation
                logger.log(BasicLevel.WARN, "Cannot invoke operation on J2EEServer MBean", e);
            }

            if (path != null) {
                // archive has been changed
                // if it was an EjbJar before, and an ear was produced
                if ((path.endsWith(".ear")) && (filename.endsWith(".jar"))) {
                    // WsGen produced an Ear from the EjbJar
                    logger.log(BasicLevel.DEBUG, "WsGen produced an EAR '" + path + "'");

                    // Copy it from the work directory to apps directory
                    File source = new File(path);
                    File destination = new File(JProp.getJonasBase(), "apps" + File.separator + source.getName());
                    try {
                        FileUtils.copyFile(source, destination);
                    } catch (FileUtilsException e) {
                        // can't copy the file
                        throw new CheckerException("Cannot copy the file from '" + path + "' to '" + filename + "'", e);
                    }
                    logger.log(BasicLevel.DEBUG, "Deploying '" + path + "' (copied from '" + source.getPath() + "') ...");

                    // invoke method on the MBeanServer
                    try {
                        ObjectName earServiceON = JonasObjectName.earService(domainName);
                        // deploy the resulting, copied ear
                        param[0] = path;
                        mbeanServer.invoke(earServiceON, "deployEarMBean", param, signature);
                    } catch (InstanceNotFoundException e) {
                        // no ear service in this instance
                        throw new CheckerException("Ear Service not available in this JOnAS instance. Cannot deploy the produced Application : " + path, e);
                    } catch (MBeanException e) {
                        // deployEarMBean thrown an Exception
                        throw new CheckerException("Cannot deploy the produced Application : " + path, e.getTargetException());
                    } catch (ReflectionException e) {
                        // cannot invoke mbean operation
                        throw new CheckerException("Cannot invoke deploy operation on Ear Service MBean for application : " + path, e);
                    }
                    return path;
                } else {
                    // WsGen did not change the archive type
                    // but produced another (updated) jar
                    logger.log(BasicLevel.DEBUG, "Archive updated, continuing deployment : " + path);
                    // replace old file with the new one...
                    try {
                        // clean existing lock on files (close IO Streams is not sufficent ...)
                        // and wait for a given amount of time :(
                        // Even the GC is not sufficent :(
                        // It is required, but it seems that windows need some time
                        // to remove file locks.
                        System.gc();
                        // TODO Not very nice ! If there is other workaround...
                        Thread.sleep(Long.getLong("jonas.wsgen.sleep", 100).longValue());

                        // delete old file
                        if (!FileUtils.delete(filename)) {
                            throw new CheckerException("Cannot delete old application : " + filename);
                        }
                        if (unpacked.booleanValue()) {
                            FileUtils.copyDirectory(new File(path), module);
                        } else {
                            FileUtils.copyFile(path, filename);
                        }
                        // remove temp file
                        FileUtils.delete(path);
                    } catch (FileUtilsException e) {
                        // can't copy the file
                        throw new CheckerException("Cannot copy the ear from '" + path + "' to '" + filename + "'", e);
                    } catch (InterruptedException e) {
                        // Required by the Thread.sleep() :(
                        throw new CheckerException("Cannot sleep for the given time period", e);
                    }
                    return filename;
                }
            } else {
                // no changes, just continue the normal deployment process
                logger.log(BasicLevel.DEBUG, "Archive has not been changed, continuing deployment : " + filename);
                return filename;
            }
        }

        // the archive don't need WsGen
        logger.log(BasicLevel.DEBUG, "Archive up to date, continuing deployment : " + filename);
        return filename;
    }

    /**
     * get the attibut value in manifest file
     * @param name : the file containing a manifest
     * @param attributeName : the attribut name
     * @return the value corresponding to the attributName if it's exist null otherwise
     */
    private static String getAttributeInManifest(String name, String attributeName) {

        File file = new File(name);
        if (file.isFile()) {
            // file mode
            JarFile jar = null;
            try {
                // open and read the jar Manifest values
                jar = new JarFile(file);
                Manifest man = jar.getManifest();
                if (man == null) {
                    // no manifest, just return null
                    // avoid a bad looking NPE ;)
                    return null;
                }
                Attributes atts = man.getMainAttributes();
                return atts.getValue(attributeName);
            } catch (IOException ioe) {
                // if a problem occurs, return null
                return null;
            } finally {
                // close the jar
                if (jar != null) {
                    try {
                        jar.close();
                    } catch (IOException e) {
                        // nothing to do
                        logger.log(BasicLevel.DEBUG, "Cannot close archive : " + name);
                    }
                }
            }
        } else {
            // directory mode
            try {
                Manifest mf = new Manifest(new FileInputStream(new File(file, "META-INF" + File.separator + "MANIFEST.MF")));
                Attributes atts = mf.getMainAttributes();
                return atts.getValue(attributeName);
            } catch (FileNotFoundException e) {
                // no Manifest
                // act as the manifest attribute was not found
                return null;
            } catch (IOException e) {
                // act as the manifest attribute was not found
                return null;
            }
        }
    }
}
