#include <cdevClientService.h>
#include <cdevClientRequestObject.h>
#include <cdevMessageBinary.h>
#include <cdevMessage.h>
#include <cdevDirectory.h>

int cdevClientService::ServerTag = 0;
int cdevClientService::HostTag   = 0;
int cdevClientService::PortTag   = 0;

cdevGenericServerTagDef cdevClientService::tags;

// *****************************************************************************
// * ClientInfo clientInfo :
// *	This is a static member of the cdevClientService class that contains
// *	descriptive information about the client process.
// *****************************************************************************
ClientInfo cdevClientService::clientInfo;

// *****************************************************************************
// * cdevClientTransaction::freeList_ :
// *	This is the free list for the cdevClientTransaction object that is
// *	used to reduce run-time memory allocations.
// *****************************************************************************
cdevClientTransaction * cdevClientTransaction::freeList_ = NULL;

// *****************************************************************************
// * This is the text string that will be used to define the cdevNameServer
// * if it has not yet been defined in the cdevDirectory file.
// *****************************************************************************
static char * NameServerDefinitionString =
"service rns { tags {PV} }\n\
class RNS { messages { query rns; get rns; monitorOn rns; monitorOff rns; monitorEntry rns; monitorEntryOff rns; } }\n\
RNS :\nNameServer\n;\n";

// *****************************************************************************
// * cdevClientService::cdevClientService :
// *	This is the constructor for the object.  Its is responsible for 
// *	initializing the underlying classes and data items.
// *****************************************************************************
cdevClientService::cdevClientService ( char * Domain, char * name, cdevSystem & system )
	: ServerInterface (),
	  syncCallback    (defaultCallback, NULL),
	  domain          (strdup(Domain)),
	  cdevService     (name, system),
	  transactions    (),
	  contexts        (),
	  nsCallbackArgs  (0)
	{
	// *********************************************************************
	// * Determine if the NameServer has been provided in the CDEVDDL file.
	// * If not, add the NameServer to the cdevDirectory.
	// *********************************************************************
	cdevDirectory & nameServer = system.nameServer();
	cdevData        request;
	cdevData        reply;
	
	request.insert("device", "NameServer");
	if(nameServer.send("queryClass", request, reply)==CDEV_NOTFOUND)
		{
		request.insert("file", NameServerDefinitionString);
		nameServer.send("update", request, NULL);
		}

	// *********************************************************************
	// * If the tags used by this service have not yet been defined in the
	// * cdevGlobalTagTable, then automatically define them here.
	// *********************************************************************
	if(ServerTag==0)
		{
		cdevData::insertTag(ServiceTagBase+1, "server");
		cdevData::insertTag(ServiceTagBase+2,   "host");
		cdevData::insertTag(ServiceTagBase+3,   "port");
		cdevData::tagC2I("server", &ServerTag);
		cdevData::tagC2I("host",   &HostTag);
		cdevData::tagC2I("port",   &PortTag);
		}
	
	// *********************************************************************
	// * Install a tag callback that will automicatically be executed each
	// * time a new tag is added to the cdevGlobalTagTable.
	// *********************************************************************
	cdevData::addTagCallback(this);
	}
	
	
// *****************************************************************************
// * cdevClientService::~cdevClientService :
// *	This is the destructor for the object.  It will call release any
// *	memory that was allocated by the class.
// *****************************************************************************
cdevClientService::~cdevClientService ( void )
	{
	AddressIndexIterator    iter (&transactions);
	StringHashIterator      sIter(&nsCallbackArgs);
	cdevClientTransaction * xobj;	

	if(domain) delete domain;
	
	for(iter.first(); (xobj = (cdevClientTransaction *)iter.data())!=NULL; iter++)
		{
		transactions.remove(iter.key());

		delete xobj;
		}
	
	NSCallbackArg * nsData;
	for(sIter.first(); (nsData = (NSCallbackArg *)sIter.data())!=NULL; sIter++)
		{
		delete nsData;
		}
		 
	cdevData::delTagCallback(this);
	}


// *****************************************************************************
// * cdevClientService::defaultCallback :
// *	This is the callback function that is used to support sendNoBlock 
// *	requests that are issued by the cdevRequestObject.
// *****************************************************************************
void cdevClientService::defaultCallback (int, void *, cdevRequestObject &, cdevData & )
	{
	}


