/*************************************************************************
Copyright © 1999-2002 Novell, Inc. All Rights Reserved. 

THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND 
TREATIES. USE AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO THE LICENSE
AGREEMENT ACCOMPANYING THE SOFTWARE DEVELOPMENT KIT (SDK) THAT CONTAINS
THIS WORK. PURSUANT TO THE SDK LICENSE AGREEMENT, NOVELL HEREBY GRANTS
TO DEVELOPER A ROYALTY-FREE, NON-EXCLUSIVE LICENSE TO INCLUDE NOVELL'S
SAMPLE CODE IN ITS PRODUCT. NOVELL GRANTS DEVELOPER WORLDWIDE DISTRIBUTION
RIGHTS TO MARKET, DISTRIBUTE, OR SELL NOVELL'S SAMPLE CODE AS A COMPONENT
OF DEVELOPER'S PRODUCTS. NOVELL SHALL HAVE NO OBLIGATIONS TO DEVELOPER OR
DEVELOPER'S CUSTOMERS WITH RESPECT TO THIS CODE.

***************************************************************************/
/*******************************************************************
* Name: SkeletonSubscriber.cpp
* Description: Platform independent code for CSkeletonDriver
* SubscriptionShim implementation
* Tabs: 4
*******************************************************************/


#include	"SkeletonSubscriber.h"
#include	"InterfaceFactory.h"
#include	"wcsfunc.h"
#include	"NdsDtd.h"

//string constants: They are defined as arrays because the L" syntax doesn't work on unix, since a wchar_t is
//32-bits rather than 16-bits as on Win32 and Netware
static const unicode	TEXT_SUBSCRIBER[]			=	{'s','u','b','s','c','r','i','b','e','r',0};
static const unicode	TEXT_CURRENT_ASSOCIATION[]	=	{'c','u','r','r','e','n','t','-','a','s','s','o','c','i','a','t','i','o','n',0};
static const unicode	TEXT_SUB_1[]				=	{'s','u','b','-','1',0};
static const unicode	MSG_CONNECT_FAILURE[]		=	{'f','a','i','l','e','d',' ','t','o',' ','c','o','n','n','e','c','t',0};
static const unicode	FORMAT_BAD_COMMAND[]		=	{'U','n','s','u','p','p','o','r','t','e','d',' ',
														'c','o','m','m','a','n','d',':',' ',0};

static const unicode	DRIVER_ID_VALUE[]			=	{'C','S','K','E','L',0};
static const unicode	DRIVER_VERSION_VALUE[]		=	{'1','.','1',0};
static const unicode	MIN_ACTIVATION_VERSION_VALUE[]		=	{'0',0};

//description of example init values we want to extract from the init document
//passed to init() and a state value we want. See skel_options.xml
const CommonImpl::ShimParamDesc SkeletonSubscriber::SUBSCRIBER_PARAMS[] = 
{
	{TEXT_SUB_1,STRING_TYPE,false},					//example init value
	{TEXT_CURRENT_ASSOCIATION,INT_TYPE,false},		//state value
	{0,0,false}
};

//method table for dispatch()
const SkeletonSubscriber::CommandEntry	SkeletonSubscriber::dispatchTable[]	=
{
	{NdsDtd_getStrings()->TAG_ADD,		&SkeletonSubscriber::addHandler},
	{NdsDtd_getStrings()->TAG_MODIFY,	&SkeletonSubscriber::modifyHandler},
	{NdsDtd_getStrings()->TAG_DELETE,	&SkeletonSubscriber::deleteHandler},
	{NdsDtd_getStrings()->TAG_RENAME,	&SkeletonSubscriber::renameHandler},
	{NdsDtd_getStrings()->TAG_MOVE,		&SkeletonSubscriber::moveHandler},
	{NdsDtd_getStrings()->TAG_QUERY,	&SkeletonSubscriber::queryHandler},
	{0,0}
};

//macros required by DirXML interface definition (see define_interface.h)
DEFINE_IMPL(SkeletonSubscriber,SubscriptionShim);
DEFINE_IMPL(SkeletonSubscriber,XmlCommandProcessor);

//++
//=========================================================================
// Purpose:
//		Constructor
// Notes:
//=========================================================================
SkeletonSubscriber::SkeletonSubscriber(
	const CommonImpl::AuthenticationParams * ap)	//pointer to auth params from driver shim
