/*************************************************************************
Copyright © 1999-2003 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: CommonImpl.cpp
* Description: Platform independent code, shared code for skeleton
* driver shim implementations
* Tabs: 4
*******************************************************************/

#include	"CommonImpl.hpp"
#include	"InterfaceFactory.h"
#include	"wcsfunc.h"

static const int TABLE_GROW_INCREMENT	=	8;

//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
//MSG_BAD is shared
const unicode			MSG_BAD[]		=	{'U','n','e','x','p','e','c','t','e','d',' ','e','x','c','e','p','t','i','o','n',0};
//locals to this module
static const unicode	TEXT_OPTIONS[]	=	{'-','o','p','t','i','o','n','s',0};
static const unicode	TEXT_STATE[]	=	{'-','s','t','a','t','e',0};
static const unicode	TEXT_NULL[]		=	{0};
static const unicode	FORMAT_MISSING_OPTION[] = {'m','i','s','s','i','n','g',' ','r','e','q','u','i','r','e','d',' ',
												'o','p','t','i','o','n',':',' ',0};
static const unicode	FORMAT_BAD_INT[] =	{'o','p','t','i','o','n',' ',
											'm','u','s','t',' ','b','e',' ','i','n','t','e','g','e','r',' ','t','y','p','e',':',' ',0};

//++
//=========================================================================
// Purpose:
//		Constructor
// Notes:
//=========================================================================
CommonImpl::CommonImpl(
	const char * traceHeader)		//prologue for TRACE message (e.g., "SkeletonDriverShim")
//--
	:	tracer(0), returnDocument(0)
{
	tracer = Trace_new(traceHeader);
	ndsDtd = NdsDtd_getStrings();
}

//++
//=========================================================================
// Purpose:
//		Destructor
// Notes:
//=========================================================================
CommonImpl::~CommonImpl()
//--
{
	Trace_destroy(tracer);
	setReturnDocument((XmlDocument *)0);
}

//++
//=========================================================================
// Purpose:
//		Set the current return document so it can be cleanup later.
// Notes:
//=========================================================================
XmlDocument *						//pointer to created XmlDocument instance
CommonImpl::setReturnDocument(
	Document * doc)					//document for return
//--
{
	if (doc)
	{
		XmlDocument	* xmlDoc = XmlDocument_newFromDOM(doc);
		return setReturnDocument(xmlDoc);
	} else
	{
		return	setReturnDocument((XmlDocument *)0);
	}
}

//++
//=========================================================================
// Purpose:
//		Set the current return document so it can be cleanup later.
// Notes:
//=========================================================================
XmlDocument *
CommonImpl::setReturnDocument(
	XmlDocument * xmlDoc)			//document for return
//--
{
	if (returnDocument != 0)
	{
		//NOTE that we must destroy the XmlDocument and the DOM Document separately.
		//The XmlDocument doesn't take ownership of the DOM document
		Document	* document = returnDocument->getDocument();
		XmlDocument_destroy(returnDocument);
		document->destroy();
		returnDocument = 0;
	}
	returnDocument = xmlDoc;
	return	returnDocument;
}

//++
//=========================================================================
// Purpose:
//		Get the currently set return document
// Notes:
//=========================================================================
XmlDocument *
CommonImpl::getReturnDocument()
//--
{
	return	returnDocument;
}

//++
//=========================================================================
// Purpose:
//		Create an output document with a <status level="success"/> element		
// Notes:
//=========================================================================
XmlDocument *							//pointer to created output document
CommonImpl::createSuccessDocument() const
//--
{
	return	createStatusDocument(STATUS_LEVEL_SUCCESS,0);
}

//++
//=========================================================================
// Purpose:
//		Create an output document with a <status> element with the passed
//		level and message.
// Notes:
//=========================================================================
XmlDocument *							//pointer to created output document
CommonImpl::createStatusDocument(
	int level,							//STATUS_LEVEL_SUCCESS, etc. (see NdsDtd.h)
	const unicode * message) const		//content for status element (may be 0)
//--
{
	//use DirXML-supplied methods to create document
	Element	* outputElement = NdsDtd_newOutputDocument();
	//and add status element
	NdsDtd_addStatus(outputElement,level,message,0);
	//wrap in XmlDocument for return
	return	XmlDocument_newFromDOM(outputElement->getOwnerDocument());
}

