Lotus Connections API - Blogs

So for the past six months I’ve been working with the Lotus Connections product team on their Atom Publishing Protocol API support. APP is *the* mechanism by which third parties can integrate with the components of the suite. The first release of the product is likely to ship support for draft-14 of the APP spec. Support for the final RFC version of the spec will come in a future update release. I’m not sure exactly when the full documentation will be published but I wanted to at least start showing some examples — starting with the Blogs component.

With Blogs, the Atom Publishing Protocol is used to manage entries and uploaded resources. Each user can have multiple blogs. Each blog has it’s own “Workspace” in the APP Service Document. Each workspace consists of two collections, one for entries and one for resources.

A client can discover the location of the service document by looking for the <link rel=”service” type=”application/atomsvc+xml” href=”…” /> link in the header of the Blogs dashboard page. The standard location for the Service document is http://{server}/blogs/services/atom. The call requires basic authentication. The content of the Service Document depends on the authenticated identity. Below is my Service Document from the internal IBM blogs deployment.

<?xml version="1.0" encoding="UTF-8"?>
<service xmlns="http://purl.org/atom/app#"
         xmlns:atom="http://www.w3.org/2005/Atom">
  <atom:generator version="1.0-bcv3" uri="http://www.ibm.com/xmlns/prod/sn">Lotus Connections - Blogs</atom:generator>
  <workspace xml:lang="en-US">
    <atom:title type="text">BlogCentral Admin Blog</atom:title>
    <collection href="http://blogs.tap.ibm.com/weblogs/services/atom/blogcentral/entries">
      <atom:title type="text">Weblog Entries</atom:title>
      <accept>entry</accept>
      <categories href="http://blogs.tap.ibm.com/weblogs/services/atom/blogcentral/categories"></categories>
    </collection>
    <collection href="http://blogs.tap.ibm.com/weblogs/services/atom/blogcentral/media">
      <atom:title type="text">Media Entries</atom:title>
      <accept>video/x-msvideo,application/octet-stream,image/gif,text/html,image/jpeg,image/png,text/plain</accept>
      <categories fixed="yes"></categories>
    </collection>
  </workspace>
  <workspace xml:lang="en-US">
    <atom:title type="text">Software Standards</atom:title>
    <collection href="http://blogs.tap.ibm.com/weblogs/services/atom/softwarestandards/entries">
      <atom:title type="text">Weblog Entries</atom:title>
      <accept>entry</accept>
      <categories href="http://blogs.tap.ibm.com/weblogs/services/atom/softwarestandards/categories"></categories>
    </collection>
    <collection href="http://blogs.tap.ibm.com/weblogs/services/atom/softwarestandards/media">
      <atom:title type="text">Media Entries</atom:title>
      <accept>video/x-msvideo,application/octet-stream,image/gif,text/html,image/jpeg,image/png,text/plain</accept>
      <categories fixed="yes"></categories>
    </collection>
  </workspace>
  <workspace xml:lang="en-US">
    <atom:title type="text">chmod 777 ibm</atom:title>
    <collection href="http://blogs.tap.ibm.com/weblogs/services/atom/jasnell@us.ibm.com/entries">
      <atom:title type="text">Weblog Entries</atom:title>
      <accept>entry</accept>
      <categories href="http://blogs.tap.ibm.com/weblogs/services/atom/jasnell@us.ibm.com/categories"></categories>
    </collection>
    <collection href="http://blogs.tap.ibm.com/weblogs/services/atom/jasnell@us.ibm.com/media">
      <atom:title type="text">Media Entries</atom:title>
      <accept>video/x-msvideo,application/octet-stream,image/gif,text/html,image/jpeg,image/png,text/plain</accept>
      <categories fixed="yes"></categories>
    </collection>
  </workspace>
</service>

The url pattern for Collections is simple: http://blogs.tap.ibm.com/weblogs/services/atom/{handle}/entries where {handle} is the URL handle of a specific blog. You should note that this is an APP Draft-14 style Service Document. The accept element and the xml namespace are not up to date with the current APP spec. You should also note that there is a categories document for each workspace. This returns the listing of tags that have been used on all the entries in the blog.

The categories document is a simple listing of atom:category elements as specified by the APP spec:

<?xml version="1.0" encoding="UTF-8"?>
<categories xml:lang="en-US"
  xmlns="http://purl.org/atom/app#"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:snx="http://www.ibm.com/xmlns/prod/sn">

  <atom:category term="bcv2" snx:frequency="17"></atom:category>
  <atom:category term="blogcentral" snx:frequency="13"></atom:category>
  <atom:category term="bcv2-feedback" snx:frequency="7"></atom:category>
   ...