// *****************************************************************************		
// * cdevClientService::nameServerCallback :
// *	This is the callback function that is executed whenever one of the
// *	connected servers becomes disconnected.  This function will call each
// *	of the request objects associated with the server and notify them
// *	of the server's change in status.
// *****************************************************************************
void cdevClientService::nameServerCallback(int, void *userarg, cdevRequestObject &, cdevData & data)
	{
	NSCallbackArg     * arg        = (NSCallbackArg *)userarg;
	cdevClientService * cService   = arg->service;
	unsigned short      port       = 0;
	int                 statusCode = -1;
	cdevData            userData;
	char                server[255];
	char                host[255];
	
	// *********************************************************************
	// * Initialize the server and host variables to empty strings, this
	// * will allow me to detect if the data was actually extracted from the 
	// * cdevData object.
	// *********************************************************************
	*server = 0;
	*host   = 0;
	
	// *********************************************************************
	// * The status code from the name server should be between 0 and 3.
	// * If the status code is out of range, display an error and return.
	// *********************************************************************
	data.get("status", &statusCode);
	if(statusCode<0)
		{
		cService->outputError(CDEV_SEVERITY_ERROR,
				      "cdevClientService",
				      "Name Server is down...");
		return;
		}
	else if(statusCode>3)
		{
		cService->outputError(CDEV_SEVERITY_ERROR, 
				      "cdevClientService",
				      "Unknown status code (%i) received from Name Server",
				      statusCode);
		return;
		}

	// *********************************************************************
	// * Read critical tags from the cdevData object.
	// *********************************************************************
	data.get("name", server, 255);
	data.get("host", host, 255);
	data.get("port", &port);
	
	// *********************************************************************
	// * Determine if a live connection still exists to the specified server
	// *********************************************************************
	ServerHandler * handler = cService->connections.find(server);
	
	// *********************************************************************
	// * If the handler is NULL this means that the application has already 
	// * become disconnected from the server.  Consequently, all restartable
	// * requests will have to be resent and all other requests will have
	// * to be terminated.  
	// * For this reason, if the statusCode coming from Name Server is 0,
	// * it should be set to 3 (indicating a server restart).  If the status
	// * code coming from the server is a 1 then it should be set to 2 
	// * (indicating a dead server).
	// *********************************************************************
	if(handler==NULL && statusCode==0) statusCode=3;
	if(handler==NULL && statusCode==1) statusCode=2;

	// *********************************************************************
	// * If the statusCode is 2 (terminated server) and the ServerHandler
	// * is not equal to NULL, then delete the ServerHandler to force the
	// * connection to be closed.
	// *********************************************************************
	if(statusCode==2 && handler!=NULL)
		{
		delete handler;
		handler = NULL;
		}

	// *********************************************************************
	// * Report the condition of the connection.
	// *********************************************************************
	cService->outputError(CDEV_SEVERITY_INFO, "cdevClientService", 
			      "Name Server reports server %s in domain %s has been %s",
			      server, cService->getDomain(),
			      ((statusCode==0)?"CONNECTED":((statusCode==1)?"DISCONNECTED":
			      ((statusCode==2)?"TERMINATED":"RESTARTED"))));
	
	// *********************************************************************
	// * Walk through the list of transactions and process those with a
	// * matching server name and a differing status code.
	// *********************************************************************
	AddressIndexIterator    iter(&cService->transactions);
	cdevClientTransaction * trans;	

	for(iter.first(); (trans = (cdevClientTransaction *)iter.data())!=NULL; iter++)
		{
		cdevTranObj             * xobj   = trans->xobj;
		cdevCallback            * cb     = xobj->userCallback_;

		// *************************************************************
		// * Process transactions associated with this specific server.
		// *************************************************************
		if(trans->statusCode!=statusCode && !strcmp(trans->server, server))
			{		
			// *****************************************************
			// * Status code of 0 indicates that the server is
			// * online and operational.  We also check the 
			// * ServerHandler to ensure that we still have a
			// * valid connection.
			// *****************************************************
			if(statusCode==0)
				{
				cb->fireCallback(CDEV_RECONNECTED, cb->userarg(), 
						 *xobj->reqObj_, userData, 1); 
				if(trans->statusCode<0) 
					{
					trans->reconnect(host, port, iter.key());
					}
				}
			// *****************************************************
			// * Status code of 1 indicates that the server has
			// * ceased sending periodic updates to the name server
			// *****************************************************
			else if (statusCode==1)
				{
				cb->fireCallback(CDEV_DISCONNECTED, cb->userarg(), 
						 *xobj->reqObj_, userData, 1);
				trans->statusCode = statusCode;
				}
			// *****************************************************
			// * Status code of 2 indicates that the server has
			// * terminated and all connections are lost.
			// *****************************************************
			else if (statusCode==2 && trans->restartable)
				{
				cb->fireCallback(CDEV_DISCONNECTED, cb->userarg(), 
						 *xobj->reqObj_, userData, 1);
				trans->statusCode = statusCode;
				}
			// *****************************************************
			// * Status code of 3 indicates that a new server with
			// * an identical name and domain has taken the place
			// * of the dead server.  If the transaction is 
			// * restartable, resubmit the request to the server.
			// *****************************************************
			else if (statusCode==3 && trans->restartable)
				{
				trans->statusCode = -1;
				trans->reconnect(host, port, iter.key());
				}
			// *****************************************************
			// * This section will catch the statusCode 2 and 3
			// * events where the request is not restartable and 
			// * will delete these transactions.
			// *****************************************************
			else if (statusCode==2 || statusCode==3)
				{
				cb->fireCallback(CDEV_ERROR, cb->userarg(), 
						 *xobj->reqObj_, userData, 0);
				cService->transactions.remove(iter.key());
				delete trans;
				}			
			}
		}
	}
	
	
