/*!
    @file           DBMCli_EventDispatcherMain.cpp
    @author         MarcW
    @brief          Event Dispatcher  - main function and class

\if EMIT_LICENCE

    ========== licence begin  GPL
    Copyright (c) 2003-2005 SAP AG

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end


\endif
*/

/* 
  -----------------------------------------------------------------------------
  includes
  -----------------------------------------------------------------------------
*/
#include <stdio.h>
#include <signal.h>

#include "hin100.h"
#include "hin105.h"
#include "hni33.h"
#include "SAPDB/RunTime/RTE_ISystem.hpp"
#include "SAPDB/ToolsCommon/Tools_ParameterParser.hpp"
#include "SAPDB/ToolsCommon/Tools_DynamicUTF8String.hpp"
#include "SAPDB/ToolsCommon/Tools_Properties.hpp"
#include "SAPDB/ToolsCommon/Tools_PipeCall.hpp"
#include "SAPDB/DBM/Cli/DBMCli_DateTime.hpp"
#include "SAPDB/DBM/Cli/DBMCli_EventingBranding.hpp"
#include "SAPDB/DBM/Cli/DBMCli_EventListener.hpp"
#include "SAPDB/DBM/Cli/DBMCli_Logo.hpp"
#include "SAPDB/DBM/Cli/DBMCli_Parameter.hpp"
#include "SAPDB/DBM/Cli/DBMCli_Stuff.hpp"

/*! @brief return code of main function if program fails */
#define PRG_FATAL        1001

/*! @brief max numner of handlers for an event */
static const int maxHandlers(128);

/*!
    @brief Event Dispatcher

    This Event Dispatcher class offers the possibility to connect to some MaxDB database and listen
    to its kernel events. By configuration, the dispatcher starts other programs (so called event
    handlers) and passes eventually required connect data and event data to them.

    The configuration is read from a configuration file. The activities of the dispatcher can be
    logged in a logfile.

    @see DBMCli_EventHandler
*/
class DBMCli_EventDispatcher : public DBMCli_EventListener
{
public:

    /*!
        @brief Constructor

        Constructor to which all runtime information can be passed. If not configuration file
        is passed, the configuration is first searched in the DB's rundirectory. If no configuration
        can be found there, <instroot>/env is seached for a configuration file If no log file is
        passed, a logfile in the DB's rundirectory will be written.

        @param aNode        [IN] DB host (connect data)
        @param aDb          [IN] DB name (connect data)
        @param aUserpw      [IN] user/pw combination (connect data)
        @param aCfgfile     [IN] path to configuration file
        @param aLogfile     [IN] path to log file
        @param aReconnect   [IN] if true, reconnect after loss of connection
        @param aQuietmode   [IN] if true, only errors are logged
        @param oMessageList [OUT] message list
    */
    DBMCli_EventDispatcher
            (const DBMCli_String& aNode,
            const DBMCli_String& aDb,
            const DBMCli_String& aUserpw,
            const DBMCli_String& aCfgfile,
            const DBMCli_String& aLogfile,
            const SAPDB_Bool& aReconnect,
            const SAPDB_Bool& aQuietmode,
            SAPDBErr_MessageList & oMessageList )
            : dispatcherprop(),
              m_userpw(aUserpw),
              m_NodeName(aNode),
              quiet(aQuietmode),
              m_Reconnect(aReconnect),
              m_Logger(tin100_GlobalLog::createObject()),
              m_doLogging(false),
              m_EvtCountSelf(0),
              m_EvtCountKernel(0),
              m_bRunning(false),
              DBMCli_EventListener(aNode, aDb, aUserpw, oMessageList) {

        // are we connected at all?
        if( !IsConnected() ) {
            // no, report error if there is one...
            if( !oMessageList.IsEmpty() ) {
                char buf[32];
                SAPDBErr_MessageList* pMsg(&oMessageList);
                while( pMsg != NULL ) {
                    sprintf(buf, "%d", pMsg->ID());
                    DumpError("could not connect to DB:\n%s (errorcode %s)\n", pMsg->Message(), buf);
                    pMsg = pMsg->NextMessage();
                }
            }
            return;
        }
                  
        //nodename
        if( m_NodeName.IsEmpty() )
            m_NodeName = RTE_ISystem::Instance().GetLocalNodeName();

        // necessary stuff to compute runtirectory and <instroot>/env folders
        DBMCli_Parameters params(GetParameters());
            SAPDBErr_MessageList msgList;
        NodeInfo().Refresh(msgList);
        const DBMCli_String instroot(NodeInfo().InstRoot());
        const char cSlash((NodeInfo().OS().Compare("UNIX") == 0) ? '/' : '\\');
        
        // log file
        if( m_Logger != NULL ) {
            DBMCli_String theLogfile(aLogfile);
            tsp00_Pathc szPath;
            if( theLogfile.GetLength() <= 0 ) {
                theLogfile = params.GetValueAsString(DBMCli_EventDispatcher::sRundirectory)
                           + cSlash
                           + DBMCli_EventDispatcher::sPrtfile;
            }
            // theLogfile contains now some filen ame (either default of the passed one)
            szPath.rawAssign((const char*) theLogfile);
            // create a sequential log file
            tin105_SequentialLogFile* logFile =
                new tin105_SequentialLogFile(szPath, LOG_ALL_MIN105);
            if( logFile != NULL ) {
                m_Logger->addFile(logFile, true);
                // do the logging only if logger could be created and logfile could be added and
                // created.
                m_doLogging = logFile->checkFile();
            }
        }
                  
        // config file
        Tools_DynamicUTF8String cfg;
        if( aCfgfile.GetLength() > 0 ) {
            // use passed logfile
            cfg = (const char*) aCfgfile;
            m_bRunning = dispatcherprop.load(cfg);
        }
        else {
            // check rundirectory first
            DBMCli_String propertiesFolder(
                params.GetValueAsString(DBMCli_EventDispatcher::sRundirectory));
            propertiesFolder += cSlash;
            cfg = (const char*)(propertiesFolder + DBMCli_EventDispatcher::sPropfileName);
            m_bRunning = dispatcherprop.load(cfg);
            if( !m_bRunning ) {
                // loading failed, try env directory
                cfg = (const char*)(instroot +
                    cSlash +
                    DBMCli_EventDispatcher::sEnvdir +
                    cSlash +
                    DBMCli_EventDispatcher::sPropfileName);
                m_bRunning = dispatcherprop.load(cfg);
            }
        }

        if( !(m_bRunning && oMessageList.IsEmpty()) ) {
            DumpError("\nEvent Dispatcher: aborting!");
            DumpError("reason:");
            if( !m_bRunning ) {
                DumpError("RTE message: %s", (const char*)dispatcherprop.getReason().StrPtr());
                DumpError("config file: %s", (const char*)cfg.StrPtr());
            }
            if( !oMessageList.IsEmpty() ) {
                char buf[32];
                SAPDBErr_MessageList* pMsg(&oMessageList);
                while( pMsg != NULL ) {
                    sprintf(buf, "%d", pMsg->ID());
                    DumpError("%s (errorcode %s)\n", pMsg->Message(), buf);
                    pMsg = pMsg->NextMessage();
                }
            }
            return;
        }

        // succesful end of cunstructor
        DumpInfo("\nusing configuration: %s", (const char*)cfg.StrPtr());

        // create and send a dispatcher start event
        static char buf[128];
        sprintf(buf, (const char*)EVT_TEXT_DISPATCHERSTART, (const char*)m_NodeName, (const char*)DatabaseName());
        DBMCli_Event dispStartEvent(
            EVT_NAME_DISPATCHERSTART,
            DBMCli_Event::prio_low,
            buf,
            m_EvtCountSelf++);
        EventProc(dispStartEvent);
}

    /*!
        @brief destructor

        Delete logger object
    */
    ~DBMCli_EventDispatcher() {
        delete m_Logger;
    }

    bool isListening() const {
        return m_bRunning;
    }

    /*! @brief implements the event listening loop  */
    bool Run() {
        SAPDBErr_MessageList& msgList(GetMsgObj());
        msgList.ClearMessageList();

        bool doListen(true);
        while ( doListen ) {

            if (DBMCli_Session::Execute("event_wait", msgList)) {
                //event_wait returned without error, so we
                //can create an event object and handle it
                DBMCli_Event oEvent(DBMCli_Session::GetResult());

                doListen = EventProc(oEvent);

                // check if we missed an event...
                if( m_EvtCountKernel == 0 )
                    m_EvtCountKernel = oEvent.GetCount();
                else {
                    if( m_EvtCountKernel + 1 < oEvent.GetCount() ) {
                        // not good
                        for( m_EvtCountKernel++; m_EvtCountKernel < oEvent.GetCount(); m_EvtCountKernel++ )
                            // create a special event for all missed events
                            HandleMissedEvent();
                    }
                    else {
                        // good
                        m_EvtCountKernel++;
                    }
                }
            }
            else {
                //error occurred while listening
                //create event object from information in msgList
                static char buf[128];
                NextActionType action(HandleListenError());
                switch( action ) {
                    case WaitListening:
                        //sleep for a while...
                        RTE_ISystem::DoSleep(300);
                        //no, I don't want a break here
                    case GoonListening:
                        doListen = true;
                        break;
                    default:
                        doListen = false;
                        // create and send a dispatcher stop event
                        sprintf(buf, (const char*)EVT_TEXT_DISPATCHERSTOP, (const char*)m_NodeName, (const char*)DatabaseName());
                        DBMCli_Event dispStopEvent(
                            EVT_NAME_DISPATCHERSTOP,
                            DBMCli_Event::prio_low,
                            buf,
                            m_EvtCountSelf++);
                        EventProc(dispStopEvent);
                        break;
                }
            }
            msgList.ClearMessageList();
        } // end while
        return msgList.IsEmpty();
    } // end DBMCli_EventListener::Run