//--
	:	common("SkeletonSubscriber"),
		params(0),
		connected(false),
		outputElement(0),
		currentAssociation(1),
		authParams(ap)
{
}

//++
//=========================================================================
// Purpose:
//		Destructor
// Notes:
//=========================================================================
SkeletonSubscriber::~SkeletonSubscriber()
//--
{
	delete	params;
}

//++
//=========================================================================
// Purpose:
//		Interface method, called by DirXML to allow subscriber to perform any
//		required initialization.
// Notes:
//=========================================================================
XmlDocument * METHOD_CALL			//result document
SkeletonSubscriber::init(
	XmlDocument * initParameters)	//document containing init parms for subscriber
//--
{
#ifndef	NO_CPP_EXCEPTIONS
	try
	{
#endif	
		common.tracer->trace("init");
		//get any non-authentication options from the init document
		params = common.getShimParams(initParameters->getDocument(),TEXT_SUBSCRIBER,SUBSCRIBER_PARAMS);
		//get any state that may have been passed in (if this is our very first invocation, there
		//won't be any state)
		//The skeleton driver fakes associations to DirXML so that it appears more like a real driver
		//see addHandler()
		int assocState = params->getIntParam(TEXT_CURRENT_ASSOCIATION);
		if (assocState != -1)
		{
			//setup our fake association for handling adds
			currentAssociation = assocState;
		}
		//perform any other initialization that might be required.
		//Note that in the skeleton driver, SkeletonDriverShim.init() sets up a shared copy of
		//any authentication parameters for use by all three shim objects
		return common.setReturnDocument(common.createSuccessDocument());
#ifndef	NO_CPP_EXCEPTIONS
	} catch (ShimException e)
	{
		return	common.setReturnDocument(common.createStatusDocument(STATUS_LEVEL_FATAL,e.getMessage()));
	} catch (...)
	{
		//something bad happened...
		return	common.setReturnDocument(common.createStatusDocument(STATUS_LEVEL_FATAL,MSG_BAD));
	}
#endif	
}

//++
//=========================================================================
// Purpose:
//		Interface method, called by DirXML to allow command subscriber to
//		perform an action in the application.
// Notes:
//=========================================================================
XmlDocument * METHOD_CALL				//result document
SkeletonSubscriber::execute(
	XmlDocument * doc,					//command document
	XmlQueryProcessor * queryInterface)	//interface through which subscriber can query DirXML if necessary
//--
{
	int	retryCount = 2;
	common.tracer->trace("execute");
	//put this trace in to more thoroughly test the native trace facility
	common.tracer->trace(doc, XML_TRACE + 3);
#ifndef	NO_CPP_EXCEPTIONS
	try
	{
#endif	
		//setup the return document for use by command handlers
		outputElement = NdsDtd_newOutputDocument();
		//try and connect with our mythical app
		while (retryCount-- > 0)
		{
#ifndef	NO_CPP_EXCEPTIONS
			try
			{
#endif			
				//connect may throw a ConnectException
				connect();
				Document * document = doc->getDocument();
				//find the <input> element
				Element * input = common.getFirstElementByTagName(document, common.ndsDtd->TAG_INPUT);
				//iterate through the children, dispatching commands
				Node * childNode = input->getFirstChild();
				while (childNode != 0)
				{
					//only elements are interesting...ignore any interspersed text, comments, etc.
					if (childNode->getNodeType() == Node::ELEMENT_NODE)
					{
						dispatch((Element *)childNode);
					}
					childNode = childNode->getNextSibling();
				}
				//return the result of whatever we were told to do
				return common.setReturnDocument(outputElement->getOwnerDocument());
#ifndef	NO_CPP_EXCEPTIONS
			} catch (ConnectException e)
			{
				if (retryCount <= 0)
				{
					//done trying
					throw	e;
				}
			}
#endif			
		}
#ifndef	NO_CPP_EXCEPTIONS
		//if we fall through here, we failed to connect, so get the the enclosing handler
		throw	ConnectException(MSG_CONNECT_FAILURE);
	} catch (ConnectException e)
	{
		//somehow failed in talking to app, tell DirXML to retry later...

		//"retry" status means the the NDS event will not be discarded and that DirXML will resubmit
		//it later (default is 30 seconds later). This status should be used for errors related to
		//connection problems since the event won't be lost. For errors such as application errors due
		//to data, etc., use "error" since it will cause DirXML to discard the event.
		return	common.setReturnDocument(common.createStatusDocument(STATUS_LEVEL_RETRY,e.getMessage()));
	} catch (ShimException e)
	{
		return	common.setReturnDocument(common.createStatusDocument(STATUS_LEVEL_ERROR,e.getMessage()));
	} catch (...)
	{
		//something bad happened...
		return	common.setReturnDocument(common.createStatusDocument(STATUS_LEVEL_FATAL,MSG_BAD));
	}
#else	
	return	common.setReturnDocument(common.createStatusDocument(STATUS_LEVEL_RETRY,0));
#endif	
}

