cmislib browser binding createDocument and createDocumentFromString now working

git-svn-id: https://svn.apache.org/repos/asf/chemistry/cmislib/trunk@1592276 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/cmislib/__init__.py b/src/cmislib/__init__.py
index 08ee007..f865650 100644
--- a/src/cmislib/__init__.py
+++ b/src/cmislib/__init__.py
@@ -20,7 +20,8 @@
 Define package contents so that they are easy to import.
 """
 
-from model import CmisClient
-from domain import Repository, Folder
+from cmislib.model import CmisClient
+from cmislib.domain import Repository, Folder
+from cmislib.cmis_services import Binding, RepositoryServiceIfc
 
-__all__ = ["CmisClient", "Repository", "Folder"]
+__all__ = ["Binding", "CmisClient", "RepositoryServiceIfc", "Repository", "Folder"]
diff --git a/src/cmislib/atompub/__init__.py b/src/cmislib/atompub/__init__.py
new file mode 100644
index 0000000..6e30c70
--- /dev/null
+++ b/src/cmislib/atompub/__init__.py
@@ -0,0 +1 @@
+__author__ = 'jpotts'
diff --git a/src/cmislib/atompub_binding.py b/src/cmislib/atompub/binding.py
similarity index 99%
rename from src/cmislib/atompub_binding.py
rename to src/cmislib/atompub/binding.py
index 43aea85..5ba1de5 100644
--- a/src/cmislib/atompub_binding.py
+++ b/src/cmislib/atompub/binding.py
@@ -20,15 +20,14 @@
 Module containing the Atom Pub binding-specific objects used to work with a CMIS
 provider.
 """
-from cmis_services import RepositoryServiceIfc
-from cmis_services import Binding
-from domain import CmisId, CmisObject, ObjectType, Property, ACL, ACE, ChangeEntry, ResultSet, Rendition
-from net import RESTService as Rest
-from exceptions import CmisException, \
+from cmislib.cmis_services import Binding, RepositoryServiceIfc
+from cmislib.domain import CmisId, CmisObject, ObjectType, Property, ACL, ACE, ChangeEntry, ResultSet, Rendition
+from cmislib.net import RESTService as Rest
+from cmislib.exceptions import CmisException, \
     ObjectNotFoundException, InvalidArgumentException, \
     NotSupportedException
-from util import parseDateTimeValue
-import messages
+from cmislib.util import multiple_replace, parsePropValue, parseBoolValue, toCMISValue, parseDateTimeValue
+import cmislib.messages
 
 from urllib import quote
 from urlparse import urlparse, urlunparse
@@ -39,7 +38,6 @@
 import StringIO
 import logging
 from xml.dom import minidom
-from util import multiple_replace, parsePropValue, parseBoolValue, toCMISValue
 
 moduleLogger = logging.getLogger('cmislib.atompub_binding')
 
@@ -1882,7 +1880,7 @@
         If the types collection is specified, the method returns the result of
         `getTypeDefinitions` and ignores any optional params passed in.
 
-        >>> from cmislib.atompub_binding import TYPES_COLL
+        >>> from cmislib.atompub.atompub_binding import TYPES_COLL
         >>> types = repo.getCollection(TYPES_COLL)
         >>> len(types)
         4
@@ -1892,7 +1890,7 @@
         Otherwise, the collection URL is invoked, and a :class:`ResultSet` is
         returned.
 
-        >>> from cmislib.atompub_binding import CHECKED_OUT_COLL
+        >>> from cmislib.atompub.atompub_binding import CHECKED_OUT_COLL
         >>> resultSet = repo.getCollection(CHECKED_OUT_COLL)
         >>> len(resultSet.getResults())
         1
@@ -1917,7 +1915,7 @@
         Returns the link HREF from the specified collectionType
         ('checkedout', for example).
 
-        >>> from cmislib.atompub_binding import CHECKED_OUT_COLL
+        >>> from cmislib.atompub.atompub_binding import CHECKED_OUT_COLL
         >>> repo.getCollectionLink(CHECKED_OUT_COLL)
         u'http://localhost:8080/alfresco/s/cmis/checkedout'
 
@@ -3189,7 +3187,7 @@
         """
         Gets the HREF for the link element with the specified rel and linkType.
 
-        >>> from cmislib.atompub_binding import ATOM_XML_FEED_TYPE
+        >>> from cmislib.atompub.atompub_binding import ATOM_XML_FEED_TYPE
         >>> docType.getLink('down', ATOM_XML_FEED_TYPE)
         u'http://localhost:8080/alfresco/s/cmis/type/cmis:document/children'
         """
