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

/*
 * AppDeployer.java
 *
 * Created on December 11, 2001, 5:37 PM
 */

package com.sun.enterprise.deployment.backend;

import java.io.File;
import java.io.IOException;
import java.rmi.RemoteException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.logging.*;
import java.util.*;
import javax.enterprise.deploy.shared.ModuleType;
import com.sun.enterprise.config.ConfigException;
import com.sun.enterprise.config.serverbeans.ServerTags;
import com.sun.enterprise.instance.ApplicationEnvironment;
import com.sun.enterprise.instance.AppsManager;
import com.sun.enterprise.instance.BaseManager;
import com.sun.enterprise.instance.InstanceEnvironment;
import com.sun.ejb.codegen.IASEJBCTimes;
import com.sun.enterprise.util.io.FileSource;
import com.sun.enterprise.util.io.FileUtils;
import com.sun.enterprise.util.zip.ZipItem;
import com.sun.enterprise.util.zip.ZipFileException;
import com.sun.enterprise.util.i18n.StringManager;
import com.sun.enterprise.util.diagnostics.Reminder;

import com.sun.enterprise.deployment.Application;
import com.sun.enterprise.deployment.BundleDescriptor;
import com.sun.enterprise.deployment.EjbBundleDescriptor;
import com.sun.enterprise.deployment.EjbDescriptor;
import com.sun.enterprise.deployment.deploy.shared.FileArchive;
import com.sun.enterprise.deployment.deploy.shared.FileArchiveFactory;
import com.sun.enterprise.deployment.deploy.shared.AbstractArchive;
import com.sun.enterprise.deployment.Descriptor;
import com.sun.enterprise.deployment.util.ModuleDescriptor;
import com.sun.enterprise.deployment.WebBundleDescriptor;
import com.sun.enterprise.deployment.deploy.shared.FileArchive;
import com.sun.enterprise.deployment.archivist.ApplicationArchivist;

// imports for dd generator
import com.sun.enterprise.deployment.interfaces.*;
import com.sun.enterprise.tools.verifier.AppVerifier;

import com.sun.enterprise.security.application.EJBSecurityManager;
import com.sun.enterprise.security.factory.EJBSecurityManagerFactory;
import com.sun.web.security.WebSecurityManagerFactory;

/** 
 * AppDeployer is responsible for Deploying Applications.  Both archives and
 * user-supplied "pre-exploded" directories are supported.
 *
 * WBN February 2, 2002 -- this code is now officially a mess!
 * @author  bnevins
 * @version 
 */
public class AppDeployer extends AppDeployerBase
{
	AppDeployer(DeploymentRequest r)  throws IASDeploymentException
	{
		super(r);
	}

	///////////////////////////////////////////////////////////////////////////

	public void doRequest() throws IASDeploymentException
	{
		doRequestPrepare();
		doRequestFinish();
	}


	///////////////////////////////////////////////////////////////////////////

	public void doRequestPrepare() throws IASDeploymentException
	{
		try
		{
			begin();
		}
		catch(Exception e)
		{
			rollback();
			String msg = localStrings.getString(
					"enterprise.deployment.backend.dorequest_exception" );
			logger.log(Level.WARNING, msg, e);
                        if (e instanceof IASDeploymentException) {
                            throw (IASDeploymentException) e;
                        } else {                        
                            throw new IASDeploymentException( msg, e);
                        }
		}
	}


	///////////////////////////////////////////////////////////////////////////

	public void doRequestFinish() throws IASDeploymentException
	{
		try
		{
			predeploy();
			deploy();
			generatePolicy();
		}
		catch(Exception e)
		{
			String msg = localStrings.getString(
					"enterprise.deployment.backend.dorequest_exception" );
			rollback();
			logger.log(Level.FINE, msg, e);
                        if (e instanceof IASDeploymentException) {
                            throw (IASDeploymentException) e;
                        } else {
                            throw new IASDeploymentException( msg, e);
                        }
		}
		finally
		{
			finish();
		}
	}

	///////////////////////////////////////////////////////////////////////////

	public void cleanup_internal()
	{
		// nothing to do.
	}

	///////////////////////////////////////////////////////////////////////////
	