  /*!
      @brief called if an event is received

      Here, the actual dispatching takes place: The configuration is evaluated and for each handler
      found there, the variables from the configuration are replaced with their actual values.
      @param oEvent kernel event received from database
      @return indicator whether event listening should continue (always true here)
  */
  bool EventProc ( const DBMCli_Event& oEvent )
    {
        static char szCommandTemplate[1024];
        static char szCommand[1024];
        static char szBuf[128];

        // look up configuration
        int num(0);
        DBMCli_String* handlers[maxHandlers];
        getHandlersForEvent(oEvent, num, handlers );

        for( int i=0; i<num; i++ ) {
            // replace variavbles from command line
            replaceVar(*handlers[i], DBMCli_EventDispatcher::DBNAME, (const char*) DatabaseName());
            replaceVar(*handlers[i], DBMCli_EventDispatcher::DBMUSERPW, (const char*) m_userpw);
            replaceVar(*handlers[i], DBMCli_EventDispatcher::EVTALL, (const char*) oEvent.serialize());
            replaceVar(*handlers[i], DBMCli_EventDispatcher::EVTDATE, (const char*) oEvent.GetDate().Get(DBMCLI_DT_INT_DATE_FMT));
            replaceVar(*handlers[i], DBMCli_EventDispatcher::EVTNAME, (const char*) oEvent.GetName());
            replaceVar(*handlers[i], DBMCli_EventDispatcher::EVTTIME, (const char*) oEvent.GetTime().Get(DBMCLI_DT_INT_TIME_FMT));
            sprintf(szBuf, "%d", oEvent.GetValue1());
            replaceVar(*handlers[i], DBMCli_EventDispatcher::EVTVAL1, szBuf);
            sprintf(szBuf, "%d", oEvent.GetValue2());
            replaceVar(*handlers[i], DBMCli_EventDispatcher::EVTVAL2, szBuf);
            // get host name even if it was not passed as parameter
            replaceVar(*handlers[i], DBMCli_EventDispatcher::SERVERNAME, m_NodeName);
            sprintf(szBuf, "%d", oEvent.GetPriority());
            replaceVar(*handlers[i], DBMCli_EventDispatcher::EVTPRIO, szBuf);
            replaceVar(*handlers[i], DBMCli_EventDispatcher::EVTTEXT, (const char*) oEvent.GetText());
            replaceVar(*handlers[i], DBMCli_EventDispatcher::EVTDESC, (const char*) oEvent.GetDescription());
            sprintf(szBuf, "%d", oEvent.GetCount());
            replaceVar(*handlers[i], DBMCli_EventDispatcher::EVTCOUNT, szBuf);

            // actually call the handler
            doCall(handlers[i]);
            // delete the command line from buffer
            delete handlers[i];
        }

        if( num == 0 ) {
            DumpInfo("\nevent not dispatched : %s", (const char *) oEvent.GetName());
        } else {
            DumpInfo("\nevent dispatched : %s", (const char *) oEvent.GetName());
        }
        return true;
    }

    /*!
        @brief dump information message

        If not quiet mode is active, message is printed to stdout. If logging was
        initialized successfully, message is also written to log.
        If format string starts with a '\n' char, a new message will be written to logfile,
        otherwise the old one will be continued. A leading '\n' will always be cut off the
        format string.
        @param format [IN] format string like in printf
        @param s0     [IN] string argument (used from format string)
        @param s1     [IN] string argument (used from format string)
        @param s2     [IN] string argument (used from format string)
        @param s3     [IN] string argument (used from format string)
        @param s4     [IN] string argument (used from format string)
    */
    void DumpInfo(const char* format,
                  const char* s0 = NULL,
                  const char* s1 = NULL,
                  const char* s2 = NULL,
                  const char* s3 = NULL,
                  const char* s4 = NULL);

