This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
expression/src/modules/dbxmlcontainers.py

553 lines
22 KiB
Python

# -*- coding: UTF-8 -*-
# Expression
# By: Frederic Peters <fpeters@entrouvert.com>
# Emmanuel Raviart <eraviart@entrouvert.com>
#
# 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.
"""DbXmlContainer Module"""
import os
import dbxml
import libxml2
import urllib
import expression.core.dataholders as dataholders
import expression.core.documents as documents
import expression.core.elements as elements
import expression.core.environs as environs
import expression.core.faults as faults
import expression.core.html as html
import expression.core.logs as logs
import expression.core.modules as modules
import expression.core.namespaces as namespaces
import expression.core.stations as stations
import expression.core.strings as strings
import expression.core.things as things
import expression.core.xpaths as xpaths
class DbXmlContainerHolder(dataholders.DataHolder):
_dbxmlContainerHolderInited = False
container = None
defaultFileNameExtension = ".dbxml"
isTemporary = False
isUriDirectory = True
mimeType = "application/x-dbxml"
queryContext = None
updateContext = None
def __init__(self, container, *arguments, **keywords):
super(DbXmlContainerHolder, self).__init__(*arguments, **keywords)
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, contents, name=""):
""" appends a document to the container
returns the new XmlDocument name
"""
try:
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(contents,)
raise
return name
def deleteDocument(self, name):
""" removes a document from the container
Handles HTTP GET.
"""
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("")
result = self.getDbXmlDocumentResultFromXpath(xpathExpression)
return result.checkAccessAndWalk(uriPathFragments, command, environs.getVar("instruction"))
def doHttpGet(self):
if not self.getInRawMode():
modeNames = self.getConfigString("yep:defaultMode", default = "list")
for modeName in modeNames.split():
uriPathFragments = [urllib.unquote(name) for name in modeName.split("/") if name]
try:
return self.walk(uriPathFragments, environs.getVar("httpCommand"))
except (faults.PathForbidden, faults.PathNotFound, faults.PathUnauthorized):
pass
return super(DbXmlContainerHolder, self).doHttpGet()
def generateXml(self, layout):
result = self.getDbXmlDocumentResultFromXpath("/*")
return result.generateXml(layout)
def generateXmlDocument(self):
result = self.getDbXmlDocumentResultFromXpath("/*")
return result.generateXmlDocument()
def getContainer(self):
return self.container
def getCount(self):
""" returns the number of documents in the container
"""
# http://www.sleepycat.com/blogs/bdb-xml/archives/2003/10/index.html
results = self.getResultCursorFromXpath(".")
return results.size()
def getCount(self, xpathExpression):
""" returns the number of documents matching xpathExpression
"""
results = self.getResultFetchedFromXpath(xpathExpression)
return results.size()
def getDbXmlDocument(self, name):
""" returns container document <name>
the return type is dbxml.XmlDocument
or None if the document doesn't exist
"""
document = None
try:
document = self.container.getDocument(name, 0)
except RuntimeError:
pass
return document
def getDbXmlDocumentResultFromXpath(self, xpathExpression):
resultDocument = documents.newTemporaryDocument(previous = self)
resultNode = resultDocument.node.newTextChild(None, "dbXmlDocumentResult", None)
namespace = resultNode.newNs(namespaces.yep.uri, namespaces.yep.name)
resultNode.setNs(namespace)
results = self.getResultCursorFromXpath(xpathExpression)
results.reset()
for value in results:
document = value.asDocument()
childNode = resultNode.newChild(None, "item", None)
childNode.newProp("id", self.getActionUri(document.getName()))
childNode.newProp("name", document.getName())
return resultDocument
def getDbXmlNodeResultFromXpath(self, xpathExpression):
resultDocument = documents.newTemporaryDocument(previous = self)
resultNode = resultDocument.node.newTextChild(None, "dbXmlNodeResult", None)
namespace = resultNode.newNs(namespaces.yep.uri, namespaces.yep.name)
resultNode.setNs(namespace)
results = self.getResultFetchedFromXpath(xpathExpression)
for value in results:
childNode = resultNode.newChild(None, "node", None)
doc = libxml2.readDoc(
value.asString(),
self.getAbsolutePath(),
None,
libxml2.XML_PARSE_DTDLOAD | libxml2.XML_PARSE_NONET)
childNode.addChild(doc.getRootElement())
return resultDocument
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
argument xpathExpression can be specified to restrict the list
"""
uriBase = self.getUriAbsolutePath()
results = self.getResultFetchedFromXpath(xpathExpression)
return ["%s/%s" % (uriBase, value.asDocument().getName()) for value in results]
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
argument xpathExpression can be specified to restrict the list
"""
uriBase = self.getUriAbsolutePath()
names = {}
results = self.getResultFetchedFromXpath(xpathExpression)
labelValue = dbxml.XmlValue("")
for value in results:
document = value.asDocument()
name = document.getName()
uri = "%s/%s" % (uriBase, name)
if document.getMetaData("http://www.sleepycat.com/2002/dbxml", "label", labelValue):
name = labelValue.asString()
names[uri] = name
return names
def getPresence(self, xpathExpression):
""" Returns whether at least one document matches xpathExpression.
"""
results = self.getResultCursorFromXpath(xpathExpression)
for value in results:
return True
return False
def getIdsAndNames(self, xpathExpression = "/*"):
""" returns a tuple (id, name) list of all documents
id is the Uri of the document
name is the dbxml:label of the document (if so defined)
argument xpathExpression can be specified to restrict the list
"""
uriBase = self.getUriAbsolutePath()
documentList = []
results = self.getResultFetchedFromXpath(xpathExpression)
for value in results:
document = value.asDocument()
uri = "%s/%s" % (uriBase, document.getName())
labelValue = dbxml.XmlValue("")
if document.getMetaData("http://www.sleepycat.com/2002/dbxml", "label", labelValue):
label = labelValue.asString()
else:
label = document.getName()
documentList.append(
(uri, label)
)
return documentList
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().getName() for value in results]
def getResultCursorFromXpath(self, xpathExpression, returnType = dbxml.XmlQueryContext.LiveValues):
""" Returns a raw dbxml.XmlResults instance, leaving the data in the container.
"""
context = self.queryContext
context.setReturnType(returnType)
context.setEvaluationType(dbxml.XmlQueryContext.Lazy)
results = self.container.getManager().query("collection('%s')%s" % (self.container.getName(), xpathExpression), context)
return results
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.
"""
context = self.queryContext
context.setReturnType(returnType)
context.setEvaluationType(dbxml.XmlQueryContext.Eager)
results = self.container.getManager().query("collection('%s')%s" % (self.container.getName(), xpathExpression), context)
results.reset()
return results
def getValues(self, xpathExpression):
""" returns the list of values extracted with the xpathExpression
"""
results = self.getResultFetchedFromXpath(xpathExpression)
return [value.asString() for value in results]
def list(self):
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
"""
try:
self.container.getManager().prepare("collection('%s')%s" % (self.container.getName(), xpath), self.queryContext)
except RuntimeError:
return False
return True
def walkToAction(self, uriPathFragments, command = None, instruction = None):
actionName = uriPathFragments[0]
if actionName.startswith("new-"):
namespaceName, elementName = actionName[len("new-"):].split("-", 1)
namespaceUri = namespaces.getUri(namespaceName)
elementFeature = None
# Try to get element features from config.
elementNodes, elementOwner = self.getConfigElementNodesAndOwner(
namespaceUri, elementName, None, ignoreGeneralValue = True)
if elementNodes:
elementNode = elementNodes[0]
elementClassNodes = xpaths.evaluateXpath(
"yep:pythonClass", contextNode = elementNode)
if elementClassNodes:
elementClass = self.convertNodesToPythonClass(
elementClassNodes, elementOwner, "yep:pythonClass", default = None)
else:
elementClass = None
schemaLocationNodes = xpaths.evaluateXpath("yep:schema", contextNode = elementNode)
if schemaLocationNodes:
schemaAbsolutePath = self.convertNodesToAbsolutePath(
schemaLocationNodes, elementOwner, "yep:schema", default = None)
else:
schemaAbsolutePath = None
descriptionLocationNodes = xpaths.evaluateXpath(
"yep:description", contextNode = elementNode)
if descriptionLocationNodes:
descriptionAbsolutePath = self.convertNodesToAbsolutePath(
descriptionLocationNodes, elementOwner, "yep:description", default = None)
else:
descriptionAbsolutePath = None
elementFeature = elements.ElementFeature(
namespaceUri, elementName, elementClass, schemaAbsolutePath,
descriptionAbsolutePath)
# Otherwise, try to get element feature from those registered by Python modules.
if elementFeature is None:
try:
elementFeature = modules.getElementFeature(namespaceUri, elementName)
except (KeyError, ValueError):
pass
# If no element feature found, the action is not possible.
if elementFeature is None:
return self, uriPathFragments
# Return the station which will create the element.
constructorStation = DbXmlDocumentConstructorStation(
actionName, elementFeature, previous = self)
return constructorStation, uriPathFragments[1:]
return super(DbXmlContainerHolder, self).walkToAction(
uriPathFragments, command, instruction)
def walkToItem(self, uriPathFragments, command = None, instruction = None):
itemName = uriPathFragments[0]
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)
class DbXmlDocumentConstructorStation(stations.HolderConstructorStation):
def walk(self, uriPathFragments, command = None, instruction = None):
namespaceUri = self.elementFeature.namespaceUri
elementName = self.elementFeature.elementName
document = self.getParent().container.getManager().createDocument()
document.setContent("""\
<?xml version="1.0"?>
<%(elementName)s xmlns="%(namespaceUri)s"/>
""" % {
"elementName": elementName,
"namespaceUri": namespaceUri,
})
newDocument = DbXmlDocumentStation(
document, previous = self.previous, uriPathFragment = self.actionName)
newDocument.isTemporary = True
return newDocument.checkAccessAndWalk(uriPathFragments, command, instruction)
class DbXmlDocumentStation(stations.AbstractStation, documents.AbstractDocument):
_description = None
_schema = None # Cache for XML schema
dbxmlDocument = None
isTemporary = False
def __init__(self, document = None, previous = None, parent = None, uriPathFragment = None):
stations.AbstractStation.__init__(
self, previous = previous, parent = parent, uriPathFragment = uriPathFragment)
assert document is not None
self.dbxmlDocument = document
try:
node = libxml2.readDoc(
self.dbxmlDocument.getContentAsString(), self.getAbsolutePath(), None,
libxml2.XML_PARSE_DTDLOAD | libxml2.XML_PARSE_NONET)
self.publicName = self.localId
except (libxml2.parserError, libxml2.treeError):
logs.info('Error while parsing XML file at "%s".' % self.getAbsolutePath())
raise
node.xincludeProcess()
documents.AbstractDocument.__init__(self, node)
def delete(self):
""" Deletes the document from its container.
"""
self.getParent().getContainer().deleteDocument(self.dbxmlDocument.getName(), self.getParent().updateContext)
def doHttpGet(self):
if not self.getInRawMode():
rootElement = self.getRootElement()
if rootElement is not None:
try:
return rootElement.checkAccessAndWalk(
[], environs.getVar("httpCommand"), environs.getVar("instruction"))
except faults.PathUnauthorized:
raise
except (faults.PathForbidden, faults.PathNotFound), fault:
logs.exception("Ignoring fault %s in %s.doHttpGet()" % (fault, rootElement))
return super(DbXmlDocumentStation, self).doHttpGet()
def doHttpPost(self):
if not self.getInRawMode():
rootElement = self.getRootElement()
if rootElement is not None:
try:
return rootElement.checkAccessAndWalk(
[], environs.getVar("httpCommand"), environs.getVar("instruction"))
except faults.PathUnauthorized:
raise
except (faults.PathForbidden, faults.PathNotFound), fault:
logs.exception("Ignoring fault %s in %s.doHttpPost()" % (fault, rootElement))
return super(DbXmlDocumentStation, self).doHttpPost()
def generatePlainText(self):
return self.dbxmlDocument.getContent()
def generateXml(self, layout):
rootElement = self.getRootElement()
if rootElement is not None:
return rootElement.generateXml(layout)
return super(DbXmlDocumentStation, self).generateXml(layout)
def generateXmlDocument(self):
rootElement = self.getRootElement()
if rootElement is not None:
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
def getChildInstanceDataXpath(self, context, modelContext):
return ""
def getDocument(self):
return self
def getLocalId(self):
return self.dbxmlDocument.getName()
def getModelId(self):
return None
def getPublicName(self):
publicName = self.node.prop("publicName")
if publicName is None:
return None
if self.isRootElder:
publicName = os.path.join(self.getDirectoryAbsolutePath(), publicName)
return publicName
def outputHttpSource(self):
data = self.dbxmlDocument.getContent()
dataHolder = self.getDataHolder()
environs.getVar("httpRequestHandler").outputData(
data, mimeType = dataholders.mimeTypes[".xml"],
modificationTime = dataHolder.getModificationTime())
def save(self):
self.dbxmlDocument.setContent(self.serialize())
container = self.getParent().getContainer()
element = self.getRootElement()
try:
label = element.getLabel()
except AttributeError:
pass
else:
self.dbxmlDocument.setMetaData("http://www.sleepycat.com/2002/dbxml", "label", dbxml.XmlValue(label))
if self.isTemporary:
name = self.getPublicName()
if name is not None:
name = strings.simplify(name)
self.dbxmlDocument.setName(name)
try:
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())
raise
self.uriPathFragment = self.getLocalId()
del self.isTemporary
else:
try:
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())
raise
def setPermanentLocalId(self):
# We do not need to set a permanentLocalId here.
# The document will have it's own id once inside the container.
pass
def walk(self, uriPathFragments, command = None, instruction = None):
try:
return super(DbXmlDocumentStation, self).walk(uriPathFragments, command, instruction)
except (faults.PathForbidden, faults.PathNotFound, faults.PathUnauthorized), fault:
rootElement = self.getRootElement()
if rootElement is None:
raise
return rootElement.checkAccessAndWalk(uriPathFragments, command, instruction)
localId = property(getLocalId)
modelId = property(getModelId)
for type in [
'DbXmlDocumentResult',
'DbXmlNodeResult',
]:
elements.registerElement(
namespaces.yep.uri,
"%s%s" % (type[0].lower(), type[1:]),
things.Thing,
"http://www.entrouvert.org/expression/schemas/%s.xsd" % type,
"http://www.entrouvert.org/expression/descriptions/%s.xml" % type,
)
DbXmlContainerHolder.register()