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 ...]