	protected void predeploy() throws IASDeploymentException
	{
		super.predeploy();
			// if the directories already exist on disk -- wipe them out
			// We do this because otherwise they'd never get this App deployed
			// without wiping the dirs manually
		
		if(request.isDeploy())
			liquidateAppDirAndStubsDirIfTheyHappenToExist();
	}
	
	///////////////////////////////////////////////////////////////////////////
	
	protected void deploy() throws IASDeploymentException
	{
		try
		{
            // retrieve the J2EECPhase deployment status
            DeploymentStatus J2EECPhaseStatus =
            request.getCurrentDeploymentStatus();

            Application app;
			if(isArchive()) {						      
                app = explodeArchive();
			} else { //directory deploy
                //create the generated/xml directory for directory deploy
                getXMLDir().mkdirs();
                getJWSDir().mkdirs();

                // construct the standard application.xml if omitted
                ApplicationArchivist appArchivist = new ApplicationArchivist();
		        String dir = request.getDeployedDirectory().getAbsolutePath();
                FileArchive appArchive = new FileArchive();
                appArchive.open(dir);
                if (!appArchivist.hasStandardDeploymentDescriptor(appArchive)) {
                    Application appDesc = 
                        Application.createApplication(appArchive,true,true);
                    request.setDescriptor(appDesc);
                }

                // load deployment descriptors
                app = loadDescriptors();
            }
			request.setDescriptor(app);
                        
                        // Set the generated XML directory in application desc
                        request.getDescriptor().setGeneratedXMLDirectory(getXMLDir().getAbsolutePath());

                        // create a DeploymentStatus for runEJBC stage
                        // it is a substage of J2EECPhase status
                        DeploymentStatus runEJBCStatus =
                            new DeploymentStatus(J2EECPhaseStatus);
                        request.setCurrentDeploymentStatus(runEJBCStatus);
			runJSPC();
			runVerifier();
			ZipItem[] clientStubs = runEJBC(); 
			
                        checkAppclientsMainClasses();
                        
			createClientJar(clientStubs);

                        // create a DeploymentStatus for postDeploy stage
                        // it is a substage of J2EECPhase status
                        DeploymentStatus postDeployStatus =
                            new DeploymentStatus(J2EECPhaseStatus);
                        request.setCurrentDeploymentStatus(postDeployStatus);
			postDeploy();

			addToSummary(getSuccessMessage()  + getAppName() + "\n*********************\n");
		}
		catch(IASDeploymentException e)
		{
			throw e;
		}
		catch(Exception e)
		{
			throw new IASDeploymentException(e);
		}
	}
	
	///////////////////////////////////////////////////////////////////////////
	
	protected void rollback()
	{
                // do not need to roll back config since the config 
                // has not been added yet. It's added after both
                // doRequestPrepare and doRequestFinish methods.

		// blow away new dirs, rename old ondestinationes back to new ones...
		try
		{
			// careful -- this method is called from inside of a catch block.
			// If we throw an exception from here, it'll be BIG trouble!
			DeleteOrKeepFailedStubs(getStubsDir());

			if(FileUtils.safeIsDirectory(getJSPDir()))
				FileUtils.whack(getJSPDir());		

                        if (FileUtils.safeIsDirectory(getXMLDir())) {
                            FileUtils.whack(getXMLDir());
                        }

                        if (FileUtils.safeIsDirectory(getJWSDir())) {
                            FileUtils.whack(getJWSDir());
                        }

			if(isArchive())
			{
				if(FileUtils.safeIsDirectory(getAppDir()))
					FileUtils.whack(getAppDir());		
			}

		}
		catch(Throwable t)
		{
			// Can't do anything about it!!
            logger.log( Level.WARNING,
                    "enterprise.deployment_rollback_error", t );
		}
	}       

	///////////////////////////////////////////////////////////////////////////
	
	private void liquidateAppDirAndStubsDirIfTheyHappenToExist() throws IASDeploymentException
	{
            if (isArchive()) {
                cleanAndCheck(getAppDir());
            }
            cleanAndCheck(getStubsDir());
            cleanAndCheck(getJSPDir());
            cleanAndCheck(getXMLDir());
            cleanAndCheck(getJWSDir());
	}
	