diff --git a/src/cmislib/browser/__init__.py b/src/cmislib/browser/__init__.py
new file mode 100644
index 0000000..6e30c70
--- /dev/null
+++ b/src/cmislib/browser/__init__.py
@@ -0,0 +1 @@
+__author__ = 'jpotts'
diff --git a/src/cmislib/browser_binding.py b/src/cmislib/browser/binding.py
similarity index 95%
rename from src/cmislib/browser_binding.py
rename to src/cmislib/browser/binding.py
index 3154c46..9fa215b 100644
--- a/src/cmislib/browser_binding.py
+++ b/src/cmislib/browser/binding.py
@@ -20,20 +20,20 @@
 Module containing the browser binding-specific objects used to work with a CMIS
 provider.
 """
-from cmis_services import RepositoryServiceIfc
-from cmis_services import Binding
-from net import RESTService as Rest
-from exceptions import CmisException, RuntimeException, ObjectNotFoundException
-from domain import CmisId, CmisObject, Repository, Relationship, Policy, ObjectType, Property, Folder, Document, ACL, ACE, ChangeEntry, ResultSet, ChangeEntryResultSet, Rendition
-from util import parsePropValueByType, parseBoolValue
+from cmislib.cmis_services import Binding, RepositoryServiceIfc
+from cmislib.domain import CmisId, CmisObject, Repository, Relationship, Policy, ObjectType, Property, Folder, Document, ACL, ACE, ChangeEntry, ResultSet, ChangeEntryResultSet, Rendition
+from cmislib.exceptions import CmisException, RuntimeException, ObjectNotFoundException
+from cmislib.net import RESTService as Rest
+from cmislib.util import parsePropValueByType, parseBoolValue
 import json
-import StringIO
 import logging
+import StringIO
+import time
 from urllib import urlencode, quote
 
 CMIS_FORM_TYPE = 'application/x-www-form-urlencoded;charset=utf-8'
 
-moduleLogger = logging.getLogger('cmislib.browser_binding')
+moduleLogger = logging.getLogger('cmislib.browser.binding')
 
 class BrowserBinding(Binding):
     def __init__(self, **kwargs):
@@ -1155,7 +1155,19 @@
         <cmislib.model.Document object at 0x101352ed0>
         """
 
-        pass
+        # if you didn't pass in a parent folder
+        if parentFolder is 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,
@@ -1189,7 +1201,47 @@
          - removeACEs
         """
 
-        pass
+        # if you didn't pass in a parent folder
+        if parentFolder is None:
+            # if the repository doesn't require fileable objects to be filed
+            if self.getCapabilities()['Unfiling']:
+                # has not been implemented
+                raise NotImplementedError
+            else:
+                # this repo requires fileable objects to be filed
+                raise InvalidArgumentException
+
+        # get the root folder URL
+        createDocUrl = self.getRootFolderUrl()
+
+        props = {"objectId" : parentFolder.id,
+                 "cmisaction" : "createDocument",
+                 "propertyId[0]" : "cmis:name",
+                 "propertyValue[0]" : name}
+
+        props["propertyId[1]"] = "cmis:objectTypeId"
+        if properties.has_key('cmis:objectTypeId'):
+            props["propertyValue[1]"] = properties['cmis:objectTypeId']
+        else:
+            props["propertyValue[1]"] = "cmis:document"
+
+        propCount = 2
+        for prop in properties:
+            props["propertyId[%s]" % propCount] = prop.key
+            props["propertyValue[%s]" % propCount] = prop
+            propCount += 1
+
+        contentType, body = encode_multipart_formdata(props, contentFile, contentType)
+
+        # invoke the URL
+        result = self._cmisClient.binding.post(createDocUrl.encode('utf-8'),
+                                               body,
+                                               contentType,
+                                               self._cmisClient.username,
+                                               self._cmisClient.password)
+
+        # return the result set
+        return BrowserDocument(self._cmisClient, self, data=result)
 
     def createDocumentFromSource(self,
                                  sourceId,
@@ -1783,7 +1835,7 @@
         of cmis:path with the relativePathSegment.
         """
 
