/*
*	This class runs on the SUT and receives transaction requests from drivers.
*
*	Written by Sergey Vasilevskiy, 2002-2003.
*/

#include "stdafx.h"

using namespace TPCE;

//Constructor
CCEServer::CCEServer(char*				szDBServer, 
					   char*				szDB, 
					   short				shPort,
					   bool					bVerbose, 
					   HANDLE				hListenStopEvent, 
					   eClientDriverType	eClientType,
					   char*				szMEEToUse)
: CBaseServer<TCEServerLocals>("localhost", shPort, max(sizeof(TMEEIndexTable), sizeof(TDriverMessage)), hListenStopEvent, bVerbose)
, m_szDBServer(NULL)
, m_szDB(NULL)
, m_bVerbose(bVerbose)
, m_eClientType(eClientType)
{	
	m_szDBServer = new char[strlen(szDBServer)+1];	// throws std::bad_alloc exception if new fails
	strcpy(m_szDBServer, szDBServer);

	m_szDB = new char[strlen(szDB)+1];	// throws std::bad_alloc exception if new fails
	strcpy(m_szDB, szDB);

	m_szMEEToUse[0] = '\0';	// for when no -m option is specified
	if (szMEEToUse != NULL)
	{
		strcpy(m_szMEEToUse, szMEEToUse);
	}

    m_MEETable.iMEECount = 0;
}
//Destructor
CCEServer::~CCEServer()
{
	if (m_iDeadlockCount > 0)
	{
		OutputStdin("Total deadlocks: %d\n", m_iDeadlockCount);
	}

	if (m_szDBServer != NULL)
		delete m_szDBServer;

	if (m_szDB != NULL)
		delete m_szDB;
}

//
// Perform Broker Volume
//
int CCEServer::DoBrokerVolume(PBrokerVolumeTxnInput pInput, TBrokerVolumeTxnOutput& Output, CBrokerVolume &BV)
{
	BV.DoTxn(pInput, &Output);
	if (m_bVerbose || (Output.status != 0))
		OutputStdin("Performed Broker Volume: list_len=%d, status=%d\n", Output.list_len, Output.status);

	return Output.status;
}

void CCEServer::PrintBrokerVolumeParams(PBrokerVolumeTxnInput pInput)
{
	int i;
	char szMsg[2*1024];
	int cbRet;

	cbRet = _snprintf(szMsg, sizeof(szMsg), "Broker Volume for sector %s\n",
		pInput->sector_name);

	for (i=0; i<(sizeof(pInput->broker_list)/sizeof(pInput->broker_list[0])); ++i)
	{
		if (pInput->broker_list[i][0] != '\0')
			cbRet += _snprintf(&szMsg[cbRet], sizeof(szMsg)-cbRet, "%s\n", pInput->broker_list[i]);
	}

	szMsg[sizeof(szMsg)-1] = '\0';

	OutputStdin(szMsg);
}

//
// Perform Checkpoint
//
int CCEServer::DoCheckpoint(PCheckpointInput pInput, CCheckpointDB &CH)
{	
	CH.DoTxn(pInput);
	if (m_bVerbose)
		OutputStdin("Performed Checkpoint\n");

	return CBaseTxnErr::SUCCESS;
}

void CCEServer::PrintCheckpointParams(PCheckpointInput pInput)
{	// no parameters for CHECKPOINT
}

//
// Perform Customer Position
//
int CCEServer::DoCustomerPosition(PCustomerPositionTxnInput pInput, TCustomerPositionTxnOutput& Output, CCustomerPosition &CP)
{
	CP.DoTxn(pInput, &Output);

	if (m_bVerbose || (Output.status != 0))
		OutputStdin("Performed Customer Position: acct_len=%d, hist_len=%d, status=%d\n",
		            Output.acct_len, Output.hist_len, Output.status);

	return Output.status;
}

