/*
 * sqlacct.cxx
 *
 * SQL accounting module for GNU Gatekeeper
 *
 * Copyright (c) 2004, Michal Zygmuntowicz
 *
 * This work is published under the GNU Public License (GPL)
 * see file COPYING for details.
 * We also explicitely grant the right to link this code
 * with the OpenH323 library.
 *
 * $Log: sqlacct.cxx,v $
 * Revision 1.13  2006/04/14 13:56:19  willamowius
 * call failover code merged
 *
 * Revision 1.1.1.1  2005/11/21 20:20:00  willamowius
 *
 *
 * Revision 1.4  2005/11/15 19:52:56  jan
 * Michal v1 (works, but on in routed, not proxy mode)
 *
 * Revision 1.12  2005/05/19 16:41:17  zvision
 * Solaris need explicit std::map
 *
 * Revision 1.11  2005/04/24 16:39:45  zvision
 * MSVC6.0 compatibility fixed
 *
 * Revision 1.10  2005/03/15 11:49:38  zvision
 * Make reconnect working correctly when a database server is down
 *
 * Revision 1.9  2005/03/08 00:13:47  zvision
 * Support for connect event in SqlAcct module, thanks to Boian Bonev
 *
 * Revision 1.8  2005/01/12 17:55:07  willamowius
 * fix gkip accounting parameter
 *
 * Revision 1.7  2005/01/05 15:42:41  willamowius
 * new accounting event 'connect', parameter substitution unified in parent class
 *
 * Revision 1.6  2005/01/04 18:13:42  willamowius
 * space in trace msg
 *
 * Revision 1.5  2004/12/15 14:43:25  zvision
 * Shutdown the gatekeeper on SQL auth/acct module config errors.
 * Thanks to Mikko Oilinki.
 *
 * Revision 1.4  2004/11/15 23:57:43  zvision
 * Ability to choose between the original and the rewritten dialed number
 *
 * Revision 1.3  2004/11/10 18:30:41  zvision
 * Ability to customize timestamp strings
 *
 * Revision 1.2  2004/07/09 22:11:36  zvision
 * SQLAcct module ported from 2.0 branch
 *
 * Revision 1.1.2.6  2004/06/22 18:41:17  zvision
 * Username, Calling-Station-Id and Called-Station-Id handling rewritten.
 * Radius modules optimized.
 *
 * Revision 1.1.2.5  2004/06/18 15:42:51  zvision
 * Better User-Name and Calling-Station-Id handling for unregistered endpoints
 *
 * Revision 1.1.2.4  2004/06/06 12:31:04  zvision
 * New SQLAcct/FileAcct parameters. Thanks to Patrick!
 *
 * Revision 1.1.2.3  2004/05/12 14:00:48  zvision
 * Header file usage more consistent. Solaris std::map problems fixed.
 * Compilation warnings removed. VSNET2003 project files added. ANSI.h removed.
 *
 * Revision 1.1.2.2  2004/04/24 10:31:57  zvision
 * Use baseclass GetConfigSectionName
 *
 * Revision 1.1.2.1  2004/04/23 16:01:16  zvision
 * New direct SQL accounting module (SQLAcct)
 *
 */
#if defined(_WIN32) && (_MSC_VER <= 1200)
#pragma warning(disable:4786) // warning about too long debug symbol off
#pragma warning(disable:4284)
#endif

#include <ptlib.h>
#include <ptlib/sockets.h>
#include <h225.h>
#include "gk_const.h"
#include "h323util.h"
#include "Toolkit.h"
#include "RasTbl.h"
#include "RasSrv.h"
#include "gksql.h"
#include "gkacct.h"
#include "sqlacct.h"

using std::vector;


