/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License).  You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the license at
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */

package com.sun.enterprise.jbi.serviceengine.core;

import com.sun.enterprise.admin.server.core.AdminService;
import com.sun.enterprise.config.serverbeans.ServerHelper;
import com.sun.enterprise.deployment.archivist.Archivist;
import com.sun.enterprise.deployment.archivist.ArchivistFactory;
import com.sun.enterprise.deployment.backend.DeploymentStatus;
import com.sun.enterprise.deployment.client.DeploymentFacility;
import com.sun.enterprise.deployment.client.DeploymentFacilityFactory;
import com.sun.enterprise.deployment.client.JESProgressObject;
import com.sun.enterprise.deployment.deploy.shared.AbstractArchive;
import com.sun.enterprise.deployment.deploy.shared.ArchiveFactory;
import com.sun.enterprise.deployment.deploy.shared.FileArchive;
import com.sun.enterprise.deployment.deploy.shared.OutputJarArchive;
import com.sun.enterprise.deployment.util.DeploymentProperties;
import com.sun.enterprise.jbi.serviceengine.util.Util;
import com.sun.enterprise.server.ApplicationServer;
import com.sun.enterprise.util.shared.ArchivistUtils;
import com.sun.enterprise.util.FileUtil;
import com.sun.logging.LogDomains;

import javax.enterprise.deploy.shared.ModuleType;
import javax.enterprise.deploy.spi.Target;
import javax.jbi.component.ComponentContext;
import javax.jbi.component.ServiceUnitManager;
import javax.jbi.management.DeploymentException;
import java.io.*;
import java.util.*;
import java.util.zip.ZipFile;
import java.util.zip.ZipEntry;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.ObjectName;

/**
 * Represents JavaEE Service Engine ServiceUnitManager, responsible for
 * deploy/undeploy/start/stop of JavaEE application packaged inside the service
 * assembly.
 *
 * @author bhavanishankar@dev.java.net
 */
public class JavaEEServiceEngineSUManager implements ServiceUnitManager {
    
    protected static Logger logger=LogDomains.getLogger(LogDomains.DPL_LOGGER);
    
    private String className = "JavaEEServiceEngineSUManager :: ";
    private DeploymentFacility deployer;
    private String target = "server"; // default target is DAS instance.
    private JBIEndpointManager epManager;
    private Set<String> dummyCompApps;

    /**
     * Creates a new instance of JavaEEServiceEngineServiceUnitManager
     */
    public JavaEEServiceEngineSUManager() {
        createDeployer();
        epManager = new JBIEndpointManager();
        dummyCompApps = new HashSet<String>();
    }
    
    // START :: ServiceUnitManager callbacks.
    
    public void init(String suId, String suPath) throws DeploymentException {
        String methodSig = className + "init(String, String)";
        logger.log(Level.FINE,
                methodSig + " suId = " + suId + ". suPath = " + suPath);
        try {
            if(isDummyApp(suPath))
                dummyCompApps.add(suId);

            epManager.storeAllEndpoints(suPath, suId);
        } catch (Exception e) {
            throw new DeploymentException(e);
        }
    }
    
    public void shutDown(String suId) throws DeploymentException {
        String methodSig = className + "shutDown(String)";
        logger.log(Level.FINE, methodSig + " suId = " + suId);
        try {
            dummyCompApps.remove(suId);
            epManager.removeAllEndpoints(suId);
        } catch (Exception e) {
            throw new DeploymentException(e);
        }
    }
    
    /**
     * Only deploy the JavaEE application and keep it in 'disable'
     * state until JBI fires start event.
     */
    public String deploy(String suId, String suPath) throws
            DeploymentException {
        String methodSig = className + "deploy(String, String)";
        logger.log(Level.FINE,
                methodSig + " suId = " + suId + ". suPath = " + suPath);
        
        if (!isDAS() || isPrivateMBeanRegistered()) {
            logger.log(Level.FINE,
                    methodSig + " either a non-DAS instance or deployment is done " +
                    "through private MBean, hence skipping deploy.");
            return Util.buildManagementMessage(
                    "STATUS_MSG",
                    "deploy",
                    "SUCCESS",
                    null,
                    suId,
                    null,
                    null);
        }
        return doDeploy(suId, suPath, target);
    }
    