// *****************************************************************************
// * cdevClientService::outputError :
// *	This mechanism is used to report errors to the system.
// *****************************************************************************
int cdevClientService::outputError(int severity, char *name, char *formatString, ...)
	{
	int         status;
	va_list     argp;

	va_start (argp, formatString);
	status = system_.vreportError(severity, name, NULL, formatString, argp);
	va_end   (argp);
	
	return status;
	}
	
	
// *****************************************************************************
// * cdevClientService::connect :
// *	This mechanism will establish a new connection with a server and will
// *	will return a pointer to the ServerHandler.  If the host and port are
// *	specified in the arguments, then the method will attempt to connect 
// *	using those values without going through the CDEV Name Server.
// * 
// *	Note:  This method will insert the ServerHandler into the connections
// *	object and will install it in the Reactor.
// *****************************************************************************
ServerHandler * cdevClientService::connect ( char * server, char * host, unsigned short port )
	{
	ServerHandler  * handler  = NULL;

	// *********************************************************************
	// * First attempt to locate the handler from the existing connections.
	// *********************************************************************
	if(server && *server && (handler = connections.find(server))==NULL)
		{
		int                 errCode = CDEV_SUCCESS;
		char                serverHost[255];
		unsigned short      serverPort;
		int                 serverStatus;
		
		// *************************************************************
		// * If the host and port have been specified, then use them
		// * rather than going through the CDEV Name Server.
		// *************************************************************
		if(host!=NULL && *host && port>0)
			{
			strcpy(serverHost, host);
			serverPort = port;
			}
		else	{
			cdevData            input;
			cdevData            output;
			cdevRequestObject & rnsReq = 
				cdevRequestObject::attachRef("NameServer", "get");

			*serverHost = 0;
			serverPort  = 0;
			
			// *****************************************************
			// * Insert domain and name in the input request object 
			// * to be sent to the CDEV Name Server.
			// *****************************************************
			input.insert("name", server);
			input.insert("domain", domain);
		
			// *****************************************************
			// * Query the Name Server to obtain the hostname and 
			// * port of the specified domain and server.
			// *****************************************************
			if((errCode=rnsReq.send(input, output))!=CDEV_SUCCESS ||
			   output.get("host", serverHost, 255)!=CDEV_SUCCESS ||
			   output.get("port", &serverPort)!=CDEV_SUCCESS ||
			   output.get("status", &serverStatus)!=CDEV_SUCCESS ||
			   *serverHost==0 || serverPort<=0 || serverStatus!=0)
				{
				errCode = CDEV_ERROR;
				
				// *********************************************
				// * If the hostname could not be found, report 
				// * the error and set the return value to 
				// * CDEV_ERROR.
				// *********************************************
				outputError(CDEV_SEVERITY_ERROR, 
				            "cdevClientService::connect",
					    "Cannot find host for server \"%s\" in domain \"%s\"",
					    server, domain);
				}
			}
			

		// *************************************************************
		// * If the hostname was discovered, then the errCode should be
		// * CDEV_SUCCESS.  Attempt to connect to the server using the
		// * cdevReactor mechanisms.
		// *************************************************************
		if(errCode==CDEV_SUCCESS)
			{
			cdevInetAddr addr;
			
			addr.set   (serverPort, serverHost);

			handler = new ServerHandler(server, this);

			if(handler->open(addr)!=0)
				{
				outputError(CDEV_SEVERITY_ERROR,
					    "cdevClientService::connect",
					    "Failed to connect to %s on port %i",
					    serverHost, 
					    serverPort);
				delete handler;
				handler = NULL;
				}
			else	{
				outputError(CDEV_SEVERITY_INFO,
					    "cdevClientService::connect",
					    "Connected to %s on port %i",
					    serverHost, 
					    serverPort);
				connections.insert(handler);
				
				// *********************************************
				// * This calls the registerFd method of the 
				// * service which will cause the FD Changed 
				// * callbacks to be triggered.
				// *********************************************
				registerFd(handler->getHandle(), 1);
				
				// *********************************************
				// * Install a Name Server monitor to maintain
				// * the status of the connection.
				// * Note:  If a monitor has already been 
				// * installed using this informations, then the
				// * CDEV Name Server service should not create 
				// * a second monitor.
				// *
				// * Note: I am using the FifoQueue associated
				// * with the specific server in order to 
				// * differentiate between the various servers.
				// *********************************************
				cdevData            monData;
				cdevCallback        monCb(nameServerCallback, (void *)getCallbackArg(server));
				cdevRequestObject & monReq = cdevRequestObject::attachRef("NameServer", "monitorOn");

				monData.insert("name", server);
				monData.insert("domain", domain);
				monReq.sendCallback(monData, monCb);
				
				// *********************************************
				// * Here is where a "set ClientInfo" message
				// * will be sent to the server to provide
				// * descriptive information about the client.
				// *********************************************
 				cdevData tagMap;
	 			int    * itags;
 				char  ** ctags;
 				int      ntags;
 				char   * binary    = NULL;
				size_t   binaryLen = 0;
				
 				cdevData::readTagTable(itags, ctags, ntags);
 				tagMap.insert(1, itags, ntags);
 				tagMap.insert(2, ctags, ntags);
 				
 				delete itags;
 				delete ctags;
 				handler->setTagChangeFlag(0);
 
				cdevMessageBinary packet(handler->getClientID(), 
							 0, 0, 0, 0, CDEV_SERVER_OP, 0, 0, 
							 NULL, "set ClientInfo",
							 &clientInfo.getClientData(), 
							 NULL, &tagMap);
		
				packet.streamOut(&binary, &binaryLen);
				packet.detachData();

				ServerInterface::enqueue(handler, binary, binaryLen);
				}
			}
		}
	return handler;
	}


