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

static const char revid[] = "$Id: TransactedContainer.cpp,v 1.13 2003/12/01 15:52:01 gmf Exp $";

#include "dbxml_config.h"
#include "dbxml/XmlPortability.hpp"
#include "dbxml/XmlException.hpp"
#include "dbxml/XmlIndexSpecification.hpp"
#include "TransactedContainer.hpp"
#include "OperationContext.hpp"

using namespace DbXml;

// This utility class wraps a method in a transaction.
//
class DbXml::Functor
{
public:
	virtual ~Functor()
	{}
	virtual int method(Container &container, DbTxn *txn, u_int32_t flags) const = 0;
};

// This utility class manages the lifetime of a transaction.
//
class Transaction
{
public:
	Transaction(DbEnv *env)
		: env(env),
		txn(0)
	{}
	~Transaction()
	{
		if (txn != 0) {
			try {
				abort();
			} catch (...) {
				// ignore errors
			}
		}
	}
	DbTxn *begin()
	{
		env->txn_begin(0, &txn, 0);
		return txn;
	}
	void commit()
	{
		if (txn != 0) {
			txn->commit(0);
			txn = 0;
		}
	}
	void abort()
	{
		if (txn != 0) {
			txn->abort();
			txn = 0;
		}
	}
	operator DbTxn*()
	{
		return txn;
	}
private:
	DbEnv *env;
	DbTxn *txn;
};

TransactedContainer::TransactedContainer(DbEnv *environment, const std::string &name, u_int32_t flags)
	: environment_(environment),
	ownsEnvironment_(false),
	autoCommit_(false)
{
	//
	// If no environment was provided we create a private one.
	// We need this in order to store all our databases in a
	// single file.
	//
	if (environment_ == 0) {
		environment_ = new DbEnv(0);
		environment_->open(0, DB_INIT_MPOOL | DB_PRIVATE | DB_THREAD | DB_CREATE, 0);
		ownsEnvironment_ = true;
	}
	Container::init(environment_, name, flags);
}

TransactedContainer::~TransactedContainer()
{
	try {
		if (isOpen()) {
			close(0);
		}
		if (ownsEnvironment_) {
			environment_->close(0);
			delete environment_;
		}
	} catch (...) {
		// Destructors don't throw.
	}
}

class OpenFunctor : public Functor
{
public:
	OpenFunctor(int mode, bool doVersionCheck) : mode_(mode), doVersionCheck_(doVersionCheck)
	{ }
	virtual int method(Container &container, DbTxn *txn, u_int32_t flags) const
	{
		return container.open(txn, flags, mode_, doVersionCheck_);
	}
private:
	int mode_;
	bool doVersionCheck_;
};

int TransactedContainer::open(DbTxn *txn, u_int32_t flags, int mode, bool doVersionCheck)
{
	// If auto commit is passed through the open call then we perform
	// auto commit for the container with container scope. We don't pass
	// the auto commit flag down to db as it'll provide local transactions
	// with method scope. Once the container is opened with auto commit
	// all container methods will be performed within local transactions,
	// unless the user provides their own transaction.
	//
	autoCommit_ = ((flags & DB_AUTO_COMMIT) != 0);
	return transactedMethod(txn, flags, OpenFunctor(mode, doVersionCheck));
}

class RemoveFunctor : public Functor
{
public:
	virtual int method(Container &container, DbTxn *txn, u_int32_t flags) const
	{
		return container.remove(txn);
	}
};

int TransactedContainer::remove(DbTxn *txn, u_int32_t flags)
{
	// See auto commit comment in open method.
	autoCommit_ = ((flags & DB_AUTO_COMMIT) != 0);
	return transactedMethod(txn, 0, RemoveFunctor());
}

class RenameFunctor : public Functor
{
public:
	RenameFunctor(const std::string &newName) : newName_(newName)
	{ }
	virtual int method(Container &container, DbTxn *txn, u_int32_t flags) const
	{
		return container.rename(txn, newName_);
	}
private:
	const std::string &newName_;
};

int TransactedContainer::rename(DbTxn *txn, const std::string &newName, u_int32_t flags)
{
	// See auto commit comment in open method.
	autoCommit_ = ((flags & DB_AUTO_COMMIT) != 0);
	return transactedMethod(txn, flags, RenameFunctor(newName));
}

class AddDocumentFunctor : public Functor
{
public:
	AddDocumentFunctor(Document &document, ID &id, UpdateContext &context)
		: document_(document), id_(id), context_(context)
	{ }
	virtual int method(Container &container, DbTxn *txn, u_int32_t flags) const
	{
		return container.addDocument(txn, document_, id_, context_, flags);
	}
private:
	Document &document_;
	ID &id_;
	UpdateContext &context_;
};

int TransactedContainer::addDocument(DbTxn *txn, Document &document, ID &id, UpdateContext &context, u_int32_t flags)
{
	return transactedMethod(txn, flags, AddDocumentFunctor(document, id, context));
}

class DeleteDocumentFunctor1 : public Functor
{
public:
	DeleteDocumentFunctor1(ID id, UpdateContext &context)
		: id_(id), context_(context)
	{ }
	virtual int method(Container &container, DbTxn *txn, u_int32_t flags) const
	{
		return container.deleteDocument(txn, id_, context_, flags);
	}
private:
	ID id_;
	UpdateContext &context_;
};

