chemistry-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From f...@apache.org
Subject svn commit: r1229067 - in /chemistry/opencmis/trunk: chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/ chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/ma...
Date Mon, 09 Jan 2012 09:19:18 GMT
Author: fmui
Date: Mon Jan  9 09:19:18 2012
New Revision: 1229067

URL: http://svn.apache.org/viewvc?rev=1229067&view=rev
Log:
more browser binding code

Modified:
    chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/AbstractBrowserBindingService.java
    chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/NavigationServiceImpl.java
    chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/ObjectServiceImpl.java
    chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/RepositoryUrlCache.java
    chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/Constants.java
    chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/JSONConstants.java
    chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/JSONConverter.java
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings/src/main/java/org/apache/chemistry/opencmis/server/impl/browser/CmisBrowserBindingServlet.java
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings/src/main/java/org/apache/chemistry/opencmis/server/impl/browser/ObjectService.java

Modified: chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/AbstractBrowserBindingService.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/AbstractBrowserBindingService.java?rev=1229067&r1=1229066&r2=1229067&view=diff
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/AbstractBrowserBindingService.java
(original)
+++ chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/AbstractBrowserBindingService.java
Mon Jan  9 09:19:18 2012
@@ -132,6 +132,21 @@ public abstract class AbstractBrowserBin
         return result;
     }
 
