# -*- coding: UTF-8 -*- # Expression # By: Frederic Peters # Emmanuel Raviart # # 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 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("""\ <%(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()