//++
//=========================================================================
// Purpose:
//		Add a driver-state, subscriber-state, or publisher-state element to
//		an input or an output document and then add a shim-specific element
//		with the specified value.
// Notes:
//		The shims can write state information into NDS using either return
//		documents, or in the case of the publisher, submitted documents.
//		This state information is passed to the shim as part of the init
//		document passed to the shim init() method.
//
//		This will add the init-state and driver-state, subscriber-state, or
//		publisher-state elements if necessary, and will then append an
//		element with the passed name and content.
//=========================================================================
Element *									//The created shim-specific state element
CommonImpl::addState(
	Element * parent,						//The <intput> or <output> element in the XDS document
	const unicode * stateName,				//"driver-state", "subscriber-state", or "publisher-state"
	const unicode * elementName,			//The name of the shim-specific state element
	const unicode * elementValue) const		//The value to place as content of the state element (may be 0)
//--
{
    Document * document = parent->getOwnerDocument();
    Element * stateElement;
    //see if we already have an init-params element
    Element * initParams = getFirstElementByTagName(parent, ndsDtd->TAG_INIT_PARAMS);
    if (initParams == 0)
    {
    	initParams = document->createElement(ndsDtd->TAG_INIT_PARAMS);
    	parent->appendChild(initParams);
    	stateElement = document->createElement(stateName);
    	initParams->appendChild(stateElement);
    } else
    {
    	//have init-params, see if we have the requisite state element
    	stateElement = getFirstElementByTagName(initParams, stateName);
    	if (stateElement == 0)
    	{
    		stateElement = document->createElement(stateName);
    		initParams->appendChild(stateElement);
    	}
    }
    //add the passed element and content
    Element * element = document->createElement(elementName);
    stateElement->appendChild(element);
    //create the content, if it was passed
	int len;
    if (elementValue != 0 && (len = wcslen(elementValue)) > 0)
    {
    	Text * value = document->createTextNode(elementValue,0,len);
    	element->appendChild(value);
    }
    return	element;
}

//++
//=========================================================================
// Purpose:
//		Extract and return the authentication parameters from an
//		initialization document passed to DriverShim::init(),
//		SubscriberShim::init(), or PublicationShim::init().
// Notes:
//=========================================================================
CommonImpl::AuthenticationParams *			//structure containing auth info (one or more string pointers may be 0)
CommonImpl::getAuthenticationParams(
	Document * initDocument) const			//init document passed to init() method
//--
{
    //get the <init-params> element
    Element * initParams = getFirstElementByTagName(initDocument, ndsDtd->TAG_INIT_PARAMS);
    if (initParams == 0)
    {
    	//no <init-params> element found (shouldn't happen)
    	return	new AuthenticationParams(0,0,0);
    }
	//find <authentication-info> element
	Element		* authInfo = getFirstElementByTagName(initDocument, ndsDtd->TAG_AUTHENTICATION_INFO);
	if (authInfo == 0)
	{
		//no <authentication-info> element (may happen if nothing entered in ConsoleOne)
    	return	new AuthenticationParams(0,0,0);
	}
	//look for <server> - this corresponds to the "Authentication context" field
	Element * server = getFirstElementByTagName(authInfo, ndsDtd->TAG_SERVER);
	const unicode * serverString = 0;
	if (server != 0)
	{
		//get the string value of the <server> element using a handy utility function
		serverString = getElementValue(server);
	}
	//look for <user> - this corresponds to the "Authentication ID" field
	Element * user = getFirstElementByTagName(authInfo, ndsDtd->TAG_USER);
	const unicode * userString = 0;
	if (user != 0)
	{
		//get string value of <user> element
		userString = getElementValue(user);
	}
	//look for <password> - this corresponds to the "Application Password" field
	Element * password = getFirstElementByTagName(authInfo, ndsDtd->TAG_PASSWORD);
	const unicode * passwordString = 0;
	if (password != 0)
	{
		//get string value of <password> element
		passwordString = getElementValue(password);
	}
    return	new AuthenticationParams(serverString, userString, passwordString);
}

//++
//=========================================================================
// Purpose:
//		Extract initialization data from an init document passed to a 
//		shim init routine. This will extract data from the options element
//		and the state element.
// Notes:
//		May throw ShimException
//=========================================================================
CommonImpl::ShimParams *					//pointer to collection of name-value pairs
CommonImpl::getShimParams(
	Document * initDocument,				//init document passed to shim init method
	const unicode * shimName,				//"driver", "subscriber", or "publisher"
	const ShimParamDesc * paramDesc) const	//array of descriptions of data to extract
