//
// See the file LICENSE for redistribution information.
//
// Copyright (c) 2002-2003
//	Sleepycat Software.  All rights reserved.
//

static const char revid[] = "$Id: XmlContainer.cpp,v 1.83 2003/12/10 23:14:03 merrells Exp $";

#include "dbxml_config.h"
#include "dbxml/XmlPortability.hpp"
#include "dbxml/XmlException.hpp"
#include "dbxml/XmlContainer.hpp"
#include "dbxml/DbXml.hpp" // For VERSION macros
#include "dbxml/XmlUpdateContext.hpp"
#include "dbxml/XmlIndexSpecification.hpp"
#include "TransactedContainer.hpp"
#include "UpdateContext.hpp"
#include "QueryExpression.hpp"

#include <db_cxx.h>
#include <cerrno>

using namespace DbXml;

// XmlContainer

XmlContainer::XmlContainer(DbEnv *environment, const std::string &name, u_int32_t flags)
{
	container_ = new TransactedContainer(environment, name, flags);
	container_->acquire();
	checkFlags("XmlContainer", flags, DB_XA_CREATE);
}

XmlContainer::~XmlContainer()
{
	container_->release();
}

void XmlContainer::setPageSize(u_int32_t pagesize)
{
	checkState("open()", /*open=*/false); // throws XmlException
	int err = 0;
	try {
		err = container_->setPageSize(pagesize);
	} catch (DbException &e) {
		throw XmlException(e);
	}
	if (err != 0)
		throw XmlException(err);
}

void XmlContainer::setResolver(const XmlResolver *resolver)
{
	container_->setResolver(resolver);
}

void XmlContainer::open(DbTxn *txn, u_int32_t flags, int mode)
{
	checkFlags("open()", flags, DB_AUTO_COMMIT | DB_CREATE | DB_DIRTY_READ | DB_EXCL | DB_NOMMAP | DB_RDONLY | DB_THREAD | DBXML_CHKSUM | DBXML_ENCRYPT);
	checkState("open()", /*open=*/false); // throws XmlException
	int err = 0;
	try {
		err = container_->open(txn, flags, mode, /*doVersionCheck=*/true);
	} catch (DbException &e) {
		if (e.get_errno() == EEXIST)
			throw XmlException(XmlException::CONTAINER_EXISTS, e.what());
		else
			throw XmlException(e);
	}
	if (err == EEXIST)
		throw XmlException(XmlException::CONTAINER_EXISTS, db_strerror(err));
	else if (err != 0)
		throw XmlException(err);
}

void XmlContainer::close(u_int32_t flags)
{
	checkFlags("close()", flags, DB_NOSYNC);
	checkState("close()", /*open=*/true); // throws XmlException
	int err = 0;
	try {
		err = container_->close(flags);
	} catch (DbException &e) {
		throw XmlException(e);
	}
	if (err != 0)
		throw XmlException(err);
}

void XmlContainer::setIndexSpecification(DbTxn *txn, const XmlIndexSpecification &index)
{
	checkState("setIndexSpecification()", /*open=*/true); // throws XmlException
	int err = 0;
	try {
		err = container_->setIndexSpecification(txn, index);
	} catch (DbException &e) {
		throw XmlException(e);
	}
	if (err != 0)
		throw XmlException(err);
}

XmlIndexSpecification XmlContainer::getIndexSpecification(DbTxn *txn) const
{
	XmlIndexSpecification index;
	checkState("getIndexSpecification()", /*open=*/true); // throws XmlException
	int err = 0;
	try {
		err = container_->getIndexSpecification(txn, index);
	} catch (DbException &e) {
		throw XmlException(e);
	}
	if (err != 0)
		throw XmlException(err);
	return index;
}

void XmlContainer::addIndex(DbTxn *txn, const std::string &uri, const std::string &name, const std::string &index)
{
	checkState("addIndex()", /*open=*/true); // throws XmlException
	int err = 0;
	try {
		err = container_->addIndex(txn, uri, name, index);
	} catch (DbException &e) {
		throw XmlException(e);
	}
	if (err != 0)
		throw XmlException(err);
}

void XmlContainer::deleteIndex(DbTxn *txn, const std::string &uri, const std::string &name, const std::string &index)
{
	checkState("deleteIndex()", /*open=*/true); // throws XmlException
	int err = 0;
	try {
		err = container_->deleteIndex(txn, uri, name, index);
	} catch (DbException &e) {
		throw XmlException(e);
	}
	if (err != 0)
		throw XmlException(err);
}

