401 lines
16 KiB
Python
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()
|