void CCEServer::PrintCustomerPositionParams(PCustomerPositionTxnInput pInput)
{
	char szMsg[512];

	if (pInput->cust_id != 0)
		_snprintf(szMsg, sizeof(szMsg), "Customer Position for cust_id %" PRId64 ", get_history = %d\n",
				pInput->cust_id, pInput->get_history);
	else
		_snprintf(szMsg, sizeof(szMsg), "Customer Position for tax_id %s, get_history = %d\n",
				pInput->tax_id, pInput->get_history);

	szMsg[sizeof(szMsg)-1] = '\0';

	OutputStdin(szMsg);
}

//
// Perform Data Maintenance
//
int CCEServer::DoDataMaintenance(PDataMaintenanceTxnInput pInput, TDataMaintenanceTxnOutput& Output, CDataMaintenance &DM)
{
	DM.DoTxn(pInput, &Output);

	if (m_bVerbose)
		OutputStdin("Performed DataMaintenance\n");
	
	return Output.status;
}

void CCEServer::PrintDataMaintenanceParams(PDataMaintenanceTxnInput pInput)
{
	char szMsg[512];
	
	_snprintf(szMsg, sizeof(szMsg), "Data Maintenance for %s\n",
			pInput->table_name);
	
	szMsg[sizeof(szMsg)-1] = '\0';

	OutputStdin(szMsg);
}

//
// Perform Market Watch
//
int CCEServer::DoMarketWatch(PMarketWatchTxnInput pInput, TMarketWatchTxnOutput& Output, CMarketWatch &MW)
{
	MW.DoTxn(pInput, &Output);

	if (m_bVerbose || (Output.status != 0))
		OutputStdin("Performed Market Watch: pct_change=%6.2f, status=%d\n", Output.pct_change, Output.status);

	return Output.status;
}

void CCEServer::PrintMarketWatchParams(PMarketWatchTxnInput pInput)
{
	char szMsg[512];
	int cbRet;

	cbRet = _snprintf(szMsg, sizeof(szMsg), "Market Watch for ");
	
	if (pInput->acct_id != 0)
		cbRet += _snprintf(&szMsg[cbRet], sizeof(szMsg)-cbRet, "acct_id %" PRId64 "\n", pInput->acct_id);
	if (pInput->industry_name[0] != '\0')
		cbRet += _snprintf(&szMsg[cbRet], sizeof(szMsg)-cbRet, "industry %s\n", pInput->industry_name);
	if (pInput->c_id != 0)
		cbRet += _snprintf(&szMsg[cbRet], sizeof(szMsg)-cbRet, "c_id %" PRId64 "\n", pInput->c_id);

	szMsg[sizeof(szMsg)-1] = '\0';

	OutputStdin(szMsg);
}

//
// Perform Security Detail
//
int CCEServer::DoSecurityDetail(PSecurityDetailTxnInput pInput, TSecurityDetailTxnOutput& Output, CSecurityDetail &SD)
{
	SD.DoTxn(pInput, &Output);

	if (m_bVerbose || (Output.status != 0))
		OutputStdin("Performed Security Detail: news_len=%d, status=%d\n", Output.news_len, Output.status);

	return Output.status;
}

void CCEServer::PrintSecurityDetailParams(PSecurityDetailTxnInput pInput)
{
	char szMsg[512];

	_snprintf(szMsg, sizeof(szMsg), "Security Detail for symb %s\n", pInput->symbol);
	szMsg[sizeof(szMsg)-1] = '\0';

	OutputStdin(szMsg);
}

//
// Perform Trade Lookup
//
int CCEServer::DoTradeLookup(PTradeLookupTxnInput pInput, TTradeLookupTxnOutput& Output, CTradeLookup &TL)
{
	TL.DoTxn(pInput, &Output);

	if (m_bVerbose || (Output.status != 0))
		OutputStdin("Performed Trade Lookup: frame_executed=%d, num_found=%d, status=%d\n",
		            Output.frame_executed, Output.num_found, Output.status);

	return Output.status;
}

