Introducing IBM Lotus Quickr REST services

Learn how to use IBM Lotus Quickr Representational State Transfer (REST) services for basic operations you can perform on documents.

Share:

Ramajeyam Gopalraj, Advisory Software Engineer, IBM

Ramajeyam Gopalraj is an Advisory Software Engineer at IBM working with the Lotus Quickr team in Research Triangle Park, NC. You can reach Ramajeyam at ramajeyam_gopalraj@us.ibm.com.



Derek Carr, Advisory Software Engineer, IBM

Derek Carr is an Advisory Software Engineer at IBM working with the Lotus Quickr team in Research Triangle Park, NC. You can reach Derek at dwcarr@us.ibm.com.



Gregory Melahn, Architect and Senior Technical Staff Member, IBM

Greg Melahn is an Architect and Senior Technical Staff Member in the Workplace, Portal, and Collaboration portfolio of IBM software. You can reach Greg at melahn@us.ibm.com.



09 October 2007

Also available in Chinese Japanese

IBM Lotus Quickr makes it easy for users to share business content. An important design principle of Lotus Quickr is to build the application on open interfaces that allow customers and partners to access content where and when they need it -- otherwise known as the principle of information on demand. This article focuses on the Lotus Quickr Representational State Transfer (REST) services, including an overview and a simple example of how you can use them. Here we introduce the basic operations you can perform using Quickr REST services; more advanced operations will be explained in a future article.

Prerequisites

You should have a good understanding of Java and Web 2.0 programming to get the most out of this article. To use the sample code, you need a basic understanding of the Eclipse framework and the Apache Abdera client toolkit. For information about the Apache Abdera client toolkit, see the developerWorks article, "Getting to know the Atom Publishing Protocol, Part 3: Introducing the Apache Abdera project."


Overview

The goal of Quickr REST services is to enable developers to build collaborative solutions with as little effort as possible. The services are designed around open standards and Web 2.0 technologies, allowing you to build applications with a basic understanding of existing Web technologies, such as HTTP and XML. Specifically, REST-style URLs are used to work with the content. The services are based on the Atom Syndication Format as described in RFC 4287 for accessing content from Lotus Quickr and on the emerging Atom Publishing Protocol (APP) for publishing content to Lotus Quickr.

In the spirit of APP, document libraries are treated as APP collections, and documents are treated as APP resources. URLs are defined for each collection and resource so that clients can invoke the appropriate HTTP methods, such as GET, POST, PUT, and DELETE, on those URLs as defined by APP. In addition, URLs follow a predictable pattern, enabling clients to compose the URLs without asking the server to provide them. For complete details on the services, refer to the IBM Lotus Quickr Developer's Guide.


About Quickr Connectors

Let's imagine you are an application developer in the Acme Corporation in which Lotus Quickr is deployed. Architects and designers at Acme use Lotus Quickr to store architecture and design documents in a Lotus Quickr place. The documents are often needed by the developers, so a smart developer like you thought it would be more productive if those documents were readily accessible in the Eclipse development platform, also widely used at Acme. Hence, you decide to write a Quickr Connector, which is an extension to an existing application or framework allowing you to access content when and where you need it. For example, there is a Quickr Connector for IBM Lotus Notes that lets you embed links to Lotus Quickr documents in mail messages.

In this example, the Quickr Connector is implemented as an Eclipse view plug-in that lets you access the document libraries. The plug-in is called QuickrNavigator and has an Eclipse view with a tree control. You can configure the view to show document libraries from a Lotus Quickr server. The tree control shows the libraries as their top-level nodes, each folder under the library is an expandable node, and the documents are the leaf nodes. The first iteration of the plug-in features the following operations:

  • Upload local files to either the library or a folder inside the library
  • Refresh a folder or library to list the latest contents
  • Download a document to a local file system
  • View the document in the associated application by double-clicking
  • Delete a document

In the example, the open source Apache Abdera client is used to parse the Atom feeds and to create HTTP requests to the server.


Getting the list of libraries

First, you must retrieve the list of libraries so that you can build the first-level nodes in the navigation tree. Recall that document libraries are represented in Atom terms as collections. Atom defines a service document as the mechanism for retrieving collections. To retrieve this service document, you need to know the server name along with a user ID and password to connect to the server. The URL that you use is the introspection URL, and because the URL format is published in the documentation, you can build it yourself for a given server. For this server, the URL looks like this:

http://quickr.acme.com/dm/atom/introspection

A GET request to this URL is used to retrieve the list of libraries. The response contains an Atom service document with a single <workspace> element and <collection> element for each library (see listing 1).

Listing 1. Atom service document
<service>
 <workspace title="Teamspace Documents">
  <collection title="Architecture Documents" href="http://quickr.acme.com/library/
  5d06ab0044ed8129bd5ebd4caeec5df1/feed">
	<accept>application/*,image/*,*/*</accept>
  </collection>
  <collection title="Design Documents" href="http://quickr.acme.com/library/
  3c06ab0044ed8129bd5ebd4cbeec5dc4/feed">
	<accept>application/*,image/*,*/*</accept>
  </collection>
 </workspace>
</service>

The value of the title attribute is what you use as the label for the tree node. The href attribute of the <collection> element contains the URL that you use later to retrieve the contents of the library when the node is expanded in the tree. Listing 2 shows the portion of the code that processes this response in the sample.

Listing 2. Partial code to process the response
try {
	httpClient.addCredentials(serverConfig.getUrl(), null, null, 
new UsernamePasswordCredentials(serverConfig.getUserId(), 
serverConfig.getPassword()));

	httpClient.usePreemptiveAuthentication(true);

	ClientResponse response = httpClient.get(serverConfig.getUrl()
            + "/dm/atom/introspection");

	if (response.getStatus() == 200) {

    Document serviceDoc = response.getDocument();
    Element element = serviceDoc.getRoot().getFirstChild();
    while (element != null) {
      if (element instanceof Workspace) {
        Workspace ws = (Workspace) element;
        Iterator collections = ws.getCollections()
          .iterator();
        while (collections.hasNext()) {
           TreeParent collection = new TreeParent(
              (Collection) collections.next(),
              true);
              invisibleRoot.addChild(collection);
            }
        }
        element = element.getNextSibling();
      }
	}
} catch (Exception e) {
	e.printStackTrace();
	showMessage(e.toString());
}

The plug-in is ready for user interaction after it has processed the Atom service document (see figure 1).

Figure 1. Plug-in showing the list of libraries retrieved from the server
Plug-in showing the list of libraries retrieved from the server

Getting the list of folders and documents

Now that you've built the tree's first-level nodes, it's time to show the documents and folders in the library. For this, you need to retrieve the contents from that library and build the child nodes (the folders and documents). Because you stored the feed URL in each of the library nodes, it is easy to retrieve the contents; you need to issue a GET request on that URL to get back an Atom Feed document (see listing 3):

http://quickr.acme.com/dm/atom/library/5d06ab0044ed8129bd5ebd4caeec5df1/feed