void XmlContainer::replaceIndex(DbTxn *txn, const std::string &uri, const std::string &name, const std::string &index)
{
	checkState("replaceIndex()", /*open=*/true); // throws XmlException
	int err = 0;
	try {
		err = container_->replaceIndex(txn, uri, name, index);
	} catch (DbException &e) {
		throw XmlException(e);
	}
	if (err != 0)
		throw XmlException(err);
}

const std::string &XmlContainer::getName() const
{
	return container_->getName();
}

void XmlContainer::setName(const std::string &name)
{
	checkState("setName()", /*open=*/false); // throws XmlException
	container_->setName(name);
}

u_int32_t XmlContainer::putDocument(DbTxn *txn, XmlDocument &document, XmlUpdateContext *context, u_int32_t flags)
{
	checkFlags("putDocument()", flags, 0);
	checkState("putDocument()", /*open=*/true); // throws XmlException
	if (context)
		checkContainer("putDocument()", "XmlUpdateContext", context->getContainer());
	int err = 0;
	ID id;
	try {
		if (context == 0) {
			XmlUpdateContext defaultContext(*this);
			err = container_->addDocument(txn, document, id, defaultContext, flags);
		} else {
			err = container_->addDocument(txn, document, id, *context, flags);
		}
	} catch (DbException &e) {
		throw XmlException(e);
	}
	if (err != 0)
		throw XmlException(err);
	return id.raw();
}

XmlDocument XmlContainer::getDocument(DbTxn *txn, u_int32_t id, u_int32_t flags)
{
	checkFlags("getDocument()", flags, DB_DIRTY_READ | DB_RMW);
	checkState("getDocument()", /*open=*/true); // throws XmlException
	XmlDocument r;
	int err = 0;
	try {
		err = container_->getDocument(txn, id, r, flags);
	} catch (DbException &e) {
		if (e.get_errno() == DB_NOTFOUND)
			throw XmlException(XmlException::DOCUMENT_NOT_FOUND, "Document not found.");
		else
			throw XmlException(e);
	}
	if (err == DB_NOTFOUND)
		throw XmlException(XmlException::DOCUMENT_NOT_FOUND, "Document not found.");
	else if (err != 0)
		throw XmlException(err);

	return r;
}

void XmlContainer::deleteDocument(DbTxn *txn, u_int32_t id, XmlUpdateContext *context, u_int32_t flags)
{
	checkFlags("deleteDocument(txn,id)", flags, 0);
	checkState("deleteDocument(txn,id)", /*open=*/true); // throws XmlException
	if (context)
		checkContainer("deleteDocument(txn,id)", "XmlUpdateContext", context->getContainer());
	int err = 0;
	try {
		if (context == 0) {
			XmlUpdateContext defaultContext(*this);
			err = container_->deleteDocument(txn, id, defaultContext, flags);
		} else {
			err = container_->deleteDocument(txn, id, *context, flags);
		}
	} catch (DbException &e) {
		if (e.get_errno() == DB_NOTFOUND)
			throw XmlException(XmlException::DOCUMENT_NOT_FOUND, e.what());
		else
			throw XmlException(e);
	}
	if (err == DB_NOTFOUND)
		throw XmlException(XmlException::DOCUMENT_NOT_FOUND, "Document not found.");
	else if (err != 0)
		throw XmlException(err);
}

void XmlContainer::deleteDocument(DbTxn *txn, XmlDocument &document, XmlUpdateContext *context, u_int32_t flags)
{
	checkFlags("deleteDocument(txn,document)", flags, 0);
	checkState("deleteDocument(txn,document)", /*open=*/true); // throws XmlException
	if (context)
		checkContainer("deleteDocument(txn,document)", "XmlUpdateContext", context->getContainer());
	int err = 0;
	try {
		if (context == 0) {
			XmlUpdateContext defaultContext(*this);
			err = container_->deleteDocument(txn, document, defaultContext, flags);
		} else {
			err = container_->deleteDocument(txn, document, *context, flags);
		}
	} catch (DbException &e) {
		if (e.get_errno() == DB_NOTFOUND)
			throw XmlException(XmlException::DOCUMENT_NOT_FOUND, "Document not found.");
		else
			throw XmlException(e);
	}
	if (err == DB_NOTFOUND)
		throw XmlException(XmlException::DOCUMENT_NOT_FOUND, "Document not found.");
	else if (err != 0)
		throw XmlException(err);
}

void XmlContainer::updateDocument(DbTxn *txn, XmlDocument &document, XmlUpdateContext *context)
{
	checkState("updateDocument()", /*open=*/true); // throws XmlException
	if (context)
		checkContainer("updateDocument()", "XmlUpdateContext", context->getContainer());
	int err = 0;
	try {
		if (context == 0) {
			XmlUpdateContext defaultContext(*this);
			err = container_->updateDocument(txn, document, defaultContext);
		} else {
			err = container_->updateDocument(txn, document, *context);
		}
	} catch (DbException &e) {
		throw XmlException(e);
	}
	if (err != 0)
		throw XmlException(err);
}