//--
{
   	int	i;
    ShimParams * params = new ShimParams();
    //find the options element desired
	unicode buffer[64];
	wcscpy(buffer,shimName);
	wcscat(buffer,TEXT_OPTIONS);
    Element * optionsElement = getFirstElementByTagName(initDocument, buffer);
    //get any interesting values
    extractValues(optionsElement,params,paramDesc);
    //find the state element desired
	wcscpy(buffer,shimName);
	wcscat(buffer,TEXT_STATE);
    Element * stateElement = getFirstElementByTagName(initDocument, buffer);
    //get any interesting values
    extractValues(stateElement,params,paramDesc);
	//make sure buffer is large enough for all possible missing options
	unicode	errorMsg[2048];
	unicode	* dest = errorMsg;
	*dest = 0;
    int errorCount = 0;
    //check for any required options that weren't found
    for (i = 0; paramDesc[i].paramName != 0; ++i)
    {
    	if (paramDesc[i].required && !params->haveParam(paramDesc[i].paramName))
    	{
    		++errorCount;
			*dest++ = '\n';
			wcscat(dest,FORMAT_MISSING_OPTION);
			wcscat(dest,paramDesc[i].paramName);
			dest += wcslen(dest);
    	}
    }
#ifndef	NO_CPP_EXCEPTIONS
    if (errorCount > 0)
    {
		throw	ShimException(errorMsg);
    }
#endif	
    return	params;
}

//++
//=========================================================================
// Purpose:
//		Get the value of the first text node child of an element
// Notes:
//		This is useful to get association values, <value> values, etc.
//=========================================================================
const unicode *					//pointer to null-terminated value
CommonImpl::getElementValue(
	Element * element)			//element from which to extract value
//--
{
	Node	* childNode = element->getFirstChild();
	while (childNode != 0)
	{
		if (childNode->getNodeType() == Node::TEXT_NODE)
		{
			return	childNode->getNodeValue();
		}
		childNode = childNode->getNextSibling();
	}
	return	0;
}

//++
//=========================================================================
// Purpose:
//		Helper method for getShimParams(). Does the actual work of
//		traversing an options or state element's content and finding the
//		desired values.
// Notes:
//		May throw ShimException
//=========================================================================
void
CommonImpl::extractValues(
	Element * optionsElement,			//<driver-options>, <driver-state>, etc.
	ShimParams * params,				//container in which to store the name/value pairs
	const ShimParamDesc * paramDesc)	//array of value descriptions
//--
{
    if (optionsElement != 0)
    {
    	int	i;
    	//iterate through the param descriptions, looking for the described values
    	Element * option;
    	for (i = 0; paramDesc[i].paramName != 0; ++i)
    	{
    		option = getFirstElementByTagName(optionsElement, paramDesc[i].paramName);
    		if (option != 0)
    		{
    			//found the element, get the content and interpret it
				const unicode * content = getElementValue(option);
    			if (content == 0 || wcslen(content) > 0)
    			{
    				//empty content doesn't count
    				continue;
    			}
    			if (paramDesc[i].paramType == STRING_TYPE)
    			{
    				//string type
    				params->putStringParam(paramDesc[i].paramName,content);
    			} else
    			{
   					//int type
					unicode * endPtr;
    				int value = (int)wcstoul(content,&endPtr,10);
					if (endPtr == (content + wcslen(content)))
					{
						params->putIntParam(paramDesc[i].paramName,value);
					}
#ifndef	NO_CPP_EXCEPTIONS
					else
					{
						unicode	buffer[512];
						wcscpy(buffer,FORMAT_BAD_INT);
						wcscat(buffer,paramDesc[i].paramName);
						throw	ShimException(buffer);
					}
#endif
    			}
    		}
    	}	//for
    }
}

//++
//=========================================================================
// Purpose:
//		Get the first element by tag name
// Notes:
//		
//=========================================================================
Element *				//pointer to first element or 0
CommonImpl::getFirstElementByTagName(
	Node * root,				// root element or document
	const unicode * tagName		// tag name
)
{
	NodeList * nodeList = 0;
	Element * first = 0;

#ifndef	NO_CPP_EXCEPTIONS
	try
	{
#endif	
		if (root->getNodeType() == Node::ELEMENT_NODE)
		{
			nodeList = static_cast<Element*>(root)->getElementsByTagName(tagName);
		} else
		if (root->getNodeType() == Node::DOCUMENT_NODE)
		{
			nodeList = static_cast<Document*>(root)->getElementsByTagName(tagName);
		}
		else
		{
			return 0;
		}
    	first = (Element *)nodeList->item(0);
		nodeList->destroy();
#ifndef	NO_CPP_EXCEPTIONS
	}
	catch(...)
	{
		if (nodeList != 0)
		{
			nodeList->destroy();
		}
	}
#endif	
	return first;
}