        /**
         *Cleans out a directory, logging a warning and aborting the current
         *operation (such as deployment, undeployment, etc.) if the directory
         *remains.
         *@param dir the directory File to be cleaned out
         *@throws IASDeploymentException if the directory is not cleaned out successfully
         */
        private void cleanAndCheck(File dir) throws IASDeploymentException {
            if(FileUtils.safeIsDirectory(dir)) {
                FileUtils.whack(dir);
                /*
                 *If the directory remains after the preceding step, then
                 *the clean-up did not succeed fully.  Report this and
                 *abort the operation so the user knows that files that
                 *should have been deleted are still there and could interfere
                 *with a subsequent redeployment, for example.
                 */
                if(FileUtils.safeIsDirectory(dir))
                    {
                        String msg = localStrings.getString("enterprise.deployment.backend.cannot_delete_dir", dir);
                        throw new IASDeploymentException(msg);
                    }
            }
        }
	///////////////////////////////////////////////////////////////////////////
	
	private Application explodeArchive() throws Exception
	{
            
            // first explode the ear file
            Application appDesc = J2EEModuleExploder.explodeEar(request.getFileSource().getFile(), getAppDir());           
            appDesc.setRegistrationName(request.getName());
            
            request.setDescriptor(appDesc);
            getXMLDir().mkdirs();
            getJWSDir().mkdirs();
            getStubsDir().mkdirs();
                        
            // now I can call the normal descriptor loading...
            loadDescriptors();            

            return appDesc;
	}

	///////////////////////////////////////////////////////////////////////////
	
	private ZipItem[] runEJBC() throws IASDeploymentException
	{
        ZipItem[] clientStubs = null;

        try {
            // ejbc timing info
            IASEJBCTimes timing	 = new IASEJBCTimes();
            EJBCompiler compiler = new EJBCompiler
            (
                getAppName(),	
                getAppDir(),
                getOldAppDir(),
                getStubsDir(),
                getOldStubsDir(),
                getManager(),
                request,
                timing
            );
            
            // runs ejbc
            clientStubs = compiler.compile();

            /*
             *Clean up the temporary jar expansion directory.
             *
             *Commenting this out to avoid degrading performance.
             */
//            cleanTempUnjarDirectories(adjustedClassPathList);
            
            // add the ejbc timing info to deployment time
            addEJBCTime(timing);

        } 
        catch(IASDeploymentException e) {
            throw e;
        }
        catch (Exception e) {
            logger.log( Level.WARNING,
            "enterprise.deployment_ejbc_error", e );
            String msg = localStrings.getString(
            "enterprise.deployment.backend.ejbc_error" );
            throw new IASDeploymentException(msg, e);
        }

        // returns the client stubs or an empty array if no stubs
        return clientStubs;
	}

	///////////////////////////////////////////////////////////////////////////
	
	private void runJSPC() throws IASDeploymentException
	{
	    if(!request.getPrecompileJSP())
		return;
	    
	    Application app = request.getDescriptor();
	    Set webBundleDescriptors = app.getWebBundleDescriptors();
            if (webBundleDescriptors == null) {
                return;
            }

            Iterator itr = webBundleDescriptors.iterator();
	    while (itr.hasNext())
	    {
		WebBundleDescriptor wbd = (WebBundleDescriptor) itr.next();
		String warDirName = FileUtils.makeFriendlyFilename(
                                wbd.getModuleDescriptor().getArchiveUri());
		File outDir = new File(getJSPDir(), warDirName);
		File inDir	= new File(getAppDir(),	warDirName);
		//String msg = "***** Call JSPCompiler(" + inDir.getPath()+ ", " + outDir.getPath() + ")";
		//logger.log(Level.SEVERE, msg);
		long time = System.currentTimeMillis();
		JSPCompiler.compile(inDir, outDir, wbd,
                                    request.getCompleteClasspath());
		addJSPCTime(System.currentTimeMillis() - time);
	    }
	}

	///////////////////////////////////////////////////////////////////////////
	