// *****************************************************************************
// * cdevClientService::disconnect :
// *	This mechanism will remove the ServerHandler pointer from the 
// *	connections list and will return the ServerHandler pointer to the 
// *	caller.  It is the responsibility of the caller to delete the 
// *	ServerHandler pointer.
// *****************************************************************************
ServerHandler * cdevClientService::disconnect ( char * server )
	{
	ServerHandler * handler;
	
	// *********************************************************************
	// * Find and remove the specified server.  This method will also check 
	// * to determine if the handler that is being disconnected is the 
	// * defaultServer - if so, the defaultServerHandler variable will be 
	// * set to NULL.
	// *********************************************************************
	if((handler = connections.remove(server))==defaultServerHandler)
		{
		defaultServerHandler = NULL;
		}
	// *********************************************************************
	// * This calls the registerFd method of the service which will cause 
	// * the FD Changed callbacks to be triggered.
	// *********************************************************************
	if(handler!=NULL && handler->getHandle()!=cdevSocket::INVALID_HANDLE)
		{
		registerFd(handler->getHandle(), 0);
		}			

	return handler;
	}

// *****************************************************************************
// * cdevClientService::flush :
// *	This is method calls the flush mechanism of the underlying 
// *	ServerInterface class.
// *****************************************************************************
int cdevClientService::flush ( void )
	{ 
	return ServerInterface::flush(); 
	}



// *****************************************************************************
// * cdevClientService::pend :
// *	This method calls the pend mechanism of the underlying
// *	ServerInterface class.
// *****************************************************************************
int cdevClientService::pend ( double seconds, int fd )
	{
	return ServerInterface::pend(seconds, fd); 
	}


// *****************************************************************************
// * cdevClientService::getFd :
// *	This method calls the getFd mechanism of the underlying
// *	ServerInterface class.
// *****************************************************************************
int cdevClientService::getFd( int * &fd, int & numFd )
	{
	return ServerInterface::getFd(fd, numFd); 
	}

	
// *****************************************************************************
// * cdevClientService::poll :
// *	This is the polling mechanism for the service.
// *****************************************************************************
int cdevClientService::poll ( void )
	{
	return ServerInterface::poll();
	}
	

// *****************************************************************************
// * cdevClientService::pend :
// *	This method does not implement the full functionality of the 
// *	cdev prototype.  Rather it pends for a default amount of time.
// *****************************************************************************
int cdevClientService::pend( int fd)
	{
	return pend(0.01, fd);
	}


// *****************************************************************************
// * cdevClientService::getNameServer :
// *	This method is used to provide a cdevDevice that can be used as the
// *	name server for the service.  It is not implemented here.
// *****************************************************************************
int cdevClientService::getNameServer ( cdevDevice * &ns )
	{
	ns = NULL;
	return CDEV_SUCCESS;
	}
	

// *****************************************************************************
// * cdevClientService::getRequestObject :
// *	This is the interface that cdev objects will use to obtain a
// *	cdevClientRequestObject object.  The cdevClientRequestObject 
// *	represents a combined device and message pair that is associated 
// *	with the cdevClientService.
// *
// *	Returns CDEV_SUCCESS on success or CDEV_ERROR on error.
// *****************************************************************************
int cdevClientService::getRequestObject ( char * device, char * message, cdevRequestObject * &req)
	{
	req = new cdevClientRequestObject (device, message, system_);
	return (req ? CDEV_SUCCESS : CDEV_ERROR);
	}


// *****************************************************************************
// * cdevClientService::enqueue :
// *	This is the method that is used by the cdevRequestObject to submit a
// *	message to the cdevServer when the name of the server is known, but a 
// *	ServerHandler object has not yet been obtained...
// *****************************************************************************
int  cdevClientService::enqueue ( char * server, cdevData * in, cdevTranObj & xobj )
	{
	ServerHandler * handler = server==NULL?defaultServerHandler:connect(server); 
	
	return enqueue(handler, in, xobj);
	}