+    protected UrlBuilder getPathUrl(String repositoryId, String objectId, String selector)
{
+        UrlBuilder result = getRepositoryUrlCache().getPathUrl(repositoryId, objectId, selector);
+
+        if (result == null) {
+            getRepositoriesInternal(repositoryId);
+            result = getRepositoryUrlCache().getPathUrl(repositoryId, objectId, selector);
+        }
+
+        if (result == null) {
+            throw new CmisObjectNotFoundException("Unknown repository!");
+        }
+
+        return result;
+    }
+
     // ---- exceptions ----
 
     /**

Modified: chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/NavigationServiceImpl.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/NavigationServiceImpl.java?rev=1229067&r1=1229066&r2=1229067&view=diff
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/NavigationServiceImpl.java
(original)
+++ chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/NavigationServiceImpl.java
Mon Jan  9 09:19:18 2012
@@ -20,8 +20,10 @@ package org.apache.chemistry.opencmis.cl
 
 import java.math.BigInteger;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.chemistry.opencmis.client.bindings.spi.BindingSession;
+import org.apache.chemistry.opencmis.client.bindings.spi.http.HttpUtils;
 import org.apache.chemistry.opencmis.commons.data.ExtensionsData;
 import org.apache.chemistry.opencmis.commons.data.ObjectData;
 import org.apache.chemistry.opencmis.commons.data.ObjectInFolderContainer;
@@ -29,6 +31,9 @@ import org.apache.chemistry.opencmis.com
 import org.apache.chemistry.opencmis.commons.data.ObjectList;
 import org.apache.chemistry.opencmis.commons.data.ObjectParentData;
 import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
+import org.apache.chemistry.opencmis.commons.impl.Constants;
+import org.apache.chemistry.opencmis.commons.impl.JSONConverter;
+import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
 import org.apache.chemistry.opencmis.commons.spi.NavigationService;
 
 /**
@@ -46,41 +51,109 @@ public class NavigationServiceImpl exten
     public ObjectInFolderList getChildren(String repositoryId, String folderId, String filter,
String orderBy,
             Boolean includeAllowableActions, IncludeRelationships includeRelationships, String
renditionFilter,
             Boolean includePathSegment, BigInteger maxItems, BigInteger skipCount, ExtensionsData
extension) {
-        // TODO Auto-generated method stub
-        return null;
+        // build URL
+        UrlBuilder url = getObjectUrl(repositoryId, folderId, Constants.SELECTOR_CHILDREN);
+        url.addParameter(Constants.PARAM_FILTER, filter);
+        url.addParameter(Constants.PARAM_ORDER_BY, orderBy);
+        url.addParameter(Constants.PARAM_ALLOWABLE_ACTIONS, includeAllowableActions);
+        url.addParameter(Constants.PARAM_RELATIONSHIPS, includeRelationships);
+        url.addParameter(Constants.PARAM_RENDITION_FILTER, renditionFilter);
+        url.addParameter(Constants.PARAM_PATH_SEGMENT, includePathSegment);
+        url.addParameter(Constants.PARAM_MAX_ITEMS, maxItems);
+        url.addParameter(Constants.PARAM_SKIP_COUNT, skipCount);
+
+        // read and parse
+        HttpUtils.Response resp = read(url);
+        Map<String, Object> json = parseObject(resp.getStream(), resp.getCharset());
+
+        return JSONConverter.convertObjectInFolderList(json);
     }
 
     public List<ObjectInFolderContainer> getDescendants(String repositoryId, String
folderId, BigInteger depth,
             String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
             String renditionFilter, Boolean includePathSegment, ExtensionsData extension)
{
-        // TODO Auto-generated method stub
-        return null;
+        // build URL
+        UrlBuilder url = getObjectUrl(repositoryId, folderId, Constants.SELECTOR_DESCENDANTS);
+        url.addParameter(Constants.PARAM_DEPTH, depth);
+        url.addParameter(Constants.PARAM_FILTER, filter);
+        url.addParameter(Constants.PARAM_ALLOWABLE_ACTIONS, includeAllowableActions);
+        url.addParameter(Constants.PARAM_RELATIONSHIPS, includeRelationships);
+        url.addParameter(Constants.PARAM_RENDITION_FILTER, renditionFilter);
+        url.addParameter(Constants.PARAM_PATH_SEGMENT, includePathSegment);
+
+        // read and parse
+        HttpUtils.Response resp = read(url);
+        List<Object> json = parseArray(resp.getStream(), resp.getCharset());
+
+        return JSONConverter.convertDescendants(json);
     }
 
     public List<ObjectInFolderContainer> getFolderTree(String repositoryId, String
folderId, BigInteger depth,
             String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
             String renditionFilter, Boolean includePathSegment, ExtensionsData extension)
{
-        // TODO Auto-generated method stub
-        return null;
+        // build URL
+        UrlBuilder url = getObjectUrl(repositoryId, folderId, Constants.SELECTOR_FOLDER_TREE);
+        url.addParameter(Constants.PARAM_DEPTH, depth);
+        url.addParameter(Constants.PARAM_FILTER, filter);
+        url.addParameter(Constants.PARAM_ALLOWABLE_ACTIONS, includeAllowableActions);
+        url.addParameter(Constants.PARAM_RELATIONSHIPS, includeRelationships);
+        url.addParameter(Constants.PARAM_RENDITION_FILTER, renditionFilter);
+        url.addParameter(Constants.PARAM_PATH_SEGMENT, includePathSegment);
+
+        // read and parse
+        HttpUtils.Response resp = read(url);
+        List<Object> json = parseArray(resp.getStream(), resp.getCharset());
+
+        return JSONConverter.convertDescendants(json);
     }
 
     public List<ObjectParentData> getObjectParents(String repositoryId, String objectId,
String filter,
             Boolean includeAllowableActions, IncludeRelationships includeRelationships, String
renditionFilter,
             Boolean includeRelativePathSegment, ExtensionsData extension) {
-        // TODO Auto-generated method stub
-        return null;
+        // build URL
+        UrlBuilder url = getObjectUrl(repositoryId, objectId, Constants.SELECTOR_PARENTS);
+        url.addParameter(Constants.PARAM_FILTER, filter);
+        url.addParameter(Constants.PARAM_ALLOWABLE_ACTIONS, includeAllowableActions);
+        url.addParameter(Constants.PARAM_RELATIONSHIPS, includeRelationships);
+        url.addParameter(Constants.PARAM_RENDITION_FILTER, renditionFilter);
+        url.addParameter(Constants.PARAM_RELATIVE_PATH_SEGMENT, includeRelativePathSegment);
+
+        // read and parse
+        HttpUtils.Response resp = read(url);
+        List<Object> json = parseArray(resp.getStream(), resp.getCharset());
+
+        return JSONConverter.convertObjectParents(json);
     }
 
     public ObjectData getFolderParent(String repositoryId, String folderId, String filter,
ExtensionsData extension) {
-        // TODO Auto-generated method stub
-        return null;
+        // build URL
+        UrlBuilder url = getObjectUrl(repositoryId, folderId, Constants.SELECTOR_PARENT);
+        url.addParameter(Constants.PARAM_FILTER, filter);
+
+        // read and parse
+        HttpUtils.Response resp = read(url);
+        Map<String, Object> json = parseObject(resp.getStream(), resp.getCharset());
+
+        return JSONConverter.convertObject(json);
     }
 
     public ObjectList getCheckedOutDocs(String repositoryId, String folderId, String filter,
String orderBy,
             Boolean includeAllowableActions, IncludeRelationships includeRelationships, String
renditionFilter,
             BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
-        // TODO Auto-generated method stub
-        return null;
-    }
+        // build URL
+        UrlBuilder url = getObjectUrl(repositoryId, folderId, Constants.SELECTOR_CHECKEDOUT);
+        url.addParameter(Constants.PARAM_FILTER, filter);
+        url.addParameter(Constants.PARAM_ORDER_BY, orderBy);
+        url.addParameter(Constants.PARAM_ALLOWABLE_ACTIONS, includeAllowableActions);
+        url.addParameter(Constants.PARAM_RELATIONSHIPS, includeRelationships);
+        url.addParameter(Constants.PARAM_RENDITION_FILTER, renditionFilter);
+        url.addParameter(Constants.PARAM_MAX_ITEMS, maxItems);
+        url.addParameter(Constants.PARAM_SKIP_COUNT, skipCount);
+
+        // read and parse
+        HttpUtils.Response resp = read(url);
+        Map<String, Object> json = parseObject(resp.getStream(), resp.getCharset());
 
+        return JSONConverter.convertObjectList(json);
+    }
 }

Modified: chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/ObjectServiceImpl.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/ObjectServiceImpl.java?rev=1229067&r1=1229066&r2=1229067&view=diff
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/ObjectServiceImpl.java
(original)
+++ chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/ObjectServiceImpl.java
Mon Jan  9 09:19:18 2012
@@ -38,6 +38,7 @@ import org.apache.chemistry.opencmis.com
 import org.apache.chemistry.opencmis.commons.impl.Constants;
 import org.apache.chemistry.opencmis.commons.impl.JSONConverter;
 import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
+import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
 import org.apache.chemistry.opencmis.commons.spi.Holder;
 import org.apache.chemistry.opencmis.commons.spi.ObjectService;
 
@@ -86,8 +87,14 @@ public class ObjectServiceImpl extends A
     }
 
     public AllowableActions getAllowableActions(String repositoryId, String objectId, ExtensionsData
extension) {
-        // TODO Auto-generated method stub
-        return null;
+        // build URL
+        UrlBuilder url = getObjectUrl(repositoryId, objectId, Constants.SELECTOR_ALLOWABLEACTIONS);
+
+        // read and parse
+        HttpUtils.Response resp = read(url);
+        Map<String, Object> json = parseObject(resp.getStream(), resp.getCharset());
+
+        return JSONConverter.convertAllowableActions(json);
     }
 
     public ObjectData getObject(String repositoryId, String objectId, String filter, Boolean
includeAllowableActions,
@@ -109,6 +116,25 @@ public class ObjectServiceImpl extends A
         return JSONConverter.convertObject(json);
     }
 
+    public ObjectData getObjectByPath(String repositoryId, String path, String filter, Boolean
includeAllowableActions,
+            IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds,
+            Boolean includeAcl, ExtensionsData extension) {
+        // build URL
+        UrlBuilder url = getPathUrl(repositoryId, path, Constants.SELECTOR_OBJECT);
+        url.addParameter(Constants.PARAM_FILTER, filter);
+        url.addParameter(Constants.PARAM_ALLOWABLE_ACTIONS, includeAllowableActions);
+        url.addParameter(Constants.PARAM_RELATIONSHIPS, includeRelationships);
+        url.addParameter(Constants.PARAM_RENDITION_FILTER, renditionFilter);
+        url.addParameter(Constants.PARAM_POLICY_IDS, includePolicyIds);
+        url.addParameter(Constants.PARAM_ACL, includeAcl);
+
+        // read and parse
+        HttpUtils.Response resp = read(url);
+        Map<String, Object> json = parseObject(resp.getStream(), resp.getCharset());
+
+        return JSONConverter.convertObject(json);
+    }
+
     public Properties getProperties(String repositoryId, String objectId, String filter,
ExtensionsData extension) {
         // TODO Auto-generated method stub
         return null;
@@ -120,17 +146,28 @@ public class ObjectServiceImpl extends A
         return null;
     }
 
-    public ObjectData getObjectByPath(String repositoryId, String path, String filter, Boolean
includeAllowableActions,
-            IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds,
-            Boolean includeAcl, ExtensionsData extension) {
-        // TODO Auto-generated method stub
-        return null;
-    }
-
     public ContentStream getContentStream(String repositoryId, String objectId, String streamId,
BigInteger offset,
             BigInteger length, ExtensionsData extension) {
-        // TODO Auto-generated method stub
-        return null;
+        ContentStreamImpl result = new ContentStreamImpl();
+
+        // build URL
+        UrlBuilder url = getObjectUrl(repositoryId, objectId, Constants.SELECTOR_CONTENT);
+        url.addParameter(Constants.PARAM_STREAM_ID, streamId);
+
+        // get the content
+        HttpUtils.Response resp = HttpUtils.invokeGET(url, getSession(), offset, length);
+
+        // check response code
+        if ((resp.getResponseCode() != 200) && (resp.getResponseCode() != 206)) {
+            throw convertStatusCode(resp.getResponseCode(), resp.getResponseMessage(), resp.getErrorContent(),
null);
+        }
+
+        result.setFileName(null);
+        result.setLength(resp.getContentLength());
+        result.setMimeType(resp.getContentTypeHeader());
+        result.setStream(resp.getStream());
+
+        return result;
     }
 
     public void updateProperties(String repositoryId, Holder<String> objectId, Holder<String>
changeToken,

Modified: chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/RepositoryUrlCache.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/RepositoryUrlCache.java?rev=1229067&r1=1229066&r2=1229067&view=diff
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/RepositoryUrlCache.java
(original)
+++ chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/browser/RepositoryUrlCache.java
Mon Jan  9 09:19:18 2012
@@ -153,4 +153,33 @@ public class RepositoryUrlCache implemen
 
         return result;
     }
+
+    /**
+     * Returns an object URL with the given selector.
+     */
+    public UrlBuilder getPathUrl(String repositoryId, String path) {
+        String root = getRootUrl(repositoryId);
+        if (root == null) {
+            return null;
+        }
+
+        UrlBuilder result = new UrlBuilder(root);
+        result.addPath(path);
+
+        return result;
+    }
+
+    /**
+     * Returns an object URL with the given selector.
+     */
+    public UrlBuilder getPathUrl(String repositoryId, String path, String selector) {
+        UrlBuilder result = getObjectUrl(repositoryId, path);
+        if (result == null) {
+            return null;
+        }
+
+        result.addParameter(Constants.PARAM_SELECTOR, selector);
+
+        return result;
+    }
 }

