/*
 * 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.cli.commands;

import com.sun.enterprise.cli.framework.CommandValidationException;
import com.sun.enterprise.cli.framework.CommandException;
import com.sun.enterprise.cli.framework.CLILogger;

import com.sun.enterprise.util.ProcessExecutor;
import com.sun.enterprise.util.RelativePathResolver;
import com.sun.enterprise.security.store.PasswordAdapter;
import com.sun.enterprise.admin.servermgmt.pe.PEFileLayout;
import com.sun.enterprise.admin.servermgmt.RepositoryManager;
import com.sun.enterprise.admin.servermgmt.DomainsManager;
import com.sun.enterprise.admin.servermgmt.DomainConfig;
import com.sun.enterprise.admin.servermgmt.DomainException;
import com.sun.enterprise.admin.servermgmt.InstancesManager;
import com.sun.enterprise.admin.common.Status;
import com.sun.enterprise.util.SystemPropertyConstants;
import com.sun.enterprise.util.io.FileUtils;
import java.io.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;

import java.io.File;
import java.lang.reflect.Method;
import java.util.BitSet;
/**
 *  This is a sample Deploy command
 *  @version  $Revision: 1.12 $
 */
public class StartDomainCommand extends BaseLifeCycleCommand
{

    private static final String SERVER_LOG_FILE_NAME = "server.log";
    private static final String LOGS_DIR             = "logs";
    private static final String VERBOSE              = "verbose";
    private static final String APPLICATION_SERVER_8_0 = "Application Server 8.0";
    private static final String DTD_FILE_8_0 = "sun-domain_1_0";
    private static final String DTD_FILE_8_1 = "sun-domain_1_1";
    private static final long   UPGRADE_TIMEOUT = 1200000;  //20min
    private static final String ADDONINSTALLER = "com.sun.enterprise.addon.AddonInstaller";
    private static final String INSTALLERMETHOD = "installAllAddons";
    private String domainName;
    private DomainConfig config;
    private DomainsManager mgr;
    private String adminPwdAlias;
    private BitSet flags;
    
	// backup message: tells user about the backup file.  This is not wanted in many
    // cases -- e.g. domain is already running, bad password.
	// note: as of Sept 26,2005 the backup of domain.xml has been deprecated.
	// Thus, this variable does nothing.  It can all be turned back on by setting
	// the enableBackups boolean
    private boolean doBackupMessage = true;
	private final static boolean enableBackups = false;
    
    /**
     *  An abstract method that validates the options 
     *  on the specification in the xml properties file
     *  This method verifies for the correctness of number of 
     *  operands and if all the required options are supplied by the client.
     *  @return boolean returns true if success else returns false
     */
    public boolean validateOptions() throws CommandValidationException
    {
        return super.validateOptions();
    }
    public void startDomain(String DomainName) 
        throws CommandException, CommandValidationException
    {       
        domainName = DomainName;
        long start  = System.currentTimeMillis();
        
        try
        { 
            doBackupMessage = false;
            init();
            checkIfRunning();
            checkIfStopping();
    
            doBackupMessage = true; 
            validateDomain();
            doBackupMessage = false;
            validateAdminPassword();
            validateMasterPassword();
            saveExtraPasswordOptions();
            doBackupMessage = true; 
            installAddons();
            checkAndExecuteUpgrade(domainName);
            mgr.startDomain(config);
            			
			if(enableBackups)
			{
				saveCopyOfConfig(config);
			}

            if (flags.get(DomainConfig.K_FLAG_START_DOMAIN_NEEDS_ADMIN_USER)) {
                CLILogger.getInstance().printDetailMessage(getLocalizedString("DomainStarted",
                                             new Object[] {domainName}));
            } else {
                CLILogger.getInstance().printDetailMessage(getLocalizedString("DomainReady",
                                             new Object[] {domainName}));
            }
            final boolean terse = getBooleanOption(TERSE);
            new DomainReporter(config, terse).report();
            long msec = System.currentTimeMillis() - start;

            /* convert milliseconds duration to xxx.y seconds, where y is rounded off properly.
             * E.g.
                27999 -> 28.0
                27449 --> 27.4
                27450 -> 27.5 
             */
            double sec = ((double)Math.round( ((double)msec) / 100.0)) / 10.0;
            CLILogger.getInstance().printDebugMessage(getLocalizedString("DomainStartTime",
                                             new Object[] { sec }));            
        }
        catch(Exception e)
        { 
            CLILogger.getInstance().printDetailMessage(e.getLocalizedMessage());            
            
			if(enableBackups)
			{
				if(doBackupMessage)
					checkOnBackup(config);
			}
            
            throw new CommandException(getLocalizedString("CannotStartDomain",
                       new Object[] {domainName}), e);
        }
    }
   
    
    /**
     *  An abstract method that Executes the command
     *  @throws CommandException
     */
    public void runCommand() throws CommandException, CommandValidationException
    {
        if (!validateOptions())
            throw new CommandValidationException("Validation is false");
        
        String domainName = null;
        try {
            domainName = getDomainName();
        } catch(Exception e)
        { 
            CLILogger.getInstance().printDetailMessage(e.getLocalizedMessage());
            domainName = domainName==null?getLocalizedString("Undefined"):domainName;
            throw new CommandException(getLocalizedString("CannotStartDomain",
                       new Object[] {domainName}), e);
        }
        startDomain(domainName);                        
    }


