{"id":37028,"date":"2010-03-01T08:00:19","date_gmt":"2010-03-01T15:00:19","guid":{"rendered":"http:\/\/localhost\/exoblog\/?p=1888"},"modified":"2010-03-01T08:00:19","modified_gmt":"2010-03-01T15:00:19","slug":"integrating-a-cmis-repository-with-zoho-using-gadgets-in-gatein","status":"publish","type":"post","link":"https:\/\/www.exoplatform.com\/blog\/integrating-a-cmis-repository-with-zoho-using-gadgets-in-gatein\/","title":{"rendered":"Integrating a CMIS repository with Zoho using gadgets in GateIn"},"content":{"rendered":"<h3>1. Introduction<\/h3>\n<p>Content Management Interoperability Services (CMIS) is a standard for improving interoperability between Enterprise Content Management systems. It&#8217;s a new standard to make Document Management more interoperable.<\/p>\n<p>A year and half ago, I already wrote a web service in java to edit documents, stored in the JCR, with Zoho. As CMIS arrive, it seems a good idea to rewrite this one with this standard and using groovy. Last time, I used a greasemonkey script to integrate it in eXo ECM, but this time, I&#8217;m going to create a gadget for that. <strong>Thanks to GateIn Gadget Dashboard, I&#8217;ll be able to follow multiple repository in one page and edit their files<\/strong>.<\/p>\n<h4>Requirements for this project<\/h4>\n<ul>\n<li>Browse the CMIS repository<\/li>\n<li>If file can be edited with Zoho, offer this possibility<\/li>\n<li>Work in the main browsers<\/li>\n<li>Work with all the public CMIS demo servers available<\/li>\n<\/ul>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-full wp-image-16515\" src=\"https:\/\/www.exoplatform.com\/blog\/\/wp-content\/uploads\/2010\/03\/4358808394_5f63ed4907.jpg\" alt=\"4358808394_5f63ed4907\" width=\"500\" height=\"441\" srcset=\"https:\/\/www.exoplatform.com\/blog\/wp-content\/uploads\/2010\/03\/4358808394_5f63ed4907.jpg 500w, https:\/\/www.exoplatform.com\/blog\/wp-content\/uploads\/2010\/03\/4358808394_5f63ed4907-300x265.jpg 300w, https:\/\/www.exoplatform.com\/blog\/wp-content\/uploads\/2010\/03\/4358808394_5f63ed4907-372x328.jpg 372w, https:\/\/www.exoplatform.com\/blog\/wp-content\/uploads\/2010\/03\/4358808394_5f63ed4907-268x236.jpg 268w, https:\/\/www.exoplatform.com\/blog\/wp-content\/uploads\/2010\/03\/4358808394_5f63ed4907-149x131.jpg 149w, https:\/\/www.exoplatform.com\/blog\/wp-content\/uploads\/2010\/03\/4358808394_5f63ed4907-100x88.jpg 100w, https:\/\/www.exoplatform.com\/blog\/wp-content\/uploads\/2010\/03\/4358808394_5f63ed4907-34x30.jpg 34w\" sizes=\"(max-width: 500px) 100vw, 500px\" \/><\/p>\n<h4>What you need to know before starting<\/h4>\n<ul>\n<li>Basic knowledge of the gadget API. If you don&#8217;t have a look at the <a href=\"http:\/\/wiki.opensocial.org\/index.php?title=Gadget_Developer%27s_Guide\" class=\"broken_link\" target=\"_blank\" rel=\"noopener\">Gadget Developer Guide<\/a><\/li>\n<li>Basic knowledge of javascript and jQuery<\/li>\n<li>Basic knowledge of what is <a href=\"http:\/\/en.wikipedia.org\/wiki\/AtomPub\" target=\"_blank\" rel=\"noopener\">Atom<\/a>, <a href=\"http:\/\/en.wikipedia.org\/wiki\/Representational_State_Transfer\" target=\"_blank\" rel=\"noopener\">REST<\/a> and <a href=\"http:\/\/en.wikipedia.org\/wiki\/Content_Management_Interoperability_Services\" target=\"_blank\" rel=\"noopener\">CMIS<\/a><\/li>\n<\/ul>\n<h3>2. Accessing to files of a CMIS servers<\/h3>\n<p>CMIS servers make available 2 types of API: SOAP and Atom. From javascript, it&#8217;s easier to use the <strong>Atom API<\/strong>. But all the browsers don&#8217;t handle namespaces the same way, so it makes it harder to parse response. We are going to use JQuery because it makes things easier than using the native javascript functions, but I guess it&#8217;s possible to do the same with YUI, Dojo or other&#8230;<\/p>\n<h4>Parsing Atom in Javascript<\/h4>\n<p>To get a namespaced node with jQuery, you can do:<\/p>\n<pre class=\"prettyprint\">\/\/this doesn't work because the : is used by the selector syntax of JQuery\nvar vendor = $(xmlNode).find(\"cmis:vendorName\").text();\n\/\/this work in IE and Firefox but not in Webkit (Chrome and safari)\nvar vendor = $(xmlNode).find(\"cmis\\\\:vendorName\").text();\n\/\/This is going to work on IE, Webkit (Chrome and safari) and Firefox.\nvar vendor = $(xmlNode).find(\"[nodeName=cmis:vendorName]\").text();<\/pre>\n<p>So with this, I created a simple CMIS response parser in javascript: <a href=\"http:\/\/github.com\/jeremi\/jquery-cmis\/\" target=\"_blank\" rel=\"noopener\">jquery.cmis.js<\/a> (jsDoc). It can parse a Service, a Feed or an Entry.<\/p>\n<p>To get the workspaces from a service:<\/p>\n<pre class=\"prettyprint\">\/\/We parse the response sent by the CMIS service\nvar workspaces = CMIS.parse(XMLDocument);\n\/\/We take the first workspace\nvar current_workspace = workspaces[0];\n\/\/ show the vendor of the first workspace\nalert(current_workspace.getVendorName());\n\/\/ Get the url pointing to the root directory listing\nvar root_feed = current_workspace.collections[\"root\"];<\/pre>\n<p>To list a feed&#8217;s entries:<\/p>\n<pre class=\"prettyprint\">\/\/We parse the response sent by the CMIS service\nvar feed = CMIS.parse(XMLDocument);\nfeed.getEntries().each(function(i, entry) {\n  \/\/ return if the entry is a folder\n  if (entry.isFolder()) {\n    \/\/ title of the folder\n    entry.title;\n    \/\/ get the link to browse the directory\n    entry.getLinks(\"down\");\n  } else {\n    \/\/ title of the document\n    entry.title;\n    \/\/URL of to get the content of the document\n    entry.getContentUrl();\n  }\n}<\/pre>\n<h4>Loading the feed from the gadget<\/h4>\n<p>So now that we know how to parse the response of a CMIS server. We can start building our gadget. Most (all?) the CMIS repository are using BasicAuth as authentication\/authorization mechanism, so if our CMIS server is on the same domain name we could directly send a request to it. But as we want to be able to integrate CMIS server from all over the internet, we need to use a proxy (remember the <a href=\"http:\/\/en.wikipedia.org\/wiki\/Same_origin_policy\" target=\"_blank\" rel=\"noopener\">same origin policy of our browsers<\/a>).I&#8217;d love to see CMIS repositories to use oAuth authorization mechanism, but as it&#8217;s not the case for now, we need to write a simple proxy in Groovy and JAX-RS to do the authentication for us:<\/p>\n<pre class=\"prettyprint\">import javax.ws.rs.FormParam\nimport javax.ws.rs.Path\nimport javax.ws.rs.POST\n\n@Path(\"proxy\")\npublic class Proxy {\n  @POST\n  @Path(\"basic_auth\")\n  public String basic_auth(@FormParam(\"url\") String url, @FormParam(\"login\") String login,\n                                         @FormParam(\"password\") String password) {\n    return get(url, login, password);\n  }\n\n  public static String get(String url, String login, String password) {\n    \/\/ Basic Auth send login and password encoded in Base 64 in the header\n    def encoded = \"$login:$password\".getBytes().encodeBase64().toString()\n    def c = new URL(url).openConnection()\n    c.setRequestProperty(\"Authorization\", \"Basic $encoded\")\n    return c.content.text\n  }\n}<\/pre>\n<p>So to get a URL authenticated you can just call a url like this:<\/p>\n<pre>http:\/\/myserver.com\/rest\/proxy\/basic_auth?url=http%3A%2F%2Fcmis.demo.nuxeo.org%2Fnuxeo%2Fsite%2Fcmis%2Fchildren%2F4fb1b8a1-6dfd-4da4-95b0-4ef41c27b920&amp;login=Administrator&amp;password=Administrator<\/pre>\n<p>From our gadget, to get the response of the CMIS server using our proxy, we are going to do:<\/p>\n<pre class=\"prettyprint\">var params = {},\nprefs = new gadgets.Prefs();\n\npostdata = {\n  url: \"http:\/\/cmis.exoplatform.org\/rest\/cmisatom\",\n  login: prefs.getString(\"login\"),\n  password: prefs.getString(\"password\")\n};\n\/\/ our proxy need a POST\nparams[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.POST;\n\/\/ We want the response as a DOM Document\nparams[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.DOM;\n\/\/ We encode our parameters\nparams[gadgets.io.RequestParameters.POST_DATA] = gadgets.io.encodeValues(postdata);\n\n\/\/ And finally we send our request\ngadgets.io.makeRequest('http:\/\/cmis.exoplatform.org\/rest\/proxy\/basic_auth', function(res) {\n  if (res.rc == 200) {\n    var response = CMIS.parse(res.data);\n    \/\/ We can do now what we want with the response...\n  } else {\n    alert(\"Error: \" + res.data);\n  }\n}, params);<\/pre>\n<p>We can now just use our response object to list the files on our repository.<\/p>\n<h3>3. Editing the files with Zoho<\/h3>\n<p>Now that we have successfully accessed to our files, we would like to edit them with Zoho editor (<a href=\"http:\/\/apihelp.wiki.zoho.com\/\" target=\"_blank\" rel=\"noopener\">API documentation<\/a>). We have to POST the file to their server, and they return the URL to edit it. When the user will save the document, the zoho server will do a POST on a callback URL (defined on our first POST) containing the document.<\/p>\n<p>You will need to get your <a href=\"http:\/\/apihelp.wiki.zoho.com\/Generate-API-Key.html\" target=\"_blank\" rel=\"noopener\">zoho API key<\/a>.<\/p>\n<p>So, here is how it&#8217;s going to work:<\/p>\n<ul>\n<li>The gadget is going to call our service<\/li>\n<li>The service will download the file from the CMIS server<\/li>\n<li>The service will generate an ID for the document. We are going to use the URL on the CMIS server and the login\/password to access it. To avoid sending the login and password to zoho, we could have store it in the JCR but it add an extra step that we are not going to show on this tutorial.<\/li>\n<li>The service will POST everything to the Zoho API corresponding to the right editor (Doc, spredsheet, presentation)<\/li>\n<li>The service will parse the response and return the url of the editor<\/li>\n<li>The gadget will redirect the user to the editor<\/li>\n<li>When the user save the document, Zoho will do a POST on our service to save it<\/li>\n<li>The service is going to push the document back on the CMIS server<\/li>\n<\/ul>\n<p>It looks like a lot of things to do, but with groovy and JAX-RS it&#8217;s quite simple.<\/p>\n<p>The gadget is going to call our <em>edit<\/em> service with the URL of the file and the credential needed in parameter:<\/p>\n<pre class=\"prettyprint\">    @POST\n    @Path(\"edit\")\n    public String edit(@FormParam(\"url\") String url,\n                       @FormParam(\"filename\") String filename,\n                       @FormParam(\"login\") String login,\n                       @FormParam(\"password\") String password) {\n\n      \/\/We prepare the request\n      def client = new HttpClient()\n      def ext = getExtension(filename)\n      def postMethod = new PostMethod(getZohoURL(ext))\n\n      \/\/Get the content of the file we want to edit and continue to construct the request for the Zoho server\n      def src = new ByteArrayPartSource(filename, get(url, login, password))\n      Part[] parts = [\n              new FilePart(\"content\", src), \/\/content of the file we want to open\n              new StringPart(\"filename\", filename), \/\/    filename of the document with extension\n              new StringPart(\"saveurl\", callbackURL), \/\/The Web URL should be a service that fetches the content of the updated document and saves it to the user specified location.\n              new StringPart(\"id\", getId(url, login, password)), \/\/unique id that will be submitted while saving the document (for reference)\n              new StringPart(\"format\", ext) \/\/ the format in which document should be saved on remote server\n      ];\n\n      postMethod.setRequestEntity(new MultipartRequestEntity(parts, postMethod.getParams()))\n\n      try {\n        \/\/ We send the request\n        def statusCode = client.executeMethod(postMethod)\n\n        \/\/ We transform the response into a hashmap\n        def resp = parseResponse(postMethod.getResponseBodyAsString())\n\n        \/\/if the status is not 200 (OK) or there was a problem in the opening of the file we throw an exception\n        if (statusCode != 200 || resp[\"RESULT\"] == \"FALSE\") {\n          throw new WebApplicationException(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(resp[\"WARNING\"]).type(\"text\/plain\").build())\n        }\n        \/\/Everything went fine, so we return the URL to edit the file\n        return resp[\"URL\"]\n      } finally {\n        postMethod.releaseConnection()\n      }\n    }\n\n    \/**\n     * from the extension of the file, find which endpoint calling\n     *\/\n    private String getZohoURL(String extension) throws IOException {\n      def url;\n      if (extension == null) {\n        throw new WebApplicationException(Response.Status.UNSUPPORTED_MEDIA_TYPE)\n      }\n      if (extension in [\"doc\", \"rtf\", \"odt\", \"sxw\", \"html\", \"txt\"])\n        url = \"http:\/\/export.writer.zoho.com\/remotedoc.im\"\n      else if (extension in [\"xls\", \"sxc\", \"csv\"])\n        url = \"http:\/\/sheet.zoho.com\/remotedoc.im\"\n      else if (extension in [\"ppt\", \"pps\"])\n        url = \"http:\/\/show.zoho.com\/remotedoc.im\"\n      else\n        throw new WebApplicationException(Response.Status.UNSUPPORTED_MEDIA_TYPE)\n      url = url + \"?apikey=\" + apiKey + \"&amp;output=url\"\n      return url;\n    }<\/pre>\n<p>Now that we have sent the file to zoho, we need to save it back on the CMIS server. Zoho will call the following method:<\/p>\n<pre class=\"prettyprint\">    @POST\n    @Path(\"save\")\n    @Consumes([\"multipart\/*\"])\n    public String save(Iterator&lt;FileItem&gt; items) {\n      def stream\n      def id\n\n      \/\/ We loop over all the POST parameters to find the one that are interesting for us.\n      while (items.hasNext()) {\n        FileItem item = items.next();\n\n        if (item.getFieldName().equalsIgnoreCase(\"content\")) {\n          stream = item.getInputStream()\n        }\n        if (item.getFieldName().equalsIgnoreCase(\"id\")) {\n          id = new String(item.get());\n        }\n      }\n\n      \/\/ We are preparing the request to send to the CMIS server\n      \/\/ This time, we are going to send a PUT to update the content stream\n      def client = new HttpClient()\n      def putMethod = new PutMethod(getUrl(id))\n      try {\n        putMethod.setRequestBody(stream)\n        putMethod.setRequestHeader(\"Authorization\", \"Basic \" + getCredential(id))\n        putMethod.setRequestHeader(\"Content-Type\", \"application\/octet-stream\")\n\n        def statusCode = client.executeMethod(putMethod)\n\n        \/\/ if everything went fine, we return a message that will be shown to the user\n        if (statusCode &gt;= 200 &amp;&amp; statusCode &lt; 300)\n          return \"Saved\"\n        \/\/ If it did not go well, we send an error, and the user will be notified that something went wrong while trying\n        \/\/ to save his document\n        throw new WebApplicationException(statusCode)\n      } finally {\n        putMethod.releaseConnection()\n      }\n    }<\/pre>\n<p>We have now everything to be able to browse a CMIS repository and edit the files with Zoho. Have a look at the code of the final gadget and feel free to remix it. You can also view the demo on the <a href=\"http:\/\/cmis.exoplatform.org\/portal\/public\/classic\/Dashboard\" class=\"broken_link\" target=\"_blank\" rel=\"noopener\">xCMIS demo server<\/a>. To deploy it in GateIn Beta5, read How to activate groovy REST services in GateIn Beta5.<\/p>\n<h3>4. Download and link to online demo<\/h3>\n<ul>\n<li>The code of this example (Download)<\/li>\n<li>Demo with xCMIS, Alfresco and Nuxeo<\/li>\n<li>jquery.cmis.js(Download)<\/li>\n<li><a href=\"http:\/\/www.xcmis.org\" class=\"broken_link\" target=\"_blank\" rel=\"noopener\">xCMIS<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"1. Introduction Content Management Interoperability Services (CMIS) is a standard for improving interoperability between Enterprise Content Management systems. It&#8217;s a new standard to make Document Management more interoperable. A year and half ago, I already wrote a web service in java to edit documents, stored in the JCR, with Zoho. As CMIS arrive, it seems [&hellip;]","protected":false},"author":7,"featured_media":12746,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[699],"tags":[],"lang":"en","translations":{"en":37028},"pll_sync_post":[],"_links":{"self":[{"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/posts\/37028"}],"collection":[{"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/comments?post=37028"}],"version-history":[{"count":0,"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/posts\/37028\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/"}],"wp:attachment":[{"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/media?parent=37028"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/categories?post=37028"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/tags?post=37028"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}