//++
//=========================================================================
// Purpose:
//		Illustrative method. Connect to supported application if not already
//		connected or if	connection has been broken.
// Notes:
//		Throws ConnectException if can't connect
//=========================================================================
void
SkeletonSubscriber::connect()
//--
{
	if (!connected)
	{
		//do whatever is required to connect to the supported application -
		//e.g., open a Socket, etc.
		//if (connection fails)
		//{
		//	throw ConnectionException(MSG_CONNECT_FAILURE);
		//}
		connected = true;
	}
}

//++
//=========================================================================
// Purpose:
//		Dispatch a command to the appropriate handler.
// Notes:
//=========================================================================
void
SkeletonSubscriber::dispatch(
	Element * command)			//The command element from the input document
//--
{
	const unicode * commandName = command->getNodeName();
	const CommandEntry * entry = dispatchTable;
	while (entry->commandName != 0)
	{
		if (wcscmp(entry->commandName,commandName) == 0)
		{
			//found handler
			(this->*entry->handler)(command);
			return;
		}
		++entry;
	}
	//didn't find command in our table. This could happen, but only if
	//a stylesheet rule adds a custom command of some sort
	//return a status element keyed to the event-id attribute of the command
	unicode	buffer[128];
	wcscpy(buffer,FORMAT_BAD_COMMAND);
	wcscat(buffer,commandName);
	NdsDtd_addStatus(outputElement,STATUS_LEVEL_ERROR,buffer,command->getAttribute(common.ndsDtd->ATTR_EVENT_ID));
}

//++
//=========================================================================
// Purpose:
//		Handler for <add> command from DirXML
// Notes:
//		This returns an add-association with a fake association value.
//		Obviously, a real driver wouldn't do this, but the skeleton driver
//		does it so that we can get modifies, deletes, etcs after we establish
//		the association.
//=========================================================================
void
SkeletonSubscriber::addHandler(
	Element * add)
//--
{
	const unicode * className = add->getAttribute(common.ndsDtd->ATTR_CLASS_NAME);
	const unicode *  eventId = add->getAttribute(common.ndsDtd->ATTR_EVENT_ID);
	const unicode *  srcDn = add->getAttribute(common.ndsDtd->ATTR_SRC_DN);
	common.tracer->trace("addHandler");
	//do whatever is required here for the supported application
	//
	//In the case of this skeleton driver, we will setup a fake association value
	//for the <add> so that we will get modifies, deletes, etc. on the associated object.
	//
	//For a real driver, we would add an association using whatever unique key the application
	//can supply for the application object (a dn, a GUID, etc.)

	//get the output document for creating stuff
	Document * document = outputElement->getOwnerDocument();
	//create the add-association element
	Element * addAssociation = document->createElement(common.ndsDtd->TAG_ADD_ASSOCIATION);
	//put it under the output element
	outputElement->appendChild(addAssociation);
	//add the attributes that tell DirXML to what NDS object this applies
	addAssociation->setAttribute(common.ndsDtd->ATTR_DEST_DN,srcDn);
	addAssociation->setAttribute(common.ndsDtd->ATTR_EVENT_ID,eventId);
	//create the association value and put it under the add-association element
	unicode buffer[]={0,0,0};
        char s[10];
        sprintf(s,"%d",currentAssociation);
        utftouni(s,buffer);

	Text * value = document->createTextNode(buffer,0,wcslen(buffer));
	addAssociation->appendChild(value);
	//increment our fake association value for the next add
	++currentAssociation;
	//and we're done
}

//++
//=========================================================================
// Purpose:
//		Handler for <modify> command from DirXML
// Notes:
//=========================================================================
void
SkeletonSubscriber::modifyHandler(
	Element * modify)
