diff --git a/src/modules/dbxmlcontainers.py b/src/modules/dbxmlcontainers.py index 0d0255d..e933a49 100644 --- a/src/modules/dbxmlcontainers.py +++ b/src/modules/dbxmlcontainers.py @@ -27,7 +27,6 @@ import os -import bsddb.db import dbxml import libxml2 import urllib @@ -53,63 +52,42 @@ class DbXmlContainerHolder(dataholders.DataHolder): isTemporary = False isUriDirectory = True mimeType = "application/x-dbxml" + queryContext = None + updateContext = None - def __del__(self): - if self.container is not None: - if self.container.isOpen(): - logs.info("DbXml Container was not closed! Closing it.") - self.container.close(0) - - def __init__(self, *arguments, **keywords): - if self._dbxmlContainerHolderInited: - return - self._dbxmlContainerHolderInited = True - + def __init__(self, container, *arguments, **keywords): super(DbXmlContainerHolder, self).__init__(*arguments, **keywords) - # FIXME : we assume self.getAbsolutePath() is a correct .dbxml filename - self.container = dbxml.XmlContainer(None, self.getAbsolutePath()) - - self.queryContext = dbxml.XmlQueryContext() + self.container = container + self.queryContext = container.getManager().createQueryContext() for ns in namespaces.nsList: self.queryContext.setNamespace(ns.name, ns.uri) + self.updateContext = container.getManager().createUpdateContext() - def addDocument(self, documentSource, name=""): + def addDocument(self, contents, name=""): """ appends a document to the container - returns the new XmlDocument + returns the new XmlDocument name """ - doc = dbxml.XmlDocument() - doc.setContent(documentSource) - doc.setName(name) - self.container.open(None, 0) try: - self.container.putDocument(None, doc) + name = self.container.putDocument(name, contents, self.updateContext, name and DBXML_GEN_NAME or 0) except RuntimeError, e: logs.error("Could not put document in container: %s" % e) - logs.debug(doc.getContent()) - self.container.close(0) + logs.debug(contents,) raise - self.container.close(0) - return doc + return name - def close(self): - self.container.close(0) - - def deleteDocument(self, documentId): + def deleteDocument(self, name): """ removes a document from the container Handles HTTP GET. """ - self.container.open(None, 0) - self.container.deleteDocument(None, documentId) - self.container.close(0) + self.container.deleteDocument(name, self.updateContext) return self.doHttpGet() def docSelect(self, xpathExpression, *uriPathFragments): """ Handles HTTP GET. """ - command = environs.getVar("httpCommand") if command != "GET": raise faults.PathNotFound("") @@ -129,11 +107,11 @@ class DbXmlContainerHolder(dataholders.DataHolder): return super(DbXmlContainerHolder, self).doHttpGet() def generateXml(self, layout): - result = self.getDbXmlDocumentResultFromXpath("*") + result = self.getDbXmlDocumentResultFromXpath("/*") return result.generateXml(layout) def generateXmlDocument(self): - result = self.getDbXmlDocumentResultFromXpath("*") + result = self.getDbXmlDocumentResultFromXpath("/*") return result.generateXmlDocument() def getContainer(self): @@ -152,19 +130,17 @@ class DbXmlContainerHolder(dataholders.DataHolder): results = self.getResultFetchedFromXpath(xpathExpression) return results.size() - def getDbXmlDocument(self, id): - """ returns container document number + def getDbXmlDocument(self, name): + """ returns container document - the return type is dbxml.DbXmlDocument + the return type is dbxml.XmlDocument or None if the document doesn't exist """ document = None - self.container.open(None, dbxml.DB_RDONLY) try: - document = self.container.getDocument(None, int(id)) + document = self.container.getDocument(name, 0) except RuntimeError: pass - self.container.close(0) return document def getDbXmlDocumentResultFromXpath(self, xpathExpression): @@ -172,11 +148,12 @@ class DbXmlContainerHolder(dataholders.DataHolder): resultNode = resultDocument.node.newTextChild(None, "dbXmlDocumentResult", None) namespace = resultNode.newNs(namespaces.yep.uri, namespaces.yep.name) resultNode.setNs(namespace) - results = self.getResultFetchedFromXpath(xpathExpression) + results = self.getResultCursorFromXpath(xpathExpression) + results.reset() for value in results: document = value.asDocument() childNode = resultNode.newChild(None, "item", None) - childNode.newProp("id", str(document.getID())) + childNode.newProp("id", self.getActionUri(document.getName())) childNode.newProp("name", document.getName()) return resultDocument @@ -196,7 +173,17 @@ class DbXmlContainerHolder(dataholders.DataHolder): childNode.addChild(doc.getRootElement()) return resultDocument - def getIds(self, xpathExpression = "*"): + def getFileSystemPath(self): + return os.path.normpath( + os.path.join( + self.container.getManager().getHome(), self.container.getName() + ) + ) + + def getConfigDoc(self): + return None + + def getIds(self, xpathExpression = "/*"): """ returns an id list of all documents id is not the numeric dbxml:id, but the Uri of the document @@ -205,9 +192,9 @@ class DbXmlContainerHolder(dataholders.DataHolder): """ uriBase = self.getUriAbsolutePath() results = self.getResultFetchedFromXpath(xpathExpression) - return ["%s/%s" % (uriBase, value.asDocument().getID()) for value in results] + return ["%s/%s" % (uriBase, value.asDocument().getName()) for value in results] - def getNames(self, xpathExpression = "*"): + def getNames(self, xpathExpression = "/*"): """ returns a hash {id: name} of all documents id is not the numeric dbxml:id, but the Uri of the document @@ -219,25 +206,23 @@ class DbXmlContainerHolder(dataholders.DataHolder): results = self.getResultFetchedFromXpath(xpathExpression) for value in results: document = value.asDocument() - uri = "%s/%s" % (uriBase, document.getID()) name = document.getName() + uri = "%s/%s" % (uriBase, name) names[uri] = name return names def getPresence(self, xpathExpression): """ Returns whether at least one document matches xpathExpression. """ - presence = False - for value in self.getResultCursorFromXpath(xpathExpression): - presence = True - break - self.close() - return presence + results = self.getResultCursorFromXpath(xpathExpression) + for value in result: + return True + return False - def getIdsAndNames(self, xpathExpression = "*"): + def getIdsAndNames(self, xpathExpression = "/*"): """ returns a tuple (id, name) list of all documents - id is not the numeric dbxml:id, but the Uri of the document + id is the Uri of the document argument xpathExpression can be specified to restrict the list """ @@ -246,52 +231,40 @@ class DbXmlContainerHolder(dataholders.DataHolder): results = self.getResultFetchedFromXpath(xpathExpression) for value in results: document = value.asDocument() - uri = "%s/%s" % (uriBase, document.getID()) + uri = "%s/%s" % (uriBase, document.getName()) name = document.getName() documentList.append( (uri, name) ) return documentList - def getIdsOnly(self, xpathExpression = "*"): - """ returns an id list of all documents - - id is the numeric dbxml:id + def getIdsOnly(self, xpathExpression = "/*"): + """ returns a name list of all documents argument xpathExpression can be specified to restrict the list """ results = self.getResultFetchedFromXpath(xpathExpression) - return [value.asDocument().getID() for value in results] + return [value.asDocument().getName() for value in results] - def getResultCursorFromXpath(self, xpathExpression, returnType = dbxml.XmlQueryContextPtr.ResultDocuments): + def getResultCursorFromXpath(self, xpathExpression, returnType = dbxml.XmlQueryContext.LiveValues): """ Returns a raw dbxml.XmlResults instance, leaving the data in the container. - - Result contents are not loaded in memory, but waiting to be fetched iteratively. - The calling program MUST call close() after this method, since we can not close the container here. - - Set returnType argument to dbxml.XmlQueryContextPtr.ResultValues to get nodes rather than documents """ context = self.queryContext context.setReturnType(returnType) context.setEvaluationType(dbxml.XmlQueryContext.Lazy) - self.container.open(None, dbxml.DB_RDONLY) - results = self.container.queryWithXPath(None, xpathExpression, context) + results = self.container.getManager().query("collection('%s')%s" % (self.container.getName(), xpathExpression), context) return results - def getResultFetchedFromXpath(self, xpathExpression, returnType = dbxml.XmlQueryContextPtr.ResultDocuments): + def getResultFetchedFromXpath(self, xpathExpression, returnType = dbxml.XmlQueryContext.LiveValues): """ Returns a raw dbxml.XmlResults instance, with all data fetched from the container. All data is loaded in memory. Consider using getResultCursorFromXpath instead of this method if you expect a large result. - - Set returnType argument to dbxml.XmlQueryContextPtr.ResultValues to get nodes rather than documents """ context = self.queryContext context.setReturnType(returnType) context.setEvaluationType(dbxml.XmlQueryContext.Eager) - self.container.open(None, dbxml.DB_RDONLY) - results = self.container.queryWithXPath(None, xpathExpression, context) - self.container.close(0) + results = self.container.getManager().query("collection('%s')%s" % (self.container.getName(), xpathExpression), context) results.reset() return results @@ -302,31 +275,26 @@ class DbXmlContainerHolder(dataholders.DataHolder): return [value.asString() for value in results] def list(self): - return self.docSelect("*") + return self.docSelect("/*") def nodeSelect(self, xpathExpression, *uriPathFragments): """ Handles HTTP GET. """ - command = environs.getVar("httpCommand") if command != "GET": raise faults.PathNotFound("") - result = self.getDbXmlNodeResultFromXpath(xpathExpression) return result.checkAccessAndWalk(uriPathFragments, command, environs.getVar("instruction")) def parsableXpath(self, xpath): """ returns True if the given xpath expression is parsable, False otherwise """ - parsable = True - self.container.open(None, dbxml.DB_RDONLY) try: - self.container.parseXPathExpression(None, xpath, self.queryContext) + self.container.getManager().prepare("collection('%s')%s" % (self.container.getName(), xpath), self.queryContext) except RuntimeError: - parsable = False - self.container.close(0) - return parsable + return False + return True def walkToAction(self, uriPathFragments, command = None, instruction = None): actionName = uriPathFragments[0] @@ -380,12 +348,11 @@ class DbXmlContainerHolder(dataholders.DataHolder): def walkToItem(self, uriPathFragments, command = None, instruction = None): itemName = uriPathFragments[0] - if itemName.isdigit(): - document = self.getDbXmlDocument(itemName) - if document: - documentStation = DbXmlDocumentStation( - document, previous = self, uriPathFragment = itemName) - return documentStation, uriPathFragments[1:] + document = self.getDbXmlDocument(itemName) + if document: + documentStation = DbXmlDocumentStation( + document, previous = self, uriPathFragment = itemName) + return documentStation, uriPathFragments[1:] return super(DbXmlContainerHolder, self).walkToItem(uriPathFragments, command, instruction) @@ -393,7 +360,7 @@ class DbXmlDocumentConstructorStation(stations.HolderConstructorStation): def walk(self, uriPathFragments, command = None, instruction = None): namespaceUri = self.elementFeature.namespaceUri elementName = self.elementFeature.elementName - document = dbxml.XmlDocument() + document = self.getParent().container.getManager().createDocument() document.setContent("""\ <%(elementName)s xmlns="%(namespaceUri)s"/> @@ -432,10 +399,7 @@ class DbXmlDocumentStation(stations.AbstractStation, documents.AbstractDocument) def delete(self): """ Deletes the document from its container. """ - container = self.getParent().getContainer() - container.open(None, 0) - container.deleteDocument(None, self.dbxmlDocument) - container.close(0) + self.getParent().getContainer().deleteDocument(self.dbxmlDocument.getName(), self.getParent().updateContext) def doHttpGet(self): if not self.getInRawMode(): @@ -464,9 +428,7 @@ class DbXmlDocumentStation(stations.AbstractStation, documents.AbstractDocument) return super(DbXmlDocumentStation, self).doHttpPost() def generatePlainText(self): - # FIXME: The following line assumes that getContentAsString returns an - # unicode string. If this is not the case, append ".decode("UTF-8")". - return self.dbxmlDocument.getContentAsString() + return self.dbxmlDocument.getContent() def generateXml(self, layout): rootElement = self.getRootElement() @@ -480,6 +442,9 @@ class DbXmlDocumentStation(stations.AbstractStation, documents.AbstractDocument) return rootElement.generateXmlDocument() return super(DbXmlDocumentStation, self).generateXmlDocument() + def getAbsolutePath(self): + return os.path.normpath(os.path.join(self.getParent().getAbsolutePath(), self.dbxmlDocument.getName())) + def getBind(self, context, modelContext): return None @@ -490,7 +455,7 @@ class DbXmlDocumentStation(stations.AbstractStation, documents.AbstractDocument) return self def getLocalId(self): - return str(self.dbxmlDocument.getID()) + return self.dbxmlDocument.getName() def getModelId(self): return None @@ -504,7 +469,7 @@ class DbXmlDocumentStation(stations.AbstractStation, documents.AbstractDocument) return publicName def outputHttpSource(self): - data = self.dbxmlDocument.getContentAsString() + data = self.dbxmlDocument.getContent() dataHolder = self.getDataHolder() environs.getVar("httpRequestHandler").outputData( data, mimeType = dataholders.mimeTypes[".xml"], @@ -512,40 +477,40 @@ class DbXmlDocumentStation(stations.AbstractStation, documents.AbstractDocument) def save(self): self.dbxmlDocument.setContent(self.serialize()) - name = self.getPublicName() - if not name: - element = self.getRootElement() - try: - name = element.getLabel() - except AttributeError: - name = "" - self.dbxmlDocument.setName(name) container = self.getParent().getContainer() - container.open(None, 0) - if self.getLocalId() == "0": + if self.isTemporary: + name = self.getPublicName() + if name is None: + element = self.getRootElement() + try: + name = element.getLabel() + except AttributeError: + pass + if name is not None: + self.dbxmlDocument.setName(name) try: - container.putDocument(None, self.dbxmlDocument) + container.putDocument( + self.dbxmlDocument, + self.getParent().updateContext, + name is None and dbxml.DBXML_GEN_NAME or 0 + ) except RuntimeError, e: logs.error("Could not put document in container: %s" % e) logs.debug(self.dbxmlDocument.getContent()) - container.close(0) raise self.uriPathFragment = self.getLocalId() - if self.isTemporary: - del self.isTemporary + del self.isTemporary else: try: - container.updateDocument(None, self.dbxmlDocument) + container.updateDocument(self.dbxmlDocument, self.getParent().updateContext) except RuntimeError, e: logs.error("Could not update document in container: %s" % e) logs.debug(self.dbxmlDocument.getContent()) - container.close(0) raise - container.close(0) def setPermanentLocalId(self): # We do not need to set a permanentLocalId here. - # The document will have it's own id once inside the dbxml. + # The document will have it's own id once inside the container. pass def walk(self, uriPathFragments, command = None, instruction = None): diff --git a/src/modules/dbxmldatabases.py b/src/modules/dbxmldatabases.py new file mode 100644 index 0000000..918d5bf --- /dev/null +++ b/src/modules/dbxmldatabases.py @@ -0,0 +1,119 @@ +# -*- coding: UTF-8 -*- + + +# Expression +# By: Frederic Peters +# Emmanuel Raviart +# Sébastien Ducoulombier +# +# Copyright (C) 2004 Entr'ouvert, Frederic Peters & Emmanuel Raviart +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +"""Berkeley DB XML Database Manager Module""" + + +import sys + +import bsddb3.db as db +import dbxml + +import expression.core.environs as environs +import expression.core.dataholders as dataholders +import expression.core.html as html +import expression.core.logs as logs + +import expression.modules.dbxmlcontainers as dbxmlcontainers + + +class DbXmlDatabase(dataholders.DataHolder): + """ A directory-like station that maps to the database environment manager. """ + _environment = None + _manager = None + _containers = None + isUriDirectory = True + + def generateXml(self, layout): + """ Just generates a page that confirms this station is the database station. """ + layout.append( + html.html( + html.body( + html.p( + "Berkeley DB XML database at %s" % DbXmlDatabase._manager.getHome() + ) + ) + ) + ) + return True + + def walkToItem(self, uriPathFragments, command = None, instruction = None): + """ Returns the next station if the uriPathFragment is a container name. """ + name = uriPathFragments[0] + if name in DbXmlDatabase._containers: + item = dbxmlcontainers.DbXmlContainerHolder( + DbXmlDatabase._containers[name], + pathFragment = name, + previous = self, + parent = self, + uriPathFragment = name, + ) + return item, uriPathFragments[1:] + return super(DbXmlDatabase, self).walkToItem(uriPathFragments, command, instruction) + + +def __init__(): + """ Opens a Berkeley DB environment and an XmlManager around it. + Then opens all configured containers. + """ + DbXmlDatabase._environment = db.DBEnv() + DbXmlDatabase._environment.set_shm_key(35) + dbHome = environs.getVar("configuration").getConfigAbsolutePath( + """yep:module[@name="%s"]/yep:environment/@dbHome""" % __init__.__module__, + "/var/lib/expression/db" + ) + DbXmlDatabase._environment.open( + dbHome, 0 + |db.DB_INIT_LOCK + |db.DB_INIT_LOG + |db.DB_INIT_MPOOL + |db.DB_INIT_REP + |db.DB_INIT_TXN + |db.DB_CREATE + |db.DB_SYSTEM_MEM + |db.DB_THREAD + , + 0 + ) + DbXmlDatabase._manager = dbxml.XmlManager(DbXmlDatabase._environment, dbxml.DBXML_ADOPT_DBENV) + DbXmlDatabase._containers = {} + for name in environs.getVar("configuration").getConfigList("""yep:module[@name="%s"]/yep:container/@name""" % __init__.__module__): + try: + DbXmlDatabase._containers[name] = DbXmlDatabase._manager.openContainer( + name, 0 + |db.DB_CREATE + |dbxml.DBXML_CHKSUM + ) + except RuntimeError: + logs.error("Could not open container %s" % name) + __del__() + raise + +def __del__(): + """ Closes containers and the database environment. + """ + del DbXmlDatabase._containers + DbXmlDatabase._environment.close() + diff --git a/xml/misc/descriptions/DbXmlDocumentResult.xml b/xml/misc/descriptions/DbXmlDocumentResult.xml index 559d5d0..61f725d 100644 --- a/xml/misc/descriptions/DbXmlDocumentResult.xml +++ b/xml/misc/descriptions/DbXmlDocumentResult.xml @@ -13,7 +13,7 @@ Documents - . +