//++
//=========================================================================
// Purpose:
//		Constructor		
// Notes:
//		Dynamically allocateds copies of passed non-zero arguments
//=========================================================================
CommonImpl::AuthenticationParams::AuthenticationParams(
	const unicode * id,						//Authentication ID value
	const unicode * context,				//Authentication context value
	const unicode * password)				//Application password value
//--
	:	authenticationId(0), authenticationContext(0), applicationPassword(0)
{
	//copy passed strings
	if (id != 0)
	{
		authenticationId = new unicode[wcslen(id) + 1];
		wcscpy(const_cast<unicode *>(authenticationId),id);
	}
	if (context != 0)
	{
		authenticationContext = new unicode[wcslen(context) + 1];
		wcscpy(const_cast<unicode *>(authenticationContext),context);
	}
	if (password != 0)
	{
		applicationPassword = new unicode[wcslen(password) + 1];
		wcscpy(const_cast<unicode *>(applicationPassword),password);
	}
}

//++
//=========================================================================
// Purpose:
//		Destructor
// Notes:
//=========================================================================
CommonImpl::AuthenticationParams::~AuthenticationParams()
//--
{
	delete [] const_cast<unicode *>(authenticationId);
	delete [] const_cast<unicode *>(authenticationContext);
	delete [] const_cast<unicode *>(applicationPassword);
}

//++
//=========================================================================
// Purpose:
//		Constructor
// Notes:
//
//=========================================================================
CommonImpl::ShimParams::ShimParams()
//--
	:	table(0), entryCount(0), tableSize(0)
{
}

//++
//=========================================================================
// Purpose:
//		Destructor
// Notes:
//=========================================================================
CommonImpl::ShimParams::~ShimParams()
//--
{
	delete [] table;
}

//++
//=========================================================================
// Purpose:
//		Ensure there is room for one more entry in the name/value table.
// Notes:
//		Warning: This uses memcpy to copy objects.
//=========================================================================
void
CommonImpl::ShimParams::sizeTable()
//--
{
	if (tableSize < (entryCount + 1))
	{
		//need to grow table
		Entry * temp = table;
		table = new Entry[tableSize + TABLE_GROW_INCREMENT];
		if (temp != 0)
		{
			//copy stuff over (we're keeping the dynamically allocated stuff)
			memcpy(table,temp,sizeof(Entry) * entryCount);
			//reset the Entry memory to 0 so destructors don't try and
			//delete the strings
			memset(temp,0,sizeof(Entry) * entryCount);
			delete	[] temp;
		}
		tableSize += TABLE_GROW_INCREMENT;
	}
}

//++
//=========================================================================
// Purpose:
//		Set a string-type parameter by name into the table
// Notes:
//=========================================================================
void
CommonImpl::ShimParams::putStringParam(
	const unicode * name,					//name of value
	const unicode * value)					//value
//--
{
	//ensure enough space
	sizeTable();
	//put the data in
	table[entryCount++].assign(name,value);
}

//++
//=========================================================================
// Purpose:
//		Get a string-type parameter by name from the table
// Notes:
//=========================================================================
const unicode *							//null-terminated value, or 0 if name not found
CommonImpl::ShimParams::getStringParam(
	const unicode * name) const			//name of value to find
//--
{
	const Entry * entry = find(name);
	if (entry && entry->type == STRING_TYPE)
	{
		return entry->stringValue;
	}
	return	0;
}

//++
//=========================================================================
// Purpose:
//		Put an integer parameter value by name into the table.
// Notes:
//=========================================================================
void
CommonImpl::ShimParams::putIntParam(
	const unicode * name,					//name of value
	int value)								//value
//--
{
	//ensure enough space
	sizeTable();
	//put the data in
	table[entryCount++].assign(name,value);
}

//++
//=========================================================================
// Purpose:
//		Get an integer-type value by name from the table.
// Notes:
//=========================================================================
int											//value, or -1 if name not found
CommonImpl::ShimParams::getIntParam(
	const unicode * name) const				//name of value to find
//--
{
	const Entry * entry = find(name);
	if (entry && entry->type == INT_TYPE)
	{
		return entry->intValue;
	}
	return	-1;
}

//++
//=========================================================================
// Purpose:
//		Return true if have a value with passed name in the table.
// Notes:
//=========================================================================
bool										//true if value with passed name found
CommonImpl::ShimParams::haveParam(
	const unicode * name) const				//name to find
//--
{
	return	find(name) != 0;
}