-        byObjectIdUrl = self._repository.getRootFolderUrl() + "?objectId=" + self.getObjectId() + "&cmisselector=parents"
+        byObjectIdUrl = self._repository.getRootFolderUrl() + "?objectId=" + self.getObjectId() + "&cmisselector=parents&includerelativepathsegment=true"
         result = self._cmisClient.binding.get(byObjectIdUrl.encode('utf-8'),
                                                    self._cmisClient.username,
                                                    self._cmisClient.password)
@@ -1793,6 +1845,7 @@
         #TODO why is the call to getObjectParents() made if it isn't used?
         for res in result:
             path = res['object']['properties']['cmis:path']['value']
+            logging.debug(path)
             relativePathSegment = res['relativePathSegment']
 
             # concat with a slash
@@ -1881,39 +1934,12 @@
         >>> testFolder.createDocumentFromString('testdoc3', contentString='hello, world', contentType='text/plain')
         """
 
-        # get the root folder URL
-        createDocUrl = self._repository.getRootFolderUrl()
-
-        props = {"objectId" : self.id,
-                 "cmisaction" : "createDocument",
-                 "propertyId[0]" : "cmis:name",
-                 "propertyValue[0]" : name}
-
-        props["propertyId[1]"] = "cmis:objectTypeId"
-        if properties.has_key('cmis:objectTypeId'):
-            props["propertyValue[1]"] = properties['cmis:objectTypeId']
-        else:
-            props["propertyValue[1]"] = "cmis:document"
-
-        propCount = 2
-        for prop in properties:
-            props["propertyId[%s]" % propCount] = prop.key
-            props["propertyValue[%s]" % propCount] = prop
-            propCount += 1
-
-        #TODO this isn't working at the moment
-        props["content"] = contentString
-
-        # invoke the URL
-        result = self._cmisClient.binding.post(createDocUrl.encode('utf-8'),
-                                               urlencode(props),
-                                               'application/x-www-form-urlencoded',
-                                               self._cmisClient.username,
-                                               self._cmisClient.password)
-
-        # return the result set
-        return BrowserDocument(self._cmisClient, self._repository, data=result)
-
+        return self._repository.createDocumentFromString(name,
+                                                         properties,
+                                                         self,
+                                                         contentString,
+                                                         contentType,
+                                                         contentEncoding)
 
     def createDocument(self, name, properties={}, contentFile=None,
             contentType=None, contentEncoding=None):
@@ -1956,40 +1982,12 @@
          - removeACEs
         """
 
-        #TODO work in progress
-
-        # get the root folder URL
-        createDocUrl = self._repository.getRootFolderUrl()
-
-        props = {"objectId" : self.id,
-                 "cmisaction" : "createDocument",
-                 "propertyId[0]" : "cmis:name",
-                 "propertyValue[0]" : name}
-
-        props["propertyId[1]"] = "cmis:objectTypeId"
-        if properties.has_key('cmis:objectTypeId'):
-            pass
-        else:
-            props["propertyValue[1]"] = "cmis:document"
-
-        propCount = 2
-        for prop in properties:
-            props["propertyId[%s]" % propCount] = prop
-            props["propertyValue[%s]" % propCount] = properties[prop]
-            propCount += 1
-
-        #TODO this isn't working at the moment
-        props["content"] = 'this is a test'
-
-        # invoke the URL
-        result = self._cmisClient.binding.post(createDocUrl.encode('utf-8'),
-                                               urlencode(props),
-                                               'application/x-www-form-urlencoded',
-                                               self._cmisClient.username,
-                                               self._cmisClient.password)
-
-        # return the result set
-        return BrowserDocument(self._cmisClient, self._repository, data=result)
+        return self._repository.createDocument(name,
+                                               properties,
+                                               self,
+                                               contentFile,
+                                               contentType,
+                                               contentEncoding)
 
     def getChildren(self, **kwargs):
 
@@ -2896,3 +2894,36 @@
     # specify baseTypeId) or if the type isn't one of the known base
     # types, give the object back
     return obj