void CCEServer::PrintTradeLookupParams(PTradeLookupTxnInput pInput)
{
	char	szMsg[512];
	int		iLen;

	iLen = _snprintf(szMsg, sizeof(szMsg), "Trade Lookup for frame %d ", pInput->frame_to_execute);
	// Output parameter info based on frame to execute
	//
	switch (pInput->frame_to_execute)
	{
		case 1:
			{
				iLen+=_snprintf(&szMsg[iLen], sizeof(szMsg)-iLen, "(tr_id1=%I64d, tr_id20=%I64d, max trd=%d)",
					pInput->trade_id[0], pInput->trade_id[19], pInput->max_trades);
				break;
			}
		case 2:		
			{
				CDateTime	dt(&pInput->start_trade_dts);
				iLen+=_snprintf(&szMsg[iLen], sizeof(szMsg)-iLen, "(account=%I64d, dts=%s, max trd=%d)",
					pInput->acct_id, dt.ToStr(11), pInput->max_trades);
				break;
			}
		case 3:		
			{
				CDateTime	dt(&pInput->start_trade_dts);
				iLen+=_snprintf(&szMsg[iLen], sizeof(szMsg)-iLen, "(account=%I64d, dts=%s)",
					pInput->acct_id, dt.ToStr(11));
				break;
			}
		case 4:		
			{
				CDateTime	dt(&pInput->start_trade_dts);
				iLen+=_snprintf(&szMsg[iLen], sizeof(szMsg)-iLen, "(symbol=%s, dts=%s, max trd=%d)",
					pInput->symbol, dt.ToStr(11), pInput->max_trades);
				break;
			}
	}

	iLen+=_snprintf(&szMsg[iLen], sizeof(szMsg)-iLen, "\n");
	szMsg[sizeof(szMsg)-1] = '\0';

	OutputStdin(szMsg);
}

//
// Perform Trade Order
//
int CCEServer::DoTradeOrder(PTradeOrderTxnInput pInput, TTradeOrderTxnOutput& Output, CTradeOrder &TO)
{
	if (m_bVerbose)
	{
		char szMsg[128];
		_snprintf(szMsg, sizeof(szMsg), "Attempting %s Trade Order for %s@%5.2f\n", 
			pInput->trade_type_id, pInput->symbol, pInput->requested_price);
		OutputStdin(szMsg);
	}

	TO.DoTxn(pInput, &Output);

	if ( Output.status == CBaseTxnErr::TOF2_ERROR1 )
	{
		char szMsg[128];
		_snprintf(szMsg, sizeof(szMsg), "Unauthorized executor! acct_id=%" PRId64 ", exec_f_name=%s, exec_l_name=%s, tax_id=%s\n",
			pInput->acct_id, pInput->exec_f_name, pInput->exec_l_name, pInput->exec_tax_id);
		OutputStdin(szMsg);
	}
	if (m_bVerbose || (Output.status != 0))
	{
		char szMsg[128];
		_snprintf(szMsg, sizeof(szMsg), "Performed %s Trade Order: trade_id=%I64d, status=%d\n", 
			pInput->trade_type_id, Output.trade_id, Output.status);
		OutputStdin(szMsg);
	}

	return Output.status;
}

void CCEServer::PrintTradeOrderParams(PTradeOrderTxnInput pInput)
{
	char szMsg[1024];
	int	cbRet;

	cbRet = _snprintf(szMsg, sizeof(szMsg), "Trade Order %s\n"
		"acct:		%" PRId64 "\n"
		"f_name:		%s\n"
		"l_name:		%s\n"
		"tax_id:		%s\n",
		pInput->trade_type_id, pInput->acct_id, pInput->exec_f_name, pInput->exec_l_name, pInput->exec_tax_id);

	if (pInput->co_name[0] == '\0')
		cbRet += _snprintf(&szMsg[cbRet], sizeof(szMsg)-cbRet, "symb:	%s\n", pInput->symbol);
	else
	{
		cbRet += _snprintf(&szMsg[cbRet], sizeof(szMsg)-cbRet, 
			"co_name:	%s\n"
			"issue:		%s\n",pInput->co_name, pInput->issue);
	}
	cbRet += _snprintf(&szMsg[cbRet], sizeof(szMsg)-cbRet,
			"is_lifo:	%d\n"
			"trade_price:	%5.2f\n"
			"trade_qty:	%d\n"
			"roll_back:	%d\n",			
			pInput->is_lifo, pInput->requested_price, pInput->trade_qty, pInput->roll_it_back);

	szMsg[sizeof(szMsg)-1] = '\0';

	OutputStdin(szMsg);
}

