/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2005 Bull S.A.
 * Contact: jonas-team@objectweb.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.objectweb.jonas.server;

// Java imports
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;

import javax.enterprise.deploy.shared.ModuleType;
import javax.management.MBeanException;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;

import org.apache.commons.modeler.BaseModelMBean;

import org.objectweb.common.Cmd;

import org.objectweb.jonas_ear.deployment.api.EarDeploymentDesc;
import org.objectweb.jonas_ear.deployment.api.EarDeploymentDescException;
import org.objectweb.jonas_ear.deployment.lib.wrapper.EarManagerWrapper;

import org.objectweb.jonas_ejb.genic.wrapper.GenicServiceWrapper;

import org.objectweb.jonas_lib.files.FileUtils;
import org.objectweb.jonas_lib.genclientstub.wrapper.ClientGenStubWrapper;

import org.objectweb.jonas_web.deployment.api.WebContainerDeploymentDescException;
import org.objectweb.jonas_web.deployment.lib.wrapper.WebManagerWrapper;

import org.objectweb.jonas_ws.wsgen.wrapper.WsGenWrapper;

import org.objectweb.jonas.common.JProp;
import org.objectweb.jonas.common.Log;
import org.objectweb.jonas.container.EJBService;
import org.objectweb.jonas.ear.EarService;
import org.objectweb.jonas.ear.EarServiceException;
import org.objectweb.jonas.resource.ResourceService;
import org.objectweb.jonas.service.ServiceException;
import org.objectweb.jonas.service.ServiceManager;
import org.objectweb.jonas.web.JWebContainerService;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

/**
 * MBean class for Server management. Two classes are used to provide all
 * management services : J2EEServer and J2EEServerMBean. This class provide the
 * notification's process when add or remove resources.
 * @author Adriana Danes
 * @author Michel-Ange Anton
 */

public class J2EEServerMBean extends BaseModelMBean {

    /**
     * Logger
     */
    private static Logger mgtLogger = Log.getLogger(Log.JONAS_MANAGEMENT_PREFIX);

    /**
     * Default constructor
     * @throws MBeanException if super constructor fails
     */
    public J2EEServerMBean() throws MBeanException {
        super();
    }

    // ------------------------------------------------------------- Public
    // methods

    /**
     * All The MBean names corresponding to the deployed J2EEModules (ear, jar,
     * war, rar).
     * @return The String's array of deployed modules objects names
     */
    public String[] getDeployedObjects() {
        ArrayList al = (ArrayList) ((J2EEServer) (this.resource)).getDeployedObjects();
        return (String[]) al.toArray(new String[al.size()]);
    }

    /**
     * All The MBean names corresponding to the deployed J2EEResources.
     * @return The String's array of deployed resources objects names
     */
    public String[] getResources() {
        ArrayList al = (ArrayList) ((J2EEServer) (this.resource)).getResources();
        return (String[]) al.toArray(new String[al.size()]);
    }

    /**
     * All The MBean names corresponding to the used JVMs.
     * @return The String's array of used JVMs objects names
     */
    public String[] getJavaVMs() {
        ArrayList al = (ArrayList) ((J2EEServer) (this.resource)).getJavaVMs();
        return (String[]) al.toArray(new String[al.size()]);
    }

    /**
     * Send a notification to the listener.
     * @param pNotification The notification to send
     */
    public void sendNotification(Notification pNotification) {
        ((J2EEServer) (this.resource)).sendNotification(pNotification);
    }

    /**
     * Add a new listener.
     * @param pListner Listener to notify
     * @param pFilter Notification filter
     * @param pHandback ??
     * @throws java.lang.IllegalArgumentException if notification is not done
     */
    public void addNotificationListener(NotificationListener pListner, NotificationFilter pFilter,
            java.lang.Object pHandback) throws java.lang.IllegalArgumentException {
        ((J2EEServer) (this.resource)).addNotificationListener(pListner, pFilter, pHandback);
    }

