/*
 *  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 *  Copyright 2000-2009 Sun Microsystems, Inc. All rights reserved. 
 *
 *  The contents of this file are subject to the terms of either the GNU
 *  General Public License Version 2 only ("GPL") or the Common Development
 *  and Distribution License ("CDDL") (collectively, 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/CDDL+GPL.html
 *  or mq/legal/LICENSE.txt.  See the License for the specific language
 *  governing permissions and limitations under the License.
 * 
 *  When distributing the software, include this License Header Notice in each
 *  file and include the License file at mq/legal/LICENSE.txt.  Sun designates
 *  this particular file as subject to the "Classpath" exception as provided by
 *  Sun in the GPL Version 2 section of the License file that accompanied this
 *  code.  If applicable, add the following below the License Header, with the
 *  fields enclosed by brackets [] replaced by your own identifying information:
 *  "Portions Copyrighted [year] [name of copyright owner]"
 * 
 *  Contributor(s):
 * 
 *  If you wish your version of this file to be governed by only the CDDL or
 *  only the GPL Version 2, indicate your decision by adding "[Contributor]
 *  elects to include this software in this distribution under the [CDDL or GPL
 *  Version 2] license."  If you don't indicate a single choice of license, a
 *  recipient has the option to distribute your version of this file under
 *  either the CDDL, the GPL Version 2 or  to extend the choice of license to
 *  its licensees as provided above.  However, if you add GPL Version 2 code
 *  and therefore, elected the GPL Version 2 license, then the option applies
 *  only if the new code is made subject to such option by the copyright holder. 
 *
 *  LocalBrokerRunner.java
 *
 *  @(#)LocalBrokerRunner.java	1.23 07/12/07
 */

package com.sun.messaging.jms.ra;

import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.Vector;
import java.util.Properties;
import java.util.Enumeration;
import java.util.StringTokenizer;

import javax.jms.JMSSecurityException;

import com.sun.messaging.jmq.jmsspi.JMSAdmin;
import com.sun.messaging.jmq.jmsspi.JMSAdminFactory;


/**
 *  Runs a local broker instance through
 *  the JMSSPI interfaces exposed for out-of-process
 *  broker lifecycle control
 */

public class LocalBrokerRunner {

    /* Properties to be used by the embedded broker */
    //private Properties brokerProps = null;

    /* The JMS Admin SPI related objects */
    private JMSAdmin jmsadmin = null;
    private JMSAdmin tjmsadmin = null;

    /* The admin username that was configured when starting the broker */
    private String adminUsername;

    /* The admin password file that was configured when starting the broker */
    private String adminPassFile;

    /* The cmd line args for starting the broker */
    String[] brokerArgs;

    /* The bin directory for starting the broker */
    String brokerBinDir;

    /* The var directory to be used by the broker */
    String brokerVarDir;

    /* The instance name to be used for starting the broker */
    String brokerInstanceName;

    /* The full path to the broker log file to be checked for broker start failures */
    String brokerLogFilename;

    /* The timeout allowed when starting the broker */
    int brokerStartTimeout;

    /* Indicates the broker was started by AppServer(LOCAL) - ok to shutdown */
    private boolean startedByAS = false;
    private boolean startingRMIRegistry = false;

    /* BrokerURL for this LocalBrokerRunner */
    private String brokerURL;

    /* The main broker thread */
    //private Thread bt = null;

    /* Loggers */
    private static transient final String _className =
            "com.sun.messaging.jms.ra.LocalBrokerRunner";
    protected static transient final String _lgrNameLifecycle =
            "javax.resourceadapter.mqjmsra.lifecycle";
    protected static transient final Logger _loggerL =
            Logger.getLogger(_lgrNameLifecycle);
    protected static transient final String _lgrMIDPrefix = "MQJMSRA_LB";
    protected static transient final String _lgrMID_EET = _lgrMIDPrefix + "1001: ";
    protected static transient final String _lgrMID_INF = _lgrMIDPrefix + "1101: ";
    protected static transient final String _lgrMID_WRN = _lgrMIDPrefix + "2001: ";
    protected static transient final String _lgrMID_ERR = _lgrMIDPrefix + "3001: ";
    protected static transient final String _lgrMID_EXC = _lgrMIDPrefix + "4001: ";