Listing 3. Atom Feed document
<?xml version="1.0" encoding="UTF-8"?>
<feed xml:base="http://quickr.acme.com/dm/atom/library/
5d06ab0044ed8129bd5ebd4caeec5df1/" xmlns="http://www.w3.org/2005/Atom">
  <generator uri="http://quickr.acme.com/dm/atom" 
  version="1.0">Teamspace Documents</generator>
  <id>urn:lsid:ibm.com:td:5d06ab0044ed8129bd5ebd4caeec5df1</id>
  <link href="feed" rel="self"></link>
  <link href="http://quickr.acme.com/wps/mypoc?uri=
  dm:5d06ab0044ed8129bd5ebd4caeec5df1&verb=view" rel="alternate"></link>
  <link href="feed?pagesize=2&page=3" rel="next"></link>
  <link href="feed?pagesize=2&page=1" rel="previous"></link>
  <collection href="feed" xmlns="http://purl.org/atom/app#">
    <atom:title type="text" xmlns:atom="http://www.w3.org/2005/
    Atom">Architecture Documents</atom:title>
    <accept>application/*,image/*,*/*</accept>
  </collection>
  <author>
    <uri>uid=jsmith,o=acme</uri>
    <name>John Smith</name>
    <email>jsmith@acme.com</email>
  </author>
  <title type="text">Architecture Documents</title>
  <updated>2007-04-10T13:07:00.672Z</updated>

  <entry xml:lang="en">
    <id>urn:lsid:ibm.com:td:7f37550044f10d5b9144bbd6afe18010</id>
    <link href="document/7f37550044f10d5b9144bbd6afe18010/entry" rel="self"></link>
    <link href="http://quickr.acme.com/wps/mypoc?uri=
    dm:7f37550044f10d5b9144bbd6afe18010&verb=view" rel="alternate"></link>
    <link href="document/7f37550044f10d5b9144bbd6afe18010/entry" rel="edit"></link>
    <link href="document/7f37550044f10d5b9144bbd6afe18010/media" 
    rel="edit-media"></link>
    <link href="document/7f37550044f10d5b9144bbd6afe18010/media" 
    rel="enclosure" type="application/msword" title="Architecture Guidelines.doc" 
    hreflang="en" length="19968"></link>
    <category term="document" scheme="tag:ibm.com,2006:td/type" 
    label="document"></category>
    <author>
      <uri>uid=jsmith,o=acme</uri>
      <name>John Smith</name>
      <email>jsmith@acme.com</email>
    </author>
    <title type="text">Architecture Guidelines.doc</title>
    <published>2007-04-11T16:51:04.594Z</published>
    <updated>2007-04-11T16:51:04.594Z</updated>
    <td:created>2007-05-13T19:03:25.500Z</td:created>
    <td:modified>2007-05-13T19:03:25.500Z</td:modified>
    <td:modifier>
      <td:uri> uid=jdoe,o=acme </td:uri>
      <td:name>John Doe</td:name>
      <td:email>jdoe@acme.com </td:email>
    </td:modifier>
    <summary type="html"><span><img align="middle" 
    src="thumbnail/7f37550044f10d5b9144bbd6afe18010/
    media></span><span>&nbsp;&nbsp;</span><span>
    General guidelines for architecture </span></summary>
    <content type="application/msword" xml:lang="en" 
    src="document/7f37550044f10d5b9144bbd6afe18010/media"></content>
  </entry>

  <entry>
    <id>urn:lsid:ibm.com:td:5dc3f38044eee4ca90d8bad6afe18010</id>
    <link href="folder/5dc3f38044eee4ca90d8bad6afe18010/entry" rel="self"></link>
    <link href="http://quickr.acme.com/wps/mypoc?uri=
    dm:5dc3f38044eee4ca90d8bad6afe18010&verb=view" rel="alternate"></link>
    <link href="folder/5dc3f38044eee4ca90d8bad6afe18010/entry" rel="edit"></link>
    <category term="folder" scheme="tag:ibm.com,2006:td/type" 
    label="folder"></category>
    <author>
      <uri>uid=jsmith,o=acme</uri>
      <name>John Smith</name>
      <email>jsmith@acme.com</email>
    </author>
    <title type="text">Johns Folder</title>
    <published>2007-04-10T23:58:29.219Z</published>
    <updated>2007-04-10T23:58:29.219Z</updated>
    <td:created>2007-05-13T19:03:25.500Z</td:created>
    <td:modified>2007-05-13T19:03:25.500Z</td:modified>
    <td:modifier>
      <td:uri> uid=jdoe,o=acme </td:uri>
      <td:name>John Doe</td:name>
      <td:email>jdoe@quickr.acme.com </td:email>
    </td:modifier>
    <summary type="text">John's architecture documents</summary>
    <content type="application/atom+xml" 
    src="folder/5dc3f38044eee4ca90d8bad6afe18010/feed"></content>
  </entry>

</feed>

The feed contains an Atom Entry for each folder and document under the library root. The <category> element tells you whether the entry corresponds to a folder or to a document. You display folders as expandable nodes in the tree because they can contain child folders and documents. Documents are displayed as leaf nodes. As you noticed in the feed document, each entry has a URL to itself through the self link, illustrating another useful pattern in Atom, which is the use of link elements. Links provide ready-to-use URLs of associated content, views, or actions on a content item. This frees the application from some of the chores of composing these URLs (though, as mentioned previously, these URLs are predictable).

For folder entries, store the self link in the TreeParent instances to later compose the feed URL for that folder. Use the feed URL to store or retrieve contents just as you used the library feed URL to store and retrieve the library contents.