Modified: chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/Constants.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/Constants.java?rev=1229067&r1=1229066&r2=1229067&view=diff
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/Constants.java
(original)
+++ chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/Constants.java
Mon Jan  9 09:19:18 2012
@@ -102,6 +102,7 @@ public final class Constants {
     public static final String SELECTOR_CONTENT = "content";
     public static final String SELECTOR_OBJECT = "object";
     public static final String SELECTOR_PROPERTIES = "properties";
+    public static final String SELECTOR_ALLOWABLEACTIONS = "allowableActions";
     public static final String SELECTOR_RENDITIONS = "renditions";
     public static final String SELECTOR_CHILDREN = "children";
     public static final String SELECTOR_DESCENDANTS = "descendants";

Modified: chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/JSONConstants.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/JSONConstants.java?rev=1229067&r1=1229066&r2=1229067&view=diff
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/JSONConstants.java
(original)
+++ chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/JSONConstants.java
Mon Jan  9 09:19:18 2012
@@ -153,6 +153,7 @@ public class JSONConstants {
         OBJECT_KEYS.add(JSON_OBJECT_RELATIONSHIPS);
         OBJECT_KEYS.add(JSON_OBJECT_CHANGE_EVENT_INFO);
         OBJECT_KEYS.add(JSON_OBJECT_ACL);
+        OBJECT_KEYS.add(JSON_OBJECT_EXACT_ACL);
         OBJECT_KEYS.add(JSON_OBJECT_POLICY_IDS);
         OBJECT_KEYS.add(JSON_OBJECT_RENDITIONS);
     }
@@ -166,9 +167,22 @@ public class JSONConstants {
 
     public static final String JSON_OBJECTINFOLDER_OBJECT = "object";
     public static final String JSON_OBJECTINFOLDER_PATH_SEGMENT = "pathSegment";
+
+    public static final Set<String> OBJECTINFOLDER_KEYS = new HashSet<String>();
+    static {
+        OBJECTINFOLDER_KEYS.add(JSON_OBJECTINFOLDER_OBJECT);
+        OBJECTINFOLDER_KEYS.add(JSON_OBJECTINFOLDER_PATH_SEGMENT);
+    }
+
     public static final String JSON_OBJECTPARENTS_OBJECT = "object";
     public static final String JSON_OBJECTPARENTS_RELATIVE_PATH_SEGMENT = "relativePathSegment";
 
+    public static final Set<String> OBJECTPARENTS_KEYS = new HashSet<String>();
+    static {
+        OBJECTPARENTS_KEYS.add(JSON_OBJECTPARENTS_OBJECT);
+        OBJECTPARENTS_KEYS.add(JSON_OBJECTPARENTS_RELATIVE_PATH_SEGMENT);
+    }
+
     public static final String JSON_PROPERTY_ID = "id";
     public static final String JSON_PROPERTY_LOCALNAME = "localName";
     public static final String JSON_PROPERTY_DISPLAYNAME = "displayName";
@@ -177,6 +191,17 @@ public class JSONConstants {
     public static final String JSON_PROPERTY_DATATYPE = "type";
     public static final String JSON_PROPERTY_CARDINALITY = "cardinality";
 
+    public static final Set<String> PROPERTY_KEYS = new HashSet<String>();
+    static {
+        PROPERTY_KEYS.add(JSON_PROPERTY_ID);
+        PROPERTY_KEYS.add(JSON_PROPERTY_LOCALNAME);
+        PROPERTY_KEYS.add(JSON_PROPERTY_DISPLAYNAME);
+        PROPERTY_KEYS.add(JSON_PROPERTY_QUERYNAME);
+        PROPERTY_KEYS.add(JSON_PROPERTY_QUERYNAME);
+        PROPERTY_KEYS.add(JSON_PROPERTY_DATATYPE);
+        PROPERTY_KEYS.add(JSON_PROPERTY_CARDINALITY);
+    }
+
     public static final String JSON_CHANGE_EVENT_TYPE = "changeType";
     public static final String JSON_CHANGE_EVENT_TIME = "changeTime";
 
@@ -222,17 +247,49 @@ public class JSONConstants {
     public static final String JSON_RENDITION_WIDTH = "width";
     public static final String JSON_RENDITION_DOCUMENT_ID = "renditionDocumentId";
 
+    public static final Set<String> RENDITION_KEYS = new HashSet<String>();
+    static {
+        RENDITION_KEYS.add(JSON_RENDITION_STREAM_ID);
+        RENDITION_KEYS.add(JSON_RENDITION_MIMETYPE);
+        RENDITION_KEYS.add(JSON_RENDITION_LENGTH);
+        RENDITION_KEYS.add(JSON_RENDITION_KIND);
+        RENDITION_KEYS.add(JSON_RENDITION_TITLE);
+        RENDITION_KEYS.add(JSON_RENDITION_HEIGHT);
+        RENDITION_KEYS.add(JSON_RENDITION_WIDTH);
+        RENDITION_KEYS.add(JSON_RENDITION_DOCUMENT_ID);
+    }
+
     public static final String JSON_OBJECTLIST_OBJECTS = "objects";
     public static final String JSON_OBJECTLIST_HAS_MORE_ITEMS = "hasMoreItems";
     public static final String JSON_OBJECTLIST_NUM_ITEMS = "numItems";
 
+    public static final Set<String> OBJECTLIST_KEYS = new HashSet<String>();
+    static {
+        OBJECTLIST_KEYS.add(JSON_OBJECTLIST_OBJECTS);
+        OBJECTLIST_KEYS.add(JSON_OBJECTLIST_HAS_MORE_ITEMS);
+        OBJECTLIST_KEYS.add(JSON_OBJECTLIST_NUM_ITEMS);
+    }
+
     public static final String JSON_OBJECTINFOLDERLIST_OBJECTS = "objects";
     public static final String JSON_OBJECTINFOLDERLIST_HAS_MORE_ITEMS = "hasMoreItems";
     public static final String JSON_OBJECTINFOLDERLIST_NUM_ITEMS = "numItems";
 
+    public static final Set<String> OBJECTINFOLDERLIST_KEYS = new HashSet<String>();
+    static {
+        OBJECTINFOLDERLIST_KEYS.add(JSON_OBJECTINFOLDERLIST_OBJECTS);
+        OBJECTINFOLDERLIST_KEYS.add(JSON_OBJECTINFOLDERLIST_HAS_MORE_ITEMS);
+        OBJECTINFOLDERLIST_KEYS.add(JSON_OBJECTINFOLDERLIST_NUM_ITEMS);
+    }
+
     public static final String JSON_OBJECTINFOLDERCONTAINER_OBJECT = "object";
     public static final String JSON_OBJECTINFOLDERCONTAINER_CHILDREN = "children";
 
+    public static final Set<String> OBJECTINFOLDERCONTAINER_KEYS = new HashSet<String>();
+    static {
+        OBJECTINFOLDERCONTAINER_KEYS.add(JSON_OBJECTINFOLDERCONTAINER_OBJECT);
+        OBJECTINFOLDERCONTAINER_KEYS.add(JSON_OBJECTINFOLDERCONTAINER_CHILDREN);
+    }
+
     public static final String JSON_TYPE_ID = "id";
     public static final String JSON_TYPE_LOCALNAME = "localName";
     public static final String JSON_TYPE_LOCALNAMESPACE = "localNamespace";

Modified: chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/JSONConverter.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/JSONConverter.java?rev=1229067&r1=1229066&r2=1229067&view=diff
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/JSONConverter.java
(original)
+++ chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/JSONConverter.java
Mon Jan  9 09:19:18 2012
@@ -109,6 +109,11 @@ import org.apache.chemistry.opencmis.com
 import org.apache.chemistry.opencmis.commons.impl.dataobjects.DocumentTypeDefinitionImpl;
 import org.apache.chemistry.opencmis.commons.impl.dataobjects.FolderTypeDefinitionImpl;
 import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectDataImpl;
+import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderContainerImpl;
+import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderDataImpl;
+import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderListImpl;
+import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectListImpl;
+import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectParentDataImpl;
 import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionDefinitionDataImpl;
 import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionMappingDataImpl;
 import org.apache.chemistry.opencmis.commons.impl.dataobjects.PolicyIdListImpl;
@@ -131,6 +136,7 @@ import org.apache.chemistry.opencmis.com
 import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriDefinitionImpl;
 import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriImpl;
 import org.apache.chemistry.opencmis.commons.impl.dataobjects.RelationshipTypeDefinitionImpl;
+import org.apache.chemistry.opencmis.commons.impl.dataobjects.RenditionDataImpl;
 import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryCapabilitiesImpl;
 import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryInfoBrowserBindingImpl;
 import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionContainerImpl;
@@ -284,7 +290,6 @@ public class JSONConverter {
         return result;
     }
 
-    @SuppressWarnings("unchecked")
     public static RepositoryInfo convertRepositoryInfo(Map<String, Object> json) {
         if (json == null) {
             return null;
@@ -1400,18 +1405,17 @@ public class JSONConverter {
         TypeDefinitionListImpl result = new TypeDefinitionListImpl();
 
         Object typesList = json.get(JSON_TYPESLIST_TYPES);
-        if (typesList instanceof List) {
-            List<TypeDefinition> list = new ArrayList<TypeDefinition>();
+        List<TypeDefinition> types = new ArrayList<TypeDefinition>();
 
+        if (typesList instanceof List) {
             for (Object type : (List) typesList) {
                 if (type instanceof Map) {
-                    list.add(convertTypeDefinition((Map<String, Object>) type));
+                    types.add(convertTypeDefinition((Map<String, Object>) type));
                 }
             }
-
-            result.setList(list);
         }
 
+        result.setList(types);
         result.setHasMoreItems(getBoolean(json, JSON_TYPESLIST_HAS_MORE_ITEMS));
         result.setNumItems(getInteger(json, JSON_TYPESLIST_NUM_ITEMS));
 
@@ -1451,10 +1455,14 @@ public class JSONConverter {
      */
     @SuppressWarnings({ "unchecked" })
     public static List<TypeDefinitionContainer> convertTypeDescendants(List<Object>
json) {
-        if (json == null || json.isEmpty()) {
+        if (json == null) {
             return null;
         }
 
+        if (json.isEmpty()) {
+            return Collections.emptyList();
+        }
+
         List<TypeDefinitionContainer> result = new ArrayList<TypeDefinitionContainer>();
 
         for (Object obj : json) {
@@ -1467,6 +1475,8 @@ public class JSONConverter {
                 Object children = jsonContainer.get(JSON_TYPESCONTAINER_CHILDREN);
                 if (children instanceof List) {
                     container.setChildren(convertTypeDescendants((List<Object>) children));
+                } else {
+                    container.setChildren((List<TypeDefinitionContainer>) Collections.EMPTY_LIST);
                 }
 
                 convertExtension(jsonContainer, container, TYPESCONTAINER_KEYS);
@@ -1489,7 +1499,7 @@ public class JSONConverter {
         ObjectDataImpl result = new ObjectDataImpl();
 
         result.setAcl(convertAcl(getMap(json.get(JSON_OBJECT_ACL)), getBoolean(json, JSON_OBJECT_EXACT_ACL)));
-        result.setAllowableActions(convertAllowableActions(getMap(json)));
+        result.setAllowableActions(convertAllowableActions(getMap(json.get(JSON_OBJECT_ALLOWABLE_ACTIONS))));
         Map<String, Object> jsonChangeEventInfo = getMap(json.get(JSON_OBJECT_CHANGE_EVENT_INFO));
         if (jsonChangeEventInfo != null) {
             ChangeEventInfoDataImpl changeEventInfo = new ChangeEventInfoDataImpl();
@@ -1502,8 +1512,30 @@ public class JSONConverter {
             result.setChangeEventInfo(changeEventInfo);
         }
         result.setIsExactAcl(getBoolean(json, JSON_OBJECT_EXACT_ACL));
-        result.setPolicyIds(convertPolicyIds(getList(JSON_OBJECT_POLICY_IDS)));
+        result.setPolicyIds(convertPolicyIds(getList(json.get(JSON_OBJECT_POLICY_IDS))));
         result.setProperties(convertProperties(getMap(json.get(JSON_OBJECT_PROPERTIES))));
+        List<Object> jsonRelationships = getList(json.get(JSON_OBJECT_RELATIONSHIPS));
+        if (jsonRelationships != null) {
+            List<ObjectData> relationships = new ArrayList<ObjectData>();
+            for (Object obj : jsonRelationships) {
+                ObjectData relationship = convertObject(getMap(obj));
+                if (relationship != null) {
+                    relationships.add(relationship);
+                }
+            }
+            result.setRelationships(relationships);
+        }
+        List<Object> jsonRenditions = getList(json.get(JSON_OBJECT_RENDITIONS));
+        if (jsonRenditions != null) {
+            List<RenditionData> renditions = new ArrayList<RenditionData>();
+            for (Object obj : jsonRenditions) {
+                RenditionData rendition = convertRendition(getMap(obj));
+                if (rendition != null) {
+                    renditions.add(rendition);
+                }
+            }
+            result.setRenditions(renditions);
+        }
 
         // TODO
 
@@ -1545,7 +1577,7 @@ public class JSONConverter {
                         ace.setPermissions(permissions);
                     }
 
-                    Map<String, Object> jsonPrincipal = getMap(JSON_ACE_PRINCIPAL);
+                    Map<String, Object> jsonPrincipal = getMap(entry.get(JSON_ACE_PRINCIPAL));
                     if (jsonPrincipal != null) {
                         AccessControlPrincipalDataImpl principal = new AccessControlPrincipalDataImpl();
 
@@ -1802,10 +1834,195 @@ public class JSONConverter {
                 property.setQueryName(getString(jsonPropertyMap, JSON_PROPERTY_QUERYNAME));
                 property.setLocalName(getString(jsonPropertyMap, JSON_PROPERTY_LOCALNAME));
 
+                convertExtension(jsonPropertyMap, property, PROPERTY_KEYS);
+
                 result.addProperty(property);
             }
         }
 
+        // TODO: properties extensions
+
+        return result;
+    }
+
+    /**
+     * Converts a rendition.
+     */
+    public static RenditionData convertRendition(Map<String, Object> json) {
+        if (json == null) {
+            return null;
+        }
+
+        RenditionDataImpl result = new RenditionDataImpl();
+
+        result.setBigHeight(getInteger(json, JSON_RENDITION_HEIGHT));
+        result.setKind(getString(json, JSON_RENDITION_KIND));
+        result.setBigLength(getInteger(json, JSON_RENDITION_LENGTH));
+        result.setMimeType(getString(json, JSON_RENDITION_MIMETYPE));
+        result.setRenditionDocumentId(getString(json, JSON_RENDITION_DOCUMENT_ID));
+        result.setStreamId(getString(json, JSON_RENDITION_STREAM_ID));
+        result.setTitle(getString(json, JSON_RENDITION_TITLE));
+        result.setBigWidth(getInteger(json, JSON_RENDITION_WIDTH));
+
+        convertExtension(json, result, RENDITION_KEYS);
+
+        return result;
+    }
+
+    /**
+     * Converts a object list.
+     */
+    public static ObjectInFolderList convertObjectInFolderList(Map<String, Object>
json) {
+        if (json == null) {
+            return null;
+        }
+
+        ObjectInFolderListImpl result = new ObjectInFolderListImpl();
+
+        List<Object> jsonChildren = getList(json.get(JSON_OBJECTINFOLDERLIST_OBJECTS));
+        List<ObjectInFolderData> objects = new ArrayList<ObjectInFolderData>();
+
+        if (jsonChildren != null) {
+            for (Object obj : jsonChildren) {
+                Map<String, Object> jsonObject = getMap(obj);
+                if (jsonObject != null) {
+                    objects.add(convertObjectInFolder(jsonObject));
+                }
+            }
+        }
+
+        result.setObjects(objects);
+        result.setHasMoreItems(getBoolean(json, JSON_OBJECTINFOLDERLIST_HAS_MORE_ITEMS));
+        result.setNumItems(getInteger(json, JSON_OBJECTINFOLDERLIST_NUM_ITEMS));
+
+        convertExtension(json, result, OBJECTINFOLDERLIST_KEYS);
+
+        return result;
+    }
+
+    /**
+     * Converts an object in a folder.
+     */
+    public static ObjectInFolderData convertObjectInFolder(Map<String, Object> json)
{
+        if (json == null) {
+            return null;
+        }
+
+        ObjectInFolderDataImpl result = new ObjectInFolderDataImpl();
+
+        result.setObject(convertObject(getMap(json.get(JSON_OBJECTINFOLDER_OBJECT))));
+        result.setPathSegment(getString(json, JSON_OBJECTINFOLDER_PATH_SEGMENT));
+
+        convertExtension(json, result, OBJECTINFOLDER_KEYS);
+
+        return result;
+    }
+
+    /**
+     * Converts a descendants tree.
+     */
+    public static List<ObjectInFolderContainer> convertDescendants(List<Object>
json) {
+        if (json == null) {
+            return null;
+        }
+
+        List<ObjectInFolderContainer> result = new ArrayList<ObjectInFolderContainer>();
+
+        for (Object obj : json) {
+            Map<String, Object> desc = getMap(obj);
+            if (desc != null) {
+                result.add(convertDescendant(desc));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Converts a descendant.
+     */
+    public static ObjectInFolderContainer convertDescendant(Map<String, Object> json)
{
+        if (json == null) {
+            return null;
+        }
+
+        ObjectInFolderContainerImpl result = new ObjectInFolderContainerImpl();
+
+        result.setObject(convertObjectInFolder(getMap(json.get(JSON_OBJECTINFOLDERCONTAINER_OBJECT))));
+
+        List<ObjectInFolderContainer> containerList = new ArrayList<ObjectInFolderContainer>();
+        List<Object> jsonContainerList = getList(json.get(JSON_OBJECTINFOLDERCONTAINER_CHILDREN));
+        if (jsonContainerList != null) {
+            for (Object obj : jsonContainerList) {
+                Map<String, Object> containerChild = getMap(obj);
+                if (containerChild != null) {
+                    containerList.add(convertDescendant(containerChild));
+                }
+            }
+        }
+
+        result.setChildren(containerList);
+
+        convertExtension(json, result, OBJECTINFOLDERCONTAINER_KEYS);
+
+        return result;
+    }
+
+    /**
+     * Converts an object parents list.
+     */
+    public static List<ObjectParentData> convertObjectParents(List<Object> json)
{
+        if (json == null) {
+            return null;
+        }
+
+        List<ObjectParentData> result = new ArrayList<ObjectParentData>();
+
+        for (Object obj : json) {
+            Map<String, Object> jsonParent = getMap(obj);
+            if (jsonParent != null) {
+                ObjectParentDataImpl parent = new ObjectParentDataImpl();
+
+                parent.setObject(convertObject(getMap(jsonParent.get(JSON_OBJECTPARENTS_OBJECT))));
+                parent.setRelativePathSegment(getString(jsonParent, JSON_OBJECTPARENTS_RELATIVE_PATH_SEGMENT));
+
+                convertExtension(jsonParent, parent, OBJECTPARENTS_KEYS);
+
+                result.add(parent);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Converts a object list.
+     */
+    public static ObjectList convertObjectList(Map<String, Object> json) {
+        if (json == null) {
+            return null;
+        }
+
+        ObjectListImpl result = new ObjectListImpl();
+
+        List<Object> jsonChildren = getList(json.get(JSON_OBJECTLIST_OBJECTS));
+        List<ObjectData> objects = new ArrayList<ObjectData>();
+
+        if (jsonChildren != null) {
+            for (Object obj : jsonChildren) {
+                Map<String, Object> jsonObject = getMap(obj);
+                if (jsonObject != null) {
+                    objects.add(convertObject(jsonObject));
+                }
+            }
+        }
+
+        result.setObjects(objects);
+        result.setHasMoreItems(getBoolean(json, JSON_OBJECTLIST_HAS_MORE_ITEMS));
+        result.setNumItems(getInteger(json, JSON_OBJECTLIST_NUM_ITEMS));
+
+        convertExtension(json, result, OBJECTLIST_KEYS);
+
         return result;
     }
 
@@ -2032,20 +2249,30 @@ public class JSONConverter {
 
     @SuppressWarnings("unchecked")
     public static Map<String, Object> getMap(Object o) {
+        if (o == null) {
+            return null;
+        }
+
         if (o instanceof Map) {
             return (Map<String, Object>) o;
         }
 
-        return null;
+        throw new CmisRuntimeException("Expected a JSON object but found a "
+                + (o instanceof List ? "JSON array" : o.getClass().getSimpleName()) + ":
" + o.toString());
     }
 
     @SuppressWarnings("unchecked")
     public static List<Object> getList(Object o) {
+        if (o == null) {
+            return null;
+        }
+
         if (o instanceof List) {
             return (List<Object>) o;
         }
 
-        return null;
+        throw new CmisRuntimeException("Expected a JSON array but found a "
+                + (o instanceof List ? "JSON object" : o.getClass().getSimpleName()) + ":
" + o.toString());
     }
 
     public static String getString(@SuppressWarnings("rawtypes") Map json, String key) {

Modified: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings/src/main/java/org/apache/chemistry/opencmis/server/impl/browser/CmisBrowserBindingServlet.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings/src/main/java/org/apache/chemistry/opencmis/server/impl/browser/CmisBrowserBindingServlet.java?rev=1229067&r1=1229066&r2=1229067&view=diff
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings/src/main/java/org/apache/chemistry/opencmis/server/impl/browser/CmisBrowserBindingServlet.java
(original)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings/src/main/java/org/apache/chemistry/opencmis/server/impl/browser/CmisBrowserBindingServlet.java
Mon Jan  9 09:19:18 2012
@@ -39,6 +39,7 @@ import static org.apache.chemistry.openc
 import static org.apache.chemistry.opencmis.commons.impl.Constants.CMISACTION_SET_CONTENT;
 import static org.apache.chemistry.opencmis.commons.impl.Constants.PARAM_OBJECT_ID;
 import static org.apache.chemistry.opencmis.commons.impl.Constants.SELECTOR_ACL;
+import static org.apache.chemistry.opencmis.commons.impl.Constants.SELECTOR_ALLOWABLEACTIONS;
 import static org.apache.chemistry.opencmis.commons.impl.Constants.SELECTOR_CHECKEDOUT;
 import static org.apache.chemistry.opencmis.commons.impl.Constants.SELECTOR_CHILDREN;
 import static org.apache.chemistry.opencmis.commons.impl.Constants.SELECTOR_CONTENT;
@@ -175,6 +176,8 @@ public class CmisBrowserBindingServlet e
 
             rootDispatcher.addResource(SELECTOR_OBJECT, METHOD_GET, ObjectService.class,
"getObject");
             rootDispatcher.addResource(SELECTOR_PROPERTIES, METHOD_GET, ObjectService.class,
"getProperties");
+            rootDispatcher.addResource(SELECTOR_ALLOWABLEACTIONS, METHOD_GET, ObjectService.class,
+                    "getAllowableActions");
             rootDispatcher.addResource(SELECTOR_RENDITIONS, METHOD_GET, ObjectService.class,
"getRenditions");
             rootDispatcher.addResource(SELECTOR_CONTENT, METHOD_GET, ObjectService.class,
"getContentStream");
             rootDispatcher.addResource(SELECTOR_CHILDREN, METHOD_GET, NavigationService.class,
"getChildren");

Modified: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings/src/main/java/org/apache/chemistry/opencmis/server/impl/browser/ObjectService.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings/src/main/java/org/apache/chemistry/opencmis/server/impl/browser/ObjectService.java?rev=1229067&r1=1229066&r2=1229067&view=diff
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings/src/main/java/org/apache/chemistry/opencmis/server/impl/browser/ObjectService.java
(original)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings/src/main/java/org/apache/chemistry/opencmis/server/impl/browser/ObjectService.java
Mon Jan  9 09:19:18 2012
@@ -62,6 +62,7 @@ import java.util.List;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.chemistry.opencmis.commons.data.AllowableActions;
 import org.apache.chemistry.opencmis.commons.data.ContentStream;
 import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData;
 import org.apache.chemistry.opencmis.commons.data.ObjectData;
@@ -364,6 +365,22 @@ public final class ObjectService {
     }
 
     /**
+     * getAllowableActions.
+     */
+    public static void getAllowableActions(CallContext context, CmisService service, String
repositoryId,
+            HttpServletRequest request, HttpServletResponse response) throws Exception {
+        // get parameters
+        String objectId = (String) context.get(CONTEXT_OBJECT_ID);
+
+        AllowableActions allowableActions = service.getAllowableActions(repositoryId, objectId,
null);
+
+        JSONObject jsonAllowableActions = JSONConverter.convert(allowableActions);
+
+        response.setStatus(HttpServletResponse.SC_OK);
+        writeJSON(jsonAllowableActions, request, response);
+    }
+
+    /**
      * getRenditions.
      */
     @SuppressWarnings("unchecked")



Mime
View raw message