    /**
     * Deploy a stand-alone J2EE module packaged in a JAR file
     * @param fileName file name
     * @return The ObjectName of the MBean associated to the deployed module
     * @throws Exception Management operation failed
     */
    public String deployJar(String fileName) throws Exception {
        return ((J2EEServer) (this.resource)).deployJar(fileName);
    }

    /**
     * Deploy a stand-alone J2EE module packaged in a War file
     * @param fileName file name
     * @throws Exception Management operation failed
     */
    public void deployWar(String fileName) throws Exception {
        ((J2EEServer) (this.resource)).deployWar(fileName);
    }

    /**
     * Deploy a J2EE application packaged in a EAR file
     * @param fileName file name
     * @return The ObjectName of the MBean associated to the deployed J2EE
     *         Application
     * @throws Exception Management operation failed
     */
    public String deployEar(String fileName) throws Exception {
        return ((J2EEServer) (this.resource)).deployEar(fileName);
    }

    /**
     * Deploy a J2EE application packaged in a RAR file
     * @param fileName file name
     * @return The ObjectName of the MBean associated to the deployed J2EE
     *         Application
     * @throws Exception Management operation failed
     */
    public String deployRar(String fileName) throws Exception {
        return ((J2EEServer) (this.resource)).deployRar(fileName);
    }

    /**
     * Test if the specified filename is already deployed or not.
     * @param fileName the name of the ear file.
     * @return true if the ear is deployed, otherwise false.
     * @throws Exception Management operation failed
     */
    public Boolean isEarDeployed(String fileName) throws Exception {
        return ((J2EEServer) (this.resource)).isEarDeployed(fileName);
    }

    /**
     * Test if the specified filename is already deployed or not.
     * @param fileName the name of the rar file.
     * @return true if the rar is deployed, otherwise false.
     * @throws Exception Management operation failed
     */
    public Boolean isRarDeployed(String fileName) throws Exception {
        return ((J2EEServer) (this.resource)).isRarDeployed(fileName);
    }

    /**
     * Test if the specified filename is already deployed or not.
     * @param fileName the name of the jar file.
     * @return true if the jar is deployed, otherwise false.
     * @throws Exception Management operation failed
     */
    public Boolean isJarDeployed(String fileName) throws Exception {
        return ((J2EEServer) (this.resource)).isJarDeployed(fileName);
    }

    /**
     * Test if the specified filename is already deployed or not.
     * @param fileName the name of the War file.
     * @return true if the jar is deployed, otherwise false.
     * @throws Exception Management operation failed
     */
    public Boolean isWarDeployed(String fileName) throws Exception {
        return ((J2EEServer) (this.resource)).isWarDeployed(fileName);
    }

    /**
     * Deploy local file and generate classes with Genic tool
     * @param pathname : local path name to the application
     * @param genicArgs : list of genic's parameters
     */
    public void deployLocalFile(String pathname, String[] genicArgs) {
        try {
               callGenic(genicArgs, pathname);
        } catch (Exception e) {
            mgtLogger.log(BasicLevel.WARN, "Cannot generate classes for this application : '" + pathname + ". " + e.getMessage() + "'.");
        }
    }

    /**
     * Apply WsGen on a  local file and generate webservices artifacts
     * @param pathname : local path name to the component
     * @param unpacked true if we want to have an unpacked directory as result
     * @return Returns the path to the modified archive
     */
    public String wsgenLocalFile(String pathname, Boolean unpacked) {
        try {
               WsGenWrapper wsgen = new WsGenWrapper();
               String outputFilename = wsgen.callWsGenExecute(pathname, unpacked);
               boolean modified = wsgen.callWsGenIsInputModifed();
               if (modified) {
                   return outputFilename;
               } else {
                   return null;
               }
        } catch (Exception e) {
            mgtLogger.log(BasicLevel.WARN, "Cannot generate web services for this component : '" + pathname + ".", e);
            return null;
        }
    }