    /*!
        @brief dump error message

        Message is printed to stderr. If logging was
        initialized successfully, message is also written to log.
        If format string starts with a '\n' char, a new message will be written to logfile,
        otherwise the old one will be continued. A leading '\n' will always be cut off the
        format string.
        @param format [IN] format string like in printf
        @param s0     [IN] string argument (used from format string)
        @param s1     [IN] string argument (used from format string)
        @param s2     [IN] string argument (used from format string)
        @param s3     [IN] string argument (used from format string)
        @param s4     [IN] string argument (used from format string)
    */
    void DumpError(const char* format,
                  const char* s0 = NULL,
                  const char* s1 = NULL,
                  const char* s2 = NULL,
                  const char* s3 = NULL,
                  const char* s4 = NULL);

private:
    /*!
        @brief action to take after creating own events

        After creating own events, it can be necessary to either
        go on listeing immediately, stop listening or to wait until
        trying to go on listening, because there is no connection.
    */
    typedef enum {
        GoonListening, /*!< go on immediately */
        WaitListening, /*!< wait before listening */
        StopListening  /*!< stop listening completely */
    } NextActionType;



    /*! @brief the configuration */
    Tools_Properties dispatcherprop;
    /*! @brief user/pw combination */
    DBMCli_String m_userpw;
    /*! @brief the DB host name */
    DBMCli_String m_NodeName;
    /*! @brief could configuration be read => can we really dispatch? */
    bool m_bRunning;

    /*!
        @brief event handlers to pass parameters to via pipe

        The last cell of this array must be filled with an empty string.
        The programs listed here will receive their parameters via pipe, so
        on unix platforms no user/pw is visible in process list.
    */
    static const DBMCli_String m_ViaPipe[4];

    /*!
        @brief call the passed command line

        If the event handler is contained in the list of handlers to be passed
        its parameters via pipe, this is done, otherwise parameters are passed
        on the command line.
        @parameter handler [IN] command line to be executed
        @see m_ViaPipe
    */
    void doCall(DBMCli_String* handler ) {
        if( handler == NULL )
            return;

        // check if we must pass arguments via pipe
        int count(0);
        bool pipeCall(false);
        while( !pipeCall && m_ViaPipe[count].GetLength() >= 0 ) {
            pipeCall = handler->Left(m_ViaPipe[count].GetLength()) == m_ViaPipe[count];
            count++;
        }

        if( pipeCall ) {
            // okay pipe is requested
            Tools_PipeCall::CallProgram((const char*) *handler, Tools_PipeCall::CallAsynchron);
        }
        else {
            // no pipe
            tni33_ABackgroundProcess proc((const char*) *handler);
        }
    }

    /*!
        @brief get all command lines for one event

        @param oEvent   [IN] event for which handlers must be returned
        @param num      [OUT] number of handlers
        @param handlers [OUT] array of command lines
    */
    void getHandlersForEvent(const DBMCli_Event &oEvent,
                int& num, DBMCli_String** handlers);
    
    /*!
        @brief replace a variable in a command line

        After the call, all variables in str will be replaced
        @param str     [IN/OUT] command line containing variables
        @param varName [IN] name of variable
        @param varVal  [IN] value to be put there instead
    */
    void replaceVar( DBMCli_String& str, const char* varName, const char* varVal);

    /*!
        @brief create own events

        checks the message list, that was generated and creates events.
        @return indicator for listening loop what to do next.
    */
    NextActionType HandleListenError();
    
    /*!
        @brief handles missed evenst

        Send a missing event event for event no. m_EvtCountKernel
        @see m_EvtCountKernel
    */
    void HandleMissedEvent();
    
    /*!
        @brief create own events

        Checks the message, that is passed and creates events.
        @param p [IN] the message to check
        @param action [OUT] used in HandleListenError
        @see NextActionType
        @see HandleListenError
    */
    void CreateEvent(SAPDBErr_MessageList* p, NextActionType& action );

    /*! @brief returns true if DB is crashed */
    bool isCrash();

    /*!
        @brief verbosity 

        If true, no information message will be produced.
    */
    bool quiet;

    /*!
        @brief reconnect after loss of connection

        If true,  and the Dispatcher loses connection to its DBMServer,
        it tries to reconnect. If false, it does not (an event indicating this
        it created then).
    */
    bool m_Reconnect;

    /*! @brief coutn of sefl-created events */
    SAPDB_Int4 m_EvtCountSelf;

    /*! @brief counter of received kernel events */
    SAPDB_Int4 m_EvtCountKernel;

    /*! @brief logger */
    tin100_GlobalLog* m_Logger;
    /*!  should logging be performed? */
    bool m_doLogging;
    /*! default name of protocol file */
    static const DBMCli_String sPrtfile;