int TransactedContainer::getDocument(DbTxn *txn, ID id, XmlDocument &document, u_int32_t flags) const
{
	return Container::getDocument(txn, id, document, flags);
}

int TransactedContainer::deleteDocument(DbTxn *txn, ID id, UpdateContext &context, u_int32_t flags)
{
	return transactedMethod(txn, flags, DeleteDocumentFunctor1(id, context));
}

class DeleteDocumentFunctor2 : public Functor
{
public:
	DeleteDocumentFunctor2(Document &document, UpdateContext &context)
		: document_(document), context_(context)
	{ }
	virtual int method(Container &container, DbTxn *txn, u_int32_t flags) const
	{
		return container.deleteDocument(txn, document_, context_, flags);
	}
private:
	Document &document_;
	UpdateContext &context_;
};

int TransactedContainer::deleteDocument(DbTxn *txn, Document &document, UpdateContext &context, u_int32_t flags)
{
	return transactedMethod(txn, flags, DeleteDocumentFunctor2(document, context));
}

class UpdateDocumentFunctor : public Functor
{
public:
	UpdateDocumentFunctor(Document &document, UpdateContext &context)
		: document_(document), context_(context)
	{ }
	virtual int method(Container &container, DbTxn *txn, u_int32_t flags) const
	{
		return container.updateDocument(txn, document_, context_);
	}
private:
	Document &document_;
	UpdateContext &context_;
};

int TransactedContainer::updateDocument(DbTxn *txn, Document &document, UpdateContext &context)
{
	return transactedMethod(txn, 0, UpdateDocumentFunctor(document, context));
}

class ModifyDocumentFunctor : public Functor
{
public:
	ModifyDocumentFunctor(const Modify &modify, XmlUpdateContext *context)
		: modify_(modify), context_(context)
		{ }
	virtual int method(Container &container, DbTxn *txn, u_int32_t flags) const
	{
		return container.modifyDocument(txn, modify_, context_, flags);
	}
private:
	const Modify &modify_;
	XmlUpdateContext *context_;
};

int TransactedContainer::modifyDocument(DbTxn *txn, const Modify &modify, XmlUpdateContext *context, u_int32_t flags)
{
	return transactedMethod(txn, flags, ModifyDocumentFunctor(modify, context));
}

class SetIndexSpecificationFunctor : public Functor
{
public:
	SetIndexSpecificationFunctor(const XmlIndexSpecification &index)
		: index_(index)
	{ }
	virtual int method(Container &container, DbTxn *txn, u_int32_t flags) const
	{
		return container.setIndexSpecification(txn, (const IndexSpecification &)index_);
	}
private:
	const XmlIndexSpecification &index_;
};

int TransactedContainer::setIndexSpecification(DbTxn *txn, const XmlIndexSpecification &index)
{
	return transactedMethod(txn, 0, SetIndexSpecificationFunctor(index));
}

int TransactedContainer::getIndexSpecification(DbTxn *txn, XmlIndexSpecification &index) const
{
	return Container::getIndexSpecification(txn, (IndexSpecification &)index);
}

class IndexFunctor : public Functor
{
public:
	enum Type { Add, Delete, Replace };
	IndexFunctor(const std::string &uri, const std::string &name, const std::string &index, Type type)
		: uri_(uri),
		name_(name),
		index_(index),
		type_(type)
	{ }
	virtual int method(Container &container, DbTxn *txn, u_int32_t flags) const
	{
		switch(type_) {
		case Add:
			return container.addIndex(txn, uri_, name_, index_);
		case Delete:
			return container.deleteIndex(txn, uri_, name_, index_);
		case Replace:
			return container.replaceIndex(txn, uri_, name_, index_);
		default:
			assert(0);
		}
		return 0;
	}
private:
	const std::string &uri_;
	const std::string &name_;
	const std::string &index_;
	Type type_;
};

int TransactedContainer::addIndex(DbTxn *txn, const std::string &uri, const std::string &name, const std::string &index)
{
	return transactedMethod(txn, 0, IndexFunctor(uri, name, index, IndexFunctor::Add));
}

int TransactedContainer::deleteIndex(DbTxn *txn, const std::string &uri, const std::string &name, const std::string &index)
{
	return transactedMethod(txn, 0, IndexFunctor(uri, name, index, IndexFunctor::Delete));
}

int TransactedContainer::replaceIndex(DbTxn *txn, const std::string &uri, const std::string &name, const std::string &index)
{
	return transactedMethod(txn, 0, IndexFunctor(uri, name, index, IndexFunctor::Replace));
}

DbEnv *TransactedContainer::getEnvironment()
{
	return (ownsEnvironment_ ? 0 : environment_);
}

int TransactedContainer::transactedMethod(DbTxn *txn, u_int32_t flags, const Functor &f)
{
	int err = 0;
	if (txn == 0 && ( autoCommit_ || (flags & DB_AUTO_COMMIT))) {
		flags &= ~DB_AUTO_COMMIT; // make sure we don't pass AUTO_COMMIT flag down to db
		Transaction transaction(environment_);
		txn = transaction.begin();
		err = f.method(*this, txn, flags);
		transaction.commit();
	} else if (txn != 0 && (flags & DB_AUTO_COMMIT)) {
		throw XmlException(XmlException::INVALID_VALUE,
				   "DB_AUTO_COMMIT may not be specified along with a transaction handle");
	} else {
		err = f.method(*this, txn, flags);
	}
	return err;
}

