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

401 lines
16 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 bsddb.db
import dbxml
import libxml2
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.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"
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
super(DbXmlContainerHolder, self).__init__(*arguments, **keywords)
# FIXME : we assume self.getAbsolutePath() is a correct .dbxml filename
self.container = dbxml.XmlContainer(None, self.getAbsolutePath())
def close(self):
self.container.close(0)
def docSelect(self, xpathExpression, *uriPathFragments):
"""
Handles HTTP GET.
"""
command = environs.getVar("httpCommand")
if command != "GET":
raise faults.PathNotFound(environs.getVar("currentStation"), "")
result = self.getDbXmlDocumentResultFromXpath(xpathExpression)
return result.checkAccessAndWalk(uriPathFragments, command, environs.getVar("instruction"))
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 getDbXmlDocumentResultFromXpath(self, xpathExpression):
resultNode = libxml2.newNode("dbXmlDocumentResult")
namespace = resultNode.newNs(namespaces.yep.uri, namespaces.yep.name)
resultNode.setNs(namespace)
results = self.getResultFetchedFromXpath(xpathExpression)
for value in results:
document = value.asDocument()
childNode = libxml2.newNode("document")
childNode.setNs(namespace)
childNode.newProp("id", str(document.getID()))
childNode.newProp("name", document.getName())
resultNode.addChild(childNode)
result = DbXmlDocumentResult(
resultNode, previous = self, uriPathFragment = self.uriPathFragment, owner = self)
return result
def getDbXmlNodeResultFromXpath(self, xpathExpression):
results = self.getResultFetchedFromXpath(xpathExpression, dbxml.XmlQueryContextPtr.ResultValues)
resultNode = libxml2.newNode("dbXmlNodeResult")
resultNode.setNs(
resultNode.newNs(namespaces.yep.uri, namespaces.yep.name))
for value in results:
childNode = libxml2.newNode("node")
doc = libxml2.readDoc(
value.asString(),
self.getAbsolutePath(),
None,
libxml2.XML_PARSE_DTDLOAD | libxml2.XML_PARSE_NONET)
childNode.addChild(doc.getRootElement())
resultNode.addChild(childNode)
return DbXmlNodeResult(
resultNode, previous = self, uriPathFragment = self.uriPathFragment, owner = self)
def getResultCursorFromXpath(self, xpathExpression, returnType = dbxml.XmlQueryContextPtr.ResultDocuments):
""" 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 = dbxml.XmlQueryContext()
context.setReturnType(returnType)
context.setEvaluationType(dbxml.XmlQueryContext.Lazy)
self.container.open(None, dbxml.DB_RDONLY)
results = self.container.queryWithXPath(None, xpathExpression, context)
return results
def getResultFetchedFromXpath(self, xpathExpression, returnType = dbxml.XmlQueryContextPtr.ResultDocuments):
""" 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 = dbxml.XmlQueryContext()
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.reset()
return results
def nodeSelect(self, xpathExpression, *uriPathFragments):
"""
Handles HTTP GET.
"""
command = environs.getVar("httpCommand")
if command != "GET":
raise faults.PathNotFound(environs.getVar("currentStation"), "")
result = self.getDbXmlNodeResultFromXpath(xpathExpression)
return result.checkAccessAndWalk(uriPathFragments, command, environs.getVar("instruction"))
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]
if itemName.isdigit():
documentId = int(itemName)
try:
self.container.open(None, dbxml.DB_RDONLY)
document = self.container.getDocument(None, documentId)
self.container.close(0)
except RuntimeError:
pass # => Call inherited method below.
else:
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 = dbxml.XmlDocument()
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 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.PathForbidden, faults.PathNotFound, faults.PathUnauthorized), 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.PathForbidden, faults.PathNotFound, faults.PathUnauthorized), fault:
logs.exception("Ignoring fault %s in %s.doHttpPost()" % (fault, rootElement))
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()
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 getLocalId(self):
return str(self.dbxmlDocument.getID())
def getBind(self, context, modelContext):
return None
def getChildInstanceDataXpath(self, context, modelContext):
return ""
def getDocument(self):
return self
def getModelId(self):
return None
def getPublicName(self):
publicName = self.node.prop("publicName")
if publicName is None:
return ""
return publicName
def outputHttpSource(self):
data = self.dbxmlDocument.getContentAsString()
dataHolder = self.getDataHolder()
environs.getVar("httpRequestHandler").outputData(
self, data, contentLocation = self.getUriAbsolutePath(),
mimeType = dataholders.mimeTypes[".xml"],
modificationTime = dataHolder.getModificationTime())
def save(self):
self.dbxmlDocument.setContent(self.serialize())
self.dbxmlDocument.setName(self.getPublicName())
container = self.getParent().getContainer()
container.open(None, 0)
if self.getLocalId() == "0":
container.putDocument(None, self.dbxmlDocument)
localId = self.getLocalId()
self.uriPathFragment = localId
if self.isTemporary:
del self.isTemporary
else:
container.updateDocument(None, self.dbxmlDocument)
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.
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)
class DbXmlDocumentResult(things.Thing):
pass
class DbXmlNodeResult(things.Thing):
pass
for clas in [
DbXmlDocumentResult,
#DbXmlNodeResult,
]:
elements.registerElement(
namespaces.yep.uri,
"%s%s" % (clas.__name__[0].lower(), clas.__name__[1:]),
clas,
"http://www.entrouvert.org/expression/schemas/%s.xsd" % clas.__name__,
"http://www.entrouvert.org/expression/descriptions/%s.xml" % clas.__name__,
)
DbXmlContainerHolder.register()