+
+def encode_multipart_formdata(fields, file, contentType):
+    """
+    fields is a sequence of (name, value) elements for regular form fields.
+    files is a sequence of (name, filename, value) elements for data to be uploaded as files
+    Return (content_type, body) ready for httplib.HTTP instance
+    """
+    boundary = 'aPacHeCheMIStrycMisLIb%s' % (int(time.time()))
+    crlf = '\r\n'
+    L = []
+    fileName = None
+    for (key, value) in fields.iteritems():
+        if (key == 'cmis:name'):
+            fileName = value
+        L.append('--' + boundary)
+        L.append('Content-Disposition: form-data; name="%s"' % key)
+        L.append('Content-Type: text/plain; charset=utf-8')
+        L.append('')
+        L.append(value)
+
+    if file:
+        L.append('--' + boundary)
+        L.append('Content-Disposition: form-data; name="%s"; filename=%s' % ('content', fileName))
+        L.append('Content-Type: %s' % contentType)
+        L.append('Content-Transfer-Encoding: binary')
+        L.append('')
+        L.append(file.read()) # content of file goes here
+
+    L.append('--' + boundary + '--')
+    L.append('')
+    body = crlf.join(L)
+    content_type = 'multipart/form-data; boundary=%s' % boundary
+    return content_type, body
\ No newline at end of file
diff --git a/src/cmislib/browser/serializers.py b/src/cmislib/browser/serializers.py
new file mode 100644
index 0000000..c92ec7b
--- /dev/null
+++ b/src/cmislib/browser/serializers.py
@@ -0,0 +1,14 @@
+import json
+
+from cmislib.browser.binding import BrowserFolder
+
+
+class FolderSerializer(object):
+    def toJSON(self, obj):
+        pass
+
+    def fromJSON(self, client, repo, jsonString):
+        obj = json.loads(jsonString)
+        objectId = obj['succinctProperties']['cmis:objectId']
+        folder = BrowserFolder(client, repo, objectId, properties=obj['succinctProperties'])
+        return folder
\ No newline at end of file
diff --git a/src/cmislib/cmis_services.py b/src/cmislib/cmis_services.py
index a6e09c0..5c0e107 100644
--- a/src/cmislib/cmis_services.py
+++ b/src/cmislib/cmis_services.py
@@ -19,7 +19,7 @@
 """
 Module containing the base Binding class and other service objects.
 """
-from exceptions import CmisException, RuntimeException, \
+from cmislib.exceptions import CmisException, RuntimeException, \
     ObjectNotFoundException, InvalidArgumentException, \
     PermissionDeniedException, NotSupportedException, \
     UpdateConflictException
diff --git a/src/cmislib/domain.py b/src/cmislib/domain.py
index a933434..b7b5327 100644
--- a/src/cmislib/domain.py
+++ b/src/cmislib/domain.py
@@ -957,7 +957,7 @@
         If the types collection is specified, the method returns the result of
         `getTypeDefinitions` and ignores any optional params passed in.
 
-        >>> from cmislib.atompub_binding import TYPES_COLL
+        >>> from cmislib.atompub.atompub_binding import TYPES_COLL
         >>> types = repo.getCollection(TYPES_COLL)
         >>> len(types)
         4
@@ -967,7 +967,7 @@
         Otherwise, the collection URL is invoked, and a :class:`ResultSet` is
         returned.
 
-        >>> from cmislib.atompub_binding import CHECKED_OUT_COLL
+        >>> from cmislib.atompub.atompub_binding import CHECKED_OUT_COLL
         >>> resultSet = repo.getCollection(CHECKED_OUT_COLL)
         >>> len(resultSet.getResults())
         1
@@ -1766,7 +1766,7 @@
         """
         Gets the HREF for the link element with the specified rel and linkType.
 
-        >>> from cmislib.atompub_binding import ATOM_XML_FEED_TYPE
+        >>> from cmislib.atompub.atompub_binding import ATOM_XML_FEED_TYPE
         >>> docType.getLink('down', ATOM_XML_FEED_TYPE)
         u'http://localhost:8080/alfresco/s/cmis/type/cmis:document/children'
         """
diff --git a/src/cmislib/model.py b/src/cmislib/model.py
index a17df1b..5e77c32 100644
--- a/src/cmislib/model.py
+++ b/src/cmislib/model.py
@@ -21,11 +21,12 @@
 keeping track of connection information. The name 'model' is no longer
 really appropriate, but it is kept for backwards compatibility.
 """
-from atompub_binding import AtomPubBinding
-from cmis_services import Binding
-from domain import CmisObject, Repository
 import logging
 
+from atompub.binding import AtomPubBinding
+from cmis_services import Binding
+
+
 moduleLogger = logging.getLogger('cmislib.model')
 
 class CmisClient(object):