	protected void postDeploy() throws ConfigException, IASDeploymentException
	{
		Application applicationDD = request.getDescriptor();
                       getManager().registerDescriptor(applicationDD.getRegistrationName(), 
                                                       applicationDD);
		DeploymentEventInfo info = new DeploymentEventInfo(
			getAppDir(), getStubsDir(), getOldStubsDir(),
			applicationDD, getRequest());
		DeploymentEvent ev = new DeploymentEvent(
			DeploymentEventType.POST_DEPLOY, info);
		DeploymentEventManager.notifyDeploymentEvent(ev);

		// need to populate web security PolicyConfig
		WebSecurityManagerFactory wsmfactory =
			WebSecurityManagerFactory.getInstance();
		for (Iterator iter = applicationDD.getWebBundleDescriptors().iterator(); iter.hasNext();)
		{
			wsmfactory.newWebSecurityManager((WebBundleDescriptor)iter.next());
		}
		// need to populate ejb security PolicyConfig
                EJBSecurityManagerFactory ejbmfactory =
                    (EJBSecurityManagerFactory)EJBSecurityManagerFactory.getInstance();
                for (Object ejbBundleDescObj : applicationDD.getEjbBundleDescriptors()) {
                    for (Object ejbDescObj : ((EjbBundleDescriptor)ejbBundleDescObj).getEjbs()) {
                        ejbmfactory.createSecurityManager((EjbDescriptor)ejbDescObj);
                    }
		}

		if(!isDirectory()) {
                    postDeployArchive();
                }

                getManager().registerDescriptor(request.getName(), request.getDescriptor());

                // save the object type in optional attributes 
                Properties optionalAttributes = request.getOptionalAttributes();
                if (optionalAttributes == null) {
                    optionalAttributes = new Properties();
                }
                String resourceType = getResourceType(getAppDir());
                if(resourceType != null) {
                    optionalAttributes.setProperty(ServerTags.OBJECT_TYPE, 
                        resourceType);
                }
	}

	///////////////////////////////////////////////////////////////////////////
	
	private void postDeployArchive() throws ConfigException, IASDeploymentException
	{
	}

	///////////////////////////////////////////////////////////////////////////

        protected File setAppDir() throws IASDeploymentException {
            // brand-new deployment, so no versioning required.
            ApplicationEnvironment aenv = getAppEnv();
            File appDirectory;
            
            if(isArchive()) {
                File parent = new File(getInstanceEnv().getApplicationRepositoryPath());
                appDirectory = new File(parent, getAppName());
                appDirectory.mkdirs();
            }
            else if(isDirectory()) {
                FileSource fileSource = request.getFileSource();
                
                if(!fileSource.exists()) {
                    String msg = localStrings.getString(
                    "enterprise.deployment.backend.file_source_does_not_exist",
                    fileSource.toString() );
                    throw new IASDeploymentException( msg );
                }
                
                assert fileSource.isDirectory();
                appDirectory = fileSource.getFile();
            }
            else {
                String msg = localStrings.getString(
                "enterprise.deployment.backend.deployment_not_dir_or_archive");
                throw new IASDeploymentException( msg );
            }
            
            return appDirectory;
        }
	
	///////////////////////////////////////////////////////////////////////////

	protected File getOldStubsDir()
	{
		return null;
	}
	
	///////////////////////////////////////////////////////////////////////////

	protected File getOldAppDir()
	{
		return null;
	}
	
	///////////////////////////////////////////////////////////////////////////

	protected String whatAreYou()
	{
		return "Deployment";
	}
	
	///////////////////////////////////////////////////////////////////////////

	protected final String getSuccessMessage()
	{
		return stars + "**** " + whatAreYou() + " successful for " + getAppName() + " ****" + stars;
	}
	
	///////////////////////////////////////////////////////////////////////////

	protected final String getFailureMessage()
	{
		return stars + "**** " + whatAreYou() + " failed for " + getAppName() + " ****" + stars;
	}

    ///////////////////////////////////////////////////////////////////////////
    /**
     * Runs the verifier on this module if verification is ON in
     * the deployment request.
     *
     * @throws  IASDeploymentException  if an error found in this module
     *                                  after verification
     */
    private void runVerifier() throws IASDeploymentException
    {
        if (request.isVerifying()) {
            try {
                String archive = request.getDeployedDirectory().getCanonicalPath();
                File jspDir = (request.getPrecompileJSP())? getJSPDir():null;
                new AppVerifier().verify(request.getDescriptor(),
                        (new FileArchiveFactory()).openArchive(archive),
                        request.getCompleteClasspath(),
                        jspDir);
            } catch (Exception e) {
                String msg = localStrings.getString(
                        "enterprise.deployment.backend.verifier_error");
                throw new IASDeploymentException(msg);
            }
        }
    }
	private final static String	stars = "\n*********************\n";	
        private static StringManager localStrings =
            StringManager.getManager( AppDeployer.class );
        
}