    public
    LocalBrokerRunner(
        String brokerInstanceName, String brokerBindAddress, int brokerPort,
        String brokerHomeDir, String brokerLibDir, String brokerVarDir, String brokerJavaDir,
        String brokerExtraArgs,
        boolean useJNDIRMIServiceURL, int rmiRegistryPort, boolean startRMIRegistry,
        boolean useSSLJMXConnector, int brokerStartTimeout,
        String adminUsername, String adminPassword, String adminPassFile, Properties sysProps)
    throws Exception
    {
        Object params[] = new Object[15];
        params[0] = brokerInstanceName;
        params[1] = brokerBindAddress;
        params[2] = Integer.toString(brokerPort);
        params[3] = brokerHomeDir;
        params[4] = brokerLibDir;
        params[5] = brokerVarDir;
        params[6] = brokerJavaDir;
        params[7] = brokerExtraArgs;
        params[8] = new Boolean(useJNDIRMIServiceURL);
        params[9] = new Integer(rmiRegistryPort);
        params[10] = new Boolean(startRMIRegistry);
        params[11] = new Boolean(useSSLJMXConnector);
        params[12] = adminUsername;
        params[13] = adminPassFile;
        params[14] = sysProps;

        this.adminUsername = adminUsername;

        _loggerL.entering(_className, "constructor()", params);

        this.brokerURL = ((brokerBindAddress == null) ? "localhost" : brokerBindAddress ) + ":" + brokerPort;

        jmsadmin = ((JMSAdminFactory)(new com.sun.messaging.jmq.admin.jmsspi.JMSAdminFactoryImpl())).getJMSAdmin(
                                brokerURL, adminUsername, adminPassword);
        tjmsadmin = ((JMSAdminFactory)(new com.sun.messaging.jmq.admin.jmsspi.JMSAdminFactoryImpl())).getJMSAdmin(
                                brokerURL, "admin", "admin");

        checkVersion(jmsadmin);

        this.brokerStartTimeout = brokerStartTimeout;

        Vector<String> v = new Vector<String>();

        if (brokerJavaDir != null) {
            v.add("-javahome");
            v.add(brokerJavaDir);
        }

        //Add extra args first; explicit config will override args 
        if (brokerExtraArgs != null && !("".equals(brokerExtraArgs)) ) {
            StringTokenizer st = new StringTokenizer(brokerExtraArgs, " ");
            while (st.hasMoreTokens()) {
                String t = st.nextToken();
                v.add(t);
            }
        }

        String key;
        String val;
        //System.out.println("SJSMQRA_EB:DebugENM_BEGIN>>>>>>>>>>>>>>>>");
        for (Enumeration e = sysProps.keys() ; e.hasMoreElements() ;) {
            key = (String)e.nextElement();
            //System.out.println("SJSMQRA_EB:DebugENM_Key="+key);
            if (key != null) {
                val = (String)sysProps.getProperty(key);
                if (val != null)
                    v.add("-D"+key+"="+val);
                //System.out.println("SJSMQRA_EB:DebugENM_Val="+val);
                //v.add("-D\""+key+"="+val+"\"");
            } else {
                //System.out.println("SJSMQRA_EB:DebugENM_Val=null");
            }
        }

        this.brokerInstanceName = brokerInstanceName;
        this.brokerBinDir = brokerHomeDir + java.io.File.separator + "bin";
        this.brokerVarDir = brokerVarDir;
        this.brokerLogFilename = brokerVarDir
                                     + java.io.File.separator + "instances"
                                     + java.io.File.separator + brokerInstanceName
                                     + java.io.File.separator + "log"
                                     + java.io.File.separator + "log.txt" ;
        this.startedByAS = false;
        this.startingRMIRegistry = false;

        if (brokerVarDir != null) {
            v.add("-varhome");
            v.add(brokerVarDir);
        }

        if (useJNDIRMIServiceURL == true) {
            if (startRMIRegistry == true) {
                v.add("-startRmiRegistry");
                this.startingRMIRegistry = true;
            } else {
                v.add("-useRmiRegistry");
                this.startingRMIRegistry = false;
            }
            v.add("-rmiRegistryPort");
            v.add(Integer.toString(rmiRegistryPort));
        }


        if ((adminPassFile != null) &&
            !("".equals(adminPassFile)) &&
            (adminUsername != null) && 
            !("".equals(adminUsername)) ) {

            v.add("-Dimq.imqcmd.user="+adminUsername);
            v.add("-passfile");
            v.add(adminPassFile);

            //Set adminPassFile to non-null
            this.adminPassFile = adminPassFile;
        }
        if (brokerBindAddress != null && !("localhost".equals(brokerBindAddress)) ) {
            v.add("-Dimq.hostname="+brokerBindAddress);
        }
        //Add -save cli option to have the broker save properties at startup
        //The properties that are to be saved are defined by the broker
        //Enables MQ cli utilities that depend on the configuration to be run
        //after AS has shut down.
        v.add("-save");
 
        brokerArgs = (String []) v.toArray(new String[0]);

        //System.out.println("SJSMQRA_LB:DebugDump_BEGIN>>>>>>>>>>>>>");
        //System.out.println("SJSMQRA_LB:brokerArgs="+brokerArgs);
        //System.out.println("SJSMQRA_LB:LocalBroker:SystemProps="+System.getProperties().toString());
        //System.out.println("SJSMQRA_LB:DebugDump___END>>>>>>>>>>>>>");

    }