//
// Perform Trade Status
//
int CCEServer::DoTradeStatus(PTradeStatusTxnInput pInput, TTradeStatusTxnOutput& Output, CTradeStatus &TS)
{
	TS.DoTxn(pInput, &Output);

	if (m_bVerbose || (Output.status != 0))
		OutputStdin("Performed Trade Status: status=%d\n", Output.status);

	/*dt2.SetToCurrent();

	if (dt2-dt1 > 30.0)
	{
		OutputStdin("Trade Status of %f secs:\n", dt2-dt1);
		PrintTradeStatusParams(pInput);
	}*/

	return Output.status;
}

void CCEServer::PrintTradeStatusParams(PTradeStatusTxnInput pInput)
{
	char szMsg[512];

	_snprintf(szMsg, sizeof(szMsg), "Trade Status for acct %" PRId64 "\n", pInput->acct_id);
	szMsg[sizeof(szMsg)-1] = '\0';

	OutputStdin(szMsg);
}

//
// Perform Trade Update
//
int CCEServer::DoTradeUpdate(PTradeUpdateTxnInput pInput, TTradeUpdateTxnOutput& Output, CTradeUpdate &TU)
{
	TU.DoTxn(pInput, &Output);

	if (m_bVerbose || (Output.status != 0))
		OutputStdin("Performed Trade Update: frame_executed=%d, num_found=%d, num_updated=%d, status=%d\n",
		            Output.frame_executed, Output.num_found, Output.num_updated, Output.status);

	return Output.status;
}

void CCEServer::PrintTradeUpdateParams(PTradeUpdateTxnInput pInput)
{
	char		szMsg[512];
	int			iLen;	

	iLen = _snprintf(szMsg, sizeof(szMsg), "Trade Update for frame %d ", pInput->frame_to_execute);
	// Output parameter info based on frame to execute
	//
	switch (pInput->frame_to_execute)
	{
		case 1:
			{
				iLen+=_snprintf(&szMsg[iLen], sizeof(szMsg)-iLen, "(tr_id1=%I64d, tr_id20=%I64d, max trd=%d, max upd=%d)",
					pInput->trade_id[0], pInput->trade_id[19], pInput->max_trades, pInput->max_updates);
				break;
			}
		case 2:		
			{
				CDateTime	dt(&pInput->start_trade_dts);
				iLen+=_snprintf(&szMsg[iLen], sizeof(szMsg)-iLen, "(account=%I64d, dts=%s, max trd=%d, max upd=%d)",
					pInput->acct_id, dt.ToStr(11), pInput->max_trades, pInput->max_updates);
				break;
			}

		case 3:		
			{
				CDateTime	dt(&pInput->start_trade_dts);
				iLen+=_snprintf(&szMsg[iLen], sizeof(szMsg)-iLen, "(symbol=%s, dts=%s, max trd=%d, max upd=%d)",
					pInput->symbol, dt.ToStr(11), pInput->max_trades, pInput->max_updates);
				break;
			}
	}

	iLen+=_snprintf(&szMsg[iLen], sizeof(szMsg)-iLen, "\n");
	szMsg[sizeof(szMsg)-1] = '\0';

	OutputStdin(szMsg);
}