    public String undeploy(String suId, String suPath) throws
            DeploymentException {
        String methodSig = className + "undeploy(String, String)";
        logger.log(Level.FINE,
                methodSig + " suId = " + suId + ". suPath = " + suPath);
        
        if (!isDAS() || isPrivateMBeanRegistered()) {
            logger.log(Level.FINE,
                    methodSig +
                    " either a non-DAS instance or undeployment is done" +
                    " through private MBean, hence skipping undeploy.");
            return Util.buildManagementMessage(
                    "STATUS_MSG",
                    "undeploy",
                    "SUCCESS",
                    null,
                    suId,
                    null,
                    null);
        }
        return doUnDeploy(suId, target);
    }
    
    /**
     * Enable the deployed JavaEE application during the start event.
     */
    public void start(String suId) throws DeploymentException {
        String methodSig = className + "start(String)";
        logger.log(Level.FINE, methodSig + " suId = " + suId);
        
        if (isDAS() && !isPrivateMBeanRegistered()) {
            logger.log(Level.FINE,
                    methodSig +
                    " either a non-DAS instance or start is done through" +
                    " private MBean, hence skipping start.");
            doStart(suId, target);
        }
        
        try {
            epManager.startAllEndpoints(suId);
        } catch (Exception e) {
            throw new DeploymentException(e);
        }
    }
    
    /**
     * Disable the deployed JavaEE application during the stop event.
     */
    public void stop(String suId) throws DeploymentException {
        String methodSig = className + "stop(String)";
        logger.log(Level.FINE, methodSig + " suId = " + suId);

        if (isDAS() && !isPrivateMBeanRegistered()) {
            logger.log(Level.FINE,
                    methodSig +
                    " either a non-DAS instance or deployment is done " +
                    "through private MBean, hence skipping stop.");
            doStop(suId, target);
        }
        
        try {
            epManager.stopAllEndpoints(suId);
        } catch (Exception e) {
            throw new DeploymentException(e);
        }
    }
    
    // END :: ServiceUnitManager callbacks.
    
    // START :: Methods exposed to the private JavaEEDeployerMBean.
    
    protected String doDeploy(String serviceUnitName,String appLocation,String target) {
        String methodSig = className + "doDeploy(String,String,String,String) "; // used for logging.
        boolean deploymentSuccessful = true;
        String deploymentError = null;
        Throwable deploymentException = null;
        String archivePath = null;
        try {
            if (!isDummyApp(appLocation)) {

                archivePath = (new File(appLocation)).isDirectory() 
                    ? createArchive(appLocation, serviceUnitName)
                    : appLocation;
                logger.log(Level.FINE,
                        methodSig + " appName = " + serviceUnitName + ", archivePath = " +
                        archivePath + ", target = " + target);

                Map deployOptions = getDefaultDeploymentOptions(archivePath,
                        serviceUnitName,
                        target);
                AbstractArchive arch =
                        (new ArchiveFactory()).openArchive(archivePath);
                AbstractArchive plan = null;
                Target[] targets = deployer.createTargets(new String[]{target});
                logger.log(Level.FINE, methodSig + " calling backend deploy");
                JESProgressObject progressObject =
                        deployer.deploy(targets, arch, plan, deployOptions);
                DeploymentStatus status = deployer.waitFor(progressObject);
                logger.log(Level.FINE,
                        methodSig + " deployment complete. status = " + status.getStatus());
                if (status.getStatus() != DeploymentStatus.SUCCESS) {
                    deploymentSuccessful = false;
                    deploymentError = methodSig + status.toString();
                    deploymentException = status.getStageException();
                }
            } else {
                dummyCompApps.add(serviceUnitName);
            }
        } catch (Exception ex) {
            deploymentSuccessful = false;
            deploymentError = methodSig + ex.getMessage();
            deploymentException = ex;
        }
        
        if(archivePath != null && (new File(appLocation)).isDirectory() ) {
            try {
                new File(archivePath).delete();
            } catch(Exception ex) {
                logger.log(Level.SEVERE, ex.getMessage(), ex);
            }
        }
        
        if(deploymentSuccessful) {
            logger.log(Level.FINE, methodSig + " successfully deployed " + serviceUnitName);
            return Util.buildManagementMessage(
                    "STATUS_MSG",
                    "deploy",
                    "SUCCESS",
                    null,
                    serviceUnitName,
                    null,
                    null);
        } else {
            logger.log(Level.SEVERE, deploymentError, deploymentException);
            return Util.buildManagementMessage(
                    "EXCEPTION_MSG",
                    "deploy",
                    "FAILED",
                    null,
                    serviceUnitName,
                    deploymentError,
                    deploymentException);
        }
    }
    