    protected synchronized void
    start()
    throws Exception
    {
        _loggerL.entering(_className, "start()");
        boolean already_running = false;
        String running_varhome = null;

        if (this.startingRMIRegistry) {
            //Condition this on the AppServer making MQ starting an RMI Registry
            //Else if we use an existsing broker that was uses AppServer's RMI
            //Registry, no asadmin command will work as the stubs will not
            //have been bound by the MQ broker into the new App Server Registry
            _loggerL.info(_lgrMID_INF+"Looking for Broker Running at:"+
                        this.brokerURL);
            try {
                jmsadmin.pingProvider();
                _loggerL.info(_lgrMID_INF+"Detected Broker Running at:"+
                        this.brokerURL);
                already_running = true;
            } catch (Exception e){
                already_running = false;
            }
            if (already_running) {
                this.startedByAS = false;
                try {
                    jmsadmin.connectToProvider();
                    running_varhome = jmsadmin.getProviderVarHome();
                } catch (Exception e){
                    //can't get the varhome directory
                    //will fall through to assume not startedbyAS and fail start
                }
                _loggerL.info(_lgrMID_INF+"Detected Broker VAR directory="+
                        running_varhome);
                if (running_varhome != null && !("".equals(running_varhome)) &&
                        (running_varhome.equals(this.brokerVarDir))){
                        //Ok to connect to the broker; This was started by us
                    this.startedByAS = true;
                    //Ok to attempt to connect and use it
                } else {
                    //This broker cannot be used by AS since it has a different
                    //varhome directory than the one AS is asking the broker to use
                    //Using it could cause problems
                    //Therefore we need to abort the start and report a failure
                    String excerrmsg = _lgrMID_EXC+"start:Broker running "+
                            "at:" + this.brokerURL +
                            " has a different var directory of:" +
                            running_varhome +
                            ":Failing ra.start()";
                    this._loggerL.severe(excerrmsg);
                    throw new Exception(excerrmsg);
                }
            } else {
                //broker not running at the designated port; attempt to start one
                jmsadmin.startProvider(brokerBinDir, brokerArgs, brokerInstanceName);
                this.startedByAS = true;
            }
        } else {
            jmsadmin.startProvider(brokerBinDir, brokerArgs, brokerInstanceName);
            this.startedByAS = true;
        }

        //Now try to connect to the provider
        boolean ready = false;
        long firstPingTime = java.lang.System.currentTimeMillis();

        while (true) {
            try {
                //Wait a sec before attempting connection
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {}
                //Change to make the connection at start()and keep it alive
                //till stop(), else trying to connect at stop() when broker
                //store fails will fail and broker will remain running
                //jmsadmin.pingProvider();
                if (!already_running){
                    jmsadmin.connectToProvider();
                }
                ready = true;
                break;
            } catch (JMSSecurityException jmsse) {
                //Here because the auth info is incorrect  and the broker was able to be started.
                //i.e. they did not use adminPassFile to prevent broker startup with wrong auth
                //Attempt to shut down broker with the default admin/admin username/password
                //And then trow an Exception back to ra.start()

                Exception startEx = new Exception(_lgrMID_EXC+"start:Unable to ping Broker due to authentication error for user " + adminUsername + " : shutting down broker.");
                startEx.initCause(jmsse);
                try {
                    tjmsadmin.connectToProvider();
                    tjmsadmin.shutdownProvider();
                } catch (Exception spe) {
                    _loggerL.warning(_lgrMID_EXC+"start:Exception on LOCAL broker shutdown after connect auth failure:msg="+spe.getMessage());
                    //spe.printStackTrace();
                }
                throw startEx;
            } catch (Exception e) {
                //If here, then broker cannot be running on the given port
                //If adminPassFile was given, then likely that auth is wrong
                //If adminPassFile was NOT given, then broker wasn't able to start for some other reason

                if (java.lang.System.currentTimeMillis() - firstPingTime >= brokerStartTimeout) {
                    _loggerL.severe(_lgrMID_EXC+"start:Ping broker failed " + (java.lang.System.currentTimeMillis() - firstPingTime) + " millis after broker start performed. Failing ra.start()");
                    _loggerL.warning(_lgrMID_EXC+"start:Aborting:Check Broker Log File at:"+brokerLogFilename);

                    if (adminPassFile != null) {
                        _loggerL.warning(_lgrMID_EXC+"start:Aborting:Check Broker Password File at:"+adminPassFile);
                    }
                    break;
                }
            }
        }
        if (!ready) {
            throw new RuntimeException(_lgrMID_EXC+"start:Aborted:Unable to ping Broker within " + brokerStartTimeout + " millis (startTimeOut)");
        }
    }