//--
{
	const unicode * className = modify->getAttribute(common.ndsDtd->ATTR_CLASS_NAME);
	const unicode * eventId = modify->getAttribute(common.ndsDtd->ATTR_EVENT_ID);
	const unicode * assocValue = getAssociation(modify);
	common.tracer->trace("modifyHandler");
	//do whatever is required here for the supported application
	//use the assocValue to look up the object in the application, then
	//apply the attribute modifications

	//for the skeleton driver, just pretend everything worked
	NdsDtd_addStatus(outputElement,STATUS_LEVEL_SUCCESS,0,eventId);
}

//++
//=========================================================================
// Purpose:
//		Handler for <delete> command from DirXML
// Notes:
//=========================================================================
void
SkeletonSubscriber::deleteHandler(
	Element * deleteElement)
//--
{
	const unicode * className = deleteElement->getAttribute(common.ndsDtd->ATTR_CLASS_NAME);
	const unicode * eventId = deleteElement->getAttribute(common.ndsDtd->ATTR_EVENT_ID);
	const unicode * assocValue = getAssociation(deleteElement);
	common.tracer->trace("deleteHandler");
	//do whatever is required here for the supported application
	//use the assocValue to look up the object in the application, then perform the delete
	//for the skeleton driver, just pretend everything worked
	NdsDtd_addStatus(outputElement,STATUS_LEVEL_SUCCESS,0,eventId);
}

//++
//=========================================================================
// Purpose:
//		Handler for <rename> command from DirXML
// Notes:
//=========================================================================
void
SkeletonSubscriber::renameHandler(
	Element * rename)
//--
{
	const unicode * className = rename->getAttribute(common.ndsDtd->ATTR_CLASS_NAME);
	const unicode * eventId = rename->getAttribute(common.ndsDtd->ATTR_EVENT_ID);
	const unicode * assocValue = getAssociation(rename);
	common.tracer->trace("renameHandler");
	//do whatever is required here for the supported application
	//use the assocValue to look up the object in the application, then perform the rename
	//for the skeleton driver, just pretend everything worked
	NdsDtd_addStatus(outputElement,STATUS_LEVEL_SUCCESS,0,eventId);
}

//++
//=========================================================================
// Purpose:
//		Handler for <move> command from DirXML
// Notes:
//=========================================================================
void
SkeletonSubscriber::moveHandler(
	Element * move)
//--
{
	const unicode * className = move->getAttribute(common.ndsDtd->ATTR_CLASS_NAME);
	const unicode * eventId = move->getAttribute(common.ndsDtd->ATTR_EVENT_ID);
	const unicode * assocValue = getAssociation(move);
	common.tracer->trace("renameHandler");
	//do whatever is required here for the supported application
	//use the assocValue to look up the object in the application, then perform the move
	//for the skeleton driver, just pretend everything worked
	NdsDtd_addStatus(outputElement,STATUS_LEVEL_SUCCESS,0,eventId);
}

//++
//=========================================================================
// Purpose:
//		Handler for <query> command from DirXML
// Notes:
//=========================================================================
void
SkeletonSubscriber::queryHandler(
	Element * query)
//--
{
	const unicode * eventId = query->getAttribute(common.ndsDtd->ATTR_EVENT_ID);
	common.tracer->trace("queryHandler");
	//The query can specify a read of a single object with one or more attributes, or
	//it can specify a search for an object of a particular class, or various other things.
	//For a real driver, we would parse the query contents and make appropriate application calls
	//to execute the query. We would place the results of the query in zero or more <instance>
	//elements under the <output> element.
	//
	//see if this is a request for driver identification information
	NodeList	* list = query->getElementsByTagName(common.ndsDtd->TAG_SEARCH_CLASS);
	if (list != 0)
	{
		Element * searchClass = reinterpret_cast<Element *>(list->item(0));
		list->destroy();
		if (searchClass != 0)
		{
			const unicode * className = searchClass->getAttribute(common.ndsDtd->ATTR_CLASS_NAME);
			if (className != 0 && wcscmp(className,common.ndsDtd->VAL_DRIVER_IDENT_CLASS) == 0)
			{
				addDriverIdentification(outputElement);
				return;
			}
		}
	}
	//for the skeleton driver, just pretend everything worked but we didn't find anything (the
	//absence of an <instance> element indicates nothing was found)
	NdsDtd_addStatus(outputElement,STATUS_LEVEL_SUCCESS,0,eventId);
}