// *****************************************************************************
// * cdevClientService::enqueue :
// *	This method is used by the cdevRequestObject to enqueue an outbound 
// *	message to the cdevServer when the ServerHandler has already been 
// *	obtained.
// *****************************************************************************
int cdevClientService::enqueue ( ServerHandler * handler, cdevData * in, cdevTranObj &xobj)
	{
	int result = CDEV_SUCCESS;

	// *********************************************************************
	// * Determine if the message will be processed locally first.  
	// * If not, then submit it to the underlying ServerInterface 
	// * for  processing.
	// *********************************************************************
	if(processLocal(in, xobj)!=0)
		{
		// *************************************************************
		// * Determine if the handler is valid or use the 
		// * defaultServerHandler
		// *************************************************************
		if(handler==NULL)
			{
			if(defaultServerHandler!=NULL) 
				{
				handler=defaultServerHandler;
				}
			else if(defaultServerHandler==NULL && defaultServer!=NULL)
				{
				handler = (defaultServerHandler=connect(defaultServer));
				}
			else	{
				system_.reportError(CDEV_SEVERITY_ERROR,
					    "cdevClientService::enqueue",
					    xobj.reqObj_,
					    "No default server has been defined for this service");
				}
			}
		
		// *************************************************************
		// * If a valid ServerHandler has been located.
		// *************************************************************
		if(handler!=NULL)
			{
			cdevClientRequestObject * reqObj;
			int                       restart;
			int                       ctxID;
			cdevClientTransaction   * node;
			unsigned                  index;
			
			reqObj  = (cdevClientRequestObject *)xobj.reqObj_;
			restart = reqObj->isRequestRestartable();
			ctxID   = reqObj->getContextID();
			node    = new cdevClientTransaction(xobj, *handler, restart, in, ctxID);
			index   = transactions.insert(node);

			if((result=enqueue(handler, *node, index))!=CDEV_SUCCESS)
				{
				transactions.remove(index);
				delete node;
				}
			else	{
				// *********************************************
				// * If the message is monitorOff, then the 
				// * method will walk through all of the active 
				// * monitors and remove those that exactly match 
				// * all attributes of the request....
				// *	 - Server    name
				// *	 - Device    name
				// *	 - Attribute name
				// *	 - Context   values
				// *********************************************
				if(reqObj->getCommandCode()==cdevClientRequestObject::MONITOR_OFF_COMMAND)
					{
					#define xCommand  ((cdevClientRequestObject *)trans->xobj->reqObj_)->getCommandCode()
					#define xDevice   xobj->reqObj_->device().name()
					#define xMessage  xobj->reqObj_->message()
					#define xUserarg  xobj->userCallback_->userarg()
					#define xCallback xobj->userCallback_->callbackFunction()
	
					AddressIndexIterator    iter(&transactions);
					cdevClientTransaction * trans;
				  	cdevMessageBinary       packet;
					char                  * device = (char *)reqObj->device().name();
					char                  * binary;
					size_t                  binaryLen;
					  	
					for(iter.first(); (trans=(cdevClientTransaction *)iter.data())!=NULL; iter++)
						{
						if(xCommand==cdevClientRequestObject::MONITOR_ON_COMMAND &&
						   (node->xUserarg==NULL || node->xUserarg==trans->xUserarg)    &&
						   (node->xCallback==NULL || node->xCallback==trans->xCallback) &&
						   !strcmp(node->xDevice, trans->xDevice)                       && 
						   !strcmp(node->xMessage+11, trans->xMessage+10)               &&
						   !strcmp(node->server, trans->server))
							{
						   	int cancelTransIdx = iter.key();

						   	// **********************************************
						   	// * Remove the transaction and delete it.
						   	// **********************************************
						   	transactions.remove(cancelTransIdx);
						   	
						   	// **********************************************
						   	// * Fire the callback with the 
						   	// * partialTransaction flag set to 0 to notify 
						   	// * the developer that the transaction is 
						   	// * finished.  Note that the status is 
						   	// * CDEV_WARNING because no actual data is 
						   	// * being delivered.
						   	// **********************************************
						   	fireCallback(CDEV_WARNING, *trans->xobj, NULL, 0);

						   	delete trans;
						   	
						   	// **********************************************
							// * Send a packet containing the transaction to 
							// * be destroyed.
							// **********************************************
							packet.set(handler->getClientID(), 0, cancelTransIdx, 0, 0, 0, 0, 1, 
			                   			   &device, xobj.reqObj_->message());

			                  		packet.streamOut(&binary, &binaryLen);
							packet.detachData();
							ServerInterface::enqueue(handler, binary, binaryLen);
							}
						}

					#undef xCommand
					#undef xDevice
					#undef XMessage
					#undef XUserarg
					#undef XCallback
					}
				}
			}
		else result = CDEV_ERROR;
		}
	
	return result;
	}
	