    /*! @brief variable for replacement in configuration file */
    static const char* const DBNAME;
    /*! @brief variable for replacement in configuration file */
    static const char* const DBMUSERPW;
    /*! @brief variable for replacement in configuration file */
    static const char* const SERVERNAME;
    /*! @brief variable for replacement in configuration file */
    static const char* const EVTALL;
    /*! @brief variable for replacement in configuration file */
    static const char* const EVTNAME;
    /*! @brief variable for replacement in configuration file */
    static const char* const EVTCOUNT;
    /*! @brief variable for replacement in configuration file */
    static const char* const EVTPRIO;
    /*! @brief variable for replacement in configuration file */
    static const char* const EVTVAL1;
    /*! @brief variable for replacement in configuration file */
    static const char* const EVTVAL2;
    /*! @brief variable for replacement in configuration file */
    static const char* const EVTDATE;
    /*! @brief variable for replacement in configuration file */
    static const char* const EVTTIME;
    /*! @brief variable for replacement in configuration file */
    static const char* const EVTDESC;
    /*! @brief variable for replacement in configuration file */
    static const char* const EVTTEXT;

    /*! @brief constant "RUNDIRECTORY" */
    static const DBMCli_String sRundirectory;
    /*! @brief constant "env" */
    static const DBMCli_String sEnvdir;
    /*! @brief constant "dbmevtdisp.cfg" */
    static const DBMCli_String sPropfileName;

    static const DBMCli_String EVT_NAME_SHUTDOWN;
    static const DBMCli_String EVT_NAME_DBCRASH;
    static const DBMCli_String EVT_NAME_UNKNOWNERR;
    static const DBMCli_String EVT_NAME_MISSEDEVENT;
    static const DBMCli_String EVT_NAME_DISPATCHERSTOP;
    static const DBMCli_String EVT_NAME_DISPATCHERSTART;
    static const DBMCli_String EVT_TEXT_DBCRASH;
    static const DBMCli_String EVT_TEXT_MISSEDEVENT;
    static const DBMCli_String EVT_TEXT_DISPATCHERSTOP;
    static const DBMCli_String EVT_TEXT_DISPATCHERSTART;
};

// event handlers tat understand parameter passing via pipe
const DBMCli_String DBMCli_EventDispatcher::m_ViaPipe[4] =
            {"dbmevthndl_display",
             "dbmevthndl_winlog",
             "dbmevthndl_exendDB",
             ""};

// variables for configueration file
const char* const DBMCli_EventDispatcher::DBNAME = "$DBNAME$";
const char* const  DBMCli_EventDispatcher::DBMUSERPW = "$DBMUSERPW$";
const char* const  DBMCli_EventDispatcher::SERVERNAME = "$SERVERNAME$";
const char* const  DBMCli_EventDispatcher::EVTALL = "$EVTALL$";
const char* const  DBMCli_EventDispatcher::EVTNAME = "$EVTNAME$";
const char* const  DBMCli_EventDispatcher::EVTVAL1 = "$EVTVAL1$";
const char* const  DBMCli_EventDispatcher::EVTVAL2 = "$EVTVAL2$";
const char* const  DBMCli_EventDispatcher::EVTDATE = "$EVTDATE$";
const char* const  DBMCli_EventDispatcher::EVTTIME = "$EVTTIME$";
const char* const  DBMCli_EventDispatcher::EVTDESC = "$EVTDESC$";
const char* const  DBMCli_EventDispatcher::EVTTEXT = "$EVTTEXT$";
const char* const  DBMCli_EventDispatcher::EVTCOUNT = "$EVTCOUNT$";
const char* const  DBMCli_EventDispatcher::EVTPRIO = "$EVTPRIO$";

// for finding configuration
const DBMCli_String DBMCli_EventDispatcher::sRundirectory("RUNDIRECTORY");
const DBMCli_String DBMCli_EventDispatcher::sEnvdir("env");
const DBMCli_String DBMCli_EventDispatcher::sPropfileName("dbmevtdisp.cfg");

// for creating own events
const DBMCli_String DBMCli_EventDispatcher::EVT_NAME_SHUTDOWN("DISPWARN:SHUTDOWN");
const DBMCli_String DBMCli_EventDispatcher::EVT_NAME_DBCRASH("DISPERR:DBCRASH");
const DBMCli_String DBMCli_EventDispatcher::EVT_NAME_UNKNOWNERR("DISPWARN:UNKNOWN");
const DBMCli_String DBMCli_EventDispatcher::EVT_NAME_MISSEDEVENT("DISPWARN:MISSEDEVT");
const DBMCli_String DBMCli_EventDispatcher::EVT_NAME_DISPATCHERSTOP("DISPINFO:DISPSTOP");
const DBMCli_String DBMCli_EventDispatcher::EVT_NAME_DISPATCHERSTART("DISPINFO:DISPSTART");
const DBMCli_String DBMCli_EventDispatcher::EVT_TEXT_DBCRASH("the database crashed");
const DBMCli_String DBMCli_EventDispatcher::EVT_TEXT_MISSEDEVENT("event is missing");
const DBMCli_String DBMCli_EventDispatcher::EVT_TEXT_DISPATCHERSTOP("dispatcher stopped. host: %s, db: %s");
const DBMCli_String DBMCli_EventDispatcher::EVT_TEXT_DISPATCHERSTART("dispatcher started. host: %s, db: %s");