//
//	Print transaction parameters.
//
void CCEServer::PrintTxnParams(PDriverMessage pTxn)
{
	switch ( pTxn->eTxnType )
	{
	case BROKER_VOLUME: PrintBrokerVolumeParams(&pTxn->input.BVInput); break;
	case CHECKPOINT: PrintCheckpointParams(&pTxn->input.CheckpointInput); break;
	case CUSTOMER_POSITION: PrintCustomerPositionParams(&pTxn->input.CPInput); break;
	case DATA_MAINTENANCE: PrintDataMaintenanceParams(&pTxn->input.DMInput); break;
	case MARKET_WATCH: PrintMarketWatchParams(&pTxn->input.MWInput); break;
	case SECURITY_DETAIL: PrintSecurityDetailParams(&pTxn->input.SDInput); break;
	case TRADE_LOOKUP: PrintTradeLookupParams(&pTxn->input.TLInput); break;
	case TRADE_ORDER: PrintTradeOrderParams(&pTxn->input.TOInput); break;
	case TRADE_STATUS: PrintTradeStatusParams(&pTxn->input.TSInput); break;
	case TRADE_UPDATE: PrintTradeUpdateParams(&pTxn->input.TUInput); break;
	default: assert(false);	// this should never happen
	}
}

//
// Receive MEE translation table
//
int CCEServer::ReceiveMEETable(PMEEIndexTable pInput)
{
	CDateTime	TimeNow;

	// Protected by a spinlock because more than one UE can connect to this SUTServer.
	m_MEETableLock.lock();	

	m_MEETable = *pInput;

	OutputStdinTimed("Received MEE table (%d entry(s))\n", m_MEETable.iMEECount);

	m_MEETableLock.unlock();

	if (m_MEETable.iMEECount == 1)
	{	// One MEE => use it by default (no need to require command-line parameter)
		//
		strcpy(m_szMEEToUse, m_MEETable.MEE[0].szMEEName);
	}
	else
	{
		if (m_szMEEToUse[0] == '\0')
		{
			PrintMEETable(&m_MEETable);

			throw new CSendToMarketErr(CSendToMarketErr::eMEENameNotSpecified, "ReceiveMEETable");
		}
	}

	if (m_bVerbose)
	{
		PrintMEETable(&m_MEETable);
	}

	return CBaseTxnErr::SUCCESS;
}

void CCEServer::PrintMEETable(PMEEIndexTable pInput)
{
	int i;

	for (i = 0; i< pInput->iMEECount; ++i)
	{
		OutputStdinTimed("%d: %s on %s:%hu\n", 
			i, 
			pInput->MEE[i].szMEEName, 
			pInput->MEE[i].szMachineName, 
			pInput->MEE[i].shPort);
	}
}

void CCEServer::CleanUp(PDriverMessage pTxn, TCEServerLocals* pLocal, CBaseErr *pErr)
{
	switch ( pTxn->eTxnType )
	{
	case BROKER_VOLUME:		pLocal->m_dbBV.Cleanup(pErr); break;
	case CHECKPOINT:		pLocal->m_CH.Cleanup(pErr); break;
	case CUSTOMER_POSITION: pLocal->m_dbCP.Cleanup(pErr); break;
	case DATA_MAINTENANCE:	pLocal->m_dbDM.Cleanup(pErr); break;
	case MARKET_WATCH:		pLocal->m_dbMW.Cleanup(pErr); break;
	case SECURITY_DETAIL:	pLocal->m_dbSD.Cleanup(pErr); break;
	case TRADE_LOOKUP:		pLocal->m_dbTL.Cleanup(pErr); break;
	case TRADE_ORDER:		pLocal->m_dbTO.Cleanup(pErr); break;
	case TRADE_STATUS:		pLocal->m_dbTS.Cleanup(pErr); break;
	case TRADE_UPDATE:		pLocal->m_dbTU.Cleanup(pErr); break;	
	default: assert(false);	// this should never happen
	}
}