SQLAcct::SQLAcct(
	const char* moduleName,
	const char* cfgSecName
	) : GkAcctLogger(moduleName, cfgSecName),
	m_sqlConn(NULL)
{
	SetSupportedEvents(SQLAcctEvents);	

	PConfig* const cfg = GkConfig();	
	const PString& cfgSec = GetConfigSectionName();
	
	const PString driverName = cfg->GetString(cfgSec, "Driver", "");
	if (driverName.IsEmpty()) {
		PTRACE(0, "GKACCT\t" << GetName() << " module creation failed: "
			"no SQL driver selected"
			);
		PTRACE(0, "GKACCT\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	}
	
	m_sqlConn = GkSQLConnection::Create(driverName, cfgSec);
	if (m_sqlConn == NULL) {
		PTRACE(0, "GKACCT\t" << GetName() << " module creation failed: "
			"could not find " << driverName << " database driver"
			);
		PTRACE(0, "GKACCT\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	}

	m_startQuery = cfg->GetString(cfgSec, "StartQuery", "");
	if (m_startQuery.IsEmpty() 
		&& (GetEnabledEvents() & GetSupportedEvents() & AcctStart) == AcctStart) {
		PTRACE(0, "GKACCT\t" << GetName() << " module creation failed: "
			"no start query configured"
			);
		PTRACE(0, "GKACCT\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	} else
		PTRACE(4, "GKACCT\t" << GetName() << " start query: " << m_startQuery);
	
	m_startQueryAlt = cfg->GetString(cfgSec, "StartQueryAlt", "");
	if (!m_startQueryAlt) 
		PTRACE(4, "GKACCT\t" << GetName() << " alternative start query: " 
			<< m_startQueryAlt
			);

	m_updateQuery = cfg->GetString(cfgSec, "UpdateQuery", "");
	if (m_updateQuery.IsEmpty() 
		&& (GetEnabledEvents() & GetSupportedEvents() & (AcctUpdate | AcctConnect)) != 0) {
		PTRACE(0, "GKACCT\t" << GetName() << " module creation failed: "
			"no update query configured"
			);
		PTRACE(0, "GKACCT\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	} else
		PTRACE(4, "GKACCT\t" << GetName() << " update query: " << m_updateQuery);

	m_stopQuery = cfg->GetString(cfgSec, "StopQuery", "");
	if (m_stopQuery.IsEmpty() 
		&& (GetEnabledEvents() & GetSupportedEvents() & AcctStop) == AcctStop) {
		PTRACE(0, "GKACCT\t" << GetName() << " module creation failed: "
			"no stop query configured"
			);
		PTRACE(0, "GKACCT\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	} else
		PTRACE(4, "GKACCT\t" << GetName() << " stop query: " << m_stopQuery);
	
	m_stopQueryAlt = cfg->GetString(cfgSec, "StopQueryAlt", "");
	if (!m_stopQueryAlt) 
		PTRACE(4, "GKACCT\t" << GetName() << " alternative stop query: " 
			<< m_stopQueryAlt
			);

	vector<PIPSocket::Address> interfaces;
	Toolkit::Instance()->GetGKHome(interfaces);
	if (interfaces.empty()) {
		PTRACE(0, "GKACCT\t" << GetName() << " cannot determine gatekeeper IP address");
		PTRACE(0, "GKACCT\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	}

	if (!m_sqlConn->Initialize(cfg, cfgSec)) {
		PTRACE(0, "GKACCT\t" << GetName() << " module creation failed: "
			"could not connect to the database"
			);
		PTRACE(0, "GKACCT\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	}
	
	m_timestampFormat = cfg->GetString(cfgSec, "TimestampFormat", "");
}

SQLAcct::~SQLAcct()
{
	delete m_sqlConn;
}

GkAcctLogger::Status SQLAcct::Log(
	GkAcctLogger::AcctEvent evt, 
	const callptr& call
	)
{
	if ((evt & GetEnabledEvents() & GetSupportedEvents()) == 0)
		return Next;
		
	if (!call) {
		PTRACE(1, "GKACCT\t" << GetName() << " - missing call info for event " << evt);
		return Fail;
	}
	
	const long callNumber = call->GetCallNumber();
		
	if (m_sqlConn == NULL) {
		PTRACE(2, "GKACCT\t" << GetName() << " failed to store accounting "
			"data (event: " << evt << ", call: " << callNumber 
			<< "): SQL connection not active"
			);
		return Fail;
	}
	
	PString query, queryAlt;
	if (evt == AcctStart) {
		query = m_startQuery;
		queryAlt = m_startQueryAlt;
	} else if (evt == AcctUpdate || evt == AcctConnect)
		query = m_updateQuery;
	else if (evt == AcctStop) {
		query = m_stopQuery;
		queryAlt = m_stopQueryAlt;
	}
	
	if (query.IsEmpty()) {
		PTRACE(2, "GKACCT\t" << GetName() << " failed to store accounting "
			"data (event: " << evt << ", call: " << callNumber 
			<< "): SQL query is empty"
			);
		return Fail;
	}

	std::map<PString, PString> params;
	SetupAcctParams(params, call, m_timestampFormat);
	GkSQLResult* result = m_sqlConn->ExecuteQuery(query, params);
	if (result == NULL)
		PTRACE(2, "GKACCT\t" << GetName() << " failed to store accounting "
			"data (event: " << evt << ", call: " << callNumber 
			<< "): timeout or fatal error"
			);
	
	if (result) {
		if (result->IsValid()) {
			if (result->GetNumRows() < 1) {
				PTRACE(4, "GKACCT\t" << GetName() << " failed to store accounting "
					"data (event: " << evt << ", call: " << callNumber 
					<< "): no rows have been updated"
					);
				delete result;
				result = NULL;
			}
		} else {
			PTRACE(2, "GKACCT\t" << GetName() << " failed to store accounting "
				"data (event: " << evt << ", call: " << callNumber 
				<< "): (" << result->GetErrorCode() << ") "
				<< result->GetErrorMessage()
				);
			delete result;
			result = NULL;
		}
	}
	
	if (result == NULL && !queryAlt) {
		result = m_sqlConn->ExecuteQuery(queryAlt, params);
		if (result == NULL)
			PTRACE(2, "GKACCT\t" << GetName() << " failed to store accounting "
				"data (event: " << evt << ", call: " << callNumber 
				<< "): timeout or fatal error"
				);
		else {
			if (result->IsValid()) {
				if (result->GetNumRows() < 1)
					PTRACE(4, "GKACCT\t" << GetName() << " failed to store accounting "
						"data (event: " << evt << ", call: " << callNumber 
						<< "): no rows have been updated"
						);
			} else
				PTRACE(2, "GKACCT\t" << GetName() << " failed to store accounting "
					"data (event: " << evt << ", call: " << callNumber 
					<< "): (" << result->GetErrorCode() << ") "
					<< result->GetErrorMessage()
					);
		}
	}

	const bool succeeded = result != NULL && result->IsValid();	
	delete result;
	return succeeded ? Ok : Fail;
}

namespace {
GkAcctLoggerCreator<SQLAcct> SQLAcctLoggerCreator("SQLAcct");
}


syntax highlighted by Code2HTML, v. 0.9.1