// logging
const DBMCli_String DBMCli_EventDispatcher::sPrtfile("dbmevtdisp.prt");

void DBMCli_EventDispatcher::DumpInfo(const char* format,
                                      const char* s0,
                                      const char* s1,
                                      const char* s2,
                                      const char* s3,
                                      const char* s4 ) {
    static char newFormat[128];
    bool newMsg(format[0]=='\n');

    if( !quiet) {
        sprintf(newFormat, "%s\n", format+(newMsg?1:0));
        fprintf(stdout, newFormat, s0, s1, s2, s3, s4);
    }
    if( m_doLogging && !quiet ) {
        if( newMsg )
            sprintf(newFormat, "Info:  %s", format+(newMsg?1:0));
        else
            sprintf(newFormat, "       %s", format+(newMsg?1:0));
        m_Logger->tin100_Log::writeEntry(newFormat, s0, s1, s2, s3, s4);
    }
}

void DBMCli_EventDispatcher::DumpError(const char* format,
                                      const char* s0,
                                      const char* s1,
                                      const char* s2,
                                      const char* s3,
                                      const char* s4 ) {

    static char newFormat[128];
    bool newMsg(format[0]=='\n');

    sprintf(newFormat, "%s\n", format+(newMsg?1:0));
    fprintf(stderr, newFormat, s0, s1, s2, s3, s4);
    if( m_doLogging ) {
        if( newMsg )
            sprintf(newFormat, "Error: %s", format+(newMsg?1:0));
        else
            sprintf(newFormat, "       %s", format+(newMsg?1:0));
        m_Logger->tin100_Log::writeEntry(newFormat, s0, s1, s2, s3, s4);
    }
}

void DBMCli_EventDispatcher::replaceVar( DBMCli_String& str, const char* varName, const char* varVal) {
    if( varVal == NULL )
        return;
    int at(str.Find(varName));
    int len(strlen(varName));
    // one variable can occur more than once...
    while( at >= 0) {
        str = str.Left(at) + varVal + str.Right(str.GetLength()-at-len);
        at = str.Find(varName);
    }
}


void DBMCli_EventDispatcher::getHandlersForEvent(
        const DBMCli_Event &oEvent, 
        int& num, DBMCli_String** handlers) {
    
    Tools_DynamicUTF8String evtname((const char*) oEvent.GetName());
    // special handling for kernel errors and kernel warnings

    if( oEvent.isKernelError() || oEvent.isKernelWarning() ) {
        char buf[32];
        sprintf(buf, "%d", oEvent.GetValue1() );
        evtname += buf;
    }

    static const Tools_DynamicUTF8String wildcard("*");
    static const Tools_DynamicUTF8String dot(".");

    num = 0;

    // is there a handler for all events?
    Tools_DynamicUTF8String handler(dispatcherprop.getProperty(wildcard));
    if( !handler.Empty() )
        handlers[num++] = new DBMCli_String(handler);

    // now check for specific handlers
    handler = dispatcherprop.getProperty(evtname);
    if( !handler.Empty() )
        handlers[num++] = new DBMCli_String(handler);

    int count(0);
    char buf[10];
    bool gotone(true);

    // search for more handlers
    // evtname.i (where i must start with 0, no gaps)
    while( gotone ) {
        Tools_DynamicUTF8String morehandlers(evtname);
        sprintf(buf,"%d",count++);
        morehandlers += dot;
        morehandlers += buf;
        handler = dispatcherprop.getProperty(morehandlers);
        if( gotone = !handler.Empty() ) {
            handlers[num++] = new DBMCli_String(handler);
        }
    }

    // now search for handlers, that are built like this
    // e.g. ALI* or ALI*.n
    for( int loop=1; loop <= (int)evtname.Length(); loop++ ) {
        Tools_DynamicUTF8String wname(evtname.SubStr(0,loop));
        wname += "*";
        handler = dispatcherprop.getProperty(wname);
        if( !handler.Empty() ) {
            handlers[num++] = new DBMCli_String(handler);
            bool gotone(true);
            while( gotone ) {
                Tools_DynamicUTF8String morehandlers(wname);
                sprintf(buf,"%d",count++);
                morehandlers += dot;
                morehandlers += buf;
                handler = dispatcherprop.getProperty(morehandlers);
                if( gotone = !handler.Empty() ) {
                    handlers[num++] = new DBMCli_String(handler);
                }
            }
        }
    }
}