//Transaction request processing function
int CCEServer::ProcessMessage(char *mes, DWORD iTotalBytesRecvd, TCEServerLocals* pLocal, int iThreadNum, PSUTReplyMessage pSUTReply)
{
	PDriverMessage		dmes = (PDriverMessage)mes;	
	int					ret = -1;
	int					iRetryCount;

    if (dmes->eTxnType == MEE_TABLE)
    {	// Means just establised connection and no txn input has been sent yet.
        // Need to receive MEE translation table next, before any real txn input.
        if (iTotalBytesRecvd < sizeof(TMEEIndexTable))	// the driver always sends the complete table
            return PARTIAL_MESSAGE;		//return to read more
    }
    else
    {	// MEE table has already been received.
        if ( iTotalBytesRecvd < TxnReqSize[dmes->eTxnType])	//assume only one driver request at a time
            return PARTIAL_MESSAGE;
    }

	// Prepare return structure
	//
    pSUTReply->eTxnType = dmes->eTxnType;
	iRetryCount = 0;

	while (iRetryCount < iMaxDeadlockRetries)
	{
		// Initialize status fields before attempting a txn.
		//
		pSUTReply->iStatus = CBaseTxnErr::SUCCESS;
		pSUTReply->iErrorCode = CBaseTxnErr::SUCCESS;

		try
		{
            if ( dmes->eTxnType == MEE_TABLE)	// uninitialized MEE table
            {	
                ret = ReceiveMEETable(PMEEIndexTable(mes));
            }
            else
            {   // Normal request

                switch ( dmes->eTxnType )
                {
                case BROKER_VOLUME:		ret = DoBrokerVolume(&dmes->input.BVInput, pSUTReply->output.BVOutput, pLocal->m_BV); break;
                case CHECKPOINT:		ret = DoCheckpoint(&dmes->input.CheckpointInput, pLocal->m_CH); break;
                case CUSTOMER_POSITION: ret = DoCustomerPosition(&dmes->input.CPInput, pSUTReply->output.CPOutput, pLocal->m_CP); break;
                case DATA_MAINTENANCE:	ret	= DoDataMaintenance(&dmes->input.DMInput, pSUTReply->output.DMOutput, pLocal->m_DM); break;
                case MARKET_WATCH:		ret = DoMarketWatch(&dmes->input.MWInput, pSUTReply->output.MWOutput, pLocal->m_MW); break;
                case SECURITY_DETAIL:	ret = DoSecurityDetail(&dmes->input.SDInput, pSUTReply->output.SDOutput, pLocal->m_SD); break;
                case TRADE_LOOKUP:		ret = DoTradeLookup(&dmes->input.TLInput, pSUTReply->output.TLOutput, pLocal->m_TL); break;
                case TRADE_ORDER:		ret = DoTradeOrder(&dmes->input.TOInput, pSUTReply->output.TOOutput, pLocal->m_TO); break;
                case TRADE_STATUS:		ret = DoTradeStatus(&dmes->input.TSInput, pSUTReply->output.TSOutput, pLocal->m_TS); break;
                case TRADE_UPDATE:		ret = DoTradeUpdate(&dmes->input.TUInput, pSUTReply->output.TUOutput, pLocal->m_TU); break;
                default:
                    OutputStdin("Unknown request from the driver (TxnType=%d)!\n", dmes->eTxnType);
                }
            }

            pSUTReply->iEGenStatus = ret;
			if (ret < 0)	// EGen error condition
			{
				pSUTReply->iStatus = ERR_FROM_EGEN;
			}
			if (ret == CBaseTxnErr::EXPECTED_ROLLBACK)
			{
				pSUTReply->iStatus = CBaseTxnErr::EXPECTED_ROLLBACK;
				pSUTReply->iEGenStatus = CBaseTxnErr::SUCCESS;	// do not count ROLLBACKs as warnings
			}

			break;	// exit from deadlock retry loop 
		}		
		catch (CODBCERR* pErr)
		{
			CDateTime	TimeNow;

			if (pErr->m_bDeadLock)
			{
				++iRetryCount;

				// Print error info only for deadlocks. For other errors
				// it'll be printed by CBaseServer::ProcessingThread.
				//
				if (iRetryCount == 1)
				{
					OutputStdin("%s Thread %d: txn %d is a deadlock victim\n", 
						TimeNow.ToStr(iDateTimeOutputStyle),
						iThreadNum,
						dmes->eTxnType);
				}
				else
				{	// tell this is not the first try
					OutputStdin("%s Thread %d: txn %d is a deadlock victim (try #%d)\n", 
						TimeNow.ToStr(iDateTimeOutputStyle),
						iThreadNum,
						dmes->eTxnType, iRetryCount);
				}

				CleanUp(dmes, pLocal, pErr);

				PrintTxnParams(dmes);

				// Send the error code back to the driver.
				//
				pSUTReply->iStatus = ERR_TYPE_ODBC;
				pSUTReply->iErrorCode = 1205;

				InterlockedIncrement(&m_iDeadlockCount);
			}
			else
			{
				OutputStdin("\r\n%s Thread %d: error for txn %d\n", 
					TimeNow.ToStr(iDateTimeOutputStyle),
					iThreadNum,
					dmes->eTxnType);

				PrintTxnParams(dmes);

				//throw;	//exit if not a deadlock
                //  Do not throw and simply return error code to the driver.
                //  This will allow the driver to continue, but log an error txn.
                //
                pSUTReply->iStatus = ERR_TYPE_ODBC;
				pSUTReply->iErrorCode = pErr->m_NativeError;
                
                // Show error to the user.
                //
                PrintError(pErr, TimeNow);

                break; // exit the loop
			}
		}		
	}	

	if (pSUTReply->iStatus == CBaseTxnErr::SUCCESS &&
		iRetryCount > 0)
	{
		// Set error code for the driver to indicate the number of retries.
		//
		pSUTReply->iErrorCode = ERR_DEADLOCK_VICTIM * iRetryCount;
	}

	return ret;
}