XmlQueryExpression XmlContainer::parseXPathExpression(DbTxn *txn, const std::string &xpath, XmlQueryContext *context) const
{
	checkState("parseXPathExpression()", /*open=*/true); // throws XmlException
	int err = 0;
	try {
		if (context == 0) {
			XmlQueryContext defaultContext;
			XmlQueryExpression expression(*this, defaultContext);
			err = container_->parseXPathExpression(txn, xpath, defaultContext, expression);
			if (err == 0)
				return expression;
		} else {
			XmlQueryExpression expression(*this, *context);
			err = container_->parseXPathExpression(txn, xpath, *context, expression);
			if (err == 0)
				return expression;
		}
	} catch (DbException &e) {
		throw XmlException(e);
	}
	if (err != 0)
		throw XmlException(err);

	return XmlQueryExpression(*this, *context); // Not reached.
}

XmlResults XmlContainer::queryWithXPath(DbTxn *txn, const string &xpath, XmlQueryContext *context, u_int32_t flags) const
{
	checkFlags("queryWithXPath(txn,xpath,context)", flags, DB_DIRTY_READ | DB_RMW);
	checkState("queryWithXPath(txn,xpath,context)", /*open=*/true); // throws XmlException
	int err = 0;
	try {
		if (context == 0) {
			XmlQueryContext defaultContext;
			XmlResults results(defaultContext, txn);
			err = container_->queryWithXPath(txn, xpath, results, defaultContext, flags);
			if(err == 0)
				return results;
		} else {
			XmlResults results(*context, txn);
			err = container_->queryWithXPath(txn, xpath, results, *context, flags);
			if(err == 0)
				return results;
		}
	} catch (DbException &e) {
		throw XmlException(e);
	}
	if (err != 0)
		throw XmlException(err);

	return XmlResults(*context, txn); // Not reached.
}

XmlResults XmlContainer::queryWithXPath(DbTxn *txn, XmlQueryExpression &expression, u_int32_t flags) const
{
	checkFlags("queryWithXPath(txn,expression)", flags, DB_DIRTY_READ | DB_RMW);
	checkState("queryWithXPath(txn,expression)", /*open=*/true); // throws XmlException
	checkContainer("queryWithXPath(txn,expression)", "XPathExpression", expression.getContainer());
	XmlResults results(expression.getQueryContext(), txn);
	int err = 0;
	try {
		err = container_->queryWithXPath(txn, expression, results, flags);
	} catch (DbException &e) {
		throw XmlException(e);
	}

	if (err != 0)
		throw XmlException(err);
	return results;
}

void XmlContainer::modifyDocument(DbTxn *txn, const XmlModify &xmlModify, XmlUpdateContext *context, u_int32_t flags)
{
	container_->modifyDocument(txn, (const Modify&)xmlModify, context, flags);
}

void XmlContainer::remove(DbTxn *txn, u_int32_t flags)
{
	checkState("remove()", /*open=*/false); // throws XmlException
	int err = 0;
	try {
		err = container_->remove(txn, flags);
	} catch (DbException &e) {
		throw XmlException(e);
	}

	if (err != 0)
		throw XmlException(err);
}

void XmlContainer::rename(DbTxn *txn, const std::string &newName, u_int32_t flags)
{
	checkState("rename()", /*open=*/false); // throws XmlException
	int err = 0;
	try {
		err = container_->rename(txn, newName, flags);
	} catch (DbException &e) {
		throw XmlException(e);
	}

	if (err != 0)
		throw XmlException(err);
}

void XmlContainer::dump(std::ostream *out, u_int32_t flags) // throws XmlException
{
	checkState("dump()", /*open=*/false); // throws XmlException
	int err = 0;

	try
	{
		err = container_->dump(out, flags);
	} catch (DbException &e)
	{
		throw XmlException(e);
	}

	if (err != 0)
		throw XmlException(err);
}

void XmlContainer::load(std::istream *in, unsigned long *lineno, u_int32_t flags) // throws XmlException
{
	checkState("load()", /*open=*/false); // throws XmlException
	int err = 0;

	try
	{
		err = container_->load(in, lineno, flags);
	} catch (DbException &e)
	{
		throw XmlException(e);
	}

	if (err != 0)
		throw XmlException(err);
}