DBMCli_EventDispatcher::NextActionType DBMCli_EventDispatcher::HandleListenError() {

    SAPDBErr_MessageList& list(GetMsgObj());
    NextActionType action(GoonListening);

    // create events depending on what is contained in the message list
    SAPDBErr_MessageList* p = &list;
    while( p != NULL ) {
        NextActionType na(GoonListening);
        CreateEvent(p, na);
        if( na > action )
            action = na;
        p = p->NextMessage();
    }
    return action;
}

void DBMCli_EventDispatcher::HandleMissedEvent() {
    RTE_ISystem::DateTime now;
    RTE_ISystem::GetLocalDateTime(now);
    DBMCli_Event missedEventEvent(
        EVT_NAME_MISSEDEVENT,
        m_EvtCountKernel,
        DBMCli_Event::prio_medium,
        EVT_TEXT_MISSEDEVENT,
        m_EvtCountSelf++,
        now);
    EventProc(missedEventEvent);
}

void DBMCli_EventDispatcher::CreateEvent(
        SAPDBErr_MessageList* p,
        NextActionType& action ) {
    if( p->ID() == (SAPDB_UInt4) -8888 ) { // connection lost
        DBMCli_Event shutdownEvent(
            EVT_NAME_SHUTDOWN,
            p->ID(),
            DBMCli_Event::prio_high,
            p->Message(),
            m_EvtCountSelf++,
            p->DateTime());
        EventProc(shutdownEvent);
        action = WaitListening;
        if( isCrash() ) {
            DBMCli_Event crashEvent(
                EVT_NAME_DBCRASH,
                p->ID(),
                DBMCli_Event::prio_high,
                EVT_TEXT_DBCRASH,
                m_EvtCountSelf++,
                p->DateTime());
            EventProc(crashEvent);
            action = WaitListening;
        }
        if( !m_Reconnect )
            action = StopListening;
    }
    else if( p->ID() == (SAPDB_UInt4) -71 ) { // shutdown
        DBMCli_Event shutdownEvent(
            EVT_NAME_SHUTDOWN,
            p->ID(),
            DBMCli_Event::prio_high,
            p->Message(),
            m_EvtCountSelf++,
            p->DateTime());
        EventProc(shutdownEvent);
        action = WaitListening;
        if( isCrash() ) {
            DBMCli_Event crashEvent(
                EVT_NAME_DBCRASH,
                p->ID(),
                DBMCli_Event::prio_high,
                EVT_TEXT_DBCRASH,
                m_EvtCountSelf++,
                p->DateTime());
            EventProc(crashEvent);
            action = WaitListening;
        }
        if( !m_Reconnect )
            action = StopListening;

    }
    else if( (p->ID() == 1) ||      // database not running
             (p->ID() == 5) ||      // database not running
             (p->ID() == (SAPDB_UInt4) -24988)) { // sql error
        action = WaitListening;
    }
    else { // unknown error
        DBMCli_Event unknownErrEvent(
            EVT_NAME_UNKNOWNERR,
            p->ID(),
            DBMCli_Event::prio_unknown,
            p->Message(),
            m_EvtCountSelf++,
            p->DateTime());
        EventProc(unknownErrEvent);
        action = WaitListening;
        if( isCrash() ) {
            DBMCli_Event crashEvent(
                EVT_NAME_DBCRASH,
                p->ID(),
                DBMCli_Event::prio_high,
                EVT_TEXT_DBCRASH,
                m_EvtCountSelf++,
                p->DateTime());
            EventProc(crashEvent);
            action = WaitListening;
        }
        if( !m_Reconnect )
            action = StopListening;
    }
}


bool DBMCli_EventDispatcher::isCrash() {
    // first check if the DB is online
    bool isOffline(true);
    SAPDBErr_MessageList list;
    if( DBMCli_Session::Execute("db_state", list) ) {
        DBMCli_Result result(DBMCli_Session::GetResult());
        DBMCli_String dbState;
        DBMCli_Session::GetResult().GetLine(dbState); // read away the word "State"
        DBMCli_Session::GetResult().GetLine(dbState); // read the real state
        isOffline = (dbState == DBMCLI_DBSTATE_OFFLINE_STR);
    }

    if( !isOffline )
        return false;

    // it's offline, now check _DIAG_SEM parameter    
    return GetParameters().GetValueAsInt("_DIAG_SEM") == 1;
}

/*! @brief dispatcher pointer used in main function */
DBMCli_EventDispatcher* pDispatcher = NULL;
/*! @brief parser pointer used in main function */
Tools_ParameterParser* pParameterParser = NULL;