        /**
         *  This api checks and executes the upgrade commands.
         *  This api invokes checkIfVersion80().  If version is 8.0, then it will
         *  try to invoke asupgrade command.
         *  @domainName -- name of domain, this parameter is required to
         *  figure out the version of domain.
         *  @throws CommandException if error invoking asupgrade 
         **/
    private void checkAndExecuteUpgrade(String domainName) throws CommandException
    {
        final String domainDir = getDomainsRoot();
        CLILogger.getInstance().printDebugMessage("domainDir = " + domainDir);
        final String installDir = System.getProperty(SystemPropertyConstants.INSTALL_ROOT_PROPERTY);
        CLILogger.getInstance().printDebugMessage("installDir = " + installDir);
        CLILogger.getInstance().printDebugMessage("domainName = " + domainName);
        if (checkIfVersion80(domainName, domainDir, installDir)) {
                //if 8.0 or 8.1 version, then invoke asupgrade
            try {
                final String sPasswordFile = createPasswordFileText();
                final String[] upgradeCmd = new String[] {installDir + File.separator +
                                                      "bin" + File.separator +
                                                      "asupgrade", "-c", "-s",
                                                      domainDir+File.separator+domainName,
                                                      "-t", installDir, "--passwordfile",
                                                      sPasswordFile, "-noprompt" };

                ProcessExecutor pe = new ProcessExecutor(upgradeCmd, UPGRADE_TIMEOUT);
                pe.execute();  // timeout in 600sec or 10min
                Process process = pe.getSubProcess();
                int exitValue = process.waitFor();
                if (exitValue != 0) {
                    System.out.println("Please exeucte asupgrade manually to upgrade the configuration");
                    throw new CommandException(getLocalizedString("UpgradeFailed"));
                }
            }
            catch (Exception e) {
                    //e.printStackTrace();
                throw new CommandException(getLocalizedString("UpgradeFailed"), e);
            }
        }
    }
    

    /**
     *  This api check if the domain version is 8.0 or 8.1.
     *  @domainName - domain name to figure out the version
     *  @domainDir - domain directory
     *  @installDir - install directory
     *  @throws CommandExcpetion if error finding version
     **/
    private boolean checkIfVersion80(String domainName, String domainDir,
                                     String installDir) throws CommandException
    {
        final PEFileLayout layout = new PEFileLayout(config);
        final File domainFile = layout.getDomainConfigFile();

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        factory.setNamespaceAware(true);

        try {

            DocumentBuilder builder = factory.newDocumentBuilder();
            builder.setEntityResolver((org.xml.sax.helpers.DefaultHandler)Class.forName
                                      ("com.sun.enterprise.config.serverbeans.ServerValidationHandler").newInstance());
            Document adminServerDoc = builder.parse(domainFile);
            String publicID = adminServerDoc.getDoctype().getPublicId();
            String systemID = adminServerDoc.getDoctype().getSystemId();
            CLILogger.getInstance().printDebugMessage("publicID = " + publicID);
            CLILogger.getInstance().printDebugMessage("systemID = " + systemID);
                //if domain.xml systemID=sun-domain_1_0 then the version is 8.0
                //if domain.xml systemID=sun-domain_1_1 then the version is 8.1
            if (publicID.indexOf(APPLICATION_SERVER_8_0) != -1 &&
                (systemID.indexOf(DTD_FILE_8_0) != -1 ||
                 systemID.indexOf(DTD_FILE_8_1) != -1 )) {
                return true;
            } else {
                return false;
            }
        }
        catch (Exception e)
        {
                //do nothing for now
                //e.printStackTrace();
        }
        return false;
    }

