########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Server/Client/Core/ResourceMetaDataClient.py,v 1.32 2005/03/28 11:27:38 mbrown Exp $
"""
ResourceMetaData repository resource client class.

Copyright 2005 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""

from Ft.Lib import Time as FtTime
from Ft.Xml import XUpdate, InputSource
from Ft.Server.Common import ResourceTypes, Schema
from Ft.Server.Common import AclConstants, DocumentReference
from Ft.Server.Client import FtServerClientException
from Ft.Server.FtRpc import Commands

import ModelClient


class ResourceMetaDataClient:
    """
    Every repository resource inherits from this class in order to
    store and provide access to information about its existence.
    """
    def __init__(self, path, connection, resourceType, root):
        self._path = path
        self._connection = connection
        self.resourceType = resourceType
        self._root = root

        #Cache some things
        self._basePath = None
        self._displayPath = None
        self._absolutePath = None
        self._absolutePathAsUri = None
        self._name = None
        self._creationDate = None
        return

    ###############################################
    #General interfaces for getting information about the object
    ###############################################
    def getPath(self):
        """
        Get the path of this resource
        """
        return self._path

    def getBasePath(self):
        """
        Get the base path of this object.
        The only difference between this and getPath is that for all
        non-container objects it will return the parent's path.
        For a container object it will return its path.
        """
        if self._basePath is None:
            self._basePath = Commands.RemoteMethodCommand('getBasePath', self._path, ()
                                                          ).send(self._connection).results
        return self._basePath

    def getDisplayPath(self):
        """
        Get the path relative to the document root
        """
        if self._displayPath is None:
            self._displayPath = Commands.RemoteMethodCommand('getDisplayPath',
                                                             self._path,
                                                             ()
                                                             ).send(self._connection).results
        return self._displayPath

    def getAbsolutePath(self):
        """
        Get the full path into the system
        """
        if self._absolutePath is None:
            self._absolutePath =  Commands.RemoteMethodCommand('getAbsolutePath',
                                                               self._path,
                                                               ()
                                                               ).send(self._connection).results
        return self._absolutePath

    def getAbsolutePathAsUri(self):
        """
        Get the full path into the system, as an absolute URI string.
        """
        if self._absolutePathAsUri is None:
            self._absolutePathAsUri =  Commands.RemoteMethodCommand('getAbsolutePathAsUri',
                                                               self._path,
                                                               ()
                                                               ).send(self._connection).results
        return self._absolutePathAsUri

    def getName(self):
        """
        Get the name of the resource.
        """
        if self._name is None:
            self._name =  Commands.RemoteMethodCommand('getName',
                                                       self._path,
                                                       ()
                                                       ).send(self._connection).results
        return self._name

    def getCreationDate(self):
        """
        Get the date that this resource was created.
        The date will be an ISO 8601 string like u'2002-08-16T22:49:56Z'.
        """
        if self._creationDate is None:
            self._creationDate = Commands.RemoteMethodCommand('getCreationDate',
                                                              self._path,
                                                              ()
                                                              ).send(self._connection).results
        return self._creationDate

    def getLastModifiedDate(self):
        """
        Get the date that this resource was last modified.
        The date will be an ISO 8601 string like u'2002-08-16T22:49:56Z'.
        """
        return Commands.RemoteMethodCommand('getLastModifiedDate',
                                            self._path,
                                            ()
                                            ).send(self._connection).results

    def getSize(self):
        """
        Get the size of the content
        """
        return Commands.RemoteMethodCommand('getSize',
                                            self._path,
                                            ()
                                            ).send(self._connection).results

    def getResourceType(self):
        """
        Get the type of this resource.
        Types are defined in Ft.Server.Common.ResourceTypes.ResourceType.
        """
        return self.resourceType

    def isResourceType(self, testType):
        """
        Determine if this resource is of a certain type
        """
        return ResourceTypes._IsResourceType(self.getResourceType(), testType)


    def getContent(self):
        """
        Get the string content of this resource
        """
        return Commands.RemoteMethodCommand('getContent',
                                            self._path,
                                            ()
                                            ).send(self._connection).results

    def getContentSlice(self, start=None, end=None):
        """
        Get a piece of our content.  Really only makes sense for large raw files
        """
        return Commands.RemoteMethodCommand('getContentSlice',
                                            self._path,
                                            (start, end)
                                            ).send(self._connection).results

    def getMetaDataResource(self):
        """
        Get the string meta data of this resource
        """
        return self


    def getContentResource(self):
        """
        Get the resource the represents the content of this object
        """
        return self.fetchResource('.;content')


    def getImt(self):
        """
        Returns the Internet Media Type of the raw file resource
        """
        return Commands.RemoteMethodCommand('getImt',
                                            self._path,
                                            ()
                                            ).send(self._connection).results

    def getValidationInfo(self):
        """
        Get the validation information associated with this resource.
        """
        return Commands.RemoteMethodCommand('getValidationInfo',
                                            self._path,
                                            ()
                                            ).send(self._connection).results

    def getParent(self):
        """
        Get the parent container of this resource
        """
        path = Commands.RemoteMethodCommand('getParent',
                                            self._path,
                                            ()
                                            ).send(self._connection).results
        if path:
            return self.fetchResource(path)
        else:
            return path

    def getRoot(self):
        """
        Get a reference to the system repository
        """
        return self._root

    def getAliases(self):
        """
        Get a list of alias objects that reference this object
        """
        return Commands.RemoteMethodCommand('getAliases',
                                            self._path,
                                            ()
                                            ).send(self._connection).results


    #####################################
    #ACL Interfaces
    #####################################
    def addAcl(self, aclKey, aclIdent, allowed=1):
        """
        Add an acl identifier to the acl key.
        If the specified key does not exist on the resource
        this function has the same functionality of setAcl.
        """
        acl = self.getAcl(aclKey).items()
        acl.append((aclIdent, allowed and AclConstants.ALLOWED or AclConstants.DENIED))
        Commands.RemoteMethodCommand('addAcl',
                                     self._path,
                                     (aclKey, aclIdent, allowed)
                                    ).send(self._connection).results

    def getAcl(self, aclKey=None):
        """
        Get a list of users and groups specified by the aclKey.
        If an aclKey is specified, then the results will be a
        list of dictionaries keyed by acl identifiers and with
        values of ALLOWED or DISALLOWED (from AclConstants).  The
        values in the list of dictionaries could be inherited from
        the parent object. If no aclKey is specified, then the
        results will be a nested dictionary.  The outer dictionary
        is keyed by all of the defined acl keys on this resource.
        Each value of the outer dictionary will be a dictionary
        identical to when this method is called with a aclKey.
        These results will not return any inherited values.
        """
        return Commands.RemoteMethodCommand('getAcl',
                                            self._path,
                                            (aclKey,)
                                            ).send(self._connection).results

    def getAclIdentifiers(self):
        """
        Get a list of all acl identifiers for the current users.
        This is equivalent to performing an "expandGroups" of the
        current user. It will also add things such as "WORLD-GROUP"
        or "USER-GROUP" if they exist.
        """
        return Commands.RemoteMethodCommand('getAclIdentifiers',
                                            self._path,
                                            ()
                                            ).send(self._connection).results

    def getAclIdent(self):
        """
        Get the ACL identifier for the current user (without expansion).
        """
        return Commands.RemoteMethodCommand('getAclIdent',
                                     self._path,
                                     ()
                                    ).send(self._connection).results

    def inheritAcl(self, aclKey):
        """
        Remove the entire key from the ACL entry so that it is
        inherited again from its parent
        """
        Commands.RemoteMethodCommand('inheritAcl',
                                     self._path,
                                     (aclKey,)
                                    ).send(self._connection).results

    def removeAcl(self, aclKey, aclIdent):
        """
        Remove the acl identifier from the acl key.
        If the specified aclKey is not present on the resource or the
        specified aclIdent is not in the key, then do nothing.
        Remember that entries inherited from the parent are *not*
        included in the acl on the actual resurce.  In order to
        override inherited permissions, set to no access rather than
        trying to remove.
        """
        Commands.RemoteMethodCommand('removeAcl',
                                     self._path,
                                     (aclKey, aclIdent)
                                    ).send(self._connection).results

    def setAcl(self,aclKey,aclIdent,allowed=1):
        """
        Replace the aclKey with a single entry of aclIdent.
        """
        Commands.RemoteMethodCommand('setAcl',
                                     self._path,
                                     (aclKey, aclIdent, allowed)
                                    ).send(self._connection).results

    def verifyAcl(self, aclKey, verifyTraverse=1):
        """
        Test to see whether the current user (and the current users set
        of acl identifiers) is allowed to perform the specified action,
        aclKey, on the current object.  If verifyTraverse is specified,
        then execute premissions are tested on all of the parents of
        this resource as well.
        """
        return Commands.RemoteMethodCommand('verifyAcl',
                                            self._path,
                                            (aclKey, verifyTraverse)
                                            ).send(self._connection).results
    def getOwner(self):
        """
        Get the owner of this resource
        """
        path = Commands.RemoteMethodCommand('getOwner',
                                            self._path,
                                            ()
                                            ).send(self._connection).results
        if path:
            return self.fetchResource(path)
        else:
            return path

    def setOwner(self, newOwner):
        """
        Change the owner of this resource
        """
        return Commands.RemoteMethodCommand('setOwner',
                                            self._path,
                                            ((None,newOwner.getAbsolutePath()),)
                                            ).send(self._connection).results


    ##########################################
    #XML Interfaces
    ##########################################

    def applyXslt(self, chain, params=None, ignorePis=1,
                  extParams=None, extModules=None):
        """
        applies the specified chain of stylesheets (with the specified
        transformation parameters) using the "self" object as source
        chain - can be a list of transforms, in which case only one
                transform run will execute, with the list of transforms
                all used such that [a.xsl, b.xsl] is equivalent to
                adding an import of b.xsl to a.xsl and applying a.xsl
                can also be a list of lists of transforms, in which case
                a chain of transforms is run,
                one pass for each inner list.  The output of each
                step in the chain is fed as the input to the next
        params - a dictionary giving the overridden top-level parameters
                for the transforms
        ignorePis - whether or not to ignore <xml-stylesheet processing
                instructions in the source document
        extParams - a special dictionary of parameters that allows the
                processor to communicate special values back to the caller
        extModules - a list of modules containing extension function and
                element implementations
        """
        if chain and isinstance(chain[0], (list, tuple)):
            chain = [ [ DocumentReference.InternalDocumentReference(j.getPath()) for j in i ] for i in chain ]
        else:
            chain = [ DocumentReference.InternalDocumentReference(i.getPath()) for i in chain ]
        return Commands.RemoteMethodCommand(
            'applyXslt',
            self._path,
            (chain, params, ignorePis, extParams, extModules)
            ).send(self._connection).results

    def asDom(self, stripElements=None):
        """
        Return this object as a non-live DOM instance.
        This means that any mutations to the returned DOM object will
        not affect the content of this resource.
        """
        from Ft.Xml import ReaderException, Domlette
        try:
            reader = Domlette.NonvalidatingReader
            comp = reader.parseString(self.getContent(), self._path)
            return comp
        except ReaderException, e:
            raise
            raise FtServerClientException(Error.XML_PARSE_EXCEPTION,
                                          message = e.params[0],
                                          lineNum = e.params[1],
                                          colNum = e.params[2])

    def toXPathContext(self, nss=None):
        """
        Create an XPath Context with the src of this RawFile.
        nss is a dictionary of namespace definitions to include in the
        context.
        """
        reader, dom = self.asReadonlyDom()
        processorNss = {'ftss':FTSERVER_NAMESPACE,
                        'xlink':XLINK_NAMESPACE}
        if nss:
            processorNss.update(nss)
        return reader, dom, Context.Context(dom, processorNss=processorNss)

    def xUpdate(self, updateSrc):
        """
        Allows XML content to be updated with the XUpdate protocol
        """
        return Commands.RemoteMethodCommand('xUpdate',
                                            self._path,
                                            (updateSrc,)
                                            ).send(self._connection).results

    #####################################
    #Query Interfaces
    #####################################
    def hasResource(self, path):
        """
        Query if the system has a resource specified by the path.
        No exception will be raised by this function.  If the current
        user cannot access the resource or the resource does not exist
        in the repository, then the results will be 0.  If the resource
        does exist, then the results will be the integer resource type
        of the resource (see
        Ft.Server.Common.ResourceTypes.ResourceTypes).
        """
        return Commands.RemoteMethodCommand('hasResource',
                                            self._path,
                                            (path,)
                                            ).send(self._connection).results

    #####################################
    #Retrieval Interfaces
    #####################################
    def fetchResource(self, path):
        """
        Fetch a resource from the system.
        path is a string.  The traverseAliases parameter is deprecated
        and the new ";traverse" path arguments should be used.  As an
        example, where before you could call:
        resource.fetchResource('path', 1) now you would call
        resource.fetchResource('path;traverse')
        """
        rsp = Commands.ResourceTypeCommand(
            path=path,
            baseObject=self._path).send(self._connection)
        path = rsp.path
        rt = rsp.resourceType

        #Very few specializations client side
        if rt == ResourceTypes.ResourceType.CONTAINER:
            if path == '/':
                return self._root
            import ContainerClient
            obj = ContainerClient.ContainerClient(path, self._connection,
                                                  rt, self._root)
        elif rt == ResourceTypes.ResourceType.XSLT_DOCUMENT_DEFINITION:
            import XsltDocumentDefinitionClient
            obj = XsltDocumentDefinitionClient.XsltDocumentDefinitionClient(
                path, self._connection, rt, self._root
                )
        elif rt == ResourceTypes.ResourceType.XPATH_DOCUMENT_DEFINITION:
            import XPathDocumentDefinitionClient
            obj = XPathDocumentDefinitionClient.XPathDocumentDefinitionClient(
                path, self._connection, rt, self._root
                )
        elif rt == ResourceTypes.ResourceType.USER:
            import UserClient
            obj = UserClient.UserClient(path, self._connection, rt,
                                        self._root)
        elif rt == ResourceTypes.ResourceType.META_DATA:
            import ResourceMetaDataClient
            obj = ResourceMetaDataClient.ResourceMetaDataClient(
                path, self._connection, rt, self._root
                )
        elif rt == ResourceTypes.ResourceType.GROUP:
            import GroupClient
            obj = GroupClient.GroupClient(
                path, self._connection, rt, self._root
                )
        elif rt == ResourceTypes.ResourceType.ALIAS:
            import AliasClient
            obj = AliasClient.AliasClient(
                path, self._connection, rt, self._root
                )
        elif rt == ResourceTypes.ResourceType.COMMAND:
            import CommandClient
            obj = CommandClient.CommandClient(
                path, self._connection, rt, self._root
                )
        elif rt == ResourceTypes.ResourceType.XSLT_DOCUMENT:
            import XsltDocumentClient
            obj = XsltDocumentClient.XsltDocumentClient(
                path, self._connection, rt, self._root
                )
        elif rt == ResourceTypes.ResourceType.XML_DOCUMENT:
            import XmlDocumentClient
            obj = XmlDocumentClient.XmlDocumentClient(
                path, self._connection, rt, self._root
                )
        elif rt == ResourceTypes.ResourceType.RAW_FILE:
            import RawFileClient
            obj = RawFileClient.RawFileClient(
                path, self._connection, rt, self._root
                )
        elif rt == ResourceTypes.ResourceType.URI_REFERENCE_FILE:
            import UriReferenceFileClient
            obj = UriReferenceFileClient.UriReferenceFileClient(
                path, self._connection, rt, self._root
                )
        elif rt == ResourceTypes.ResourceType.RDF_DOCUMENT:
            import RdfDocumentClient
            obj = RdfDocumentClient.RdfDocumentClient(
                path, self._connection, rt, self._root
                )
        elif rt == ResourceTypes.ResourceType.SERVER:
            import ServerClient
            obj = ServerClient.ServerClient(
                path, self._connection, rt, self._root
                )
        else:
            raise FtServerClientException(Error.UNKNOWN_RESOURCE_TYPE, type=rt)
        return obj


    ##########################################
    #Mutation Interfaces
    ##########################################
    def setContent(self, src):
        """
        Set the string content of this resource
        """
        return Commands.RemoteMethodCommand('setContent',
                                            self._path,
                                            (src,)
                                            ).send(self._connection).results

    def delete(self):
        """
        Delete this object from the repository
        """
        return Commands.RemoteMethodCommand('delete',
                                            self._path,
                                            ()
                                            ).send(self._connection).results



    ###################################
    #Creation Interfaces
    ###################################
    def addAlias(self, path, docDef = None):
        """
        Adds an alias for this resource to the repository.
        """
        path = Commands.RemoteMethodCommand('addAlias',
                                            self._path,
                                            (path, docDef)
                                            ).send(self._connection).results
        return self.fetchResource(path)


    ######################################
    #Delete Interface
    ######################################
    def removeAlias(self, path):
        """
        Removes the specified alias for this resource
        """
        raise NotImplementedError("removeAlias")


    ########################################
    #RDF Interfaces
    ########################################

    def getModel(self):
        """
        Retrieves the common model associated w/ the repository
        """
        return ModelClient.ModelClient(self._connection, self._path)

    def addLiteralProperty(self, predicate, object, scope='',
                           statementUri = ''):
        """
        Adds a statement about this repository object,
        with a literal property, to the model
        """
        raise NotImplementedError("addLiteralProperty")

    def addResourceProperty(self, predicate, object, scope='',
                            statementUri=''):
        """
        Adds a statement about this repository object, with a resource
        property, to the model
        """

        raise NotImplementedError("addResourceProperty")

    def removeResourceProperty(self, predicate, object):
        """
        Remove a statement about this repository object, with a resource preoperty, from
        the model
        """
        raise NotImplementedError("removeResourceProperty")

    def removeLiteralProperty(self, predicate, object):
        """
        Removes a statement about this repository object, with a literal property, from
        the model
        """
        raise NotImplementedError("rmeoveLiteralProperty")

    def getStatementsAbout(self):
        """
        Returns all statements about this object in the model
        """
        raise NotImplementedError("getStatementsAbout")

    def getStatementsGeneratedBy(self):
        """
        Returns all statements in the model generated by this object
        """
        raise NotImplementedError("getStatementsGeneratedBy")


    ########################################
    #Temporary file Interfaces
    ########################################
    def markTemporary(self, timeToLive):
        """
        Mark this resource as temporary.  Temporary objects will be removed from the repository when thier
        timeToLive has expired and purgeTemporaryResources is called the repository.
        Time to live is seconds from now.
        """
        raise NotImplementedError("markTemporary")


    ##########################################
    #String interfaces
    ##########################################
    def __str__(self):
        return "<%s at %x path='%s'>" % (ResourceTypes.g_resourceTypeMap[self.getResourceType()],
                                         id(self),
                                         self.getAbsolutePath())
    def __cmp__(self, other):
        if isinstance(other, self.__class__):
            return cmp(other.getPath(), self.getPath())
        return -1

    def __hash__(self):
        return id(self)

    def pprint(self):
        print "Resource: %s" % self.getPath()
        print "Resource Type: %s" % Schema.g_rdfResourceTypes[self.resourceType]
        print "Creation Date: %s" % FtTime.FromISO8601(self.getCreationDate()).asRFC822DateTime(local=1)
        print "Modified Date: %s" % FtTime.FromISO8601(self.getLastModifiedDate()).asRFC822DateTime(local=1)
        print "Internet Media Type: %s" % self.getImt()
        print "Size: %s" % self.getSize()
        owner = self.getOwner()
        if owner:
            print "Owner: %s" % owner.getAbsolutePath()
        else:
            print "Owner: Error retrieving owner"
        print

        from Ft.Server.Common.Util import RepoPathToUri
        temp_stmts = self.getModel().complete(
            #RepoPathToUri(self.getAbsolutePath()),
            self.getAbsolutePath(),
            Schema.TIME_TO_LIVE, None)
        if temp_stmts:
            #now = Time.FromPythonTime().asISO8601DateTime()
            #if temp_stmts[0].object <= cur:
            print "This is a temporary resource scheduled to be deleted after ", temp_stmts[0].object
            print

        acl = self.getAcl()

        for name, key in (('Read', AclConstants.READ_ACCESS),
                          ('Write', AclConstants.WRITE_ACCESS),
                          ('Execute', AclConstants.EXECUTE_ACCESS),
                          ('Delete', AclConstants.DELETE_ACCESS),
                          ('Change Permissions', AclConstants.CHANGE_PERMISSIONS_ACCESS),
                          ('Change Owner', AclConstants.CHANGE_OWNER_ACCESS),
                          ('Write User Model', AclConstants.WRITE_USER_MODEL_ACCESS),
                          ):

            if key in acl:
                print "%s ACL" % name
            else:
                print "%s ACL (inherited)" % name

            for ident, allowed in self.getAcl(key).items():
                if ident[0] == ':':
                    ident = ident[1:]
                print "  %s --> %s" % (ident, allowed == AclConstants.ALLOWED and 'allowed' or 'denied')