    protected String doUnDeploy(String serviceUnitName,String target) {
        String methodSig = className + "doUnDeploy(String,String,String) "; // used for logging.
        boolean undeploymentSuccessful = true;
        String undeploymentError = null;
        Throwable undeploymentException = null;
        try {
            //TODO: clear registries (wsdlCache)
            if(!dummyCompApps.remove(serviceUnitName)) {
                Target[] targets = deployer.createTargets(new String[]{target});
                JESProgressObject progressObject =
                        deployer.undeploy(targets, serviceUnitName, getUnDeploymentOptions());
                DeploymentStatus status = deployer.waitFor(progressObject);
                if (status.getStatus() != DeploymentStatus.SUCCESS) {
                    undeploymentSuccessful = false;
                    undeploymentError = methodSig + status.toString();
                    undeploymentException = status.getStageException();
                }
            }
            
        } catch (Exception ex) {
            undeploymentSuccessful = false;
            undeploymentError = methodSig + ex.getMessage();
            undeploymentException = ex;
        }
        
        if(undeploymentSuccessful) {
            logger.log(Level.FINE, methodSig + " successfully undeployed " + serviceUnitName);
            return Util.buildManagementMessage(
                    "STATUS_MSG",
                    "undeploy",
                    "SUCCESS",
                    null,
                    serviceUnitName,
                    null,
                    null);
        } else {
            logger.log(Level.SEVERE, undeploymentError, undeploymentException);
            return Util.buildManagementMessage(
                    "EXCEPTION_MSG",
                    "undeploy",
                    "FAILED",
                    null,
                    serviceUnitName,
                    undeploymentError,
                    undeploymentException);
        }
    }
    
    protected void doStart(String serviceUnitName, String target) {
        String methodSig = className + "doStart(String,String) "; // used for logging.
        boolean startSuccessful = true;
        String startError = null;
        Throwable startException = null;
        
        try {
            if(!dummyCompApps.contains(serviceUnitName)) {
                Target[] targets = deployer.createTargets(new String[]{target});
                JESProgressObject progressObject =
                        deployer.enable(targets, serviceUnitName);
                DeploymentStatus status = deployer.waitFor(progressObject);
                if (status.getStatus() != DeploymentStatus.SUCCESS) {
                    startSuccessful = false;
                    startError = methodSig + status.toString();
                    startException = status.getStageException();
                }
            }
            
        } catch (Exception ex) {
            startSuccessful = false;
            startError = methodSig + ex.getMessage();
            startException = ex;
        }
        
        if(startSuccessful) {
            logger.log(Level.FINE, methodSig + " successfully started " + serviceUnitName);
        } else {
            logger.log(Level.SEVERE, startError, startException);
        }
    }
    
    protected void doStop(String serviceUnitName, String target) {
        String methodSig = className + "doStop(String,String) "; // used for logging.
        boolean stopSuccessful = true;
        String stopError = null;
        Throwable stopException = null;
        
        try {
            if(!dummyCompApps.contains(serviceUnitName)) {
                Target[] targets = deployer.createTargets(new String[]{target});
                JESProgressObject progressObject =
                        deployer.disable(targets, serviceUnitName);
                DeploymentStatus status = deployer.waitFor(progressObject);
                if (status.getStatus() != DeploymentStatus.SUCCESS) {
                    stopSuccessful = false;
                    stopError = methodSig + status.toString();
                    stopException = status.getStageException();
                }
            }
            
        } catch (Exception ex) {
            stopSuccessful = false;
            stopError = methodSig + ex.getMessage();
            stopException = ex;
        }
        
        if(stopSuccessful) {
            logger.log(Level.FINE, methodSig + " successfully stopped " + serviceUnitName);
        } else {
            logger.log(Level.SEVERE, stopError, stopException);
        }
    }
    
    // END :: Methods exposed to the private JavaEEDeployerMBean.
    
    // START : Utility methods.
    
    private String createArchive(String dir, String archiveName) {
        String archivePath;
        try {
            Archivist archivist = ArchivistFactory.getArchivistForArchive(dir);
            String pathExtn = ".jar";
            if (ModuleType.EAR.equals(archivist.getModuleType())) {
                pathExtn = ".ear";
            } else if (ModuleType.WAR.equals(archivist.getModuleType())) {
                pathExtn = ".war";
            } else if (ModuleType.RAR.equals(archivist.getModuleType())) {
                pathExtn = ".rar";
            }
            archivePath = dir + File.separator + archiveName + pathExtn;
            createJar(dir, archivePath);
        } catch (IOException ioe) {
            archivePath = null;
        }
        return archivePath;
    }
    