        /**
         * create a temporary passwordfile.txt that contains
         * admin password and master password that is passed
         * to asupgrade command.
         */
    private String createPasswordFileText() throws Exception
    {
        //create a temp passwordfile
        final File fPasswordFile = File.createTempFile("passwordfile", null);
        fPasswordFile.deleteOnExit();
        PrintWriter out = new PrintWriter(new BufferedWriter(
                                          new FileWriter(fPasswordFile.toString())));
        out.println("AS_ADMIN_PASSWORD = " + (String)config.get(DomainConfig.K_PASSWORD));
        out.println("AS_ADMIN_MASTERPASSWORD = " + (String)config.get(DomainConfig.K_MASTER_PASSWORD));
        out.close();
        return fPasswordFile.toString();
    }
    
    

    /**
     *  Returns the log file location for a domain
     *  @throws CommandException
     */
    private String getDomainLogFile(String domainName) throws CommandException
    {
        String logFile = getDomainsRoot() + File.separator + domainName + 
                         File.separator + LOGS_DIR + File.separator + SERVER_LOG_FILE_NAME;
        return logFile;
    }

    private boolean isNotRunning(DomainsManager mgr, DomainConfig cfg) throws Exception
    {
        // note that checking for kInstanceRunningCode is not desired here
        
        InstancesManager im = mgr.getInstancesManager(cfg);
        int state = im.getInstanceStatus();

        return state == Status.kInstanceNotRunningCode;
    }

    private boolean isStopping(DomainsManager mgr, DomainConfig cfg) throws Exception
    {
        InstancesManager im = mgr.getInstancesManager(cfg);
        int state = im.getInstanceStatus();

        return state == Status.kInstanceStoppingCode;
    }

    private void checkOnBackup(DomainConfig config)
    {
        // the try/catch is extra protection because we're being
        // called from inside a catch block.

        if(config == null)
            return; // nothing we can do here...
        
        try
        {
            File domainsRoot    = new File(config.getRepositoryRoot());
            File domainRoot     = new File(domainsRoot, config.getDomainName());
            File configDir      = new File(domainRoot, "config");
            File domainxml      = new File(configDir, "domain.xml");
            File backup         = new File(configDir, "domain_bu.xml");
        
            if(backup.exists())
                pr("NoStartAdvice", backup.getAbsolutePath(), domainxml.getAbsolutePath());
        }
        catch(Exception e)
        {
        }
    }

    private void init() throws CommandException
    {
        mgr = getFeatureFactory().getDomainsManager();
        CLILogger.getInstance().printDetailMessage(getLocalizedString("StartingDomain",
                                         new Object[] {domainName}));
        if (!getBooleanOption(VERBOSE))
        {
            CLILogger.getInstance().printDetailMessage(getLocalizedString("LogRedirectedTo",
                new Object[] {getDomainLogFile(domainName)}));
        }
        config = getDomainConfig(domainName);
    }
    
    private void installAddons() {
        try {
            Class main = Class.forName(ADDONINSTALLER);
            Object obj = main.newInstance();
            Class[] types = new Class[] { String.class, String.class};
            Method method = main.getMethod(INSTALLERMETHOD, types);
            String installDir = System.getProperty(SystemPropertyConstants.INSTALL_ROOT_PROPERTY);
            String domainInstanceRoot = getDomainsRoot() + File.separator + getDomainName();
            Object[] args1 = new Object[] { installDir, domainInstanceRoot };
            method.invoke(obj,args1);
        }catch(Exception ex) {
            CLILogger.getInstance().printDetailMessage(ex.getLocalizedMessage());
        }
    
    }
    
