########################################################################
#
# File Name: 		Database.py
#
# Documentation:	http://docs.ftsuite.com/4ODS/Database.py.html
#
"""
Implements the Database interface.
WWW: http://4suite.org/4ODS         e-mail: support@4suite.org

Copyright (c) 1999-2001 Fourthought Inc, USA.   All Rights Reserved.
See  http://4suite.org/COPYRIGHT  for license and copyright information
"""

import string
from Ft.Ods import Transaction, Exception, LocalThreading
from Ft.Ods.Exception import TransactionInProgress
from Ft.Ods.Exception import TransactionNotInProgress, DatabaseOpen,DatabaseClosed, FtodsUnknownError
import Ft.Ods
from Ft.Ods.StorageManager.Adapters import Constants


class Database:

    DatbaseOpen = Exception.DatabaseOpen
    DatabaseNotFound = Exception.DatabaseNotFound
    ObjectNameNotUnique = Exception.ObjectNameNotUnique
    ObjectNameNotFound = Exception.ObjectNameNotFound
    

    #A dictionary of current transactions
    #{TxId:transaction instance}
    _transactions = {}
    #A list of threads and transactions
    #{threadId:TxId}
    _threads = {}

    #Our thread safe lock
    #We must make sure that only one call at a time goes to our storage manager
    _lock = LocalThreading.RLock()

    
    def __init__(self):


        #The name of the current database to be opened
        #{threadId:name}
        self.__dbName = None


    def new(self):
        tx = None
        try:
            Database._lock.acquire()

            #First up, do we know about this thread.
            #One transaction per thread only
            newThreadId = LocalThreading.GetThreadId()
            if Database._threads.has_key(newThreadId):
                raise TransactionInProgress()
            if not self.__dbName:
                raise DatabaseClosed(dbName="Unknown")
            tx = Transaction.Transaction(self,self.__dbName)
            txId = tx._4ods_getId()
            Database._transactions[txId] = tx
            Database._threads[newThreadId] = txId

        finally:
            Database._lock.release()
        return tx

        
    def open(self, database_name):
        try:
            Database._lock.acquire()
            if self.__dbName:
                raise DatabaseOpen(dbName=self.__dbName)
            self.__dbName = database_name
        finally:
            Database._lock.release()
            
    def close(self):
        try:
            Database._lock.acquire()
            if not self.__dbName:
                raise DatabaseClosed()
            txIds = Database._transactions.keys()[:]

            #Abort any outstanding transactions
            for txId in txIds:
                Database._transactions[txId]._4ods_close()

            self.__dbName = None
            Database._transactions = {}
            Database._threads = {}

        finally:
            Database._lock.release()

    def bind(self, object, name):
        return self.__getCurrentTransaction()._4ods_bind(object,name)

    def unbind(self, name):
        return self.__getCurrentTransaction()._4ods_unbind(name)


    def lookup(self, name):
        return self.__getCurrentTransaction()._4ods_lookup(name)

    def keys(self,object=None):
        if object:
            return self.__getCurrentTransaction()._4ods_getObjectBindings(object)
        else:
            return self.__getCurrentTransaction()._4ods_getAllBindings()
        
    def isBound(self,object):
        return self.__getCurrentTransaction()._4ods_isBound(object)

    def schema(self):
        return self.__getCurrentTransaction()._4ods_getRepositoryObject(1)

    def _4ods_query(self,st):
        return self.__getCurrentTransaction()._4ods_runQuery(st)

    def _4ods_getRegisteredOperation(self,repoId):
        return self.__getCurrentTransaction()._4ods_getRegisteredOperation(repoId)
        

    def _4ods_registerOperation(self,repoId,functionName,functionModule):
        return self.__getCurrentTransaction()._4ods_registerOperation(repoId,functionName,functionModule)
        

    ## Private methods

    def __getCurrentTransaction(self):
        Database._lock.acquire()
        try:
            threadId = LocalThreading.GetThreadId()
            if not Database._threads.has_key(threadId):
                raise TransactionNotInProgress()
            return Database._transactions[Database._threads[threadId]]
        finally:
            Database._lock.release()

    current = __getCurrentTransaction
            
    def __getCurrentTransactionId(self):
        Database._lock.acquire()
        try:
            threadId = LocalThreading.GetThreadId()
            if not Database._threads.has_key(threadId):
                raise TransactionNotInProgress()
            return Database._threads[threadId]
        finally:
            Database._lock.release()

    def _4ods_inTransaction(self):
        return Database._threads.has_key(LocalThreading.GetThreadId())

    def _4ods_isOpen(self):
        return self.__dbName != None
        
    ### Internal Callback Methods ###

    def _4ods_endTransaction(self, txId):
        Database._lock.acquire()
        try:
            del Database._transactions[txId]
            for tId in Database._threads.keys():
                if Database._threads[tId] == txId:
                    del Database._threads[tId]
            
        finally:
            Database._lock.release()

    def _4ods_getPersistentObjectById(self,_typ,id):
        tx = self.__getCurrentTransaction()
        if _typ == Constants.Types.ROBJECT:
            return tx._4ods_getRepositoryObject(id)
        elif _typ == Constants.Types.POBJECT:
            return tx._4ods_getObject(id)
        elif Constants.g_listTypes[_typ]:
            if _typ == Constants.Types.DICTIONARY_COLLECTION:
                return tx._4ods_getDictionary(id)
            return tx._4ods_getCollection(id)
        else:
            raise FtodsUnknownError(msg="Unknown Type %s for lookup" % _typ)

    def _4ods_loadExtent(self,id):
        tx = self.__getCurrentTransaction()
        return tx._4ods_loadExtent(id)

    def _4ods_loadCollectionData(self,c,index=None):
        tx = self.__getCurrentTransaction()
        return tx._4ods_loadCollectionData(c,index)

    def _4ods_registerThread(self,tx):
        #We want to register the current thread with the txId specified
        Database._lock.acquire()
        try:
            txId = tx._4ods_getId()
            threadId = LocalThreading.GetThreadId()
            if Database._threads.has_key(threadId):
                raise TransactionInProgress()
            Database._threads[threadId] = txId
        finally:
            Database._lock.release()
            
    def _4ods_unregisterThread(self,tx):
        #We need to remove this transaction from the threads
        Database._lock.acquire()
        try:
            txId = tx._4ods_getId()
            threadId = LocalThreading.GetThreadId()
            if not Database._threads.has_key(threadId):
                raise TransactionNotInProgress()

            del Database._threads[threadId]
        finally:
            Database._lock.release()