void XmlContainer::verify(std::ostream *out, u_int32_t flags) // throws XmlException
{
	checkState("verify()", /*open=*/false); // throws XmlException
	int err = 0;

	try
	{
		err = container_->verify(out, flags);
	} catch (DbException &e)
	{
		throw XmlException(e);
	}

	if (err != 0)
		throw XmlException(err);
}

void XmlContainer::upgrade(u_int32_t flags) // throws XmlException
{
	checkState("upgrade()", /*open=*/false); // throws XmlException
	int err = 0;

	try
	{
		err = container_->upgrade(flags);
	} catch (DbException &e)
	{
		throw XmlException(e);
	}

	if (err != 0)
		throw XmlException(err);
}

bool XmlContainer::exists(DbTxn *txn)
{
	return container_->exists(txn);
}

bool XmlContainer::isOpen() const
{
	return container_->isOpen();
}

void XmlContainer::checkState(const char *function, bool open) const // throws XmlException
{
	if (container_->isOpen() != open) {
		string s(function);
		if (open) {
			s += " not allowed on closed XmlContainer";
			throw XmlException(XmlException::CONTAINER_CLOSED, s);
		} else {
			s += " not allowed on open XmlContainer";
			throw XmlException(XmlException::CONTAINER_OPEN, s);
		}
	}
}

void XmlContainer::checkContainer(const char *function, const char *objectclass, const XmlContainer &container) const // throws XmlException
{
	if (container_ != container.container_) {
		string s;
		s += function;
		s += " was called on container ";
		s += getName();
		s += " and was passed a ";
		s += objectclass;
		s += " object that belonged to container ";
		s += container.getName();
		s += ".";
		throw XmlException(XmlException::CONTAINER_PASSED_WRONG_OBJECT, s);
	}
}

XmlContainer::operator Container *()
{
	return container_;
}

DbEnv *XmlContainer::getEnvironment()
{
	return container_->getEnvironment();
}

std::string flagsAsText(u_int32_t flags)
{
	std::string s;
	if (flags&DB_CREATE)
		s += "DB_CREATE|";
	if (flags&DB_CXX_NO_EXCEPTIONS)
		s += "DB_CXX_NO_EXCEPTIONS|";
	if (flags&DB_FORCE)
		s += "DB_FORCE|";
	if (flags&DB_NOMMAP)
		s += "DB_NOMMAP|";
	if (flags&DB_RDONLY)
		s += "DB_RDONLY|";
	if (flags&DB_RECOVER)
		s += "DB_RECOVER|";
	if (flags&DB_THREAD)
		s += "DB_THREAD|";
	if (flags&DB_TRUNCATE)
		s += "DB_TRUNCATE|";
	if (flags&DB_TXN_NOSYNC)
		s += "DB_TXN_NOSYNC|";
	if (flags&DB_USE_ENVIRON)
		s += "DB_USE_ENVIRON|";
	if (flags&DB_USE_ENVIRON_ROOT)
		s += "DB_USE_ENVIRON_ROOT|";
	if (flags&DB_AUTO_COMMIT)
		s += "DB_AUTO_COMMIT|";
	if (flags&DB_DIRTY_READ)
		s += "DB_DIRTY_READ|";
	if (flags&DBXML_CHKSUM)
		s += "DBXML_CHKSUM|";
	if (flags&DBXML_ENCRYPT)
		s += "DBXML_ENCRYPT|";
	return s;
}

void XmlContainer::checkFlags(const char *function, u_int32_t flags, u_int32_t mask) const
{
	if (flags&~mask) {
		ostringstream s;
		s	<< "Flags check failed for XmlContainer::"
		<< function
		<< ". Expected some combination of '"
		<< flagsAsText(mask)
		<< "', but was passed '"
		<< flagsAsText(flags)
		<< "'.";
		container_->log(C_CONTAINER, L_ERROR, s);
		throw XmlException(XmlException::INVALID_VALUE, s.str());
	}
}

DBXML_EXPORT const char *DbXml::dbxml_version(int *majorp, int *minorp, int *patchp)
{
	if (majorp != NULL)
		*majorp = DBXML_VERSION_MAJOR;
	if (minorp != NULL)
		*minorp = DBXML_VERSION_MINOR;
	if (patchp != NULL)
		*patchp = DBXML_VERSION_PATCH;
	return DBXML_VERSION_STRING;
}

DBXML_EXPORT void DbXml::setLogLevel(LogLevel level, bool enabled)
{
	implSetLogLevel((ImplLogLevel)level, enabled);
}

DBXML_EXPORT void DbXml::setLogCategory(LogCategory category, bool enabled)
{
	implSetLogCategory((ImplLogCategory)category, enabled);
}