TCEServerLocals* CCEServer::InitializeLocal()
{
	//Create new transaction objects (open db connections)
	PSUTServerLocals pLocal = new TCEServerLocals(m_szDBServer, m_szDB, &m_MEETable, m_eClientType, &m_szMEEToUse[0]);

	return pLocal;
}

//
//  Helper routine to display an error.
//
void CCEServer::PrintError(TPCE::CODBCERR* pODBCErr, TPCE::CDateTime& TimeNow)
{
    int                 cbRet;
    char				szMsg[2*1024];

    cbRet = _snprintf(szMsg, sizeof(szMsg), "%s ",TimeNow.ToStr(iDateTimeOutputStyle));
    
    //  Add the main error text.
    //
    cbRet += _snprintf(&szMsg[cbRet], sizeof(szMsg)-cbRet, "\r\n%s", pODBCErr->ErrorText());

    if (pODBCErr->ErrorNum() != 0)
    {
        cbRet += _snprintf(&szMsg[cbRet], sizeof(szMsg)-cbRet, " error %d", pODBCErr->ErrorNum());
    }

    // Add Action output for ODBC errors.
    //
    if (((CODBCERR*)pODBCErr)->m_eAction != CODBCERR::eNone)
    {
        cbRet += _snprintf(&szMsg[cbRet], sizeof(szMsg)-cbRet, "\r\nAction: %i", 
            ((CODBCERR*)pODBCErr)->m_eAction);
    }

    // Add location if it exists.
    //
    if (pODBCErr->ErrorLoc() != NULL)
    {
        cbRet += _snprintf(&szMsg[cbRet], sizeof(szMsg)-cbRet, " in %s", pODBCErr->ErrorLoc());				
    }
    _snprintf(&szMsg[cbRet], sizeof(szMsg)-cbRet, "\n");
    szMsg[sizeof(szMsg)-1] = '\0';	//null-terminate if sprintf'ed on the last byte in szMsg

    OutputStdin(szMsg);
}