    protected synchronized void
    stop()
    {
        _loggerL.entering(_className, "stop()");
        if (jmsadmin != null) {
            if (!this.startedByAS){
                //We cannot shut down this broker as it was not started by AS
                //It does not have the same varhome directory
                _loggerL.warning(_lgrMID_WRN+"stop:" +
                        "Skipping LOCAL broker shutdown:"+
                        "Broker not started by App Server");
                return;
            }
            try {
                //Chg: use jmsadmin connection that was acquired at start()
                //jmsadmin.connectToProvider();
                jmsadmin.shutdownProvider();
            } catch (Exception bse) {
                _loggerL.warning(_lgrMID_EXC+"stop:Exception on LOCAL broker shutdown:msg="+bse.getMessage());
                if (_loggerL.isLoggable(Level.FINER)) {
                    bse.printStackTrace();
                }
            }
        }
    }

    private static void
    checkVersion(JMSAdmin jmsadm)
    {
        float ver_f = 0.0f;
        String ver_s = "?.?";
        try {
            ver_s = jmsadm.getVersion();
            ver_f = Float.parseFloat(ver_s);
        } catch (Exception e) {
            throw new RuntimeException("Error while parsing SJSMQ SPI version string (" + ver_s + ").");
        }
        if (ver_f < 2.0 || ver_f >= 3.0) {
            throw new RuntimeException("Incorrect SJSMQ SPI version detected (" + ver_s + ").");
        }
    }
}