// *****************************************************************************
// * cdevClientService::enqueue :
// *	This method is called by the internals of the cdevClientService to
// *	submit a message to a server.  This method is called by either the
// *	enqueue method (to initially submit a request), or the nameServerCallback
// *	method ( to resubmit a request when a server has restarted).
// *****************************************************************************
int cdevClientService::enqueue ( ServerHandler * handler, cdevClientTransaction &trans, unsigned transID )
	{
	int result;

	// *********************************************************************
	// * If the server handler is valid and a connection has been 
	// * established.
	// *********************************************************************
	if(handler!=NULL)
		{
		cdevTranObj             * xobj    = trans.xobj;
		cdevClientRequestObject * reqObj  = (cdevClientRequestObject *)xobj->reqObj_;
		char                    * device  = (char *)reqObj->device().name();
		int                       ctxID   = trans.contextID;
 		cdevData                * in      = trans.userData;
 		cdevData                * context = NULL;
		cdevData                * tagMap  = NULL;
		int                       opCode  = CDEV_NORMAL_OP;
		
		// *************************************************************
		// * Set the operation code to CDEV_SERVER_OP if the message
		// * should be processed by the cdevGenericServer Engine rather
		// * than the user defined server engine.
		// *************************************************************
		if(reqObj->getMessageCode()==cdevClientRequestObject::GET_CLIENTINFO_MESSAGE ||
		   reqObj->getMessageCode()==cdevClientRequestObject::GET_SERVERINFO_MESSAGE)
			{
			opCode=CDEV_SERVER_OP;
			}
		
		// *************************************************************
		// * Switch to the contextID specified in trans parameter.
		// *************************************************************
		if(handler->getContextID()!=ctxID)
 			{
 			context = contexts.find(ctxID);
 			handler->setContextID(ctxID);
 			}
 			
 		// *************************************************************
 		// * If the tag map has changed, include it in the transmission
 		// *************************************************************
 		if(handler->getTagChangeFlag())
 			{
 			int  *  itags;
 			char ** ctags;
 			int     ntags;
 				
 			cdevData::readTagTable(itags, ctags, ntags);
 			tagMap = new cdevData;
 			tagMap->insert(1, itags, ntags);
 			tagMap->insert(2, ctags, ntags);
 				
 			delete itags;
 			delete ctags;
 			handler->setTagChangeFlag(0);
 			}

		// *************************************************************
		// * Construct the outbound packet.
		// *************************************************************
		cdevMessageBinary packet(handler->getClientID(), transID, 
					 0, 0, 0, opCode, 0, 1, 
					 &device,
					 reqObj->message(),
					 in,
					 context,
					 tagMap);
		
		// *************************************************************
		// * Delete the tag map if it was allocated.
		// *************************************************************
		if(tagMap) delete tagMap;
		
		// *************************************************************
		// * Extract the binary stream representation of the data and 
		// * then use the detach data mechanism to ensure that the 
		// * binary buffer is not deleted when the packet object is 
		// * destroyed.
		// *************************************************************
		char * binary    = NULL;
		size_t binaryLen = 0;
		packet.streamOut(&binary, &binaryLen);
		packet.detachData();


		// *************************************************************
		// * Enqueue the message into the outbound queue.  
		// *************************************************************
		result=ServerInterface::enqueue(handler, binary, binaryLen);
		}
	else result = CDEV_ERROR;
	
	return result;
	}

// *****************************************************************************
// * cdevClientService::cancel :
// *	This method is used to cancel a transaction that has taken too long
// *	to be processed.
// *****************************************************************************
int cdevClientService::cancel ( cdevTranObj & xobj )
	{
	AddressIndexIterator    iter(&transactions);
	cdevClientTransaction * node;
		
	for(iter.first(); (node=(cdevClientTransaction *)iter.data())!=NULL && node->xobj!=&xobj; iter++);
	if(node!=NULL)
		{
		transactions.remove(iter.key());
		delete node;
		}

	return CDEV_SUCCESS;
	}	


// *****************************************************************************
// * cdevClientService::enqueue :
// *	This is the mechanism that is used by the underlying ServerInterface
// *	to return the result of a transmission to the caller.  DO NOT DELETE
// *	THE BINARY PROVIDED TO THIS CLASS.
// *****************************************************************************
int cdevClientService::enqueue ( int status, ServerHandler * /*handler*/, char * binary, size_t binaryLen )
	{
	cdevMessage message(binary, binaryLen);
	cdevClientTransaction * node;
	
	if((node=(cdevClientTransaction *)transactions.find(message.getTransIndex()))!=NULL)
		{
		int completionCode = (status==FAILED_TO_SEND)?CDEV_ERROR:message.getCompletionCode();

		if(status==FAILED_TO_SEND) node->permanent = 0;

		fireCallback(completionCode, *node->xobj, message.getData(), node->permanent);

		if(!node->permanent)
			{
			transactions.remove(message.getTransIndex());
			delete node;
			}
		} 

	return CDEV_SUCCESS;
	}