For document entries, you are interested in the contents of the document. Hence, you store the edit-media link of the document entry in its TreeObject instance. Refer to the Atom Publishing Protocol specifications for a description of these link relationships. Listing 4 shows the portion of the code that processes the Atom Entries into tree nodes.

Listing 4. Partial code to process Atom Entries into tree nodes
ClientResponse response = httpClient.get(url);
if (response.getStatus() == 200) {
	Feed feed = (Feed) response.getDocument().getRoot();
	Iterator entries = feed.getEntries().iterator();
	while (entries.hasNext()) {
        boolean isFolder = false;
		Entry entry = (Entry) entries.next();
		try {
          Iterator categories = entry.getCategories(
            "tag:ibm.com,2006:td/type").iterator();
        while (categories.hasNext()) {
            Category cat = (Category) categories.next();
            if (cat.getTerm().equals("folder")) {
	              isFolder = true;
                  break;
				}
			}
		} catch (IRISyntaxException e) {
			e.printStackTrace();
		}
		if (isFolder) {
			TreeParent folder = new TreeParent(entry, false);
			addChild(folder);
		} else {
			TreeObject document = new TreeObject(entry);
			addChild(document);
		}
	}
}

After processing the Atom Feed document on the library, the plug-in shows the contents of the library (see figure 2).

Figure 2. Plug-in showing contents of the library
Plug-in showing contents of the library

Getting document content

The media content of a document represents the bits that the user wants to read or update. For example, media content could be a PDF file. After the list of documents is displayed in a library, the user can view the media content of an individual document by double-clicking it. When the user double-clicks the document, you need to launch the document in its associated application (such as the Adobe Reader for a PDF file). First, you need to bring down the media content of the document so the application has something to work with. You do this using the edit-media link that you saved in the TreeObject instance for the document node:

http://quickr.acme.com/dm/atom/library/5d06ab0044ed8129bd5ebd4caeec5df1/
document/7f37550044f10d5b9144bbd6afe18010/media

When you make a GET request on that URL, you get back the document's media content in the response. Read the response, save it in a temporary file, and then launch the temporary file using the associated application. Listing 5 shows the code that performs this operation.

