Prabhat Jha, one of the JBoss developers collaborating with the eXo team to build GateIn, shows how to add a tic-tac-toe gadget to GateIn on his blog today. Here’s how he describes it:
If you thought Portal was only about serious stuffs such as content aggregation, integration of different applications, out of box personalization and natural front end to SOA etc then think again. Using GateIn’s gadgets, you already could import different cool gadgets say from Google to your dashboard and page. Now you can tic-tac-toe as well. Here is a screen shot from GateIn Portal for you i-dont-believe-until-i-see kinds.
Content Management Interoperability Services (CMIS) is a standard for improving interoperability between Enterprise Content Management systems. It’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 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’m going to create a gadget for that. Thanks to GateIn Gadget Dashboard, I’ll be able to follow multiple repository in one page and edit their files.
Requirements for this project
Browse the CMIS repository
If file can be edited with Zoho, offer this possibility
Work in the main browsers
Work with all the public CMIS demo servers available
What you need to know before starting
Basic knowledge of the gadget API. If you don’t have a look at the Gadget Developer Guide
CMIS servers make available 2 types of API: SOAP and Atom. From javascript, it’s easier to use the Atom API. But all the browsers don’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’s possible to do the same with YUI, Dojo or other…
Parsing Atom in Javascript
To get a namespaced node with jQuery, you can do:
//this doesn't work because the : is used by the selector syntax of JQuery
var vendor = $(xmlNode).find("cmis:vendorName").text();
//this work in IE and Firefox but not in Webkit (Chrome and safari)
var vendor = $(xmlNode).find("cmis\\:vendorName").text();
//This is going to work on IE, Webkit (Chrome and safari) and Firefox.
var vendor = $(xmlNode).find("[nodeName=cmis:vendorName]").text();
So with this, I created a simple CMIS response parser in javascript: jquery.cmis.js (jsDoc). It can parse a Service, a Feed or an Entry.
To get the workspaces from a service:
//We parse the response sent by the CMIS service
var workspaces = CMIS.parse(XMLDocument);
//We take the first workspace
var current_workspace = workspaces[0];
// show the vendor of the first workspace
alert(current_workspace.getVendorName());
// Get the url pointing to the root directory listing
var root_feed = current_workspace.collections["root"];
To list a feed’s entries:
//We parse the response sent by the CMIS service
var feed = CMIS.parse(XMLDocument);
feed.getEntries().each(function(i, entry) {
// return if the entry is a folder
if (entry.isFolder()) {
// title of the folder
entry.title;
// get the link to browse the directory
entry.getLinks("down");
} else {
// title of the document
entry.title;
//URL of to get the content of the document
entry.getContentUrl();
}
}
Loading the feed from the gadget
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 same origin policy of our browsers).I’d love to see CMIS repositories to use oAuth authorization mechanism, but as it’s not the case for now, we need to write a simple proxy in Groovy and JAX-RS to do the authentication for us:
import javax.ws.rs.FormParam
import javax.ws.rs.Path
import javax.ws.rs.POST
@Path("proxy")
public class Proxy {
@POST
@Path("basic_auth")
public String basic_auth(@FormParam("url") String url, @FormParam("login") String login,
@FormParam("password") String password) {
return get(url, login, password);
}
public static String get(String url, String login, String password) {
// Basic Auth send login and password encoded in Base 64 in the header
def encoded = "$login:$password".getBytes().encodeBase64().toString()
def c = new URL(url).openConnection()
c.setRequestProperty("Authorization", "Basic $encoded")
return c.content.text
}
}
So to get a URL authenticated you can just call a url like this:
From our gadget, to get the response of the CMIS server using our proxy, we are going to do:
var params = {},
prefs = new gadgets.Prefs();
postdata = {
url: "http://cmis.exoplatform.org/rest/cmisatom",
login: prefs.getString("login"),
password: prefs.getString("password")
};
// our proxy need a POST
params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.POST;
// We want the response as a DOM Document
params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.DOM;
// We encode our parameters
params[gadgets.io.RequestParameters.POST_DATA] = gadgets.io.encodeValues(postdata);
// And finally we send our request
gadgets.io.makeRequest('http://cmis.exoplatform.org/rest/proxy/basic_auth', function(res) {
if (res.rc == 200) {
var response = CMIS.parse(res.data);
// We can do now what we want with the response...
} else {
alert("Error: " + res.data);
}
}, params);
We can now just use our response object to list the files on our repository.
3. Editing the files with Zoho
Now that we have successfully accessed to our files, we would like to edit them with Zoho editor (API documentation). 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.
The service will download the file from the CMIS server
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.
The service will POST everything to the Zoho API corresponding to the right editor (Doc, spredsheet, presentation)
The service will parse the response and return the url of the editor
The gadget will redirect the user to the editor
When the user save the document, Zoho will do a POST on our service to save it
The service is going to push the document back on the CMIS server
It looks like a lot of things to do, but with groovy and JAX-RS it’s quite simple.
The gadget is going to call our edit service with the URL of the file and the credential needed in parameter:
@POST
@Path("edit")
public String edit(@FormParam("url") String url,
@FormParam("filename") String filename,
@FormParam("login") String login,
@FormParam("password") String password) {
//We prepare the request
def client = new HttpClient()
def ext = getExtension(filename)
def postMethod = new PostMethod(getZohoURL(ext))
//Get the content of the file we want to edit and continue to construct the request for the Zoho server
def src = new ByteArrayPartSource(filename, get(url, login, password))
Part[] parts = [
new FilePart("content", src), //content of the file we want to open
new StringPart("filename", filename), // filename of the document with extension
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.
new StringPart("id", getId(url, login, password)), //unique id that will be submitted while saving the document (for reference)
new StringPart("format", ext) // the format in which document should be saved on remote server
];
postMethod.setRequestEntity(new MultipartRequestEntity(parts, postMethod.getParams()))
try {
// We send the request
def statusCode = client.executeMethod(postMethod)
// We transform the response into a hashmap
def resp = parseResponse(postMethod.getResponseBodyAsString())
//if the status is not 200 (OK) or there was a problem in the opening of the file we throw an exception
if (statusCode != 200 || resp["RESULT"] == "FALSE") {
throw new WebApplicationException(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(resp["WARNING"]).type("text/plain").build())
}
//Everything went fine, so we return the URL to edit the file
return resp["URL"]
} finally {
postMethod.releaseConnection()
}
}
/**
* from the extension of the file, find which endpoint calling
*/
private String getZohoURL(String extension) throws IOException {
def url;
if (extension == null) {
throw new WebApplicationException(Response.Status.UNSUPPORTED_MEDIA_TYPE)
}
if (extension in ["doc", "rtf", "odt", "sxw", "html", "txt"])
url = "http://export.writer.zoho.com/remotedoc.im"
else if (extension in ["xls", "sxc", "csv"])
url = "http://sheet.zoho.com/remotedoc.im"
else if (extension in ["ppt", "pps"])
url = "http://show.zoho.com/remotedoc.im"
else
throw new WebApplicationException(Response.Status.UNSUPPORTED_MEDIA_TYPE)
url = url + "?apikey=" + apiKey + "&output=url"
return url;
}
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:
@POST
@Path("save")
@Consumes(["multipart/*"])
public String save(Iterator items) {
def stream
def id
// We loop over all the POST parameters to find the one that are interesting for us.
while (items.hasNext()) {
FileItem item = items.next();
if (item.getFieldName().equalsIgnoreCase("content")) {
stream = item.getInputStream()
}
if (item.getFieldName().equalsIgnoreCase("id")) {
id = new String(item.get());
}
}
// We are preparing the request to send to the CMIS server
// This time, we are going to send a PUT to update the content stream
def client = new HttpClient()
def putMethod = new PutMethod(getUrl(id))
try {
putMethod.setRequestBody(stream)
putMethod.setRequestHeader("Authorization", "Basic " + getCredential(id))
putMethod.setRequestHeader("Content-Type", "application/octet-stream")
def statusCode = client.executeMethod(putMethod)
// if everything went fine, we return a message that will be shown to the user
if (statusCode >= 200 && statusCode < 300)
return "Saved"
// If it did not go well, we send an error, and the user will be notified that something went wrong while trying
// to save his document
throw new WebApplicationException(statusCode)
} finally {
putMethod.releaseConnection()
}
}
Last week we announced that eXo Platform has built an open source implementation of OASIS’s Content Management Interoperability Services CMIS specification. We licensed this under the LGPL license, and released it officially as xCMIS 1.0 beta1.
This release includes CMIS server with all the services implemented according to Content Management Interoperability Services (CMIS) Version 1.0 Committee Draft 06 for REST AtomPub and Web Services (SOAP/WSDL) protocol bindings. The project is hosted on the Google Code forge, check it out on http://code.google.com/p/xcmis/. I wanted to take a few minutes to answer some of the most common questions from developers about the new xCMIS project.
What is the xCMIS project, and what does it do?
xCMIS is an open source, server side Java CMIS implementation that is able to expose the content in existing content repositories according to the protocols defined in the CMIS spec
xCMIS will give developers a way to make their content repositories “pluggable” on the server side – thanks to an internal Storage Provider Interface and additional protocol on-demandbindings
xCMIS will provide (several) CMIS client frameworks for repository-application and repository-repository interactions. The programming language and supported protocol can be selected by the user. (For example, the reasonable choice for use with web applications, gadgets, and/or mashups is JavaScript or GWT over REST AtomPub, while for inter-repository exchange it may be Java over Web Services, i.e. WSDL/SOAP.)
Both the server and client sides of xCMIS are easily integrated in the eXo Platform 3.0 infrastructure. In particular, xCMIS exposes the eXo JCR content repository and provides a framework for building web applications and gadgets for the GateIn portal
Enough talk already! How do I download and start to play with xCMIS on my local workstation?
The xCMIS server is packaged as a J2EE Web archive xcmis.war, which you can download and install on any Java servlet container. Or, you can build it on your own from the source code by following these simple instructions. Finally, the easiest option might be to use the “download and go” version that we prepared – it’s basically Apache Tomcat bundled with an xCMIS server on /xcmis context path.
By default the xCMIS server includes both REST AtomPub and Web Services (SOAP/WSDL) protocol bindings.
If you really wanted to get “under the hood”, you can explore the capabilities of xCMIS using curl or Poster FireFox add-on, or just try using the information from a WADL file.
The xCMIS bundle includes one CMIS repository with an empty eXo JCR and JCR WebDAV server inside. The name of the JCR repository is “repository” and the name of JCR workspace is “cmis”. So, it is possible to obtain access to the same content using WebDAV URL
http://localhost:8080/xcmis/rest/jcr/repository/cmis/
If you want to save time, you can download the xCMIS server with a full-featured CMIS GWT UI gadget inside (loaded remotely from xcmis.org site). It can be run the same way as a bare server; then you can go to http://localhost:8080/xcmis/xcmis-demo-gadget/GadgetWrapper.html to check out the CMIS visually. It should look like this:
The interface is pretty simple and intuitive, and includes a toolbar, right-button context menu, drag-and-drop features, etc.
How do I use xCMIS remotely?
We created the dedicated resource xcmix.org that has xCMIS deployed on the GateIn portal. Here you can find and use the CMIS Expert gadget, and a really cool CMIS Zoho gadget. This one demonstrates multiple CMIS implementations (xCMIS, Alfresco CMIS and Nuxeo CMIS) in action. You can browse the different CMIS repositories, see the content stored within them, and most importantly – you can view and modify files in Zoho editor.
Feel free to build GateIn yourself and add a local xCMIS server as described in the wiki, or use a remote one (http://xcmis.org/rest/cmisatom – for REST AtomPub protocol). However, you might want to note that xCMIS beta1 uses eXo JCR 1.12 CR1, so make sure you use an appropriate version of GateIn. It should be at least as recent as GateIn 3.0 beta 5 (as of the Feb 11, 2010 release, only GateIn built using trunk is suitable!).
And, of course, it is possible to use third-party CMIS clients such as IBM CMIS Firefox Connector, CMIS Spaces Flex+AIR and (I am pretty sure) other clients that are compatible with CMIS 1.0, all in the same way described in the xCMIS wiki (just using a remote server).
What’s next for xCMIS?
The CMIS specification is close to the final state, so that means we’re close as well - once CMIS is officially out, we’ll put out the final xCMIS release shortly after that
Open the source code for the GWT CMIS framework and move it to xCMIS project
Finalize the Storage Provider Interface architecture
Refactor the search engine, decoupling it from JCR storage
Refactor a CMIS configuration to make it more clear (trying different types of configurations for different IoC containers)
Add federated search between several types of CMIS repositories
Check out other types of clients
And other cool stuff…
We’d love to hear your feedback. If you want to discuss the project, talk about new ideas, make suggestions for improving the documentation, or anything else, please get involved!
To learn more, check out the project Wiki: http://code.google.com/p/xcmis/w/list
To see the source code: http://code.google.com/p/xcmis/source/checkout
To download the binaries: http://code.google.com/p/xcmis/downloads/list
To play with the latest demos: http://xcmis.org/portal/public/classic/CMISExpert
Join eXo Platform for a free one-hour webinar on 24 February, 2010, when we will provide a quick overview of the CMIS spec, an intro to the features of xCMIS, and a step-by-step tutorial for creating gadgets that can make your newly-unlocked content even more useful.
The CMIS specification promises great benefits for application developers – your content applications no longer have to be tied to their respective content repositories. When building new applications, you can now access content in multiple repositories, without concern for the development platform and language dependencies of these separate content systems. eXo Platform is adding to these interoperability benefits with xCMIS, the new CMIS implementation from eXo Platform.
The inherent challenge to developing software is maintaining and improving the quality of the code as you add features and expand the codebase over time.
To manage our code quality, we rely on Sonar. Sonar provides us views ranging from a high level global dashboard down to the most granular detail about individual lines of code – from these we can extract quality indicators from our code development.
GateIn, the new portal that eXo is co-developing with JBoss, provides a dashboard where you can install gadget and customize them. So we wrote gadgets that you can add to your dashboard and customize to your needs. As Gadget is a standard, it’s also working in Jira. Arnaud explain more about this:
Managing a product isn’t only about focusing on quality. We also have to deliver on time, meet our subscription customers’ requirements, and incorporate as many community feature requests as possible. To keep an eye on everything, we wanted to leverage the flexible interface of the GateIn portal framework. By integrating Sonar’s gadgets, we’ll be able to quickly create our own set of customized dashboards to track all the metrics we need, like the activity of our teams, development roadblocks, tasks and issues on products, and a lot more. We’re even able to reuse Jira Gadgets in GateIn – it saves us a lot of time and is definitely more effective.
Sonar Gadget in GateIn Dashboard:
Sonar Gadget in Jira4:
If you want to try these gadgets in your own development environment, you can grab them from Google App Gallery. They can work in any standard OpenSocial/gadget container, and they’ve been tested with GateIn and Jira.
If you want to look under the hood, go to the Sonar gadget repository on github or download them. You can also easily add your own metrics to the gadgets.