#!/usr/bin/env python
"""
A persistent Repository driver using Berkeley (BSD) database files

Requires pybsddb (http://pybsddb.sourceforge.net/bsddb3.html), which is
the standard bsddb module in Python 2.3 and up. However, even if you are
using Python 2.3 up, it is possible that you won't have a functioning
bsddb at all, or it may be wrapping the db185 Berkeley DB C libraries.

If necessary, update your C libs (db-4.3.27 preferred for compatibility with
Berkeley DB XML). And if possible, upgrade or install pybsddb (this is
mandatory on Python 2.2, if you want this driver to work).

You can determine your pbsddb3 version this way:
Type "help", "copyright", "credits" or "license" for more information.
>>> from bsddb3 import db
>>> db.version()
(4, 3, 27)

This driver can also store XML content in a heirarchy of Berkeley DB XML (2.0.9) Containers

see:

* http://www.sleepycat.com/xmldocs/gsg_xml/cxx/containers.html
* http://www.sleepycat.com/xmldocs/gsg_xml/cxx/xquery.html
* http://www.sleepycat.com/xmldocs/gsg_xml/cxx/examineresults.html
* http://www.sleepycat.com/xmldocs/gsg_xml/cxx/indices.html

Documents are stored as Whole Documents (not individual nodes) unless the
'OneMBDocuments' driver property is set to true (it's false by default).
When the 'DbXml' driver property is 'true', the following containers are setup

 XML Databases (containers which key resources by their reposository paths)):
   - ftss_metadata_xml.dbxml ('path' --> content)   Metadata Documents
   - ftss_repo_xml.dbxml     ('path' --> content)   Repsitory XML Documents
   - ftss_user_all.dbxml     ('path' --> content)   User, non-repository Documents
     o used when 'DataStoreContainers' driver property is 'false' (by default)

Repository Documents are:

Servers,Users,Groups,Containers,Document Definitions, RDF Documents,Commands, and Aliases

Non-XML content is stored in the following databases
nonXML Databases (Berkeley DB databases keyed by their repository paths):
   - ftss_content.db ('path' --> content)
     o stores XML content here as well when 'DbXml' driver property is 'false' (default)
     
   - ftss_cache.db   ('path  --> cache)
   - ftss_session.db   ('path'  --> session)
   - ftss_types.db ('path' -> ResourceType) (an index of resource types)
   - ftss_metadata_strings.db ('path' --> metadata document string)
     o used when 'DbXml' driver property is 'false' (default)

Copyright 2005 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""

from distutils.dir_util import remove_tree
import urllib, types, sys, cStringIO, re,os,sha
from Ft.Server.Server.Drivers.PathImp import Path
from Ft.Rdf import Model
from Ft.Xml import XPath
from Ft.Xml.Domlette import NonvalidatingReader, PrettyPrint
from string import join, split
from Ft.Server import FTSERVER_NAMESPACE
from Ft.Server.Server import FtServerServerException, Error
from Ft.Server.Common import ResourceTypes
from Ft.Rdf.Drivers import BerkeleyDB as BerkeleyDB4RDF
from Ft.Server.Common.Schema import g_resourceTypeFromRdf, g_rdfResourceTypes
from BerkeleyDBContainer import BerkeleyDBContainer,FTSS_CONTAIMENT_DB_NAME
from Util import GetContainer,GetDB,BerkeleyDBFlags
from Ft import GetConfigVar

VERSION="0.1"
SYSTEM_MODEL_NAME = 'ftss_system.db'
USER_MODEL_NAME = 'ftss_user.db'
SESSION_DB_NAME = 'ftss_session.db'
CACHE_DB_NAME = 'ftss_cache.db'
CONTENT_DB_NAME = 'ftss_content.db'
RESOURCE_TYPE_DB_NAME = 'ftss_types.db'
MD_STRINGS_DB_NAME = 'ftss_metadata_strings.db'

MD_XML_CONTENT_DB_NAME = 'ftss_metdata_xml.dbxml'
REPOSITORY_DOCUMENTS_DB_NAME = 'ftss_repo_xml.dbxml'
USER_XML_CONTENT_DB_NAME = 'ftss_user_all.dbxml'

XmlDbNames = [MD_XML_CONTENT_DB_NAME,REPOSITORY_DOCUMENTS_DB_NAME]

nonXmlDbNames = [SESSION_DB_NAME,
                 CACHE_DB_NAME,
                 CONTENT_DB_NAME,
                 RESOURCE_TYPE_DB_NAME,
                 FTSS_CONTAIMENT_DB_NAME]

DATABASE_DIR = os.path.join(GetConfigVar('LOCALSTATEDIR'), 'BsdDb') 
DATABASE_DIR = os.environ.get('FT_DATABASE_DIR', DATABASE_DIR)

DBXML_FETCH_FTSS_CONTAINERS_XQUERY="""declare namespace ftss = '%s';for $container in collection('%s')/ftss:MetaData[@type='%s'] return dbxml:metadata('dbxml:name', $container)"""%(
    FTSERVER_NAMESPACE,
    MD_XML_CONTENT_DB_NAME,
    g_rdfResourceTypes[ResourceTypes.ResourceType.CONTAINER]
  )

XML_INDICES=True

FILE_RESOURCE_CLASS=0
USER_DOCUMENT_RESOURCE_CLASS=1
REPOSITORY_DOCUMENT_RESOURCE_CLASS=2
METADATA_DOCUMENT_RESOURCE_CLASS=3

def createDbXmlContainerPath(path):
    """
    Influenced by the old Ft.Server.Server.Drivers.FlatFile.createFlatFilePath
    Takes a repository path and creates a name for the corresponding Bdb Xml container.  XQuery can be used to search
    within non-repository XML documents in containers using the name mapped by this mechanism
    Drivers setup with DataStoreContainer = 'true' (currently disabled) make use of this to map repository container paths to
    a Filesystem path (relative to the driver root) of a corresponding Berkeley DB XML container
    FIXME: DB_RUNRECOVERY (PANIC: No such file or directory) is raised when DataStoreContainer is set to true
    """
    return sha.new(path).hexdigest()
    # Enforce sanity
    if path[:1] != '/':
        raise TypeError('createDbXmlContainerPath called with a bad path: %r' % path)

    
    path = path[1:]

    # FIXME: Replace filesystem-unsafe characters in path segments.
    # (Mainly affects Windows). This cannot be done right now because
    # FtssDriver and PathImp cannot handle anything the least bit
    # weird in a repo path, like "<", "&", ":", "%" ...

    # Replace path separators as needed
    if os.sep != '/':
        path = path.replace('/', os.sep)

    path = path.replace('/', '#')

    return path

def InitializeModule():
    """
    Post-import hook to initialize module's runtime variables that are not
    required at import time, but will be needed before the module-level
    functions are called.
    """
    global DBXML_TRANSACTIONAL,DBEnv,XmlManager,XmlContainer,XmlValue,RepositoryTypeXPath
    
    RepositoryTypeXPath = XPath.Compile('string(/ftss:MetaData/@type)')

    try:
        # For Pythons w/distutils pybsddb
        from bsddb3.db import DBEnv, DB_THREAD
    except ImportError:
        # For Python 2.3 - not advised
        from bsddb.db import DBEnv, DB_THREAD
        
    try:
        from dbxml import DBXML_TRANSACTIONAL,XmlManager,XmlContainer,XmlValue
    except ImportError:
        #Can't DbXml 
        pass
    
    from Ft.Rdf.Drivers import BerkeleyDB as BerkeleyDB4RDF
    BerkeleyDB4RDF.InitializeModule()

def _DbNames(dbXmlContainers=False,dbXml=False):
    DbNames = [SESSION_DB_NAME,
               CACHE_DB_NAME,
               CONTENT_DB_NAME,
               RESOURCE_TYPE_DB_NAME,
               FTSS_CONTAIMENT_DB_NAME,
               SYSTEM_MODEL_NAME,
               USER_MODEL_NAME,
               ]
    if dbXml:
        DbNames += XmlDbNames
        if not dbXmlContainers:
            DbNames.append(USER_XML_CONTENT_DB_NAME)
    else:
        DbNames.append(MD_STRINGS_DB_NAME)

    return DbNames

def Exists(properties):
    """See if this repo exists.  The return value for this is three state.
    1.  Everything is present
    0.  Some things are there
    -1  Nothing is there.
    """
    dbList = _DbNames(properties.get('DataStoreContainers','false') in ['1','true'],
                      properties.get('DbXml','false') in ['1','true'])

    root = os.path.join(DATABASE_DIR, properties['Root'])
    existingDbs = [dbName for dbName in dbList if os.path.exists(os.path.join(root,dbName))]

    if len(existingDbs) == 0 and not os.path.exists(root):
        rt = -1
    elif len(existingDbs) < len(dbList) - 2:
        rt = 0
    else:
        InitializeModule() 
        if BerkeleyDB4RDF.ExistsDb(SYSTEM_MODEL_NAME) == 1 and BerkeleyDB4RDF.ExistsDb(USER_MODEL_NAME) == 1:
            rt = 1
        else:
            rt = 0
    return rt
    
def Initialize(properties):
    """Initialize a new copy of the repo.  This is not the same as a 4ss_manager init.  This
    is very raw.  The adapter will take care of calling our other interfaces
    (createContainer, createUser, etc) with all of the information
    about the rest of the 'init' stuff to do
    """
    root = os.path.join(DATABASE_DIR, properties['Root'])    
    
    if os.path.isdir(DATABASE_DIR):
        try:
            os.makedirs(root)
        except OSError, e:
            raise FtServerServerException(Error.UNABLE_TO_INITALIZE_REPO,
                                      msg=str(e),
                                      traceback="creating root directory")
    else:
        raise FtServerServerException(Error.UNABLE_TO_INITALIZE_REPO,
                                      msg="Directory %s does not exist" % DATABASE_DIR)
    
    #Create BerkeleyDB4RDF Datastore
    InitializeModule() 
    BerkeleyDB4RDF.CreateDb(SYSTEM_MODEL_NAME)
    BerkeleyDB4RDF.CreateDb(USER_MODEL_NAME)

    #Create Berkeley DB Environment (used with all databases)
    environment = DBEnv()    
    environment.open(root, BerkeleyDBFlags,0)
    xmlEnvironment = DBEnv()    
    xmlEnvironment.open(root, BerkeleyDBFlags,0)    

    #Basically, this property indicates whether or not to store XML Documents as 'Whole Documents' or as individual nodes
    bigDocs  = properties.get('OneMBDocuments','false')
    
    #Use Berkeley DB XML for XML content?
    useDbXml = properties.get('DbXml','false')
    
    #Create Berkeley DB XML containers per 4Suite Repository container?
    dataStoreContainers = properties.get('DataStoreContainers','false')

    txn = environment.txn_begin()

    if useDbXml in ['1','true']:
        #Use BerkeleyDB XML for XML content
        #Fetch XmlManager 
        mgr = XmlManager(xmlEnvironment, 0)

        if bigDocs in ['false','0']:
            mgr.setDefaultContainerType(XmlContainer.WholedocContainer)

        mgr.setDefaultContainerType(XmlContainer.WholedocContainer)

        _xmlDbs = XmlDbNames
        if not properties.get('DataStoreContainers','false') in ['1','true']:
            _xmlDbs.append(USER_XML_CONTENT_DB_NAME)
            
        xtxn = mgr.createTransaction()
        createdContainers=[]
        for xmlContainer in _xmlDbs:
            container = mgr.createContainer(xtxn,xmlContainer)
            createdContainers.append(container)
            #Add indices
            #Indices for common metadata elements/attributes
            #Indices for common repository document elements / attributes
            if xmlContainer == MD_XML_CONTENT_DB_NAME and XML_INDICES:
                container.addIndex(xtxn,"", "name", "node-metadata-substring-string", mgr.createUpdateContext())
                container.addIndex(xtxn,"", "type", "node-attribute-equality-string", mgr.createUpdateContext())
                container.addIndex(xtxn,"", "path", "node-attribute-equality-string", mgr.createUpdateContext())
                container.addIndex(xtxn,"", "creation-date", "node-attribute-equality-string", mgr.createUpdateContext())
                container.addIndex(xtxn,FTSERVER_NAMESPACE, "Owner", "node-element-equality-string", mgr.createUpdateContext())
                container.addIndex(xtxn,FTSERVER_NAMESPACE, "Imt", "node-element-equality-string", mgr.createUpdateContext())
                container.addIndex(xtxn,FTSERVER_NAMESPACE, "Size", "node-element-equality-string", mgr.createUpdateContext())

            elif xmlContainer == REPOSITORY_DOCUMENTS_DB_NAME and XML_INDICES:
                container.addIndex(xtxn,"", "userName", "node-attribute-equality-string", mgr.createUpdateContext())
                container.addIndex(xtxn,"", "key", "node-attribute-equality-string", mgr.createUpdateContext())
                container.addIndex(xtxn,"", "name", "edge-attribute-equality-string", mgr.createUpdateContext())
                container.addIndex(xtxn,FTSERVER_NAMESPACE, "ServerName", "edge-element-equality-string", mgr.createUpdateContext())
                container.addIndex(xtxn,"", "running", "node-element-equality-boolean", mgr.createUpdateContext())
                
        xtxn.commit()
        
    else:
        pass

    liveDbs = [GetDB(name,environment,txn=txn)[-1] for name in nonXmlDbNames]

    if not useDbXml in ['1','true']:
        liveDbs.append(GetDB(MD_STRINGS_DB_NAME,environment,txn=txn)[-1])

    txn.commit()

    for db in liveDbs:
        db.close()
        del db

    if useDbXml in ['1','true']:
        for container in createdContainers:
            container.close()
        del container

    environment.close(0)
    xmlEnvironment.close(0)
    
def Destroy(properties,tryOnly=0):
    """Completly destroy the repo"""
    root = os.path.join(DATABASE_DIR,properties['Root'])

    #This is a simple as a rm -rf :)
    if os.path.exists(root):
        try:
            remove_tree(root)
            InitializeModule() 
            BerkeleyDB4RDF.DestroyDb(SYSTEM_MODEL_NAME)
            BerkeleyDB4RDF.DestroyDb(USER_MODEL_NAME)            
        except OSError:
            if not tryOnly:
                raise

def Maintain(properties):
    """
    Perform any maintenance on the db
    or periodic actions (inference, integrity check..?!)
    """    
    pass
            


def LocateContent(path,typ,typeDB):
    """    
    0 - NonXmlContent
    1 - Non-repository Document
    2 - Repository Document (not Metadata)
    3 - Metadata
    4 - Cache
    5 - Session
    """
    if typ == ResourceTypes.RESOURCE_METADATA:
        return 3
    elif typ == ResourceTypes.RESOURCE_CONTENT:
        txn,typeDb = typeDB
        _type = typeDb.get(path,txn=txn)
        repositoryType=int(_type)
        
        if repositoryType in ResourceTypes.XML_DOCUMENTS:
            if repositoryType in REPO_DOCUMENTS:
                return 2            
            else:
                return 1
        else:
            return 0
    elif typ == ResourceTypes.SESSION_DATA:
        return 4
    elif typ == ResourceTypes.RESOURCE_CACHE:
        return 5


class BerkeleyDBDriver(BerkeleyDBContainer):
    """
    Store information in an RDF and Berleley DB (XML)? store"""    

    def __init__(self,properties):
        self.__dbProperties = properties
        self.root = os.path.join(DATABASE_DIR, properties['Root'])
        self._dBs = {}
        self.__useDbXml = properties.get('DbXml','false') in ['1','true']

        root = os.path.join(DATABASE_DIR, properties['Root'])    
        self.environment = DBEnv()
        self.environment.open(root,BerkeleyDBFlags,0)
        self.xmlEnvironment = DBEnv()
        self.xmlEnvironment.open(root,BerkeleyDBFlags,0)        
        
        self.dbTxn = self.environment.txn_begin()

        if self.__useDbXml:
            self.XmlMgr = XmlManager(self.xmlEnvironment, 0)
            self.dbXmlTxn = self.XmlMgr.createTransaction()

            if properties.get('OneMBDocuments','false') in ['false','0']:
                self.XmlMgr.setDefaultContainerType(XmlContainer.WholedocContainer)

            self._repositorydB = GetContainer(REPOSITORY_DOCUMENTS_DB_NAME,self.root,self.xmlEnvironment,txn=self.dbXmlTxn)

            if not properties.get('DataStoreContainers','false') in ['1','true']:
                self._userXmldB = GetContainer(USER_XML_CONTENT_DB_NAME,self.root,self.xmlEnvironment,self.dbXmlTxn)

            self._dBs[ResourceTypes.RESOURCE_METADATA]  = GetContainer(MD_XML_CONTENT_DB_NAME,self.root,self.xmlEnvironment,self.dbXmlTxn)
        else:
            self._dBs[ResourceTypes.RESOURCE_METADATA]  = GetDB(MD_STRINGS_DB_NAME,self.environment,self.dbTxn)
            
        self._contentType                          = GetDB(RESOURCE_TYPE_DB_NAME,self.environment,txn=self.dbTxn)
        self._dBs[ResourceTypes.RESOURCE_CONTENT]  = GetDB(CONTENT_DB_NAME,self.environment,self.dbTxn)
        self._dBs[ResourceTypes.RESOURCE_CACHE]    = GetDB(CACHE_DB_NAME,self.environment,self.dbTxn)
        self._dBs[ResourceTypes.SESSION_DATA]      = GetDB(SESSION_DB_NAME,self.environment,self.dbTxn)
        self._dBs[FTSS_CONTAIMENT_DB_NAME]         = GetDB(FTSS_CONTAIMENT_DB_NAME, self.environment,self.dbTxn)

        d = BerkeleyDB4RDF.DbAdapter(SYSTEM_MODEL_NAME)
        d.begin()
        self._systemRdfModel = Model.Model(d)
        d = BerkeleyDB4RDF.DbAdapter(USER_MODEL_NAME)
        d.begin()
        self._userRdfModel = Model.Model(d)
 
    def createFile(self,path,typ,content):
        #print "creating %s of type %s"%(path,typ)
        if not isinstance(content, types.StringType):
            type_name = type(content).__name__
            raise TypeError("Content must be a string, not %r" % type_name)
        #Necessary for BerkeleyDB (expects strings for names, not unicode)
        path = path.encode('utf-8')
        if typ in [ResourceTypes.RESOURCE_METADATA,ResourceTypes.RESOURCE_CACHE]:
            #Resource metadata or resource cache
            
            if typ == ResourceTypes.RESOURCE_METADATA:
                #Determine database to store content via metadata and store in metadata container
                mdDOM = NonvalidatingReader.parseString(content.encode('utf-8'), uri='urn:uuid:metadata')
                mdContext = XPath.Context.Context(mdDOM,processorNss={'ftss':'http://xmlns.4suite.org/reserved'})
                typRes = RepositoryTypeXPath.evaluate(mdContext)
                typRes = str(g_resourceTypeFromRdf[typRes])
                txn,typeDB = self._contentType
                #Store Repository type for later reference
                typeDB.put(path,typRes,self.dbTxn)

                if self.__useDbXml:
                    #Store metadata document DbXml Container for metadata
                    txn,xmlDB = self._dBs[ResourceTypes.RESOURCE_METADATA]
                    uc = self.XmlMgr.createUpdateContext()
                    xmlDB.putDocument(self.dbXmlTxn,path, content,uc)
                else:
                    #Store metadata XML string in Berkeley DB for metadata strings
                    txn,db = self._dBs[typ]
                    db.put(path,content,self.dbTxn)
                
            else:
                #resource cache, put in Berkeley DB
                txn,db = self._dBs[typ]
                db.put(path,content,self.dbTxn)
                               
        elif typ == ResourceTypes.SESSION_DATA:
            txn,db = self._dBs[typ]
            db.put(path,content,self.dbTxn)
                
        else:
            #Actual content
            #Store in db specified by looking at repositoryType
            txn,typeDb = self._contentType
            repositoryType=int(typeDb.get(path,txn=self.dbTxn))

            if repositoryType in ResourceTypes.XML_DOCUMENTS and self.__useDbXml:
                #An XML Document (and we are configured to use Berkeley DB XML)
                #Check if resource is a repository object.  
                if not repositoryType in REPO_DOCUMENTS:
                    #Non-repository XML documents
                    #Store in user xml container, if data store containers aren't being used                    
                    #Otherwise, map the repository path to the mirror-heirarchy of user xml content and store in container
                    #NOTE:  Currently disabled but should be considered in future updates
                    #if self.__dbProperties.get('DataStoreContainers','false') in ['1','true']:
                        #Using Data Store containers, store in dbxml container
                        #dataStorePath=createDbXmlContainerPath(self.root,path.getParentPath().absolutePath())
                        #dataStorePath=RepoPath2DataStoreContainer(path.getParentPath().absolutePath(),DATASTORE_XML_CONTAINER_NAME)
                        #dataStorePath=createDbXmlContainerPath(path.getParentPath().absolutePath())
                        #txn,container = GetContainer(dataStorePath,self.root,self.environment,self.dbXmlTxn)
                    #else:
                    txn,container = self._userXmldB                        
                    
                else:
                    #A Repository Document
                    txn,container = self._repositorydB

                    #NOTE: Currently disabled but should be considered in future updates
                    #if repositoryType == ResourceTypes.ResourceType.CONTAINER and self.__dbProperties.get('DataStoreContainers','false') in ['1','true'] and path != '/':
                        #A repository container modeled as a Berkeley DB XML Container for non-repo XML documents
                        #Create the physical dbxml container
                    
                        #dbxmlContPath=createDbXmlContainerPath(path)
                        #print " creating %s as datastore container (%s)"%(path,dbxmlContPath)
                        #self.XmlMgr.createContainer(self.dbXmlTxn,dbxmlContPath)
                    
                try:
                    uc = self.XmlMgr.createUpdateContext()
                    container.putDocument(self.dbXmlTxn,path, content, uc)
                except Exception, e:
                    if str(e).find('Parse error') != -1:
                        print e, ':\n',content
                        self.rollback()
                        raise NotImplementedError('Xerces parse error - DTD and DTD entity references?')
                    else:
                        raise
                    
            else:
                #Non XML content
                txn,contentDB = self._dBs[typ]
                contentDB.put(path,content,self.dbTxn)
        
    def hasFile(self,path,typ):
        """See if we have any meta information about this resource"""
        #print "hasFile(%s,%s)"%(path,typ)
        #Necessary for BerkeleyDB (expects strings for names, not unicode)
        path = path.encode('utf-8')
        #First, if not cache or session, check typeDB - there should be an entry there if it exists
        if not typ in [ResourceTypes.SESSION_DATA,ResourceTypes.RESOURCE_CACHE]:
            txn,typeDb = self._contentType
            if not typeDb.has_key(path,txn):
                return None

        #Entry in typeDB, check if it exists in the appropriate DB
        locationCode = LocateContent(path,typ,self._contentType)
        #Necessary for BerkeleyDB (expects strings for names, not unicode)
        path = path.encode('utf-8')
        if locationCode == FILE_RESOURCE_CLASS:
            txn,db = self._dBs[typ]
            return not not db.get(path,txn=txn)
        elif locationCode == USER_DOCUMENT_RESOURCE_CLASS:
            if self.__useDbXml:
                if self.__dbProperties.get('DataStoreContainers','false') in ['1','true']:
                    #Check within underlying DbXml container
                    #NOTE: Disabled right now
                    #txn,container = GetContainer(
                    #    createDbXmlContainerPath(path.getParentPath().absolutePath()),
                    #    self.root,
                    #    self.environment,
                    #    self.dbXmlTxn)
                    pass
                else:
                    txn,container = self._userXmldB                        

                doc=container.getDocument(self.dbXmlTxn,path)
                del doc
                return True

            else:
                #Not using DbXml, check in content DB
                txn,db = self._dBs[typ]
                return not not db.get(path,txn=txn)                

        elif locationCode == REPOSITORY_DOCUMENT_RESOURCE_CLASS:
            if self.__useDbXml:            
                txn,repoDB = self._repositorydB
                doc=repoDB.getDocument(self.dbXmlTxn,path)
                del doc
                return True                    

            else:
                #Not using DbXml, check in content DB
                txn,db = self._dBs[typ]
                return not not db.get(path,txn=txn)                
            
        elif locationCode == METADATA_DOCUMENT_RESOURCE_CLASS:
            if self.__useDbXml:
                txn,mdContainer = self._dBs[typ]
                doc=mdContainer.getDocument(self.dbXmlTxn,path)
                del doc
                return True
            else:
                #Not using DbXml, check in metadata strings DB
                txn,db = self._dBs[typ]
                return not not db.get(path,txn=txn)           
        else:
            txn,db = self._dBs[typ]
            return not not db.get(path,txn=txn)

    def fetchFile(self, path, typ):
        #print "fetching %s (type: %s)"%(path,typ)
        #Necessary for BerkeleyDB (expects strings for names, not unicode)
        path = path.encode('utf-8')        
        #If it's not session data or cache and there isn't an entry in the Type DB then we can assume it doesn't exist
        if not typ in [ResourceTypes.SESSION_DATA,ResourceTypes.RESOURCE_CACHE]:
            txn,typeDb = self._contentType
            if not typeDb.has_key(path,txn):
                return None
        
        locationCode = LocateContent(path,typ,self._contentType)
        if locationCode == FILE_RESOURCE_CLASS:
            txn,db = self._dBs[typ]
            return db.get(path,txn=self.dbTxn)

        elif locationCode == USER_DOCUMENT_RESOURCE_CLASS:
            if self.__useDbXml:
                if self.__dbProperties.get('DataStoreContainers','false') in ['1','true']:
                    #Check within underlying DbXml container
                    #NOTE: Disabled right now
                    #txn,container = GetContainer(
                    #    createDbXmlContainerPath(path.getParentPath().absolutePath()),
                    #    self.root,
                    #    self.environment,
                    #    self.dbXmlTxn)
                    pass
                else:
                    txn,container = self._userXmldB

                doc=container.getDocument(self.dbXmlTxn,path)
                return doc.getContent()
            else:
                #Not using DbXml, retrieve from  content DB                
                txn,db = self._dBs[typ]
                return db.get(path,txn=self.dbTxn)
            
        elif locationCode == REPOSITORY_DOCUMENT_RESOURCE_CLASS:
            if self.__useDbXml:         
                txn,repoDB = self._repositorydB
                doc=repoDB.getDocument(self.dbXmlTxn,path)
                return doc.getContent()
            else:
                #Not using DbXml, check in content DB
                txn,db = self._dBs[typ]
                return db.get(path,txn=self.dbTxn)
            
        elif locationCode == METADATA_DOCUMENT_RESOURCE_CLASS:
            if self.__useDbXml:
                txn,mdContainer = self._dBs[typ]
                doc=mdContainer.getDocument(self.dbXmlTxn,path)
                rt=doc.getContent()
                return rt
            else:
                #Not using DbXml, check in MD strings DB
                txn,db = self._dBs[typ]
                return db.get(path,txn=self.dbTxn)

        else:
            txn,db = self._dBs[typ]
            return db.get(path,txn=self.dbTxn)
    
    def updateFile(self, path, typ, content):
        #print "updateFile(%s,%s,%s)"%(path,typ,len(content))
        #Necessary for BerkeleyDB (expects strings for names, not unicode)
        path = path.encode('utf-8')        
        #If it's not session data or cache and there isn't an entry in the Type DB then we can assume it doesn't exist
        if not typ in [ResourceTypes.SESSION_DATA,ResourceTypes.RESOURCE_CACHE]:        
            txn,typeDb = self._contentType
            if not typeDb.has_key(path,txn):
                return 

        locationCode = LocateContent(path,typ,self._contentType)
        if locationCode == FILE_RESOURCE_CLASS:
            txn,db = self._dBs[typ]
            db.put(path,content,self.dbTxn)
            
        elif locationCode == USER_DOCUMENT_RESOURCE_CLASS:
            if self.__useDbXml:
                if self.__dbProperties.get('DataStoreContainers','false') in ['1','true']:
                    #Check within underlying DbXml container
                    #NOTE: Disabled right now
                    #txn,container = GetContainer(
                    #    createDbXmlContainerPath(path.getParentPath().absolutePath()),
                    #    self.root,
                    #    self.environment,
                    #    self.dbXmlTxn)
                    pass
                else:
                    txn,container = self._userXmldB

                doc=container.getDocument(self.dbXmlTxn,path)
                doc.setContent(content)
                container.updateDocument(self.dbXmlTxn,doc,self.XmlMgr.createUpdateContext())
            else:
                #Not using DbXml, update in content DB                
                txn,db = self._dBs[typ]
                db.put(path,content,self.dbTxn)                
            
        elif locationCode == REPOSITORY_DOCUMENT_RESOURCE_CLASS:
            if self.__useDbXml:   
                txn,repoDB = self._repositorydB
                doc=repoDB.getDocument(self.dbXmlTxn,path)
                doc.setContent(content)
                repoDB.updateDocument(self.dbXmlTxn,doc,self.XmlMgr.createUpdateContext())
            else:
                #Not using DbXml, update in content DB                
                txn,db = self._dBs[typ]
                db.put(path,content,self.dbTxn)
                
        elif locationCode == METADATA_DOCUMENT_RESOURCE_CLASS:
            if self.__useDbXml:
                txn,mdContainer = self._dBs[typ]
                doc=mdContainer.getDocument(self.dbXmlTxn,path)
                doc.setContent(content)
                mdContainer.updateDocument(self.dbXmlTxn,doc,self.XmlMgr.createUpdateContext())

            else:
                #Not using DbXml, update in metadata strings DB                
                txn,db = self._dBs[typ]
                db.put(path,content,self.dbTxn)

        else:
            txn,db = self._dBs[typ]
            db.put(path,content,self.dbTxn)            
                
    def deleteFile(self, path, typ):
        """Delete an object"""
        #print "deleteFile(%s,%s)"%(path,typ)
        #Necessary for BerkeleyDB (expects strings for names, not unicode)
        path = path.encode('utf-8')        
        #If it's not session or cache data and there isn't an entry in the Type DB then we can assume it doesn't exist

        if not typ in [ResourceTypes.SESSION_DATA,ResourceTypes.RESOURCE_CACHE]:                
            txn,typeDb = self._contentType
            if not typeDb.has_key(path,txn):
                return
            
        locationCode = LocateContent(path,typ,self._contentType)
        if locationCode == FILE_RESOURCE_CLASS:
            txn,db = self._dBs[typ]
            db.delete(path,self.dbTxn)            
            
        elif locationCode == USER_DOCUMENT_RESOURCE_CLASS:
            if self.__useDbXml:
                if self.__dbProperties.get('DataStoreContainers','false') in ['1','true']:
                    #Check within underlying DbXml container
                    #NOTE: Disabled right now
                    #txn,container = GetContainer(
                    #    createDbXmlContainerPath(path.getParentPath().absolutePath()),
                    #    self.root,
                    #    self.environment,
                    #    self.dbXmlTxn)
                    pass
                else:
                    txn,container = self._userXmldB

                container.deleteDocument(self.dbXmlTxn,path,self.XmlMgr.createUpdateContext())
            else:
                #Not using DbXml, delete from content DB
                txn,db = self._dBs[typ]
                db.delete(path,self.dbTxn) 
            
        elif locationCode == REPOSITORY_DOCUMENT_RESOURCE_CLASS:
            if self.__useDbXml:         
                txn,repoDB = self._repositorydB
                repoDB.deleteDocument(self.dbXmlTxn,path,self.XmlMgr.createUpdateContext())
                #txn,typeDB = self._contentType
                #repositoryType=int(typeDb.get(path,txn=self.dbTxn))
                #if repositoryType == ResourceTypes.ResourceType.CONTAINER and self.__dbProperties.get('DataStoreContainers','false') in ['1','true'] and path != '/':
                    #A container and the driver is using berkeley dbxml containers, so delete the underlying container as well
                #    self.XmlMgr.removeContainer(self.dbXmlTxn,createDbXmlContainerPath(path))
            else:
                #Not using DbXml, delete from content DB
                txn,db = self._dBs[typ]
                db.delete(path,self.dbTxn) 
        elif locationCode == METADATA_DOCUMENT_RESOURCE_CLASS:
            if self.__useDbXml:
                txn,mdContainer = self._dBs[typ]
                mdContainer.deleteDocument(self.dbXmlTxn,path,self.XmlMgr.createUpdateContext())
            else:
                #Not using DBXml delete from metadata strings DB
                txn,db = self._dBs[typ]
                db.delete(path,self.dbTxn)

        else:
            txn,db = self._dBs[typ]
            db.delete(path,self.dbTxn)            

        #Delete it from Type DB if it's resource content (assume it's metadata has already been deleted)
        if typ == ResourceTypes.RESOURCE_CONTENT:
            txn,typeDb = self._contentType
            typeDb.delete(path,txn)
            
    def getSystemModel(self):
        return self._systemRdfModel

    def getUserModel(self):
        return self._userRdfModel

    
    def commit(self):
        if self.__useDbXml:
            self.dbXmlTxn.commit(0)        
        self.dbTxn.commit(0)
        self._closeDatabase()
        
    def _closeDatabase(self):
        #NOTE: If datastore containers are being used, they must retrieved and closed individually by
        #searching for the paths of of all containers and retrieving them by name
        #if self.__dbProperties.get('DataStoreContainers','false') in ['1','true']:
        #    print DBXML_FETCH_FTSS_CONTAINERS_XQUERY
        #    rt = self.XmlMgr.query(self.dbXmlTxn,DBXML_FETCH_FTSS_CONTAINERS_XQUERY,self.XmlMgr.createQueryContext())
        #    print "containers: "
        #    rt.reset()
        #    for value in  rt:
        #        repoContainerPath=value.asString()
        #        if repoContainerPath == '/':
        #            continue
        #        print repoContainerPath
        #        c = GetContainer(createDbXmlContainerPath(repoContainerPath),self.root,self.environment,txn=self.dbXmlTxn)
        #        c.close()
        if self.__useDbXml:
            self._userXmldB[-1].close()
            self._repositorydB[-1].close()
            
        self._contentType[-1].close()
        self._dBs[ResourceTypes.RESOURCE_METADATA][-1].close()
        self._dBs[ResourceTypes.RESOURCE_CONTENT][-1].close()
        self._dBs[ResourceTypes.RESOURCE_CACHE][-1].close()
        self._dBs[ResourceTypes.SESSION_DATA][-1].close()
        self._dBs[FTSS_CONTAIMENT_DB_NAME][-1].close()
        self.environment.close(0)
        self.xmlEnvironment.close(0)        
        self._dBs = {}
        self._systemRdfModel._driver.commit()
        self._userRdfModel._driver.commit()
        self._repositorydB = self._contentType = self._systemRdfModel._db = self._systemRdfModel = self._userRdfModel._db = self._userRdfModel = None
        
    def rollback(self):
        """Undo it"""
        if self.__useDbXml:
            self.dbXmlTxn.abort()
        self.dbTxn.abort()
        self._closeDatabase()

def Begin(**properties):
    """Begin a new transaction.  Every driver must support this interface.
    The properties keyword arguments are passed from the config file
    (or where ever) to the driver.  The Begin file is responsible
    for doing what ever is needed to validate these arguements
    """
    return BerkeleyDBDriver(properties)

NAME='BerkeleyDB'

    
REPO_DOCUMENTS = [
    ResourceTypes.ResourceType.CONTAINER,
    ResourceTypes.ResourceType.ALIAS,
    ResourceTypes.ResourceType.USER,
    ResourceTypes.ResourceType.GROUP,
    ResourceTypes.ResourceType.XPATH_DOCUMENT_DEFINITION,
    ResourceTypes.ResourceType.COMMAND,
    ResourceTypes.ResourceType.SERVER,
    ResourceTypes.ResourceType.XSLT_DOCUMENT,
    ResourceTypes.ResourceType.XSLT_DOCUMENT_DEFINITION,
    ResourceTypes.ResourceType.RDF_DOCUMENT
 ]