// *****************************************************************************
// * cdevClientService::fireCallback :
// *	 This method is used to deploy a callback on a specific transaction.
// *****************************************************************************
void cdevClientService::fireCallback ( int status, 
				       cdevTranObj &xobj, 
				       cdevData *resultData,
				       int partialTransaction)
	{
	static cdevData dummy;
	
	if(resultData==NULL) 
		{
		dummy.remove();
		resultData = &dummy;
		}
	if(xobj.resultData_!=NULL) *xobj.resultData_ = *resultData;

	xobj.userCallback_->fireCallback
	       (status,
		xobj.userCallback_->userarg(),
		*xobj.reqObj_,
		xobj.resultData_!=NULL?*xobj.resultData_:*resultData,
		partialTransaction);
	}

	
// *****************************************************************************
// * cdevClientService::processLocal:
// *	This method performs any localized data processing that the service
// *	might require.  If the message is processed locally, this method should
// *	return 0... otherwise, the method should return non-zero.
// *****************************************************************************
int  cdevClientService::processLocal ( cdevData * in, cdevTranObj & xobj)
	{
	int                       result = 1;
	int                       status = CDEV_SUCCESS;
	cdevClientRequestObject * reqObj = (cdevClientRequestObject *)xobj.reqObj_;
	
	switch(reqObj->getMessageCode())
		{
		case cdevClientRequestObject::GET_SERVERS_MESSAGE:
			{ 
			char                queryMsg[256];
			cdevData            input;
			cdevData            output;
			cdevRequestObject & nsReq = 
				cdevRequestObject::attachRef("NameServer", "query");
				
			sprintf(queryMsg, "domain=='%s'", getDomain());
			input.insert("queryMsg", queryMsg);
			if((status=nsReq.send(input, output))==CDEV_SUCCESS)
				{
				output.changeTag ("name", "value");
				}
			fireCallback(status, xobj, &output);
			result = CDEV_SUCCESS; 
			}
		break;

		case cdevClientRequestObject::GET_DEFAULT_MESSAGE:
			{ 
			cdevData  resultData;
			resultData.insert("value", getDefault());
			fireCallback(CDEV_SUCCESS, xobj, &resultData); 
			result = CDEV_SUCCESS; 
			}
		break;
		
		case cdevClientRequestObject::SET_DEFAULT_MESSAGE:
			{ 
			cdevData resultData;
			char     DefaultServer[128];

			*DefaultServer = 0;
			
			if(in!=NULL && in->get("value", DefaultServer, 128)==CDEV_SUCCESS && *DefaultServer!=0)
				{
				setDefault(DefaultServer);
				}
			else status=CDEV_ERROR; 
			fireCallback(status, xobj, &resultData); 
			result = CDEV_SUCCESS; 
			} 
		break;
		
		case cdevClientRequestObject::DISCONNECT_MESSAGE:
			{
			cdevData resultData;
			char     server [128];
			int      status;

			if(xobj.reqObj_->getContext().get("server", server, 128)==CDEV_SUCCESS)
				{
				disconnect(server);
				status = CDEV_SUCCESS;
				}
			else status = CDEV_ERROR;
	
			fireCallback(status, xobj, &resultData); 
			result = CDEV_SUCCESS; 	
			}
		break;
		
		default:
		break;
		}
	return result;
	}
	

// *****************************************************************************
// * cdevClientService::isPacketValid :
// *	This method is called by the ServerHandler after it has been restarted.
// *	If the ServerHandler inherits packets that are already in the queue, it
// *	will call this method for each packet in order to determine if they are
// *	valid.  If the packets are no longer valid (i.e., there is no longer 
// *	a cdevTransObj associated with them, they will be deleted.  Otherwise, 
// *	they will be submitted to the server.
// *****************************************************************************
int cdevClientService::isPacketValid ( char * binary, size_t binaryLen )
	{
	int               result;
	
	if(binary!=NULL && binaryLen>0)
		{
		cdevMessageBinary message;
		unsigned int trans;
		message.getTransIndex(trans);
		message.attachData(binary, binaryLen);
		result = transactions.find(trans)?1:0;
		message.detachData();
		}
	else result = 0;
	
	return result;
	}	


// *****************************************************************************
// * cdevClientService::callback :
// *	This method is called by the cdevData object whenever a new tag is
// *	added.
// *****************************************************************************
void cdevClientService::callback ( int, char * )
	{
	for(int idx=0; connections[idx]!=NULL; idx++)
		{
		connections[idx]->setTagChangeFlag(1);
		}
	}
	