    /**
     * Deploy file (GenIC), needed for Ishmael to work. The file is in the
     * directory specified by the property "jonas.service.deployment.directory"
     * @param typeparam type of the file (EJB, WAR, EAR, RAR, CAR)
     * @param bfile bytes array of the file
     * @param filename basename of the file to be deployed (if moveIntoDeployableDirectory full path name because local file)
     * @param genicArgs arguments for GenIC
     * @param moveIntoDeployableDirectory true to copy the file into the deployable JOnAS_BASE directory
     * @throws RemoteException
     * @throws EarServiceException
     * @throws ResourceServiceException
     * @throws JWebContainerServiceException
     * @return absolute path of the file
     * @author Dean Jennings. <da_jennings@junta.com.au>
     */
    public String deployFile(Integer typeparam, java.lang.Byte[] bfile, String filename, String[] genicArgs, Boolean moveIntoDeployableDirectory) {
        String directory = "";
        int type = typeparam.intValue();

        ServiceManager serviceManager = null;
        try {
            serviceManager = ServiceManager.getInstance();
        } catch (Exception e) {
            // TODO
            mgtLogger.log(BasicLevel.ERROR, "Cannot get the instance of the Service Manager : '" + e.getMessage()
                    + "'.");
        }

        boolean isJar = false;
        if (type == ModuleType.EJB.getValue()) {
                directory = ((EJBService) serviceManager.getEjbService()).getEjbjarsDirectory();
                isJar = true;
        } else if (type == ModuleType.EAR.getValue()) {
                directory = ((EarService) serviceManager.getEarService()).getAppsDirectory();
        } else if (type == ModuleType.WAR.getValue()) {
                directory = ((JWebContainerService) serviceManager.getWebContainerService()).getWebappsDirectory();
        } else if (type == ModuleType.RAR.getValue()) {
                directory = ((ResourceService) serviceManager.getResourceService()).getRarsDirectory();
        } else if (type == ModuleType.CAR.getValue()) {
            // do nothing
        }
        File file;
        if (moveIntoDeployableDirectory.booleanValue()) {
           file = new File(directory + filename);
        } else {
           file = new File(filename);
        }
        try {
            FileOutputStream out = new FileOutputStream(file);
            byte[] bfileused = new byte[bfile.length];
            for (int i = 0; i < bfile.length; i++) {
                bfileused[i] = bfile[i].byteValue();
            }
            out.write(bfileused);
            out.close();
            if (type == ModuleType.EJB.getValue()) {
                    try {
                        callGenic(genicArgs, file.getAbsolutePath());
                    } catch (ServiceException e) {
                        mgtLogger.log(BasicLevel.WARN, "deploy File error " + filename + e.getClass().getName() + " "
                                + e.getMessage());
                    }
            } else if (type == ModuleType.EAR.getValue()) {
                unpackAndCompileEar(file, genicArgs);
            } else if (type == ModuleType.WAR.getValue()) {
                 checkWebAppDeploymentDesc(file);
            } else {
                if (mgtLogger.isLoggable(BasicLevel.DEBUG)) {
                    mgtLogger.log(BasicLevel.DEBUG, "Deployment not yet implemented for type '" + type + ".");
                }
            }
        } catch (IOException ioe) {
            mgtLogger.log(BasicLevel.ERROR, "Cannot dump to outputstream the file '" + file + "' : '"
                    + ioe.getMessage() + "'.", ioe);
            // TODO
        }

        // Ugly but don't work without it ...
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }

        if (!filename.endsWith(".rar")) {
            String modifiedArchive = null;
            ClientGenStubWrapper clientWrapper = new ClientGenStubWrapper();
            try {
                modifiedArchive = clientWrapper.callClientGenStubExecute(file.getPath());
            } catch (Exception e) {
                mgtLogger.log(BasicLevel.ERROR, "Cannot launch ClientGenStub on the file '" + file + "' : '"
                        + e.getCause().getMessage() + "'.", e);
            }

            boolean modified = false;
            try {
                modified = clientWrapper.callClientGenStubIsInputModifed();
            } catch (Exception e) {
                mgtLogger.log(BasicLevel.ERROR, "Cannot launch ClientGenStub.isInputModified '" + file + "' : '"
                        + e.getCause().getMessage() + "'.", e);
                // assume file has been modified
                modified = true;
            }

            if (modified && modifiedArchive != null ) {
                // assume also that we can call WsGen after
                File newFile = new File(modifiedArchive);
                try {
                    FileUtils.copyFile(newFile, file);
                } catch (Exception e) {
                    mgtLogger.log(BasicLevel.ERROR, "Error when copying file '" + newFile + "'. : " + e.getMessage());
                }

                // Ugly but don't work without it ...
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }

                WsGenWrapper ww = new WsGenWrapper();
                try {
                    modifiedArchive = ww.callWsGenExecute(file.getPath());
                } catch (Exception e) {
                    mgtLogger.log(BasicLevel.ERROR, "Cannot launch WsGen on the file '" + file + "' : '"
                            + e.getCause().getMessage() + "'.", e);
                }

                try {
                    modified = ww.callWsGenIsInputModifed();
                } catch (Exception e) {
                    mgtLogger.log(BasicLevel.ERROR, "Cannot launch WsGen on the file '" + file + "' : '"
                            + e.getCause().getMessage() + "'.", e);
                }

                if (modified) {
                    // assume also that we can call WsGen after
                    File newFile2 = new File(modifiedArchive);
                    // Check for modifiedArchive suffix
                    if (modifiedArchive.endsWith(".ear")) {
                        // check if input file is a jar
                        if (isJar) {
                            // this case is special: we got a JAR in input and generate a EAR in output
                            // the output goes to apps directory
                            String modifiedDirectory = ((EarService) serviceManager.getEarService()).getAppsDirectory();
                            file = new File(modifiedDirectory + newFile2.getName());
                        }
                    }
                    try {
                        FileUtils.copyFile(newFile2, file);
                    } catch (Exception e) {
                        mgtLogger.log(BasicLevel.ERROR, "Error when copying file '" + newFile2 + "'. : " + e.getMessage());
                    }
                    return file.getPath();
                }

                return modifiedArchive;
            } else {
                // don't call wsgen as it will fail with DTD too
                return file.getPath();
            }
        } else {
            return file.getPath();
        }
    }


    /**
     * Call GenIC by using a wrapper
     * @param genicArgs arguments for GenIC
     * @param jarPath path of the jar file
     */
    private void callGenic(String[] genicArgs, String jarPath) {
        String[] args;
        if (genicArgs != null) {
            args = new String[genicArgs.length + 1];
            for (int i = 0; i < genicArgs.length; i++) {
                args[i] = genicArgs[i];
            }
            args[genicArgs.length] = jarPath;
        } else {
            args = new String[1];
            args[0] = jarPath;
        }
        if (mgtLogger.isLoggable(BasicLevel.DEBUG)) {
            mgtLogger.log(BasicLevel.DEBUG, "Calling GenIC with arguments :" + Arrays.asList(args));
        }
        GenicServiceWrapper.callGenic(args);
    }

    /**
     * Unpack and then launch GenIC on each ejb jar files
     * @param file ear file
     * @param genicArgs arguments for GenIC
     * @throws ServiceException if this step fails
     */
    private void unpackAndCompileEar(File file, String[] genicArgs) throws ServiceException {
        URL[] earUrl = new URL[1];

        try {
            earUrl[0] = file.toURL();
        } catch (MalformedURLException e) {
            // TODO
            mgtLogger.log(BasicLevel.ERROR, "The url for the file '" + file + "' cannot be built : '" + e.getMessage()
                    + "'.");
        }

        //Create classLoader
        //parent classloader is the current classloader
        ClassLoader currentLoader = Thread.currentThread().getContextClassLoader();
        URLClassLoader loaderCls = new URLClassLoader(earUrl, currentLoader);

        EarDeploymentDesc desc = null;
        try {
            desc = EarManagerWrapper.getDeploymentDesc(earUrl[0].getFile(), loaderCls);
        } catch (EarDeploymentDescException e) {
            // TODO
            mgtLogger.log(BasicLevel.ERROR, "Cannot get deployment descriptor for the Ear file '" + file + "' : '"
                    + e.getMessage() + "'.");
        }

        String[] ejbTags = desc.getEjbTags();
        if (ejbTags.length != 0) {
            //  need to call GENIC
            /*
             * Example of a jar command: jar -uf ../output/ejbjars/sb.jar -C
             * /tmp/genic1547.tmp a/b/C1.class -C /tmp/genic1547.tmp
             * a/b/C2.class .....
             */
            String javaHomeBin = System.getProperty("java.home", "");
            if (!("".equals(javaHomeBin))) {
                javaHomeBin = javaHomeBin + File.separator + ".." + File.separator + "bin" + File.separator;
            }

            Cmd cmd = null;
            cmd = new Cmd(javaHomeBin + "jar");
            cmd.addArgument("-xf");
            cmd.addArgument(file.getAbsolutePath());

            for (int i = 0; i < ejbTags.length; i++) {
                cmd.addArgument(ejbTags[i]);
            }

            boolean exitCmd = cmd.run();
            // Analyse the result of the jar command
            if (!exitCmd) {
                throw new EarServiceException("Failed when extracting the the ejb jar " + "in the given jar file '"
                        + file + "'.");
            }

            for (int i = 0; i < ejbTags.length; i++) {
                try {
                    callGenic(genicArgs, ejbTags[i]);
                } catch (ServiceException e) {
                    throw (e);
                }
            }
            // add modified jar into ear
            cmd = new Cmd(javaHomeBin + "jar");
            cmd.addArgument("-uf");
            cmd.addArgument(file.getAbsolutePath());

            for (int i = 0; i < ejbTags.length; i++) {
                cmd.addArgument(ejbTags[i]);
            }
            exitCmd = cmd.run();
            // Analyse the result of the jar command
            if (!exitCmd) {
                throw new EarServiceException("Failed when extracting the the ejb jar " + "in the given jar file '"
                        + file + "'.");
            }

            for (int i = 0; i < ejbTags.length; i++) {
                File del = new File(ejbTags[i]);
                del.delete();
            }
        }
        desc = null;
    }

    /**
     * Validate deployment descriptor of the War file
     * @param file given file
     */
    private void checkWebAppDeploymentDesc(File file) {
        URL warUrl = null;

        try {
            warUrl = file.toURL();
        } catch (MalformedURLException mue) {
            mgtLogger.log(BasicLevel.ERROR, "The url for the file '" + file + "' cannot be built : '"
                    + mue.getMessage() + "'.");
            throw new RuntimeException(mue);
        }

        URLClassLoader warCl = new URLClassLoader(new URL[] {warUrl});

        try {
            WebManagerWrapper.getDeploymentDesc(warUrl, warCl, null);
        } catch (WebContainerDeploymentDescException e) {
            String err = "Cannot read the deployment descriptors '" + warUrl.getFile() + "'";
            mgtLogger.log(BasicLevel.ERROR, err + ": " + e);
            e.printStackTrace(System.err);
            throw new RuntimeException(err, e);
        }

    }

    /**
     * Add an object name to the <code>resources</code> list.
     * @param pObjectName Object name correspondig to a J2EEResource MBean
     */
    //
    //    public static void addResource(String pObjectName) {
    //        try {
    //            Server oServer = Server.getInstance();
    //            J2EEServer oJ2EEServer = oServer.getJ2EEServer();
    //            oJ2EEServer.addResource(pObjectName);
    //        } catch (Exception e) {
    //            mgtLogger.log(BasicLevel.WARN
    //                , "Add resource error (object=" + pObjectName + ") : "
    //                + e.getClass().getName() + " " + e.getMessage());
    //        }
    //    }
    /**
     * Remove an object name from the <code>resources</code> list.
     * @param pObjectName Object name correspondig to a J2EEResource MBean
     * @return Object name to the removed J2EEResource MBean
     */

    //    public static String removeResource(String pObjectName) {
    //        String sRet = null;
    //        try {
    //            Server oServer = Server.getInstance();
    //            J2EEServer oJ2EEServer = oServer.getJ2EEServer();
    //            sRet = oJ2EEServer.removeResource(pObjectName);
    //        } catch (Exception e) {
    //            mgtLogger.log(BasicLevel.WARN
    //                , "Remove resource object error (object=" + pObjectName + ") : "
    //                + e.getClass().getName() + " " + e.getMessage());
    //        }
    //        return sRet;
    //    }


    /**
     * Send a file to JOnAS Base with the given input stream and write it
     * in the corresponding directory, ie JONAS_BASE/apps, JONAS_BASE/ejbjars, etc.
     * It is based on the filename extension
     * @param fileContent the content (bytes) of the file
     * @param fileName name of the file
     * @param replaceExisting replace existing file if any
     * @return the path of the file
     * @throws Exception if file is already present and if the user don't want to replace existing file.
     */
    public String sendFile(byte[] fileContent, String fileName, boolean replaceExisting) throws Exception {

        File directoryUploadedFile = null;
        FileOutputStream fos = null;
        try {
            // Get directory where to upload file, based on extension
            String dir = getFolderDir(fileName);

            // set the dest file
            directoryUploadedFile = new File(dir, fileName);
            // check, by default we can't overwrite an existing file.
            if (directoryUploadedFile.exists() && !replaceExisting) {
                throw new Exception("File '" + directoryUploadedFile + "' already exists on the server.");
            }

            // write the bytes to the given file
            fos = new FileOutputStream(directoryUploadedFile);
            fos.write(fileContent);
        } finally {
            if (fos != null) {
                try {
                    // close the output stream
                    fos.close();
                } catch (IOException ioe) {
                    mgtLogger.log(BasicLevel.DEBUG, "Cannot close the output stream", ioe);
                }
            }

        }
        if (directoryUploadedFile != null) {
            return directoryUploadedFile.getPath();
        } else {
            return "error, no uploaded file";
        }
    }

    /**
     * Remove a specified J2EE module
     * @param fileName Name of file to remove
     * @return true if file has been removed
     * @throws Exception if remove fails
     */
    public Boolean removeModuleFile(String fileName) throws Exception {

        // File could exists (absolute file)
        File searchedFile = new File(fileName);
        if (!searchedFile.exists()) {
            // Get directory where to search given file
            String dir = getFolderDir(fileName);
            searchedFile = new File(dir, fileName);
        }
        if (searchedFile.exists()) {
            return Boolean.valueOf(searchedFile.delete());
        } else {
            throw new Exception("File '" + searchedFile + "' was not found on the JOnAS server. Cannot remove it");
        }

    }


    /**
     * Get directory based on a filename
     * @param fileName name of the file
     * @return folder of type JONAS_BASE/XXXX/
     * @throws Exception if file is not a J2EE archive
     */
    private String getFolderDir(String fileName) throws Exception {
        String jBase = JProp.getJonasBase();
        // based on extension
        String dir = null;
        // EJB
        if (fileName.toLowerCase().endsWith(".jar")) {
            dir = jBase + File.separator + "ejbjars";
        } else if (fileName.toLowerCase().endsWith(".war")) {
            // War file
            dir = jBase + File.separator + "webapps";
        } else if (fileName.toLowerCase().endsWith(".ear")) {
            // ear file
            dir = jBase + File.separator + "apps";
        } else if (fileName.toLowerCase().endsWith(".rar")) {
            // rar file
            dir = jBase + File.separator + "rars";
        } else {
            // invalid type
            throw new Exception("Invalid extension for the file '" + fileName
                    + "'. Valid are .jar, .war, .ear, .rar");
        }
        return dir;
    }

}