/*!
    @brief main function

    This program is can either be started from the command line manually or the DBMServer
    starts it if DBM parameter RunEventDispatcher is set to "yes". DB connect data
    must be on the command line (with -d, -u, -n). Reads arguments from command line
    and initializes the event dispatcher class.
*/
int main (int argcIn, char* argvIn[])
{
    int        argc            = 0; 
    char **    argv            = NULL;
    int nRC = Tools_PipeCall::ReadArguments(
            argcIn,
            argvIn,
            argc,
            argv,
            Tools_PipeCall::ReadXUser | Tools_PipeCall::PromptForUserOnRequest);

    if( nRC != 0 ) {
        fprintf(stderr, "%s: could not read arguments from commandline. reason: %s\n",
            argvIn[0],
            Tools_PipeCall::ErrorReason());
        fflush(stderr);
        exit(1);
    }


    // read arguments
    pParameterParser  = new Tools_ParameterParser();

    DBMCli_String sName, sDesc;
    //node
    sName = "n";
    sDesc = "node      (name of servernode)";
    Tools_Parameter node(sName, sDesc, false);
    //dbname
    sName = "d";
    sDesc = "dbname    (name of datbase)";
    Tools_Parameter db(sName, sDesc);
    //userpw
    sName = "u";
    sDesc = "user,pwd  (user for authorization; prompt if empty)";
    Tools_Parameter userpw(sName, sDesc, false);
    //configuration file
    sName = "cf";
    sDesc = "cfgfile   (configuration file)";
    Tools_Parameter cfgfile(sName, sDesc, false);
    //log file
    sName = "lf";
    sDesc = "logfile   (log file)";
    Tools_Parameter logfile(sName, sDesc, false);
    //verbosity
    sName = "q";
    sDesc = "quiet     (no output to stdout)";
    Tools_Parameter quietmode(sName, sDesc, false, false);
    //reconnect after loss of connection
    sName = "r";
    sDesc = "reconnect (try reconnect after loss of connection)";
    Tools_Parameter reconnect(sName, sDesc, false, false);
    //
    sName = "U";
    sDesc = "user_key  (from XUSER)";
    Tools_Parameter xuserkey(sName, sDesc, false, true);

    pParameterParser->addFormalParameter(node);
    pParameterParser->addFormalParameter(db);
    pParameterParser->addFormalParameter(userpw);
    pParameterParser->addFormalParameter(xuserkey);
    pParameterParser->addFormalParameter(logfile);
    pParameterParser->addFormalParameter(cfgfile);
    pParameterParser->addFormalParameter(quietmode);
    pParameterParser->addFormalParameter(reconnect);

    pParameterParser->setActualParameters( argc, argv );

    if( !pParameterParser->isParameterSet(quietmode.getName()) ) {
        DBMCli_Logo oLogo;
        oLogo.PrintLogo(DBMCli_EventingBranding::sProductName + ", Dispatcher", true);
        oLogo.PrintVersion(DBMCli_EventingBranding::sVersionLabel, true);
    }

    if( !pParameterParser->isParameterlistValid() ){
        pParameterParser->printUsage(stderr);
        delete pParameterParser;
        exit(PRG_FATAL);
    }

  SAPDBErr_MessageList   oMessageList;
  SAPDBErr_MessageList* pMsg(&oMessageList);
  oMessageList.ClearMessageList();

  int exitCode(0);

  // parameter list was valid, so create dispatcher
  pDispatcher = new DBMCli_EventDispatcher(node.getValue(),
                                           db.getValue(),
                                           userpw.getValue(),
                                           cfgfile.getValue(),
                                           logfile.getValue(),
                                           pParameterParser->isParameterSet(reconnect),
                                           pParameterParser->isParameterSet(quietmode),
                                           oMessageList);

  // did something go wrong during dispatcher creation?
  if( !(pDispatcher->isListening() && oMessageList.IsEmpty()) ) {
    exitCode = PRG_FATAL;
  }
  else {
    // start listening
    pDispatcher->Run();

    if (!pDispatcher->LastMessage().IsEmpty()) {
        const SAPDBErr_MessageList* pMsg = &(pDispatcher->LastMessage());
      
        if( !oMessageList.IsEmpty() ) {
            bool foundError(false);
            pDispatcher->DumpInfo("\nmessages during listening:");      
            while( pMsg != NULL ) {
                char buf[32];
                sprintf(buf, "%d", pMsg->ID());
                if( pMsg->Type() == SAPDBErr_MessageList::Error ) {
                    foundError = true;
                    pDispatcher->DumpError("\n%s (errorcode %d)\n", pMsg->Message(), buf);
                }
                else {
                    pDispatcher->DumpInfo("\n%s (errorcode %d)\n", pMsg->Message(), buf);
                }
                pMsg = pMsg->NextMessage();
            }
            if( foundError )
                exitCode = PRG_FATAL;
        }
    } // end if
  }
  delete pDispatcher;
  delete pParameterParser;
  return exitCode;
} // end main