    private void checkIfRunning() throws Exception
    {
        // note that isNotRunning declares 'throws Exception' !!!
        
        if(!isNotRunning(mgr, config))
        {
            // bnevins Oct 2004
            // This has officially been defined as an error.  So we have to throw an Exception.

            // bnevins Sept 2005
            // we don't want to print a message saying that they should try
            // using the backup config for this situation.
            throw new CommandException(getLocalizedString("CannotStartDomainAlreadyRunning",
                   new Object[] {domainName}));
        }
    }

    private void checkIfStopping() throws Exception
    {
        if(isStopping(mgr, config))
        {
            throw new CommandException(getLocalizedString("CannotStartStoppingDomain",
                   new Object[] {domainName}));
        }
    }

    private void validateDomain() throws DomainException
    {
        // if the next line throws -- print backup availability message
        mgr.validateDomain(config, true);
    }
    private void validateAdminPassword() throws CommandValidationException, DomainException, CommandException
    {
        //In PE, the admin user and password are options that are not needed and as
        //such are ignored.
        flags = mgr.getDomainFlags();
        
        if (flags.get(DomainConfig.K_FLAG_START_DOMAIN_NEEDS_ADMIN_USER)) {
            config.put(DomainConfig.K_USER, getUser());
            config.put(DomainConfig.K_PASSWORD, getPassword());  
            //Validate the admin user and password that was provided.
            if (getOption(S1ASCommand.PASSWORDFILE) != null ) 
                adminPwdAlias = RelativePathResolver.getAlias( (String)config.get(DomainConfig.K_PASSWORD));
            if (adminPwdAlias==null) {
                mgr.validateAdminUserAndPassword(config);
            }
        } 
    }

    private void validateMasterPassword() throws Exception
    {
        final String masterPassword = getMasterPassword(new RepositoryManager(), config);
        config.put(DomainConfig.K_MASTER_PASSWORD, masterPassword);

        mgr.validateMasterPassword(config);
        if (adminPwdAlias!=null) {
            String domainsRoot =  (String)config.get(DomainConfig.K_DOMAINS_ROOT);
            String keyStoreFile= domainsRoot+ File.separator + domainName +  File.separator + "config" + File.separator + 
                      PasswordAdapter.PASSWORD_ALIAS_KEYSTORE;
            PasswordAdapter p = 
                new PasswordAdapter(keyStoreFile, masterPassword.toCharArray());
            String clearPwd = p.getPasswordForAlias(adminPwdAlias);
            config.put(DomainConfig.K_PASSWORD, clearPwd);  
            mgr.validateAdminUserAndPassword(config);
        }
    }
    private void saveExtraPasswordOptions() throws CommandValidationException, CommandException, DomainException
    {
            String[] extraPasswordOptions = mgr.getExtraPasswordOptions(config);
            if (extraPasswordOptions != null) {
                config.put(DomainConfig.K_EXTRA_PASSWORDS, getExtraPasswords(extraPasswordOptions));
            }
    }

    private void saveCopyOfConfig(DomainConfig config)
    {
        File domainsRoot    = new File(config.getRepositoryRoot());
        File domainRoot     = new File(domainsRoot, config.getDomainName());
        File configDir      = new File(domainRoot, "config");
        File domainxml      = new File(configDir, "domain.xml");
        File backup         = new File(configDir, "domain_bu.xml");

        try
        {
            FileUtils.copy(domainxml.getAbsolutePath(), backup.getAbsolutePath());
            pr("ConfigBackedupOK", backup.getAbsolutePath());
        }
        catch(Exception e)
        {
            pr("ConfigBackedupNot", domainxml.getAbsolutePath(), backup.getAbsolutePath());
        }
    }
    
    private void pr(String id, Object o)
    {
        String s = getLocalizedString(id, new Object[]{ o } );
        CLILogger.getInstance().printDetailMessage(s);
    }

    private void pr(String id, Object o1, Object o2)
    {
        String s = getLocalizedString(id, new Object[]{ o1, o2 } );
        CLILogger.getInstance().printDetailMessage(s);
    }
}