Listing 5. Partial code that performs download and launch of a document
public void doOpen(String url) {
  TreeObject item = (TreeObject) ((IStructuredSelection) viewer
        .getSelection()).getFirstElement();
  try {
String tmpDir = File.createTempFile("qkr",
 "tmp").getParent();
        File file = new File(tmpDir + "/" + item.getName());
        if (file.exists()) {
          file.delete();
		}
		ClientResponse response = httpClient.get(url);
		if (response.getStatus() == 200) {
          BufferedInputStream bis = new BufferedInputStream(
 response.getInputStream());
          file.createNewFile();
          BufferedOutputStream bos = new BufferedOutputStream(
            new FileOutputStream(file));
			byte[] bytes = new byte[1024];
			int count = 0;
			while ((count = bis.read(bytes)) != -1) {
              bos.write(bytes, 0, count);
			}
			bis.close();
			bos.close();

			// open the local file in editor
			IWorkspace ws = ResourcesPlugin.getWorkspace();
			IProject project = ws.getRoot().getProject("Quickr
 Files");
			if (!project.exists()) {
            project.create(null);
			}
			if (!project.isOpen()) {
            project.open(null);
			}
			IPath location = new Path(file.getPath());
			IFile iFile =
 project.getFile(location.lastSegment());
			if (!iFile.exists()) {
				iFile.createLink(location, IResource.NONE, 
                null);
			}
        FileEditorInput fileEditorInput = new
 FileEditorInput(iFile);
        IEditorRegistry registry = PlatformUI.getWorkbench()
          .getEditorRegistry();
        IWorkbenchPage page =
 getViewSite().getWorkbenchWindow()
          .getActivePage();
        if (page != null) {				
if( registry.isSystemExternalEditorAvailable(
          File.getName()))
{
          page.openEditor( fileEditorInput,	
          IEditorRegistry.SYSTEM_EXTERNAL_EDITOR_ID);

          } else if
          (registry.isSystemInPlaceEditorAvailable(iFile
            .getName())) {

            page.openEditor(fileEditorInput,
            IEditorRegistry.SYSTEM_INPLACE_EDITOR_ID);
            } else {
            showMessage("No viewer available for this file type. 
            You can download it instead.");
            }
          }
        }
    response.release();
    } catch (IOException e) {
      e.printStackTrace();
    } catch (CoreException e) {
      e.printStackTrace();
    }
	}

Uploading files

Now suppose you want to store one of your local files in Lotus Quickr by selecting Upload from the context menu on the library (or folder). To do this, use the feed URL stored in the TreeParent instance of that tree node:

http://quickr.acme.com/dm/atom/library/5d06ab0044ed8129bd5ebd4caeec5df1/feed

Recall that you used this URL to retrieve the contents of the library by sending a GET request to that URL. Now, use that URL to store a document by sending a POST request. This is defined by the APP specification, which uses a POST operation to create a resource. When you send a POST request, you write the file media content in the body of the request. But how does the server know where and under what name the file gets saved in the library?

The answer is the Slug header, which provides a way for you to suggest a name for Lotus Quickr to use as the file name for the document. Set the value of the Slug header as the relative path of the document from the library root. For example, if you want to store the file MyFile.doc in a folder called MyFolder under the library, set the Slug header to /MyFolder/MyFile.doc. Listing 6 shows the code to do this in the plug-in.

Listing 6. Code to store a local file in a document library
public void doUpload(String url) {
  FileDialog dlg = new FileDialog(getViewSite().getShell(), SWT.OPEN);
  String path = dlg.open();
  if (path != null) {
    File file = new File(path);
    RequestOptions options = new RequestOptions();
    options.setSlug(file.getName());
    options.setContentType("unknown/unkown");
    options.setHeader("Content-Length", String.valueOf(file.length()));
    try {
      ClientResponse response = httpClient.post(url,
        new FileInputStream(file), options);
      if (response.getStatus() != 201) {
        showMessage(response.getStatusText());
      } else {
            ISelection selection = viewer.getSelection();
            TreeParent parent = (TreeParent) ((IStructuredSelection) selection)
              .getFirstElement();
              parent.clear();
              viewer.refresh(parent);
        }
      response.release();
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    }
      }
	}

After successfully processing this request, the service returns a response with a status code 201. The response also contains a Location header that holds the URL to the contents of the newly created document (remember the edit-media link?). You could use this information to update the tree with the new document node. For simplicity, the plug-in refreshes the parent to show the newly added document, and in the end, the file is added to the library and shows up in the tree.


Deleting a document

Now let's look at how to delete a document from the library using the plug-in. To do this, use the self link stored in each node under the library node. Quickr REST services does not support deleting the library itself, so we do not expose the Delete menu on a library node (see listing 7).

Listing 7. Code to delete a document
public void doDelete(String url) {
  String entryUrl = url;
  if (url.endsWith("/media")) {
    entryUrl = entryUrl.substring(0, url.lastIndexOf('/'));
    entryUrl += "/entry";
  }
  TreeObject item = (TreeObject) ((IStructuredSelection) viewer
      .getSelection()).getFirstElement();
    TreeParent parent = item.getParent();
    RequestOptions options = new RequestOptions();
    options.setHeader("X-Method-Override", "DELETE");
    ClientResponse response = httpClient.post(entryUrl,
      new StringRequestEntity(""), options);
    if (response.getStatus() == 200) {
      parent.clear();
      viewer.refresh(parent);
  }
 }

Finally, a few words about the other menus. The Refresh action is the same as getting the contents of a library or folder. The Download action is already part of the Doubleclick action, which downloads and launches the file.


Conclusion

Lotus Quickr REST services offer a simple way to work with documents. Because operations are defined through REST-style URLs, you can use your programming language of choice (Java in this example). We have shown how to use the services to perform basic operations on documents and explained the URL request and response for each operation.


Downloads

DescriptionNameSize
Code sampleQuickrNavigator.zip22KB
Code sampleQuickrNavigator_1.0.0.jar2.1MB

Resources

Learn

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into IBM collaboration and social software on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Lotus
ArticleID=259891
ArticleTitle=Introducing IBM Lotus Quickr REST services
publish-date=10092007