/*
 * 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.config.ConfigContext;
import com.sun.enterprise.config.ConfigException;
import com.sun.enterprise.config.ConfigFactory;
import com.sun.enterprise.config.serverbeans.ServerHelper;
import com.sun.enterprise.config.serverbeans.HttpListener;
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 com.sun.enterprise.util.OS;
import com.sun.enterprise.config.ConfigContext;
import com.sun.enterprise.config.ConfigContextFactory;
import java.io.File;
import java.lang.reflect.Method;
import java.util.BitSet;
/**
 *  This is a sample Deploy command
 *  @version  $Revision: 1.20 $
 */
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 String domainName;
    private DomainConfig config;
    private ConfigContext cc;
    private DomainsManager mgr;
    private String adminPwdAlias;

	// 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();
            if (!checkIfRunning())
            {
                checkIfStopping();

                doBackupMessage = true;
                validateDomain();
                doBackupMessage = false;
                validateAdminPassword();
                validateMasterPassword();
                saveExtraPasswordOptions();
                doBackupMessage = true;
                configureAddons();
                checkAndExecuteUpgrade(domainName);
                mgr.startDomain(config);

                if(enableBackups)
                {
                    saveCopyOfConfig(config);
                }

                if (startupNeedsAdminCreds()) {
                    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, cc).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
    {
        validateOptions();

        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;
                /*Need to prefix the upgrade command with CMD /c in case of Windows*/
                if(OS.isWindows())
                        upgradeCmd = new String[]    {"CMD",
                                                        "/c",
                                                      installDir + File.separator +
                                                      "bin" + File.separator +
                                                      "asupgrade", "-c", "-s",
                                                      domainDir+File.separator+domainName,
                                                      "-t", installDir +File.separator+ "domains",
                                                      "--adminuser",
                                                      getAdminUser(),
                                                      "--passwordfile",
                                                      sPasswordFile,
                                                      "-noprompt" };
                else
                            upgradeCmd = new String[] {installDir + File.separator +
                                                      "bin" + File.separator +
                                                      "asupgrade", "-c", "-s",
                                                      domainDir+File.separator+domainName,
                                                      "-t", installDir +File.separator+ "domains",
                                                      "--adminuser",
                                                      getAdminUser(),
                                                      "--passwordfile",
                                                      sPasswordFile,
                                                      "-noprompt" };

                ProcessExecutor pe = new ProcessExecutor(upgradeCmd, UPGRADE_TIMEOUT);
                /*
                 * ProcessExecutor's constructor replaces all the '/'s with '\' in case the OS is Windows.
                 * We don't want that for CMD /c. Hence need to replace it again
                 */
                if(OS.isWindows())
                        upgradeCmd[1] ="/c";

                System.out.println(getLocalizedString("StartingUpgrade"));
                pe.execute();  // timeout in 600sec or 10min
                Process process = pe.getSubProcess();
                int exitValue = process.waitFor();
                if (exitValue != 0) {
                    System.out.println(getLocalizedString("UpgradeFailed"));
                    throw new CommandException(getLocalizedString("UpgradeFailed"));
                }else {
                    /*
                     * If the upgrade was successful, need to replace the domainconfig since domainsroot has changed.
                     * DomainConfig has no public interfaces to modify the domainsroot alone.
                     */
                    config.put("domains.root", installDir +File.separator+ "domains");
                    System.setProperty("com.sun.aas.instanceRoot", installDir +File.separator+ "domains" + File.separator + domainName);
                    DomainConfig dconfig= new DomainConfig(config.getDomainName(), installDir +File.separator+ "domains");
                    dconfig.putAll(config);
                    dconfig.put("domains.root", installDir +File.separator+ "domains");
                    config =dconfig;
                    //refresh the config context
                    ConfigContext configContext = ConfigContextFactory.getConfigContextFromCache(config.get("domains.root") + File.separator + domainName + File.separator + "config" + File.separator + "domain.xml");
                    if (configContext != null) configContext.refresh(true);
                    System.out.println(getLocalizedString("UpgradeSuccessful"));
                }
            }
            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, ConfigException
    {
        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);
        final String xmlPath = new PEFileLayout(config).
            getDomainConfigFile().getAbsolutePath();
        this.cc = ConfigFactory.
            createConfigContext(xmlPath, true, false, false, true);

        
            //get port from ConfigFactory
        final HttpListener admin = ServerHelper.getHttpListener(cc,
                                   SystemPropertyConstants.DEFAULT_SERVER_INSTANCE_NAME,
                                   ServerHelper.ADMIN_HTTP_LISTNER_ID);
        if (admin != null) {
            final String port = admin.getPort();
                //set port and host options so that password can be retrieved from .asadminpass
            setOption("port", port);
            setOption("host", "localhost");            
        }
    }
    

    private void configureAddons() {
        try {
            AddonControl ac = new AddonControl();
            String domainInstanceRoot = getDomainsRoot() + File.separator + getDomainName();
            ac.configureDAS(new File(domainInstanceRoot));
        }catch(Throwable t) {
            CLILogger.getInstance().printDetailMessage(t.getLocalizedMessage());
        }

    }

        /**
         * If already running, then return true.
         */
    private boolean 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.
            CLILogger.getInstance().printDetailMessage(getLocalizedString("CannotStartDomainAlreadyRunning",
                   new Object[] {domainName}));
            return true;
        }
        return false;
    }

    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.

        if (startupNeedsAdminCreds()) {
            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);
    }
    private boolean startupNeedsAdminCreds() {
        try {
            return ( ServerHelper.isClusterAdminSupported(this.cc) );
        } catch (final ConfigException ce) {
            throw new RuntimeException (ce); //Unrecoverable
        }
    }
}