// *****************************************************************************
// * cdevClientService::getCallbackArg :
// *	This method creates a unique callback argument that can be passed to the
// *	CDEV Name Server service to allow it to differentiate between two 
// *	servers that are being accessed through the same service.
// *****************************************************************************
NSCallbackArg * cdevClientService::getCallbackArg ( char * server )
	{
	NSCallbackArg * ptr;
	if((ptr = (NSCallbackArg *)nsCallbackArgs.find(server))==NULL)
		{
		ptr = new NSCallbackArg(server, this);
		nsCallbackArgs.insert(ptr->server, ptr);
		}
	return ptr;
	}

// *****************************************************************************
// * cdevClientTransaction::operator new:
// * Allocation function for the object.  It will get the next preallocated 
// * cdevClientTransaction object from the free list, or, if none are available, 
// * refill the free list and then return a new cdevClientTransaction object.
// *****************************************************************************
void * cdevClientTransaction::operator new ( size_t )
	{
	cdevClientTransaction * result = NULL;
	
	if(freeList_==NULL)
		{
		freeList_ = ::new cdevClientTransaction[ALLOCATION_COUNT];
		for(int i=0; i<ALLOCATION_COUNT; i++) 
			{
			freeList_[i].freeListNext_ = 
				(i<(ALLOCATION_COUNT-1))?&freeList_[i+1]:(cdevClientTransaction *)NULL;
			}
		}
	
	if(freeList_!=NULL) 
		{
		result                = freeList_;
		freeList_             = result->freeListNext_;
		result->freeListNext_ = NULL;
		}
		
	return result;
	}

// *****************************************************************************
// * cdevClientTransaction::delete:
// *	Rather than deallocating the cdevClientTransaction object, this function
// *	returns it to the free list where it may be retrieved by a later call 
// *	of new.
// *****************************************************************************
void cdevClientTransaction::operator delete ( void * ptr )
	{
	cdevClientTransaction * node = (cdevClientTransaction *)ptr;
	if(node != NULL) 
		{
		node->freeListNext_ = freeList_;
		freeList_           = node;
		}
	}

// *****************************************************************************
// * cdevClienTransaction::cdevClientTransaction :
// *	Array constructor for the cdevClientTransaction object.
// *****************************************************************************
cdevClientTransaction::cdevClientTransaction (void)
	: xobj       (NULL),
	  permanent  (0),
	  statusCode (0),
	  restartable(0),
	  contextID  (0),
	  userData   (NULL)
	{
	*server = 0;
	}


// *****************************************************************************
// * cdevClienTransaction::cdevClientTransaction :
// *	Constructor for the cdevClientTransaction object.
// *****************************************************************************
cdevClientTransaction::cdevClientTransaction (cdevTranObj & XObj, ServerHandler &handler,
					      int Restartable, cdevData * data,
					      unsigned ContextID)
	: xobj       (&XObj),
	  permanent  (0),
	  statusCode (0),
	  restartable(Restartable),
	  contextID  (ContextID),
	  userData   (NULL)
	{
	permanent = ((cdevClientRequestObject *)xobj->reqObj_)->getCommandCode()==
		      cdevClientRequestObject::MONITOR_ON_COMMAND;
	strncpy(server, handler.getServer(), 256);
	server[255] = 0;

	if(restartable && data) userData = new cdevData(*data);
	else                    userData = data;
	}

// *****************************************************************************
// * cdevClientTransaction::~cdevClientTransaction :
// *	Destructor for the cdevClientTransaction object.
// *****************************************************************************
cdevClientTransaction::~cdevClientTransaction ( void )
	{
	if(xobj)                    delete xobj;
	if(restartable && userData) delete userData;
	}


// *****************************************************************************
// * cdevClientTransaction::reconnect :
// *	This method will attempt to recreate a connection for a server to 
// *	a specified host and port.  If the host and port are not specified,
// *	the the cdevClientService::connect and cdevClientService::find methods
// *	will be used to reattach to the specified server.
// *****************************************************************************
void cdevClientTransaction::reconnect ( char * host, unsigned short port, unsigned key)
	{
	cdevClientService &service = (cdevClientService &)xobj->reqObj_->service();

	if(key==0) key = service.transactions.find((void *)this);
	
	if(restartable && statusCode<0 && key)
		{
		ServerHandler *handler = NULL;
		
		// *************************************************************
		// * Use the find method to locate the ServerHandler if 
		// * possible, otherwise use the connect method to reconnect to
		// * the server.
		// *************************************************************
		if((handler=service.connections.find(server))==NULL)
			handler = service.connect(server, host, port);

		// *************************************************************
		// * If a connection was established to the server, then use
		// * the enqueue method to resubmit the request to the server.
		// *************************************************************
		if(handler!=NULL)
			{
			if(service.enqueue(handler, *this, key)==CDEV_SUCCESS)
				{
				cdevData cData;
				statusCode = 0;
				xobj->userCallback_->fireCallback
					(CDEV_RECONNECTED, 
					xobj->userCallback_->userarg(), 
					*xobj->reqObj_, cData, 1);
				}
			}
		}
	}	