//++
//=========================================================================
// Purpose:
//		Return the association value for a command element (add, modify, etc.)
// Notes:
//=========================================================================
const unicode *
SkeletonSubscriber::getAssociation(
	Element * command)
//--
{
	//find the association element as a child of the command
	Node * childNode = command->getFirstChild();
	while (childNode != 0)
	{
		if (childNode->getNodeType() == Node::ELEMENT_NODE &&
			wcscmp(childNode->getNodeName(), common.ndsDtd->TAG_ASSOCIATION) == 0)
		{
			return	common.getElementValue((Element *)childNode);
		}
		childNode = childNode->getNextSibling();
	}
	return	0;
}

//++
//=========================================================================
// Purpose:
//		Add subscriber state to the document to be returned at shutdown.
//		This is called by CSkeletonDriver::shutdown().
// Notes:
//=========================================================================
void
SkeletonSubscriber::setState(
	Element * output)
//--
{
	//add our state to be saved at shutdown
	unicode buffer[]={0,0,0};
        char s[10];
        sprintf(s,"%d",currentAssociation);
        utftouni(s,buffer);
	common.addState(output,common.ndsDtd->TAG_SUBSCRIBER_STATE,TEXT_CURRENT_ASSOCIATION,buffer);
}

//++
//=========================================================================
// Purpose:
//		Add driver identification information to the document in response
//		to a query by the engine.
// Notes:
//=========================================================================
void
SkeletonSubscriber::addDriverIdentification(
	Element * output)
//--
{
	Document * doc = output->getOwnerDocument();
	Element	* instance = doc->createElement(common.ndsDtd->TAG_INSTANCE);
	output->appendChild(instance);
	instance->setAttribute(common.ndsDtd->ATTR_CLASS_NAME,common.ndsDtd->VAL_DRIVER_IDENT_CLASS);
	Element	* attr = doc->createElement(common.ndsDtd->TAG_ATTR);
	instance->appendChild(attr);
	attr->setAttribute(common.ndsDtd->ATTR_ATTR_NAME,common.ndsDtd->VAL_DRIVER_ID);
	Element * value = doc->createElement(common.ndsDtd->TAG_VALUE);
	attr->appendChild(value);
	value->setAttribute(common.ndsDtd->ATTR_TYPE,common.ndsDtd->VAL_STRING);
	Text * text = doc->createTextNode(DRIVER_ID_VALUE,0,wcslen(DRIVER_ID_VALUE));
	value->appendChild(text);
	attr = doc->createElement(common.ndsDtd->TAG_ATTR);
	instance->appendChild(attr);
	attr->setAttribute(common.ndsDtd->ATTR_ATTR_NAME,common.ndsDtd->VAL_DRIVER_VERSION);
	value = doc->createElement(common.ndsDtd->TAG_VALUE);
	attr->appendChild(value);
	value->setAttribute(common.ndsDtd->ATTR_TYPE,common.ndsDtd->VAL_STRING);
	text = doc->createTextNode(DRIVER_VERSION_VALUE,0,wcslen(DRIVER_VERSION_VALUE));
	value->appendChild(text);
	attr = doc->createElement(common.ndsDtd->TAG_ATTR);
	instance->appendChild(attr);
	attr->setAttribute(common.ndsDtd->ATTR_ATTR_NAME,common.ndsDtd->VAL_MIN_ACTIVATION_VERSION);
	value = doc->createElement(common.ndsDtd->TAG_VALUE);
	attr->appendChild(value);
	value->setAttribute(common.ndsDtd->ATTR_TYPE,common.ndsDtd->VAL_STATE);
	text = doc->createTextNode(MIN_ACTIVATION_VERSION_VALUE,0,wcslen(MIN_ACTIVATION_VERSION_VALUE));
	value->appendChild(text);
}

//++
//=========================================================================
// Purpose:
//		Exception to throw for connection problems.
// Notes:
//=========================================================================
ConnectException::ConnectException(
	const unicode * message)
//--
	:	ShimException(message)
{
}

// This function converts char to unicode this is only true for Ascii characters
//--
void utftouni(char *s,unicode *p)
{
 int i,len;
 len = strlen(s);
 for(i=0;i<len;i++)
   {
     *p=*s;
      s++; 
      p++;
   }
}