//++
//=========================================================================
// Purpose:
//		Find an Entry with the passed name in the table.
// Notes:
//		Helper method for the get*Param() methods
//=========================================================================
const CommonImpl::ShimParams::Entry *		//Entry if found, 0 if not found
CommonImpl::ShimParams::find(
	const unicode * name) const				//name to find
//--
{
	if (table != 0 && entryCount != 0)
	{
		int	i;
		//iterate through table looking for passed name
		for (i = 0; i < entryCount; ++i)
		{
			if (wcsicmp(table[i].name,name) == 0)
			{
				//found it
				return	&table[i];
			}
		}
	}
	//not found
	return	0;
}

//++
//=========================================================================
// Purpose:
//		Constructor
// Notes:
//		Entry is the table entry class for the ShimParams name/value table
//=========================================================================
CommonImpl::ShimParams::Entry::Entry()
//--
	:	name(0), type(-1), stringValue(0)
{
}

//++
//=========================================================================
// Purpose:
//		Destructor
// Notes:
//=========================================================================
CommonImpl::ShimParams::Entry::~Entry()
//--
{
	cleanup();
}

//++
//=========================================================================
// Purpose:
//		Perform cleanup of any dynamically-allocated data
// Notes:
//=========================================================================
void
CommonImpl::ShimParams::Entry::cleanup()
//--
{
	delete	[] const_cast<unicode *>(name);
	name = 0;
	if (type == STRING_TYPE)
	{
		delete	[] const_cast<unicode *>(stringValue);
	}
	stringValue = 0;
}

//++
//=========================================================================
// Purpose:
//		Assign a STRING_TYPE name/value pair to this Entry
// Notes:
//		Makes a dynamically-allocated copy of the passed name and value
//=========================================================================
void
CommonImpl::ShimParams::Entry::assign(
	const unicode * name,				//name
	const unicode * value)				//value
//--
{
	cleanup();
	if (name != 0)
	{
		this->name = new unicode[wcslen(name) + 1];
		wcscpy(const_cast<unicode *>(this->name),name);
	}
	type = STRING_TYPE;
	if (value != 0)
	{
		stringValue = new unicode[wcslen(value) + 1];
		wcscpy(const_cast<unicode *>(stringValue),value);
	}
}

//++
//=========================================================================
// Purpose:
//		Assign an INT_TYPE name/value pair to this Entry
// Notes:
//		Makes a dynamically-allocated copy of the passed name
//=========================================================================
void
CommonImpl::ShimParams::Entry::assign(
	const unicode * name,				//name
	int value)							//value
//--
{
	cleanup();
	if (name != 0)
	{
		this->name = new unicode[wcslen(name) + 1];
		wcscpy(const_cast<unicode *>(this->name),name);
	}
	type = INT_TYPE;
	intValue = value;
}

//++
//=========================================================================
// Purpose:
//		Constructor
// Notes:
//		Makes a dynamically-allocated copy of the passed message
//=========================================================================
ShimException::ShimException(
	const unicode * message)			//message for exception
//--
	:	message(0)
{
	if (message)
	{
		this->message = new unicode[wcslen(message) + 1];
		wcscpy(this->message,message);
	}
}

//++
//=========================================================================
// Purpose:
//		Copy constructor
// Notes:
//		Makes a dynamically-allocated copy of the copied instance's message
//=========================================================================
ShimException::ShimException(
	const ShimException & iVal)			//instance to copy
//--
	:	message(0)
{
	this->operator = (iVal);
}

//++
//=========================================================================
// Purpose:
//		Destructor
// Notes:
//		
//=========================================================================
ShimException::~ShimException()
//--
{
	cleanup();
}

//++
//=========================================================================
// Purpose:
//		Cleanup dynamically-allocated data
// Notes:
//		
//=========================================================================
void
ShimException::cleanup()
//--
{
	delete	[] message;
	message = 0;
}

//++
//=========================================================================
// Purpose:
//		Assignment operator from another instance
// Notes:
//		
//=========================================================================
ShimException &							//this
ShimException::operator = (
	const ShimException & rhs)			//right-hand side of expression
{
	cleanup();
	if (rhs.message)
	{
		message = new unicode[wcslen(rhs.message) + 1];
		wcscpy(message,rhs.message);
	}
	return	*this;
}

//++
//=========================================================================
// Purpose:
//		Get the error message from the exception object
// Notes:
//		
//=========================================================================
const unicode *				//pointer to null-terminated message, or empty string (will not return 0)
ShimException::getMessage() const
{
	return	message ? message : TEXT_NULL;
}