</categories>

Note the use of the snx:frequency attribute. First off, the “http://www.ibm.com/xmlns/prod/sn” XML namespace is used for all extensions used by the Lotus Connections suite. Make note of it because you’ll be seeing a lot of this namespace. The snx:frequency attribute is used on atom:category elements to indicate how many times the tag has been used.

Retrieving the Collection of entries is done in the typical APP way by GET’ing the Collection URL. The Atom feed document that is returned is a straightforward listing of entries ordered according to atom:updated (most recent first). In this implementation, atom:updated and app:edited are the same. Each entry will have an app:control element that may specify <app:draft>yes</app:draft> and will contain an snx:comments element like <snx:comments enabled=”yes” days=”0″ />. The snx:comments control specifies whether or not comments are enabled on the entry and for how long. The snx:comments/@enabled attribute can either be “yes” or “no”; snx:comments/@days can be any positive integer, with 0 indicating that comments are enabled for unlimited days.

The following rules apply to each blog post:

  • Titles are plain text. HTML and XHTML titles are not supported
  • Author always represents the authenticated identity of the user. client provided author details are ignored
  • Entries tags are represented by atom:category elements with no scheme attribute
  • atom:content is always preserved as escaped HTML. If you pass in text, or xhtml, it will come out as escaped HTML.
  • Draft entries are specified using <app:draft>yes</app:draft>
  • The atom:summary element is autogenerated from the content. Client supplied summaries will be ignored
  • Arbitrary extensions are ignored.
  • The rights element is ignored.

Below is some Abdera sample code illustrating how to interact with the Blogs component:

First, we initialize Abdera and set our credentials:

Abdera abdera = new Abdera();
Client client = new CommonsClient(abdera);
client.addCredentials("http://blogs.tap.ibm.com/weblogs", null, null, new UsernamePasswordCredentials("userid","password"));

Then we do some introspection to find the location of the collection.

ClientResponse resp = client.get("http://blogs.tap.ibm.com/weblogs/services/atom");
Document<Service> service_doc = resp.getDocument();
Service service = service_doc.getRoot();
Collection collection = service.getCollection("chmod 777 ibm", "Weblog Entries");
String coll_uri = collection.getResolvedHref().toASCIIString();

Once we have the collection URI we can prepare and post an entry:

Entry entry = abdera.newEntry();
entry.newId();            // ignored by the server but APP requires it
entry.addAuthor("james"); // ignored by the server but APP requires it
entry.setUpdated(new Date());
entry.setTitle("Test entry");
entry.setContentAsHtml("<p>This is the content of the entry</p>");
entry.addCategory("tag1");
entry.addCategory("tag2");
entry.setDraft(true);
entry.setLanguage("en-US");

RequestOptions options = client.getDefaultRequestOptions();
options.setSlug("entryanchor");

resp = client.post(coll_uri, entry, options);

switch(resp.getType()) {
  case SUCCESS:
    String location = resp.getLocation().toASCIIString();
    System.out.println("New entry created at: " + location);
    break;
  default:
    System.out.println("Error: " + resp.getStatusText());
}

The response will contain a representation of the entry created by the server. To get it, simply call resp.getDocument(). Within that entry you will find all of the information you need to locate the entry later (alternate link, id, edit uri, etc).

The media collections are equally simple.

First get the collection URI:

ClientResponse resp = client.get("http://blogs.tap.ibm.com/weblogs/services/atom");
Document<Service> service_doc = resp.getDocument();
Service service = service_doc.getRoot();
Collection collection = service.getCollection("chmod 777 ibm", "Media Entries");
String coll_uri = collection.getResolvedHref().toASCIIString();

Then prepare the post:

InputStream in = new ByteArrayInputStream("foo".getBytes()); // can be any kind of input stream

RequestOptions options = client.getDefaultRequestOptions();
options.setContentType("text/plain");
options.setSlug("somefile.txt");

resp = client.post(coll_uri, in, options);

switch(resp.getType()) {
  case SUCCESS:
    String location = resp.getLocation().toASCIIString();
    System.out.println("New entry created at: " + location);
    break;
  default:
    System.out.println("Error: " + resp.getStatusText());
}

As far as the APP specific features are concerned, that’s about it. I will have more info on the APP support in the other Lotus Connections components later, including a look at the rest of the extensions used throughout the suite.

One Response to “Lotus Connections API - Blogs”

  1. Simon Scullion Says:

    Hi James, great stuff!

    I have recently been looking in a bit more detail at the Connections beta, and really looking forward to playing with some of these extension points.

    Opportunities to integrate Connections with other applications can only help increase their value to an organisation and go beyond simple “social networking”.