    private void createJar(String sourcePath, String destinationPath)
    throws IOException {
        FileArchive source = new FileArchive();
        OutputJarArchive destination = new OutputJarArchive();
        try {
            source.open(sourcePath);
            Enumeration entries = source.entries();
            destination.create(destinationPath);
            while(entries.hasMoreElements()) {
                String entry = String.class.cast(entries.nextElement());
                InputStream is = null;
                OutputStream os = null;
                try {
                    is = source.getEntry(entry);
                    os = destination.putNextEntry(entry);
                    ArchivistUtils.copyWithoutClose(is, os);
                } finally {
                    if (is != null) is.close();
                    if (os != null) destination.closeEntry();
                }
            }
        } finally {
            source.close();
            destination.close();
        }
    }
    
    private Map getDefaultDeploymentOptions(String archivePath,
            String appName,
            String target) {
        DeploymentProperties deploymentOptions = new DeploymentProperties();
        deploymentOptions.setName(appName);
        deploymentOptions.setForce(true);
        deploymentOptions.setArchiveName(archivePath);
        deploymentOptions.setEnable(false);
        deploymentOptions.setTarget(target);
        deploymentOptions.setExternallyManaged(true);
        return deploymentOptions;
    }

    private Map getUnDeploymentOptions() {
        DeploymentProperties undeploymentOptions = new DeploymentProperties();
        undeploymentOptions.setExternallyManaged(true);
        return undeploymentOptions;
    }
    
    private boolean isPrivateMBeanRegistered() {
        try {
            ComponentContext jbiContext =
                    JavaEEServiceEngineContext.getInstance().getJBIContext();
            ObjectName privateMBeanName =
                    jbiContext.getMBeanNames().createCustomComponentMBeanName("JavaEEDeployer");
            boolean isPrivateMBeanRegistered =
                    jbiContext.getMBeanServer().isRegistered(privateMBeanName);
            logger.log(Level.FINE,
                    "isPrivateMBeanRegistered = " + isPrivateMBeanRegistered);
            return isPrivateMBeanRegistered;
        } catch(Exception ex) {
            logger.log(Level.SEVERE, ex.getMessage(), ex);
            return false;
        }
    }
    
    private void createDeployer() {
        deployer = DeploymentFacilityFactory.getLocalDeploymentFacility();
    }
    
    private boolean isDAS() {
        try {
            return ServerHelper.isDAS(
                    AdminService.getAdminService().getAdminContext().getAdminConfigContext(),
                    ApplicationServer.getServerContext().getInstanceName());
        } catch (Exception ex) {
            logger.log(Level.SEVERE, className + ex.getMessage(), ex);
            //better be more restrictive by returning false
            return false;
        }
    }

    /**
     * Check whether this is a dummy JavaEE application. In such case deployment 
     * will be skipped as the application will be deployed outside of the 
     * composite application.
     * In a dummy application allowed entries are -
     * META-INF/jbi.xml and META-INF/MANINFEST.MF.
     */
    private boolean isDummyApp(String appLocation) throws IOException {
        File app = new File(appLocation);
        if(app.isDirectory()) {
            for (Object fileObj : FileUtil.getAllFilesAndDirectoriesUnder(app)) {
                String path = ((File)fileObj).getPath();
                if(!("META-INF"+File.separator+"jbi.xml").equalsIgnoreCase(path) &&
                        !("META-INF"+File.separator+"MANIFEST.MF").equalsIgnoreCase(path) &&
                        !"META-INF".equalsIgnoreCase(path))
                    return false;
            }
        } else {
            ZipFile zipFile = new ZipFile(appLocation);
            for (Enumeration e = zipFile.entries(); e.hasMoreElements();) {
                String entryName = ((ZipEntry)e.nextElement()).getName();
                if(!("META-INF/jbi.xml").equalsIgnoreCase(entryName) &&
                        !("META-INF/MANIFEST.MF").equalsIgnoreCase(entryName) &&
                        !("META-INF/").equalsIgnoreCase(entryName)) {
                    return false;
                }
            }
        }
        return true;
    }
    // END : Utility methods.
}
