You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@chemistry.apache.org by jp...@apache.org on 2013/02/27 22:39:05 UTC
svn commit: r1450978 [5/6] - in /chemistry/cmislib/trunk/src:
cmislib/__init__.py cmislib/atompub_binding.py cmislib/browser_binding.py
cmislib/cmis_services.py cmislib/domain.py cmislib/model.py cmislib/net.py
cmislib/util.py tests/cmislibtest.py
Modified: chemistry/cmislib/trunk/src/cmislib/model.py
URL: http://svn.apache.org/viewvc/chemistry/cmislib/trunk/src/cmislib/model.py?rev=1450978&r1=1450977&r2=1450978&view=diff
==============================================================================
--- chemistry/cmislib/trunk/src/cmislib/model.py (original)
+++ chemistry/cmislib/trunk/src/cmislib/model.py Wed Feb 27 21:39:05 2013
@@ -17,78 +17,17 @@
# under the License.
#
"""
-Module containing the domain objects used to work with a CMIS provider.
+Module containing the CmisClient object, which is responsible for
+keeping track of connection information. The name 'model' is no longer
+really appropriate, but it is kept for backwards compatibility.
"""
-from net import RESTService as Rest
-from exceptions import CmisException, RuntimeException, \
- ObjectNotFoundException, InvalidArgumentException, \
- PermissionDeniedException, NotSupportedException, \
- UpdateConflictException
-import messages
-
-from urllib import quote
-from urllib2 import HTTPError
-from urlparse import urlparse, urlunparse
-import re
-import mimetypes
-from xml.parsers.expat import ExpatError
-import datetime
-import time
-import iso8601
-import StringIO
+from atompub_binding import AtomPubBinding
+from cmis_services import Binding
+from domain import CmisObject, Repository
import logging
-# would kind of like to not have any parsing logic in this module,
-# but for now I'm going to put the serial/deserialization in methods
-# of the CMIS object classes
-from xml.dom import minidom
-
-# Namespaces
-ATOM_NS = 'http://www.w3.org/2005/Atom'
-APP_NS = 'http://www.w3.org/2007/app'
-CMISRA_NS = 'http://docs.oasis-open.org/ns/cmis/restatom/200908/'
-CMIS_NS = 'http://docs.oasis-open.org/ns/cmis/core/200908/'
-
-# Content types
-# Not all of these patterns have variability, but some do. It seemed cleaner
-# just to treat them all like patterns to simplify the matching logic
-ATOM_XML_TYPE = 'application/atom+xml'
-ATOM_XML_ENTRY_TYPE = 'application/atom+xml;type=entry'
-ATOM_XML_ENTRY_TYPE_P = re.compile('^application/atom\+xml.*type.*entry')
-ATOM_XML_FEED_TYPE = 'application/atom+xml;type=feed'
-ATOM_XML_FEED_TYPE_P = re.compile('^application/atom\+xml.*type.*feed')
-CMIS_TREE_TYPE = 'application/cmistree+xml'
-CMIS_TREE_TYPE_P = re.compile('^application/cmistree\+xml')
-CMIS_QUERY_TYPE = 'application/cmisquery+xml'
-CMIS_ACL_TYPE = 'application/cmisacl+xml'
-
-# Standard rels
-DOWN_REL = 'down'
-FIRST_REL = 'first'
-LAST_REL = 'last'
-NEXT_REL = 'next'
-PREV_REL = 'prev'
-SELF_REL = 'self'
-UP_REL = 'up'
-TYPE_DESCENDANTS_REL = 'http://docs.oasis-open.org/ns/cmis/link/200908/typedescendants'
-VERSION_HISTORY_REL = 'version-history'
-FOLDER_TREE_REL = 'http://docs.oasis-open.org/ns/cmis/link/200908/foldertree'
-RELATIONSHIPS_REL = 'http://docs.oasis-open.org/ns/cmis/link/200908/relationships'
-ACL_REL = 'http://docs.oasis-open.org/ns/cmis/link/200908/acl'
-CHANGE_LOG_REL = 'http://docs.oasis-open.org/ns/cmis/link/200908/changes'
-POLICIES_REL = 'http://docs.oasis-open.org/ns/cmis/link/200908/policies'
-RENDITION_REL = 'alternate'
-
-# Collection types
-QUERY_COLL = 'query'
-TYPES_COLL = 'types'
-CHECKED_OUT_COLL = 'checkedout'
-UNFILED_COLL = 'unfiled'
-ROOT_COLL = 'root'
-
moduleLogger = logging.getLogger('cmislib.model')
-
class CmisClient(object):
"""
@@ -110,6 +49,10 @@ class CmisClient(object):
self.username = username
self.password = password
self.extArgs = kwargs
+ if (kwargs.has_key('binding') and (isinstance(kwargs['binding'], Binding))):
+ self.binding = kwargs['binding']
+ else:
+ self.binding = AtomPubBinding(**kwargs)
self.logger = logging.getLogger('cmislib.model.CmisClient')
self.logger.info('Creating an instance of CmisClient')
@@ -128,19 +71,7 @@ class CmisClient(object):
[{'repositoryName': u'Main Repository', 'repositoryId': u'83beb297-a6fa-4ac5-844b-98c871c0eea9'}]
"""
- result = self.get(self.repositoryUrl, **self.extArgs)
-
- workspaceElements = result.getElementsByTagNameNS(APP_NS, 'workspace')
- # instantiate a Repository object using every workspace element
- # in the service URL then ask the repository object for its ID
- # and name, and return that back
-
- repositories = []
- for node in [e for e in workspaceElements if e.nodeType == e.ELEMENT_NODE]:
- repository = Repository(self, node)
- repositories.append({'repositoryId': repository.getRepositoryId(),
- 'repositoryName': repository.getRepositoryInfo()['repositoryName']})
- return repositories
+ return self.binding.getRepositoryService().getRepositories(self)
def getRepository(self, repositoryId):
@@ -151,16 +82,7 @@ class CmisClient(object):
>>> repo.getRepositoryName()
u'Main Repository'
"""
-
- doc = self.get(self.repositoryUrl, **self.extArgs)
- workspaceElements = doc.getElementsByTagNameNS(APP_NS, 'workspace')
-
- for workspaceElement in workspaceElements:
- idElement = workspaceElement.getElementsByTagNameNS(CMIS_NS, 'repositoryId')
- if idElement[0].childNodes[0].data == repositoryId:
- return Repository(self, workspaceElement)
-
- raise ObjectNotFoundException(url=self.repositoryUrl)
+ return self.binding.getRepositoryService().getRepository(self, repositoryId)
def getDefaultRepository(self):
@@ -174,4008 +96,8 @@ class CmisClient(object):
u'83beb297-a6fa-4ac5-844b-98c871c0eea9'
"""
- doc = self.get(self.repositoryUrl, **self.extArgs)
- moduleLogger.debug('type of returned obj: %s' % type(doc))
- workspaceElements = doc.getElementsByTagNameNS(APP_NS, 'workspace')
- # instantiate a Repository object with the first workspace
- # element we find
- repository = Repository(self, [e for e in workspaceElements if e.nodeType == e.ELEMENT_NODE][0])
- return repository
-
- def get(self, url, **kwargs):
-
- """
- Does a get against the CMIS service. More than likely, you will not
- need to call this method. Instead, let the other objects do it for you.
-
- For example, if you need to get a specific object by object id, try
- :class:`Repository.getObject`. If you have a path instead of an object
- id, use :class:`Repository.getObjectByPath`. Or, you could start with
- the root folder (:class:`Repository.getRootFolder`) and drill down from
- there.
- """
-
- # merge the cmis client extended args with the ones that got passed in
- if (len(self.extArgs) > 0):
- kwargs.update(self.extArgs)
-
- resp, content = Rest().get(url,
- username=self.username,
- password=self.password,
- **kwargs)
-
- if resp['status'] != '200':
- self._processCommonErrors(resp, url)
- return content
- else:
- try:
- return minidom.parseString(content)
- except ExpatError:
- raise CmisException('Could not parse server response', url)
-
- def delete(self, url, **kwargs):
-
- """
- Does a delete against the CMIS service. More than likely, you will not
- need to call this method. Instead, let the other objects do it for you.
-
- For example, to delete a folder you'd call :class:`Folder.delete` and
- to delete a document you'd call :class:`Document.delete`.
- """
-
- # merge the cmis client extended args with the ones that got passed in
- if (len(self.extArgs) > 0):
- kwargs.update(self.extArgs)
-
- resp, content = Rest().delete(url,
- username=self.username,
- password=self.password,
- **kwargs)
- if resp['status'] != '200':
- self._processCommonErrors(resp, url)
- return content
- else:
- pass
-
- def post(self, url, payload, contentType, **kwargs):
-
- """
- Does a post against the CMIS service. More than likely, you will not
- need to call this method. Instead, let the other objects do it for you.
-
- For example, to update the properties on an object, you'd call
- :class:`CmisObject.updateProperties`. Or, to check in a document that's
- been checked out, you'd call :class:`Document.checkin` on the PWC.
- """
-
- # merge the cmis client extended args with the ones that got passed in
- if (len(self.extArgs) > 0):
- kwargs.update(self.extArgs)
-
- resp, content = Rest().post(url,
- payload,
- contentType,
- username=self.username,
- password=self.password,
- **kwargs)
- if resp['status'] == '200':
- try:
- return minidom.parseString(content)
- except ExpatError:
- raise CmisException('Could not parse server response', url)
- elif resp['status'] == '201':
- try:
- return minidom.parseString(content)
- except ExpatError:
- raise CmisException('Could not parse server response', url)
- else:
- self._processCommonErrors(resp, url)
- return resp
-
- def put(self, url, payload, contentType, **kwargs):
-
- """
- Does a put against the CMIS service. More than likely, you will not
- need to call this method. Instead, let the other objects do it for you.
-
- For example, to update the properties on an object, you'd call
- :class:`CmisObject.updateProperties`. Or, to check in a document that's
- been checked out, you'd call :class:`Document.checkin` on the PWC.
- """
-
- # merge the cmis client extended args with the ones that got passed in
- if (len(self.extArgs) > 0):
- kwargs.update(self.extArgs)
-
- resp, content = Rest().put(url,
- payload,
- contentType,
- username=self.username,
- password=self.password,
- **kwargs)
- moduleLogger.debug("Back from PUT, status:%s" % resp['status'])
- if resp['status'] != '200' and resp['status'] != '201':
- self._processCommonErrors(resp, url)
- return content
- else:
- #if result.headers['content-length'] != '0':
- try:
- return minidom.parseString(content)
- except ExpatError:
- # This may happen and is normal
- return None
-
- def _processCommonErrors(self, error, url):
-
- """
- Maps HTTPErrors that are common to all to exceptions. Only errors
- that are truly global, like 401 not authorized, should be handled
- here. Callers should handle the rest.
- """
+ return self.binding.getRepositoryService().getDefaultRepository(self)
- if error['status'] == '401':
- raise PermissionDeniedException(error['status'], url)
- elif error['status'] == '400':
- raise InvalidArgumentException(error['status'], url)
- elif error['status'] == '404':
- raise ObjectNotFoundException(error['status'], url)
- elif error['status'] == '403':
- raise PermissionDeniedException(error['status'], url)
- elif error['status'] == '405':
- raise NotSupportedException(error['status'], url)
- elif error['status'] == '409':
- raise UpdateConflictException(error['status'], url)
- elif error['status'] == '500':
- raise RuntimeException(error['status'], url)
defaultRepository = property(getDefaultRepository)
repositories = property(getRepositories)
-
-
-class Repository(object):
-
- """
- Represents a CMIS repository. Will lazily populate itself by
- calling the repository CMIS service URL.
-
- You must pass in an instance of a CmisClient when creating an
- instance of this class.
- """
-
- def __init__(self, cmisClient, xmlDoc=None):
- """ Constructor """
- self._cmisClient = cmisClient
- self.xmlDoc = xmlDoc
- self._repositoryId = None
- self._repositoryName = None
- self._repositoryInfo = {}
- self._capabilities = {}
- self._uriTemplates = {}
- self._permDefs = {}
- self._permMap = {}
- self._permissions = None
- self._propagation = None
- self.logger = logging.getLogger('cmislib.model.Repository')
- self.logger.info('Creating an instance of Repository')
-
- def __str__(self):
- """To string"""
- return self.getRepositoryName()
-
- def reload(self):
- """
- This method will re-fetch the repository's XML data from the CMIS
- repository.
- """
- self.logger.debug('Reload called on object')
- self.xmlDoc = self._cmisClient.get(self._cmisClient.repositoryUrl.encode('utf-8'))
- self._initData()
-
- def _initData(self):
- """
- This method clears out any local variables that would be out of sync
- when data is re-fetched from the server.
- """
- self._repositoryId = None
- self._repositoryName = None
- self._repositoryInfo = {}
- self._capabilities = {}
- self._uriTemplates = {}
- self._permDefs = {}
- self._permMap = {}
- self._permissions = None
- self._propagation = None
-
- def getSupportedPermissions(self):
-
- """
- Returns the value of the cmis:supportedPermissions element. Valid
- values are:
-
- - basic: indicates that the CMIS Basic permissions are supported
- - repository: indicates that repository specific permissions are supported
- - both: indicates that both CMIS basic permissions and repository specific permissions are supported
-
- >>> repo.supportedPermissions
- u'both'
- """
-
- if not self.getCapabilities()['ACL']:
- raise NotSupportedException(messages.NO_ACL_SUPPORT)
-
- if not self._permissions:
- if self.xmlDoc == None:
- self.reload()
- suppEls = self.xmlDoc.getElementsByTagNameNS(CMIS_NS, 'supportedPermissions')
- assert len(suppEls) == 1, 'Expected the repository service document to have one element named supportedPermissions'
- self._permissions = suppEls[0].childNodes[0].data
-
- return self._permissions
-
- def getPermissionDefinitions(self):
-
- """
- Returns a dictionary of permission definitions for this repository. The
- key is the permission string or technical name of the permission
- and the value is the permission description.
-
- >>> for permDef in repo.permissionDefinitions:
- ... print permDef
- ...
- cmis:all
- {http://www.alfresco.org/model/system/1.0}base.LinkChildren
- {http://www.alfresco.org/model/content/1.0}folder.Consumer
- {http://www.alfresco.org/model/security/1.0}All.All
- {http://www.alfresco.org/model/system/1.0}base.CreateAssociations
- {http://www.alfresco.org/model/system/1.0}base.FullControl
- {http://www.alfresco.org/model/system/1.0}base.AddChildren
- {http://www.alfresco.org/model/system/1.0}base.ReadAssociations
- {http://www.alfresco.org/model/content/1.0}folder.Editor
- {http://www.alfresco.org/model/content/1.0}cmobject.Editor
- {http://www.alfresco.org/model/system/1.0}base.DeleteAssociations
- cmis:read
- cmis:write
- """
-
- if not self.getCapabilities()['ACL']:
- raise NotSupportedException(messages.NO_ACL_SUPPORT)
-
- if self._permDefs == {}:
- if self.xmlDoc == None:
- self.reload()
- aclEls = self.xmlDoc.getElementsByTagNameNS(CMIS_NS, 'aclCapability')
- assert len(aclEls) == 1, 'Expected the repository service document to have one element named aclCapability'
- aclEl = aclEls[0]
- perms = {}
- for e in aclEl.childNodes:
- if e.localName == 'permissions':
- permEls = e.getElementsByTagNameNS(CMIS_NS, 'permission')
- assert len(permEls) == 1, 'Expected permissions element to have a child named permission'
- descEls = e.getElementsByTagNameNS(CMIS_NS, 'description')
- assert len(descEls) == 1, 'Expected permissions element to have a child named description'
- perm = permEls[0].childNodes[0].data
- desc = descEls[0].childNodes[0].data
- perms[perm] = desc
- self._permDefs = perms
-
- return self._permDefs
-
- def getPermissionMap(self):
-
- """
- Returns a dictionary representing the permission mapping table where
- each key is a permission key string and each value is a list of one or
- more permissions the principal must have to perform the operation.
-
- >>> for (k,v) in repo.permissionMap.items():
- ... print 'To do this: %s, you must have these perms:' % k
- ... for perm in v:
- ... print perm
- ...
- To do this: canCreateFolder.Folder, you must have these perms:
- cmis:all
- {http://www.alfresco.org/model/system/1.0}base.CreateChildren
- To do this: canAddToFolder.Folder, you must have these perms:
- cmis:all
- {http://www.alfresco.org/model/system/1.0}base.CreateChildren
- To do this: canDelete.Object, you must have these perms:
- cmis:all
- {http://www.alfresco.org/model/system/1.0}base.DeleteNode
- To do this: canCheckin.Document, you must have these perms:
- cmis:all
- {http://www.alfresco.org/model/content/1.0}lockable.CheckIn
- """
-
- if not self.getCapabilities()['ACL']:
- raise NotSupportedException(messages.NO_ACL_SUPPORT)
-
- if self._permMap == {}:
- if self.xmlDoc == None:
- self.reload()
- aclEls = self.xmlDoc.getElementsByTagNameNS(CMIS_NS, 'aclCapability')
- assert len(aclEls) == 1, 'Expected the repository service document to have one element named aclCapability'
- aclEl = aclEls[0]
- permMap = {}
- for e in aclEl.childNodes:
- permList = []
- if e.localName == 'mapping':
- keyEls = e.getElementsByTagNameNS(CMIS_NS, 'key')
- assert len(keyEls) == 1, 'Expected mapping element to have a child named key'
- permEls = e.getElementsByTagNameNS(CMIS_NS, 'permission')
- assert len(permEls) >= 1, 'Expected mapping element to have at least one permission element'
- key = keyEls[0].childNodes[0].data
- for permEl in permEls:
- permList.append(permEl.childNodes[0].data)
- permMap[key] = permList
- self._permMap = permMap
-
- return self._permMap
-
- def getPropagation(self):
-
- """
- Returns the value of the cmis:propagation element. Valid values are:
- - objectonly: indicates that the repository is able to apply ACEs
- without changing the ACLs of other objects
- - propagate: indicates that the repository is able to apply ACEs to a
- given object and propagate this change to all inheriting objects
-
- >>> repo.propagation
- u'propagate'
- """
-
- if not self.getCapabilities()['ACL']:
- raise NotSupportedException(messages.NO_ACL_SUPPORT)
-
- if not self._propagation:
- if self.xmlDoc == None:
- self.reload()
- propEls = self.xmlDoc.getElementsByTagNameNS(CMIS_NS, 'propagation')
- assert len(propEls) == 1, 'Expected the repository service document to have one element named propagation'
- self._propagation = propEls[0].childNodes[0].data
-
- return self._propagation
-
- def getRepositoryId(self):
-
- """
- Returns this repository's unique identifier
-
- >>> repo = client.getDefaultRepository()
- >>> repo.getRepositoryId()
- u'83beb297-a6fa-4ac5-844b-98c871c0eea9'
- """
-
- if self._repositoryId == None:
- if self.xmlDoc == None:
- self.reload()
- self._repositoryId = self.xmlDoc.getElementsByTagNameNS(CMIS_NS, 'repositoryId')[0].firstChild.data
- return self._repositoryId
-
- def getRepositoryName(self):
-
- """
- Returns this repository's name
-
- >>> repo = client.getDefaultRepository()
- >>> repo.getRepositoryName()
- u'Main Repository'
- """
-
- if self._repositoryName == None:
- if self.xmlDoc == None:
- self.reload()
- self._repositoryName = self.xmlDoc.getElementsByTagNameNS(CMIS_NS, 'repositoryName')[0].firstChild.data
- return self._repositoryName
-
- def getRepositoryInfo(self):
-
- """
- Returns a dict of repository information.
-
- >>> repo = client.getDefaultRepository()>>> repo.getRepositoryName()
- u'Main Repository'
- >>> info = repo.getRepositoryInfo()
- >>> for k,v in info.items():
- ... print "%s:%s" % (k,v)
- ...
- cmisSpecificationTitle:Version 1.0 Committee Draft 04
- cmisVersionSupported:1.0
- repositoryDescription:None
- productVersion:3.2.0 (r2 2440)
- rootFolderId:workspace://SpacesStore/aa1ecedf-9551-49c5-831a-0502bb43f348
- repositoryId:83beb297-a6fa-4ac5-844b-98c871c0eea9
- repositoryName:Main Repository
- vendorName:Alfresco
- productName:Alfresco Repository (Community)
- """
-
- if not self._repositoryInfo:
- if self.xmlDoc == None:
- self.reload()
- repoInfoElement = self.xmlDoc.getElementsByTagNameNS(CMISRA_NS, 'repositoryInfo')[0]
- for node in repoInfoElement.childNodes:
- if node.nodeType == node.ELEMENT_NODE and node.localName != 'capabilities':
- try:
- data = node.childNodes[0].data
- except:
- data = None
- self._repositoryInfo[node.localName] = data
- return self._repositoryInfo
-
- def getCapabilities(self):
-
- """
- Returns a dict of repository capabilities.
-
- >>> caps = repo.getCapabilities()
- >>> for k,v in caps.items():
- ... print "%s:%s" % (k,v)
- ...
- PWCUpdatable:True
- VersionSpecificFiling:False
- Join:None
- ContentStreamUpdatability:anytime
- AllVersionsSearchable:False
- Renditions:None
- Multifiling:True
- GetFolderTree:True
- GetDescendants:True
- ACL:None
- PWCSearchable:True
- Query:bothcombined
- Unfiling:False
- Changes:None
- """
-
- if not self._capabilities:
- if self.xmlDoc == None:
- self.reload()
- capabilitiesElement = self.xmlDoc.getElementsByTagNameNS(CMIS_NS, 'capabilities')[0]
- for node in [e for e in capabilitiesElement.childNodes if e.nodeType == e.ELEMENT_NODE]:
- key = node.localName.replace('capability', '')
- value = parseBoolValue(node.childNodes[0].data)
- self._capabilities[key] = value
- return self._capabilities
-
- def getRootFolder(self):
- """
- Returns the root folder of the repository
-
- >>> root = repo.getRootFolder()
- >>> root.getObjectId()
- u'workspace://SpacesStore/aa1ecedf-9551-49c5-831a-0502bb43f348'
- """
- # get the root folder id
- rootFolderId = self.getRepositoryInfo()['rootFolderId']
- # instantiate a Folder object using the ID
- folder = Folder(self._cmisClient, self, rootFolderId)
- # return it
- return folder
-
- def getFolder(self, folderId):
-
- """
- Returns a :class:`Folder` object for a specified folderId
-
- >>> someFolder = repo.getFolder('workspace://SpacesStore/aa1ecedf-9551-49c5-831a-0502bb43f348')
- >>> someFolder.getObjectId()
- u'workspace://SpacesStore/aa1ecedf-9551-49c5-831a-0502bb43f348'
- """
-
- retObject = self.getObject(folderId)
- return Folder(self._cmisClient, self, xmlDoc=retObject.xmlDoc)
-
- def getTypeChildren(self,
- typeId=None):
-
- """
- Returns a list of :class:`ObjectType` objects corresponding to the
- child types of the type specified by the typeId.
-
- If no typeId is provided, the result will be the same as calling
- `self.getTypeDefinitions`
-
- These optional arguments are current unsupported:
- - includePropertyDefinitions
- - maxItems
- - skipCount
-
- >>> baseTypes = repo.getTypeChildren()
- >>> for baseType in baseTypes:
- ... print baseType.getTypeId()
- ...
- cmis:folder
- cmis:relationship
- cmis:document
- cmis:policy
- """
-
- # Unfortunately, the spec does not appear to present a way to
- # know how to get the children of a specific type without first
- # retrieving the type, then asking it for one of its navigational
- # links.
-
- # if a typeId is specified, get it, then get its "down" link
- if typeId:
- targetType = self.getTypeDefinition(typeId)
- childrenUrl = targetType.getLink(DOWN_REL, ATOM_XML_FEED_TYPE_P)
- typesXmlDoc = self._cmisClient.get(childrenUrl.encode('utf-8'))
- entryElements = typesXmlDoc.getElementsByTagNameNS(ATOM_NS, 'entry')
- types = []
- for entryElement in entryElements:
- objectType = ObjectType(self._cmisClient,
- self,
- xmlDoc=entryElement)
- types.append(objectType)
- # otherwise, if a typeId is not specified, return
- # the list of base types
- else:
- types = self.getTypeDefinitions()
- return types
-
- def getTypeDescendants(self, typeId=None, **kwargs):
-
- """
- Returns a list of :class:`ObjectType` objects corresponding to the
- descendant types of the type specified by the typeId.
-
- If no typeId is provided, the repository's "typesdescendants" URL
- will be called to determine the list of descendant types.
-
- >>> allTypes = repo.getTypeDescendants()
- >>> for aType in allTypes:
- ... print aType.getTypeId()
- ...
- cmis:folder
- F:cm:systemfolder
- F:act:savedactionfolder
- F:app:configurations
- F:fm:forums
- F:wcm:avmfolder
- F:wcm:avmplainfolder
- F:wca:webfolder
- F:wcm:avmlayeredfolder
- F:st:site
- F:app:glossary
- F:fm:topic
-
- These optional arguments are supported:
- - depth
- - includePropertyDefinitions
-
- >>> types = alfRepo.getTypeDescendants('cmis:folder')
- >>> len(types)
- 17
- >>> types = alfRepo.getTypeDescendants('cmis:folder', depth=1)
- >>> len(types)
- 12
- >>> types = alfRepo.getTypeDescendants('cmis:folder', depth=2)
- >>> len(types)
- 17
- """
-
- # Unfortunately, the spec does not appear to present a way to
- # know how to get the children of a specific type without first
- # retrieving the type, then asking it for one of its navigational
- # links.
- if typeId:
- targetType = self.getTypeDefinition(typeId)
- descendUrl = targetType.getLink(DOWN_REL, CMIS_TREE_TYPE_P)
-
- else:
- descendUrl = self.getLink(TYPE_DESCENDANTS_REL)
-
- if not descendUrl:
- raise NotSupportedException("Could not determine the type descendants URL")
-
- typesXmlDoc = self._cmisClient.get(descendUrl.encode('utf-8'), **kwargs)
- entryElements = typesXmlDoc.getElementsByTagNameNS(ATOM_NS, 'entry')
- types = []
- for entryElement in entryElements:
- objectType = ObjectType(self._cmisClient,
- self,
- xmlDoc=entryElement)
- types.append(objectType)
- return types
-
- def getTypeDefinitions(self, **kwargs):
-
- """
- Returns a list of :class:`ObjectType` objects representing
- the base types in the repository.
-
- >>> baseTypes = repo.getTypeDefinitions()
- >>> for baseType in baseTypes:
- ... print baseType.getTypeId()
- ...
- cmis:folder
- cmis:relationship
- cmis:document
- cmis:policy
- """
-
- typesUrl = self.getCollectionLink(TYPES_COLL)
- typesXmlDoc = self._cmisClient.get(typesUrl, **kwargs)
- entryElements = typesXmlDoc.getElementsByTagNameNS(ATOM_NS, 'entry')
- types = []
- for entryElement in entryElements:
- objectType = ObjectType(self._cmisClient,
- self,
- xmlDoc=entryElement)
- types.append(objectType)
- # return the result
- return types
-
- def getTypeDefinition(self, typeId):
-
- """
- Returns an :class:`ObjectType` object for the specified object type id.
-
- >>> folderType = repo.getTypeDefinition('cmis:folder')
- """
-
- objectType = ObjectType(self._cmisClient, self, typeId)
- objectType.reload()
- return objectType
-
- def getLink(self, rel):
- """
- Returns the HREF attribute of an Atom link element for the
- specified rel.
- """
- if self.xmlDoc == None:
- self.reload()
-
- linkElements = self.xmlDoc.getElementsByTagNameNS(ATOM_NS, 'link')
-
- for linkElement in linkElements:
-
- if linkElement.attributes.has_key('rel'):
- relAttr = linkElement.attributes['rel'].value
-
- if relAttr == rel:
- return linkElement.attributes['href'].value
-
- def getCheckedOutDocs(self, **kwargs):
-
- """
- Returns a ResultSet of :class:`CmisObject` objects that
- are currently checked out.
-
- >>> rs = repo.getCheckedOutDocs()
- >>> len(rs.getResults())
- 2
- >>> for doc in repo.getCheckedOutDocs().getResults():
- ... doc.getTitle()
- ...
- u'sample-a (Working Copy).pdf'
- u'sample-b (Working Copy).pdf'
-
- These optional arguments are supported:
- - folderId
- - maxItems
- - skipCount
- - orderBy
- - filter
- - includeRelationships
- - renditionFilter
- - includeAllowableActions
- """
-
- return self.getCollection(CHECKED_OUT_COLL, **kwargs)
-
- def getUnfiledDocs(self, **kwargs):
-
- """
- Returns a ResultSet of :class:`CmisObject` objects that
- are currently unfiled.
-
- >>> rs = repo.getUnfiledDocs()
- >>> len(rs.getResults())
- 2
- >>> for doc in repo.getUnfiledDocs().getResults():
- ... doc.getTitle()
- ...
- u'sample-a.pdf'
- u'sample-b.pdf'
-
- These optional arguments are supported:
- - folderId
- - maxItems
- - skipCount
- - orderBy
- - filter
- - includeRelationships
- - renditionFilter
- - includeAllowableActions
- """
-
- return self.getCollection(UNFILED_COLL, **kwargs)
-
- def getObject(self,
- objectId,
- **kwargs):
-
- """
- Returns an object given the specified object ID.
-
- >>> doc = repo.getObject('workspace://SpacesStore/f0c8b90f-bec0-4405-8b9c-2ab570589808')
- >>> doc.getTitle()
- u'sample-b.pdf'
-
- The following optional arguments are supported:
- - returnVersion
- - filter
- - includeRelationships
- - includePolicyIds
- - renditionFilter
- - includeACL
- - includeAllowableActions
- """
-
- return getSpecializedObject(CmisObject(self._cmisClient, self, objectId, **kwargs), **kwargs)
-
- def getObjectByPath(self, path, **kwargs):
-
- """
- Returns an object given the path to the object.
-
- >>> doc = repo.getObjectByPath('/jeff test/sample-b.pdf')
- >>> doc.getTitle()
- u'sample-b.pdf'
-
- The following optional arguments are not currently supported:
- - filter
- - includeAllowableActions
- """
-
- # get the uritemplate
- template = self.getUriTemplates()['objectbypath']['template']
-
- # fill in the template with the path provided
- params = {
- '{path}': quote(path, '/'),
- '{filter}': '',
- '{includeAllowableActions}': 'false',
- '{includePolicyIds}': 'false',
- '{includeRelationships}': '',
- '{includeACL}': 'false',
- '{renditionFilter}': ''}
-
- options = {}
- addOptions = {} # args specified, but not in the template
- for k, v in kwargs.items():
- pKey = "{" + k + "}"
- if template.find(pKey) >= 0:
- options[pKey] = toCMISValue(v)
- else:
- addOptions[k] = toCMISValue(v)
-
- # merge the templated args with the default params
- params.update(options)
-
- byObjectPathUrl = multiple_replace(params, template)
-
- # do a GET against the URL
- result = self._cmisClient.get(byObjectPathUrl.encode('utf-8'), **addOptions)
-
- # instantiate CmisObject objects with the results and return the list
- entryElements = result.getElementsByTagNameNS(ATOM_NS, 'entry')
- assert(len(entryElements) == 1), "Expected entry element in result from calling %s" % byObjectPathUrl
- return getSpecializedObject(CmisObject(self._cmisClient, self, xmlDoc=entryElements[0], **kwargs), **kwargs)
-
- def query(self, statement, **kwargs):
-
- """
- Returns a list of :class:`CmisObject` objects based on the CMIS
- Query Language passed in as the statement. The actual objects
- returned will be instances of the appropriate child class based
- on the object's base type ID.
-
- In order for the results to be properly instantiated as objects,
- make sure you include 'cmis:objectId' as one of the fields in
- your select statement, or just use "SELECT \*".
-
- If you want the search results to automatically be instantiated with
- the appropriate sub-class of :class:`CmisObject` you must either
- include cmis:baseTypeId as one of the fields in your select statement
- or just use "SELECT \*".
-
- >>> q = "select * from cmis:document where cmis:name like '%test%'"
- >>> resultSet = repo.query(q)
- >>> len(resultSet.getResults())
- 1
- >>> resultSet.hasNext()
- False
-
- The following optional arguments are supported:
- - searchAllVersions
- - includeRelationships
- - renditionFilter
- - includeAllowableActions
- - maxItems
- - skipCount
-
- >>> q = 'select * from cmis:document'
- >>> rs = repo.query(q)
- >>> len(rs.getResults())
- 148
- >>> rs = repo.query(q, maxItems='5')
- >>> len(rs.getResults())
- 5
- >>> rs.hasNext()
- True
- """
-
- if self.xmlDoc == None:
- self.reload()
-
- # get the URL this repository uses to accept query POSTs
- queryUrl = self.getCollectionLink(QUERY_COLL)
-
- # build the CMIS query XML that we're going to POST
- xmlDoc = self._getQueryXmlDoc(statement, **kwargs)
-
- # do the POST
- #print 'posting:%s' % xmlDoc.toxml(encoding='utf-8')
- result = self._cmisClient.post(queryUrl.encode('utf-8'),
- xmlDoc.toxml(encoding='utf-8'),
- CMIS_QUERY_TYPE)
-
- # return the result set
- return ResultSet(self._cmisClient, self, result)
-
- def getContentChanges(self, **kwargs):
-
- """
- Returns a :class:`ResultSet` containing :class:`ChangeEntry` objects.
-
- >>> for changeEntry in rs:
- ... changeEntry.objectId
- ... changeEntry.id
- ... changeEntry.changeType
- ... changeEntry.changeTime
- ...
- 'workspace://SpacesStore/0e2dc775-16b7-4634-9e54-2417a196829b'
- u'urn:uuid:0e2dc775-16b7-4634-9e54-2417a196829b'
- u'created'
- datetime.datetime(2010, 2, 11, 12, 55, 14)
- 'workspace://SpacesStore/bd768f9f-99a7-4033-828d-5b13f96c6923'
- u'urn:uuid:bd768f9f-99a7-4033-828d-5b13f96c6923'
- u'updated'
- datetime.datetime(2010, 2, 11, 12, 55, 13)
- 'workspace://SpacesStore/572c2cac-6b26-4cd8-91ad-b2931fe5b3fb'
- u'urn:uuid:572c2cac-6b26-4cd8-91ad-b2931fe5b3fb'
- u'updated'
-
- The following optional arguments are supported:
- - changeLogToken
- - includeProperties
- - includePolicyIDs
- - includeACL
- - maxItems
-
- You can get the latest change log token by inspecting the repository
- info via :meth:`Repository.getRepositoryInfo`.
-
- >>> repo.info['latestChangeLogToken']
- u'2692'
- >>> rs = repo.getContentChanges(changeLogToken='2692')
- >>> len(rs)
- 1
- >>> rs[0].id
- u'urn:uuid:8e88f694-93ef-44c5-9f70-f12fff824be9'
- >>> rs[0].changeType
- u'updated'
- >>> rs[0].changeTime
- datetime.datetime(2010, 2, 16, 20, 6, 37)
- """
-
- if self.getCapabilities()['Changes'] == None:
- raise NotSupportedException(messages.NO_CHANGE_LOG_SUPPORT)
-
- changesUrl = self.getLink(CHANGE_LOG_REL)
- result = self._cmisClient.get(changesUrl.encode('utf-8'), **kwargs)
-
- # return the result set
- return ChangeEntryResultSet(self._cmisClient, self, result)
-
- def createDocumentFromString(self,
- name,
- properties={},
- parentFolder=None,
- contentString=None,
- contentType=None,
- contentEncoding=None):
-
- """
- Creates a new document setting the content to the string provided. If
- the repository supports unfiled objects, you do not have to pass in
- a parent :class:`Folder` otherwise it is required.
-
- This method is essentially a convenience method that wraps your string
- with a StringIO and then calls createDocument.
-
- >>> repo.createDocumentFromString('testdoc5', parentFolder=testFolder, contentString='Hello, World!', contentType='text/plain')
- <cmislib.model.Document object at 0x101352ed0>
- """
-
- # if you didn't pass in a parent folder
- if parentFolder == None:
- # if the repository doesn't require fileable objects to be filed
- if self.getCapabilities()['Unfiling']:
- # has not been implemented
- #postUrl = self.getCollectionLink(UNFILED_COLL)
- raise NotImplementedError
- else:
- # this repo requires fileable objects to be filed
- raise InvalidArgumentException
-
- return parentFolder.createDocument(name, properties, StringIO.StringIO(contentString),
- contentType, contentEncoding)
-
- def createDocument(self,
- name,
- properties={},
- parentFolder=None,
- contentFile=None,
- contentType=None,
- contentEncoding=None):
-
- """
- Creates a new :class:`Document` object. If the repository
- supports unfiled objects, you do not have to pass in
- a parent :class:`Folder` otherwise it is required.
-
- To create a document with an associated contentFile, pass in a
- File object. The method will attempt to guess the appropriate content
- type and encoding based on the file. To specify it yourself, pass them
- in via the contentType and contentEncoding arguments.
-
- >>> f = open('sample-a.pdf', 'rb')
- >>> doc = folder.createDocument('sample-a.pdf', contentFile=f)
- <cmislib.model.Document object at 0x105be5e10>
- >>> f.close()
- >>> doc.getTitle()
- u'sample-a.pdf'
-
- The following optional arguments are not currently supported:
- - versioningState
- - policies
- - addACEs
- - removeACEs
- """
-
- postUrl = ''
- # if you didn't pass in a parent folder
- if parentFolder == None:
- # if the repository doesn't require fileable objects to be filed
- if self.getCapabilities()['Unfiling']:
- # has not been implemented
- #postUrl = self.getCollectionLink(UNFILED_COLL)
- raise NotImplementedError
- else:
- # this repo requires fileable objects to be filed
- raise InvalidArgumentException
- else:
- postUrl = parentFolder.getChildrenLink()
-
- # make sure a name is set
- properties['cmis:name'] = name
-
- # hardcoding to cmis:document if it wasn't
- # passed in via props
- if not properties.has_key('cmis:objectTypeId'):
- properties['cmis:objectTypeId'] = CmisId('cmis:document')
- # and if it was passed in, making sure it is a CmisId
- elif not isinstance(properties['cmis:objectTypeId'], CmisId):
- properties['cmis:objectTypeId'] = CmisId(properties['cmis:objectTypeId'])
-
- # build the Atom entry
- xmlDoc = getEntryXmlDoc(self, None, properties, contentFile,
- contentType, contentEncoding)
-
- # post the Atom entry
- result = self._cmisClient.post(postUrl.encode('utf-8'), xmlDoc.toxml(encoding='utf-8'), ATOM_XML_ENTRY_TYPE)
-
- # what comes back is the XML for the new document,
- # so use it to instantiate a new document
- # then return it
- return Document(self._cmisClient, self, xmlDoc=result)
-
- def createDocumentFromSource(self,
- sourceId,
- properties={},
- parentFolder=None):
- """
- This is not yet implemented.
-
- The following optional arguments are not yet supported:
- - versioningState
- - policies
- - addACEs
- - removeACEs
- """
- # TODO: To be implemented
- raise NotImplementedError
-
- def createFolder(self,
- parentFolder,
- name,
- properties={}):
-
- """
- Creates a new :class:`Folder` object in the specified parentFolder.
-
- >>> root = repo.getRootFolder()
- >>> folder = repo.createFolder(root, 'someFolder2')
- >>> folder.getTitle()
- u'someFolder2'
- >>> folder.getObjectId()
- u'workspace://SpacesStore/2224a63c-350b-438c-be72-8f425e79ce1f'
-
- The following optional arguments are not yet supported:
- - policies
- - addACEs
- - removeACEs
- """
-
- return parentFolder.createFolder(name, properties)
-
- def createRelationship(self, sourceObj, targetObj, relType):
- """
- Creates a relationship of the specific type between a source object
- and a target object and returns the new :class:`Relationship` object.
-
- The following optional arguments are not currently supported:
- - policies
- - addACEs
- - removeACEs
- """
- return sourceObj.createRelationship(targetObj, relType)
-
- def createPolicy(self, properties):
- """
- This has not yet been implemented.
-
- The following optional arguments are not currently supported:
- - folderId
- - policies
- - addACEs
- - removeACEs
- """
- # TODO: To be implemented
- raise NotImplementedError
-
- def getUriTemplates(self):
-
- """
- Returns a list of the URI templates the repository service knows about.
-
- >>> templates = repo.getUriTemplates()
- >>> templates['typebyid']['mediaType']
- u'application/atom+xml;type=entry'
- >>> templates['typebyid']['template']
- u'http://localhost:8080/alfresco/s/cmis/type/{id}'
- """
-
- if self._uriTemplates == {}:
-
- if self.xmlDoc == None:
- self.reload()
-
- uriTemplateElements = self.xmlDoc.getElementsByTagNameNS(CMISRA_NS, 'uritemplate')
-
- for uriTemplateElement in uriTemplateElements:
- template = None
- templType = None
- mediatype = None
-
- for node in [e for e in uriTemplateElement.childNodes if e.nodeType == e.ELEMENT_NODE]:
- if node.localName == 'template':
- template = node.childNodes[0].data
- elif node.localName == 'type':
- templType = node.childNodes[0].data
- elif node.localName == 'mediatype':
- mediatype = node.childNodes[0].data
-
- self._uriTemplates[templType] = UriTemplate(template,
- templType,
- mediatype)
-
- return self._uriTemplates
-
- def getCollection(self, collectionType, **kwargs):
-
- """
- Returns a list of objects returned for the specified collection.
-
- If the query collection is requested, an exception will be raised.
- That collection isn't meant to be retrieved.
-
- If the types collection is specified, the method returns the result of
- `getTypeDefinitions` and ignores any optional params passed in.
-
- >>> from cmislib.model import TYPES_COLL
- >>> types = repo.getCollection(TYPES_COLL)
- >>> len(types)
- 4
- >>> types[0].getTypeId()
- u'cmis:folder'
-
- Otherwise, the collection URL is invoked, and a :class:`ResultSet` is
- returned.
-
- >>> from cmislib.model import CHECKED_OUT_COLL
- >>> resultSet = repo.getCollection(CHECKED_OUT_COLL)
- >>> len(resultSet.getResults())
- 1
- """
-
- if collectionType == QUERY_COLL:
- raise NotSupportedException
- elif collectionType == TYPES_COLL:
- return self.getTypeDefinitions()
-
- result = self._cmisClient.get(self.getCollectionLink(collectionType).encode('utf-8'), **kwargs)
-
- # return the result set
- return ResultSet(self._cmisClient, self, result)
-
- def getCollectionLink(self, collectionType):
-
- """
- Returns the link HREF from the specified collectionType
- ('checkedout', for example).
-
- >>> from cmislib.model import CHECKED_OUT_COLL
- >>> repo.getCollectionLink(CHECKED_OUT_COLL)
- u'http://localhost:8080/alfresco/s/cmis/checkedout'
-
- """
-
- collectionElements = self.xmlDoc.getElementsByTagNameNS(APP_NS, 'collection')
- for collectionElement in collectionElements:
- link = collectionElement.attributes['href'].value
- for node in [e for e in collectionElement.childNodes if e.nodeType == e.ELEMENT_NODE]:
- if node.localName == 'collectionType':
- if node.childNodes[0].data == collectionType:
- return link
-
- def _getQueryXmlDoc(self, query, **kwargs):
-
- """
- Utility method that knows how to build CMIS query xml around the
- specified query statement.
- """
-
- cmisXmlDoc = minidom.Document()
- queryElement = cmisXmlDoc.createElementNS(CMIS_NS, "query")
- queryElement.setAttribute('xmlns', CMIS_NS)
- cmisXmlDoc.appendChild(queryElement)
-
- statementElement = cmisXmlDoc.createElementNS(CMIS_NS, "statement")
- cdataSection = cmisXmlDoc.createCDATASection(query)
- statementElement.appendChild(cdataSection)
- queryElement.appendChild(statementElement)
-
- for (k, v) in kwargs.items():
- optionElement = cmisXmlDoc.createElementNS(CMIS_NS, k)
- optionText = cmisXmlDoc.createTextNode(v)
- optionElement.appendChild(optionText)
- queryElement.appendChild(optionElement)
-
- return cmisXmlDoc
-
- capabilities = property(getCapabilities)
- id = property(getRepositoryId)
- info = property(getRepositoryInfo)
- name = property(getRepositoryName)
- rootFolder = property(getRootFolder)
- permissionDefinitions = property(getPermissionDefinitions)
- permissionMap = property(getPermissionMap)
- propagation = property(getPropagation)
- supportedPermissions = property(getSupportedPermissions)
-
-
-class ResultSet(object):
-
- """
- Represents a paged result set. In CMIS, this is most often an Atom feed.
- """
-
- def __init__(self, cmisClient, repository, xmlDoc):
- ''' Constructor '''
- self._cmisClient = cmisClient
- self._repository = repository
- self._xmlDoc = xmlDoc
- self._results = []
- self.logger = logging.getLogger('cmislib.model.ResultSet')
- self.logger.info('Creating an instance of ResultSet')
-
- def __iter__(self):
- ''' Iterator for the result set '''
- return iter(self.getResults())
-
- def __getitem__(self, index):
- ''' Getter for the result set '''
- return self.getResults()[index]
-
- def __len__(self):
- ''' Len method for the result set '''
- return len(self.getResults())
-
- def _getLink(self, rel):
- '''
- Returns the link found in the feed's XML for the specified rel.
- '''
- linkElements = self._xmlDoc.getElementsByTagNameNS(ATOM_NS, 'link')
-
- for linkElement in linkElements:
-
- if linkElement.attributes.has_key('rel'):
- relAttr = linkElement.attributes['rel'].value
-
- if relAttr == rel:
- return linkElement.attributes['href'].value
-
- def _getPageResults(self, rel):
- '''
- Given a specified rel, does a get using that link (if one exists)
- and then converts the resulting XML into a dictionary of
- :class:`CmisObject` objects or its appropriate sub-type.
-
- The results are kept around to facilitate repeated calls without moving
- the cursor.
- '''
- link = self._getLink(rel)
- if link:
- result = self._cmisClient.get(link.encode('utf-8'))
-
- # return the result
- self._xmlDoc = result
- self._results = []
- return self.getResults()
-
- def reload(self):
-
- '''
- Re-invokes the self link for the current set of results.
-
- >>> resultSet = repo.getCollection(CHECKED_OUT_COLL)
- >>> resultSet.reload()
-
- '''
-
- self.logger.debug('Reload called on result set')
- self._getPageResults(SELF_REL)
-
- def getResults(self):
-
- '''
- Returns the results that were fetched and cached by the get*Page call.
-
- >>> resultSet = repo.getCheckedOutDocs()
- >>> resultSet.hasNext()
- False
- >>> for result in resultSet.getResults():
- ... result
- ...
- <cmislib.model.Document object at 0x104851810>
- '''
- if self._results:
- return self._results
-
- if self._xmlDoc:
- entryElements = self._xmlDoc.getElementsByTagNameNS(ATOM_NS, 'entry')
- entries = []
- for entryElement in entryElements:
- cmisObject = getSpecializedObject(CmisObject(self._cmisClient,
- self._repository,
- xmlDoc=entryElement))
- entries.append(cmisObject)
-
- self._results = entries
-
- return self._results
-
- def hasObject(self, objectId):
-
- '''
- Returns True if the specified objectId is found in the list of results,
- otherwise returns False.
- '''
-
- for obj in self.getResults():
- if obj.id == objectId:
- return True
- return False
-
- def getFirst(self):
-
- '''
- Returns the first page of results as a dictionary of
- :class:`CmisObject` objects or its appropriate sub-type. This only
- works when the server returns a "first" link. Not all of them do.
-
- >>> resultSet.hasFirst()
- True
- >>> results = resultSet.getFirst()
- >>> for result in results:
- ... result
- ...
- <cmislib.model.Document object at 0x10480bc90>
- '''
-
- return self._getPageResults(FIRST_REL)
-
- def getPrev(self):
-
- '''
- Returns the prev page of results as a dictionary of
- :class:`CmisObject` objects or its appropriate sub-type. This only
- works when the server returns a "prev" link. Not all of them do.
- >>> resultSet.hasPrev()
- True
- >>> results = resultSet.getPrev()
- >>> for result in results:
- ... result
- ...
- <cmislib.model.Document object at 0x10480bc90>
- '''
-
- return self._getPageResults(PREV_REL)
-
- def getNext(self):
-
- '''
- Returns the next page of results as a dictionary of
- :class:`CmisObject` objects or its appropriate sub-type.
- >>> resultSet.hasNext()
- True
- >>> results = resultSet.getNext()
- >>> for result in results:
- ... result
- ...
- <cmislib.model.Document object at 0x10480bc90>
- '''
-
- return self._getPageResults(NEXT_REL)
-
- def getLast(self):
-
- '''
- Returns the last page of results as a dictionary of
- :class:`CmisObject` objects or its appropriate sub-type. This only
- works when the server is returning a "last" link. Not all of them do.
-
- >>> resultSet.hasLast()
- True
- >>> results = resultSet.getLast()
- >>> for result in results:
- ... result
- ...
- <cmislib.model.Document object at 0x10480bc90>
- '''
-
- return self._getPageResults(LAST_REL)
-
- def hasNext(self):
-
- '''
- Returns True if this page contains a next link.
-
- >>> resultSet.hasNext()
- True
- '''
-
- if self._getLink(NEXT_REL):
- return True
- else:
- return False
-
- def hasPrev(self):
-
- '''
- Returns True if this page contains a prev link. Not all CMIS providers
- implement prev links consistently.
-
- >>> resultSet.hasPrev()
- True
- '''
-
- if self._getLink(PREV_REL):
- return True
- else:
- return False
-
- def hasFirst(self):
-
- '''
- Returns True if this page contains a first link. Not all CMIS providers
- implement first links consistently.
-
- >>> resultSet.hasFirst()
- True
- '''
-
- if self._getLink(FIRST_REL):
- return True
- else:
- return False
-
- def hasLast(self):
-
- '''
- Returns True if this page contains a last link. Not all CMIS providers
- implement last links consistently.
-
- >>> resultSet.hasLast()
- True
- '''
-
- if self._getLink(LAST_REL):
- return True
- else:
- return False
-
-
-class CmisObject(object):
-
- """
- Common ancestor class for other CMIS domain objects such as
- :class:`Document` and :class:`Folder`.
- """
-
- def __init__(self, cmisClient, repository, objectId=None, xmlDoc=None, **kwargs):
- """ Constructor """
- self._cmisClient = cmisClient
- self._repository = repository
- self._objectId = objectId
- self._name = None
- self._properties = {}
- self._allowableActions = {}
- self.xmlDoc = xmlDoc
- self._kwargs = kwargs
- self.logger = logging.getLogger('cmislib.model.CmisObject')
- self.logger.info('Creating an instance of CmisObject')
-
- def __str__(self):
- """To string"""
- return self.getObjectId()
-
- def reload(self, **kwargs):
-
- """
- Fetches the latest representation of this object from the CMIS service.
- Some methods, like :class:`^Document.checkout` do this for you.
-
- If you call reload with a properties filter, the filter will be in
- effect on subsequent calls until the filter argument is changed. To
- reset to the full list of properties, call reload with filter set to
- '*'.
- """
-
- self.logger.debug('Reload called on CmisObject')
- if kwargs:
- if self._kwargs:
- self._kwargs.update(kwargs)
- else:
- self._kwargs = kwargs
-
- templates = self._repository.getUriTemplates()
- template = templates['objectbyid']['template']
-
- # Doing some refactoring here. Originally, we snagged the template
- # and then "filled in" the template based on the args passed in.
- # However, some servers don't provide a full template which meant
- # supported optional args wouldn't get passed in using the fill-the-
- # template approach. What's going on now is that the template gets
- # filled in where it can, but if additional, non-templated args are
- # passed in, those will get tacked on to the query string as
- # "additional" options.
-
- params = {
- '{id}': self.getObjectId(),
- '{filter}': '',
- '{includeAllowableActions}': 'false',
- '{includePolicyIds}': 'false',
- '{includeRelationships}': '',
- '{includeACL}': 'false',
- '{renditionFilter}': ''}
-
- options = {}
- addOptions = {} # args specified, but not in the template
- for k, v in self._kwargs.items():
- pKey = "{" + k + "}"
- if template.find(pKey) >= 0:
- options[pKey] = toCMISValue(v)
- else:
- addOptions[k] = toCMISValue(v)
-
- # merge the templated args with the default params
- params.update(options)
-
- # fill in the template
- byObjectIdUrl = multiple_replace(params, template)
-
- self.xmlDoc = self._cmisClient.get(byObjectIdUrl.encode('utf-8'), **addOptions)
- self._initData()
-
- # if a returnVersion arg was passed in, it is possible we got back
- # a different object ID than the value we started with, so it needs
- # to be cleared out as well
- if options.has_key('returnVersion') or addOptions.has_key('returnVersion'):
- self._objectId = None
-
- def _initData(self):
-
- """
- An internal method used to clear out any member variables that
- might be out of sync if we were to fetch new XML from the
- service.
- """
-
- self._properties = {}
- self._name = None
- self._allowableActions = {}
-
- def getObjectId(self):
-
- """
- Returns the object ID for this object.
-
- >>> doc = resultSet.getResults()[0]
- >>> doc.getObjectId()
- u'workspace://SpacesStore/dc26102b-e312-471b-b2af-91bfb0225339'
- """
-
- if self._objectId == None:
- if self.xmlDoc == None:
- self.logger.debug('Both objectId and xmlDoc were None, reloading')
- self.reload()
- props = self.getProperties()
- self._objectId = CmisId(props['cmis:objectId'])
- return self._objectId
-
- def getObjectParents(self, **kwargs):
- """
- Gets the parents of this object as a :class:`ResultSet`.
-
- The following optional arguments are supported:
- - filter
- - includeRelationships
- - renditionFilter
- - includeAllowableActions
- - includeRelativePathSegment
- """
- # get the appropriate 'up' link
- parentUrl = self._getLink(UP_REL)
-
- if parentUrl == None:
- raise NotSupportedException('Root folder does not support getObjectParents')
-
- # invoke the URL
- result = self._cmisClient.get(parentUrl.encode('utf-8'), **kwargs)
-
- # return the result set
- return ResultSet(self._cmisClient, self._repository, result)
-
- def getPaths(self):
- """
- Returns the object's paths as a list of strings.
- """
- # see sub-classes for implementation
- pass
-
- def getAllowableActions(self):
-
- """
- Returns a dictionary of allowable actions, keyed off of the action name.
-
- >>> actions = doc.getAllowableActions()
- >>> for a in actions:
- ... print "%s:%s" % (a,actions[a])
- ...
- canDeleteContentStream:True
- canSetContentStream:True
- canCreateRelationship:True
- canCheckIn:False
- canApplyACL:False
- canDeleteObject:True
- canGetAllVersions:True
- canGetObjectParents:True
- canGetProperties:True
- """
-
- if self._allowableActions == {}:
- self.reload(includeAllowableActions=True)
- allowElements = self.xmlDoc.getElementsByTagNameNS(CMIS_NS, 'allowableActions')
- assert len(allowElements) == 1, "Expected response to have exactly one allowableActions element"
- allowElement = allowElements[0]
- for node in [e for e in allowElement.childNodes if e.nodeType == e.ELEMENT_NODE]:
- actionName = node.localName
- actionValue = parseBoolValue(node.childNodes[0].data)
- self._allowableActions[actionName] = actionValue
-
- return self._allowableActions
-
- def getTitle(self):
-
- """
- Returns the value of the object's cmis:title property.
- """
-
- if self.xmlDoc == None:
- self.reload()
-
- titleElement = self.xmlDoc.getElementsByTagNameNS(ATOM_NS, 'title')[0]
-
- if titleElement and titleElement.childNodes:
- return titleElement.childNodes[0].data
-
- def getProperties(self):
-
- """
- Returns a dict of the object's properties. If CMIS returns an
- empty element for a property, the property will be in the
- dict with a value of None.
-
- >>> props = doc.getProperties()
- >>> for p in props:
- ... print "%s: %s" % (p, props[p])
- ...
- cmis:contentStreamMimeType: text/html
- cmis:creationDate: 2009-12-15T09:45:35.369-06:00
- cmis:baseTypeId: cmis:document
- cmis:isLatestMajorVersion: false
- cmis:isImmutable: false
- cmis:isMajorVersion: false
- cmis:objectId: workspace://SpacesStore/dc26102b-e312-471b-b2af-91bfb0225339
-
- The optional filter argument is not yet implemented.
- """
-
- #TODO implement filter
- if self._properties == {}:
- if self.xmlDoc == None:
- self.reload()
- propertiesElement = self.xmlDoc.getElementsByTagNameNS(CMIS_NS, 'properties')[0]
- #cpattern = re.compile(r'^property([\w]*)')
- for node in [e for e in propertiesElement.childNodes if e.nodeType == e.ELEMENT_NODE and e.namespaceURI == CMIS_NS]:
- #propertyId, propertyString, propertyDateTime
- #propertyType = cpattern.search(node.localName).groups()[0]
- propertyName = node.attributes['propertyDefinitionId'].value
- if node.childNodes and \
- node.getElementsByTagNameNS(CMIS_NS, 'value')[0] and \
- node.getElementsByTagNameNS(CMIS_NS, 'value')[0].childNodes:
- valNodeList = node.getElementsByTagNameNS(CMIS_NS, 'value')
- if (len(valNodeList) == 1):
- propertyValue = parsePropValue(valNodeList[0].
- childNodes[0].data,
- node.localName)
- else:
- propertyValue = []
- for valNode in valNodeList:
- propertyValue.append(parsePropValue(valNode.
- childNodes[0].data,
- node.localName))
- else:
- propertyValue = None
- self._properties[propertyName] = propertyValue
-
- for node in [e for e in self.xmlDoc.childNodes if e.nodeType == e.ELEMENT_NODE and e.namespaceURI == CMISRA_NS]:
- propertyName = node.nodeName
- if node.childNodes:
- propertyValue = node.firstChild.nodeValue
- else:
- propertyValue = None
- self._properties[propertyName] = propertyValue
-
- return self._properties
-
- def getName(self):
-
- """
- Returns the value of cmis:name from the getProperties() dictionary.
- We don't need a getter for every standard CMIS property, but name
- is a pretty common one so it seems to make sense.
-
- >>> doc.getName()
- u'system-overview.html'
- """
-
- if self._name == None:
- self._name = self.getProperties()['cmis:name']
- return self._name
-
- def updateProperties(self, properties):
-
- """
- Updates the properties of an object with the properties provided.
- Only provide the set of properties that need to be updated.
-
- >>> folder = repo.getObjectByPath('/someFolder2')
- >>> folder.getName()
- u'someFolder2'
- >>> props = {'cmis:name': 'someFolderFoo'}
- >>> folder.updateProperties(props)
- <cmislib.model.Folder object at 0x103ab1210>
- >>> folder.getName()
- u'someFolderFoo'
-
- """
-
- self.logger.debug('Inside updateProperties')
-
- # get the self link
- selfUrl = self._getSelfLink()
-
- # if we have a change token, we must pass it back, per the spec
- args = {}
- if (self.properties.has_key('cmis:changeToken') and
- self.properties['cmis:changeToken'] != None):
- self.logger.debug('Change token present, adding it to args')
- args = {"changeToken": self.properties['cmis:changeToken']}
-
- # the getEntryXmlDoc function may need the object type
- objectTypeId = None
- if (self.properties.has_key('cmis:objectTypeId') and
- not properties.has_key('cmis:objectTypeId')):
- objectTypeId = self.properties['cmis:objectTypeId']
- self.logger.debug('This object type is:%s' % objectTypeId)
-
- # build the entry based on the properties provided
- xmlEntryDoc = getEntryXmlDoc(self._repository, objectTypeId, properties)
-
- self.logger.debug('xmlEntryDoc:' + xmlEntryDoc.toxml())
-
- # do a PUT of the entry
- updatedXmlDoc = self._cmisClient.put(selfUrl.encode('utf-8'),
- xmlEntryDoc.toxml(encoding='utf-8'),
- ATOM_XML_TYPE,
- **args)
-
- # reset the xmlDoc for this object with what we got back from
- # the PUT, then call initData we dont' want to call
- # self.reload because we've already got the parsed XML--
- # there's no need to fetch it again
- self.xmlDoc = updatedXmlDoc
- self._initData()
- return self
-
- def move(self, sourceFolder, targetFolder):
-
- """
- Moves an object from the source folder to the target folder.
-
- >>> sub1 = repo.getObjectByPath('/cmislib/sub1')
- >>> sub2 = repo.getObjectByPath('/cmislib/sub2')
- >>> doc = repo.getObjectByPath('/cmislib/sub1/testdoc1')
- >>> doc.move(sub1, sub2)
- """
-
- postUrl = targetFolder.getChildrenLink()
-
- args = {"sourceFolderId": sourceFolder.id}
-
- # post the Atom entry
- result = self._cmisClient.post(postUrl.encode('utf-8'), self.xmlDoc.toxml(encoding='utf-8'), ATOM_XML_ENTRY_TYPE, **args)
-
- def delete(self, **kwargs):
-
- """
- Deletes this :class:`CmisObject` from the repository. Note that in the
- case of a :class:`Folder` object, some repositories will refuse to
- delete it if it contains children and some will delete it without
- complaint. If what you really want to do is delete the folder and all
- of its descendants, use :meth:`~Folder.deleteTree` instead.
-
- >>> folder.delete()
-
- The optional allVersions argument is supported.
- """
-
- url = self._getSelfLink()
- result = self._cmisClient.delete(url.encode('utf-8'), **kwargs)
-
- def applyPolicy(self, policyId):
-
- """
- This is not yet implemented.
- """
-
- # depends on this object's canApplyPolicy allowable action
- if self.getAllowableActions()['canApplyPolicy']:
- raise NotImplementedError
- else:
- raise CmisException('This object has canApplyPolicy set to false')
-
- def createRelationship(self, targetObj, relTypeId):
-
- """
- Creates a relationship between this object and a specified target
- object using the relationship type specified. Returns the new
- :class:`Relationship` object.
-
- >>> rel = tstDoc1.createRelationship(tstDoc2, 'R:cmiscustom:assoc')
- >>> rel.getProperties()
- {u'cmis:objectId': u'workspace://SpacesStore/271c48dd-6548-4771-a8f5-0de69b7cdc25', u'cmis:creationDate': None, u'cmis:objectTypeId': u'R:cmiscustom:assoc', u'cmis:lastModificationDate': None, u'cmis:targetId': u'workspace://SpacesStore/0ca1aa08-cb49-42e2-8881-53aa8496a1c1', u'cmis:lastModifiedBy': None, u'cmis:baseTypeId': u'cmis:relationship', u'cmis:sourceId': u'workspace://SpacesStore/271c48dd-6548-4771-a8f5-0de69b7cdc25', u'cmis:changeToken': None, u'cmis:createdBy': None}
-
- """
-
- if isinstance(relTypeId, str):
- relTypeId = CmisId(relTypeId)
-
- props = {}
- props['cmis:sourceId'] = self.getObjectId()
- props['cmis:targetId'] = targetObj.getObjectId()
- props['cmis:objectTypeId'] = relTypeId
- xmlDoc = getEntryXmlDoc(self._repository, properties=props)
-
- url = self._getLink(RELATIONSHIPS_REL)
- assert url != None, 'Could not determine relationships URL'
-
- result = self._cmisClient.post(url.encode('utf-8'),
- xmlDoc.toxml(encoding='utf-8'),
- ATOM_XML_TYPE)
-
- # instantiate CmisObject objects with the results and return the list
- entryElements = result.getElementsByTagNameNS(ATOM_NS, 'entry')
- assert(len(entryElements) == 1), "Expected entry element in result from relationship URL post"
- return getSpecializedObject(CmisObject(self._cmisClient, self, xmlDoc=entryElements[0]))
-
- def getRelationships(self, **kwargs):
-
- """
- Returns a :class:`ResultSet` of :class:`Relationship` objects for each
- relationship where the source is this object.
-
- >>> rels = tstDoc1.getRelationships()
- >>> len(rels.getResults())
- 1
- >>> rel = rels.getResults().values()[0]
- >>> rel.getProperties()
- {u'cmis:objectId': u'workspace://SpacesStore/271c48dd-6548-4771-a8f5-0de69b7cdc25', u'cmis:creationDate': None, u'cmis:objectTypeId': u'R:cmiscustom:assoc', u'cmis:lastModificationDate': None, u'cmis:targetId': u'workspace://SpacesStore/0ca1aa08-cb49-42e2-8881-53aa8496a1c1', u'cmis:lastModifiedBy': None, u'cmis:baseTypeId': u'cmis:relationship', u'cmis:sourceId': u'workspace://SpacesStore/271c48dd-6548-4771-a8f5-0de69b7cdc25', u'cmis:changeToken': None, u'cmis:createdBy': None}
-
- The following optional arguments are supported:
- - includeSubRelationshipTypes
- - relationshipDirection
- - typeId
- - maxItems
- - skipCount
- - filter
- - includeAllowableActions
- """
-
- url = self._getLink(RELATIONSHIPS_REL)
- assert url != None, 'Could not determine relationships URL'
-
- result = self._cmisClient.get(url.encode('utf-8'), **kwargs)
-
- # return the result set
- return ResultSet(self._cmisClient, self._repository, result)
-
- def removePolicy(self, policyId):
-
- """
- This is not yet implemented.
- """
-
- # depends on this object's canRemovePolicy allowable action
- if self.getAllowableActions()['canRemovePolicy']:
- raise NotImplementedError
- else:
- raise CmisException('This object has canRemovePolicy set to false')
-
- def getAppliedPolicies(self):
-
- """
- This is not yet implemented.
- """
-
- # depends on this object's canGetAppliedPolicies allowable action
- if self.getAllowableActions()['canGetAppliedPolicies']:
- raise NotImplementedError
- else:
- raise CmisException('This object has canGetAppliedPolicies set to false')
-
- def getACL(self):
-
- """
- Repository.getCapabilities['ACL'] must return manage or discover.
-
- >>> acl = folder.getACL()
- >>> acl.getEntries()
- {u'GROUP_EVERYONE': <cmislib.model.ACE object at 0x10071a8d0>, 'jdoe': <cmislib.model.ACE object at 0x10071a590>}
-
- The optional onlyBasicPermissions argument is currently not supported.
- """
-
- if self._repository.getCapabilities()['ACL']:
- # if the ACL capability is discover or manage, this must be
- # supported
- aclUrl = self._getLink(ACL_REL)
- result = self._cmisClient.get(aclUrl.encode('utf-8'))
- return ACL(xmlDoc=result)
- else:
- raise NotSupportedException
-
- def applyACL(self, acl):
-
- """
- Updates the object with the provided :class:`ACL`.
- Repository.getCapabilities['ACL'] must return manage to invoke this
- call.
-
- >>> acl = folder.getACL()
- >>> acl.addEntry(ACE('jdoe', 'cmis:write', 'true'))
- >>> acl.getEntries()
- {u'GROUP_EVERYONE': <cmislib.model.ACE object at 0x10071a8d0>, 'jdoe': <cmislib.model.ACE object at 0x10071a590>}
- """
-
- if self._repository.getCapabilities()['ACL'] == 'manage':
- # if the ACL capability is manage, this must be
- # supported
- # but it also depends on the canApplyACL allowable action
- # for this object
- if not isinstance(acl, ACL):
- raise CmisException('The ACL to apply must be an instance of the ACL class.')
- aclUrl = self._getLink(ACL_REL)
- assert aclUrl, "Could not determine the object's ACL URL."
- result = self._cmisClient.put(aclUrl.encode('utf-8'), acl.getXmlDoc().toxml(encoding='utf-8'), CMIS_ACL_TYPE)
- return ACL(xmlDoc=result)
- else:
- raise NotSupportedException
-
- def _getSelfLink(self):
-
- """
- Returns the URL used to retrieve this object.
- """
-
- url = self._getLink(SELF_REL)
-
- assert len(url) > 0, "Could not determine the self link."
-
- return url
-
- def _getLink(self, rel, ltype=None):
-
- """
- Returns the HREF attribute of an Atom link element for the
- specified rel.
- """
-
- if self.xmlDoc == None:
- self.reload()
- linkElements = self.xmlDoc.getElementsByTagNameNS(ATOM_NS, 'link')
-
- for linkElement in linkElements:
-
- if ltype:
- if linkElement.attributes.has_key('rel'):
- relAttr = linkElement.attributes['rel'].value
-
- if ltype and linkElement.attributes.has_key('type'):
- typeAttr = linkElement.attributes['type'].value
-
- if relAttr == rel and ltype.match(typeAttr):
- return linkElement.attributes['href'].value
- else:
- if linkElement.attributes.has_key('rel'):
- relAttr = linkElement.attributes['rel'].value
-
- if relAttr == rel:
- return linkElement.attributes['href'].value
-
- allowableActions = property(getAllowableActions)
- name = property(getName)
- id = property(getObjectId)
- properties = property(getProperties)
- title = property(getTitle)
- ACL = property(getACL)
-
-
-class Document(CmisObject):
-
- """
- An object typically associated with file content.
- """
-
- def checkout(self):
-
- """
- Performs a checkout on the :class:`Document` and returns the
- Private Working Copy (PWC), which is also an instance of
- :class:`Document`
-
- >>> doc.getObjectId()
- u'workspace://SpacesStore/f0c8b90f-bec0-4405-8b9c-2ab570589808;1.0'
- >>> doc.isCheckedOut()
- False
- >>> pwc = doc.checkout()
- >>> doc.isCheckedOut()
- True
- """
-
- # get the checkedout collection URL
- checkoutUrl = self._repository.getCollectionLink(CHECKED_OUT_COLL)
- assert len(checkoutUrl) > 0, "Could not determine the checkedout collection url."
-
- # get this document's object ID
- # build entry XML with it
- properties = {'cmis:objectId': self.getObjectId()}
- entryXmlDoc = getEntryXmlDoc(self._repository, properties=properties)
-
- # post it to to the checkedout collection URL
- result = self._cmisClient.post(checkoutUrl.encode('utf-8'),
- entryXmlDoc.toxml(encoding='utf-8'),
- ATOM_XML_ENTRY_TYPE)
-
- # now that the doc is checked out, we need to refresh the XML
- # to pick up the prop updates related to a checkout
- self.reload()
-
- return Document(self._cmisClient, self._repository, xmlDoc=result)
-
- def cancelCheckout(self):
- """
- Cancels the checkout of this object by retrieving the Private Working
- Copy (PWC) and then deleting it. After the PWC is deleted, this object
- will be reloaded to update properties related to a checkout.
-
- >>> doc.isCheckedOut()
- True
- >>> doc.cancelCheckout()
- >>> doc.isCheckedOut()
- False
- """
-
- pwcDoc = self.getPrivateWorkingCopy()
- if pwcDoc:
- pwcDoc.delete()
- self.reload()
-
- def getPrivateWorkingCopy(self):
-
- """
- Retrieves the object using the object ID in the property:
- cmis:versionSeriesCheckedOutId then uses getObject to instantiate
- the object.
-
- >>> doc.isCheckedOut()
- False
- >>> doc.checkout()
- <cmislib.model.Document object at 0x103a25ad0>
- >>> pwc = doc.getPrivateWorkingCopy()
- >>> pwc.getTitle()
- u'sample-b (Working Copy).pdf'
- """
-
- # reloading the document just to make sure we've got the latest
- # and greatest PWC ID
- self.reload()
- pwcDocId = self.getProperties()['cmis:versionSeriesCheckedOutId']
- if pwcDocId:
- return self._repository.getObject(pwcDocId)
-
- def isCheckedOut(self):
-
- """
- Returns true if the document is checked out.
-
- >>> doc.isCheckedOut()
- True
- >>> doc.cancelCheckout()
- >>> doc.isCheckedOut()
- False
- """
-
- # reloading the document just to make sure we've got the latest
- # and greatest checked out prop
- self.reload()
- return parseBoolValue(self.getProperties()['cmis:isVersionSeriesCheckedOut'])
-
- def getCheckedOutBy(self):
-
- """
- Returns the ID who currently has the document checked out.
- >>> pwc = doc.checkout()
- >>> pwc.getCheckedOutBy()
- u'admin'
- """
-
- # reloading the document just to make sure we've got the latest
- # and greatest checked out prop
- self.reload()
- return self.getProperties()['cmis:versionSeriesCheckedOutBy']
-
- def checkin(self, checkinComment=None, **kwargs):
-
- """
- Checks in this :class:`Document` which must be a private
- working copy (PWC).
-
- >>> doc.isCheckedOut()
- False
- >>> pwc = doc.checkout()
- >>> doc.isCheckedOut()
- True
- >>> pwc.checkin()
- <cmislib.model.Document object at 0x103a8ae90>
- >>> doc.isCheckedOut()
- False
-
- The following optional arguments are supported:
- - major
- - properties
- - contentStream
- - policies
- - addACEs
- - removeACEs
- """
-
- # Add checkin to kwargs and checkinComment, if it exists
- kwargs['checkin'] = 'true'
- kwargs['checkinComment'] = checkinComment
-
- # Build an empty ATOM entry
- entryXmlDoc = getEmptyXmlDoc()
-
- # Get the self link
- # Do a PUT of the empty ATOM to the self link
- url = self._getSelfLink()
- result = self._cmisClient.put(url.encode('utf-8'), entryXmlDoc.toxml(encoding='utf-8'), ATOM_XML_TYPE, **kwargs)
-
- return Document(self._cmisClient, self._repository, xmlDoc=result)
-
- def getLatestVersion(self, **kwargs):
-
- """
- Returns a :class:`Document` object representing the latest version in
- the version series.
-
- The following optional arguments are supported:
- - major
- - filter
- - includeRelationships
- - includePolicyIds
- - renditionFilter
- - includeACL
- - includeAllowableActions
-
- >>> latestDoc = doc.getLatestVersion()
- >>> latestDoc.getProperties()['cmis:versionLabel']
- u'2.1'
- >>> latestDoc = doc.getLatestVersion(major='false')
- >>> latestDoc.getProperties()['cmis:versionLabel']
- u'2.1'
- >>> latestDoc = doc.getLatestVersion(major='true')
- >>> latestDoc.getProperties()['cmis:versionLabel']
- u'2.0'
- """
-
- doc = None
- if kwargs.has_key('major') and kwargs['major'] == 'true':
- doc = self._repository.getObject(self.getObjectId(), returnVersion='latestmajor')
- else:
- doc = self._repository.getObject(self.getObjectId(), returnVersion='latest')
-
- return doc
-
- def getPropertiesOfLatestVersion(self, **kwargs):
-
- """
- Like :class:`^CmisObject.getProperties`, returns a dict of properties
- from the latest version of this object in the version series.
-
- The optional major and filter arguments are supported.
- """
-
- latestDoc = self.getLatestVersion(**kwargs)
-
- return latestDoc.getProperties()
-
- def getAllVersions(self, **kwargs):
-
- """
- Returns a :class:`ResultSet` of document objects for the entire
- version history of this object, including any PWC's.
-
- The optional filter and includeAllowableActions are
- supported.
- """
-
- # get the version history link
- versionsUrl = self._getLink(VERSION_HISTORY_REL)
-
- # invoke the URL
- result = self._cmisClient.get(versionsUrl.encode('utf-8'), **kwargs)
-
- # return the result set
- return ResultSet(self._cmisClient, self._repository, result)
-
- def getContentStream(self):
-
- """
- Returns the CMIS service response from invoking the 'enclosure' link.
-
- >>> doc.getName()
- u'sample-b.pdf'
- >>> o = open('tmp.pdf', 'wb')
- >>> result = doc.getContentStream()
- >>> o.write(result.read())
- >>> result.close()
- >>> o.close()
- >>> import os.path
- >>> os.path.getsize('tmp.pdf')
- 117248
-
- The optional streamId argument is not yet supported.
- """
-
- # TODO: Need to implement the streamId
-
- contentElements = self.xmlDoc.getElementsByTagNameNS(ATOM_NS, 'content')
-
- assert(len(contentElements) == 1), 'Expected to find exactly one atom:content element.'
-
- # if the src element exists, follow that
- if contentElements[0].attributes.has_key('src'):
- srcUrl = contentElements[0].attributes['src'].value
-
- # the cmis client class parses non-error responses
- result, content = Rest().get(srcUrl.encode('utf-8'),
- username=self._cmisClient.username,
- password=self._cmisClient.password,
- **self._cmisClient.extArgs)
- moduleLogger.debug('back from GET, status: %s' % result['status'])
- if result['status'] != '200':
- raise CmisException(result['status'])
- return StringIO.StringIO(content)
- else:
- # otherwise, try to return the value of the content element
- if contentElements[0].childNodes:
- return contentElements[0].childNodes[0].data
-
- def setContentStream(self, contentFile, contentType=None):
-
- """
- Sets the content stream on this object.
-
- The following optional arguments are not yet supported:
- - overwriteFlag=None
- """
-
- # get this object's content stream link
- contentElements = self.xmlDoc.getElementsByTagNameNS(ATOM_NS, 'content')
-
- assert(len(contentElements) == 1), 'Expected to find exactly one atom:content element.'
-
- # if the src element exists, follow that
- if contentElements[0].attributes.has_key('src'):
- srcUrl = contentElements[0].attributes['src'].value
-
- # there may be times when this URL is absent, but I'm not sure how to
- # set the content stream when that is the case
- assert(srcUrl), 'Unable to determine content stream URL.'
-
- # need to determine the mime type
- mimetype = contentType
- if not mimetype and hasattr(contentFile, 'name'):
- mimetype, encoding = mimetypes.guess_type(contentFile.name)
-
- if not mimetype:
- mimetype = 'application/binary'
-
- # if we have a change token, we must pass it back, per the spec
- args = {}
- if (self.properties.has_key('cmis:changeToken') and
- self.properties['cmis:changeToken'] != None):
- self.logger.debug('Change token present, adding it to args')
- args = {"changeToken": self.properties['cmis:changeToken']}
-
- # put the content file
- result = self._cmisClient.put(srcUrl.encode('utf-8'),
- contentFile.read(),
- mimetype,
- **args)
-
- # what comes back is the XML for the updated document,
- # which is not required by the spec to be the same document
- # we just updated, so use it to instantiate a new document
- # then return it
- return Document(self._cmisClient, self._repository, xmlDoc=result)
-
- def deleteContentStream(self):
-
- """
- Delete's the content stream associated with this object.
- """
-
- # get this object's content stream link
[... 1684 lines stripped ...]