ShowTable of Contents
Introduction
Most social networks (Twitter, Blogger, etc.) provide Representational State Transfer (REST) APIs to allow users to extend functionality and interact with their content. In the world of Enterprise Social Networks you, as a Lotus Connections user, can create your own applications and scripts that manipulate Connections content and functionality through the REST / Atom Syndication Format (Atom) API. There are many situations in which you can do smarter work by automating some everyday task, exporting important information to another application, or importing pre-prepared data to your collaborative social web.
One of the most useful features of Lotus Connections is the Wikis application, with which you can collaboratively create pages in rich text, HTML, or Wiki text, sharing them across your network or keeping them as your personal work diaries. Using the REST API you are able to send the data from your selected Wiki page to an external editor or save it as a part of a HTML document. You can also save your Wiki content for off-line editing and synchronize it later with the on-line version.
In this article we demonstrate the possibilities of the Lotus Connections API---specifically, the Wikis application API---and guide you through the process of writing your own REST/Atom client in Python. We introduce some basic scripts, explaining them line by line, and show how to authenticate with Lotus Connections and retrieve and parse a Wiki, or Wiki page Atom entry. You will also learn how to get and post the page content, all of which is made possible by use of the REST/Atom API.
About the code we use
The code samples in the following sections illustrate key points in our sample application. They should be combined in sequence to build a working application that authenticates with Lotus Connections Wikis and gets and sets page content. All the code samples use the official
IBM Lotus Connections Wikis 2.5/3.0 API .
We write all the code in Python 2.6, using only standard libraries, so it should be compatible with all Python 2.x versions. The code must be changed to run in Python 3.x due to changes in standard libraries. It is assumed that you have Python installed and running.
Setup
The first step is to import the libraries that we are to use (see listing 1). We need the cookielib library for maintaining a cookie for authentication, urllib for URL encoding, and urllib2 for connecting to the server. We also require the xml.dom library for XML manipulation on the Atom feeds and the string standard library for string manipulations. You can import a whole library, as in the case of urllib2, or specific functions, as in the case of urllib.
Listing 1. Importing libraries
from cookielib import LWPCookieJar # needed for form authentication
from urllib import urlencode, quote
import urllib2
import string # to find and replace content in strings
from xml.dom import minidom # XML manipulation of Atom feeds
It would be nice to set some basic parameters that will be used throughout the code. You can get them as your program arguments or from a configuration file. For the ease of use we are just going to set them inside the application (see listing 2).
Listing 2. Setting basic parameters
SERVER_NAME = 'your-server-name-here'
USER_NAME = 'your-username-here'
PASSWORD = 'you-password-here'
WIKI_NAME = 'an-existing-wiki-you-can-contribute-to'
PAGE_NAME = 'an-existing-page-you-can-edit-in-the-above-wiki'
# if port numbers are needed for your server, fill in below, otherwise leave them as is. Example: HTTP_PORT = ':9080'.
HTTP_PORT = ''
HTTPS_PORT = ''
Logging in to Lotus Connections
The next step is to authenticate with your Connections server. To do this, we make a secure HTTP connection using a urllib2 URLopener object and post the credentials through that secure connection to the j_security_check servlet. We store the received session cookies (if any) in the LWPCookieJar object. The opener connects to the LWPCookieJar using a HTTPCookieProcessor (see listing 3).
Listing 3. Authenticating with Connections server
# Create authenticated server opener
cookieProcessor = urllib2.HTTPCookieProcessor(LWPCookieJar())
opener = urllib2.build_opener(cookieProcessor)
# encoded parameters sent in a POST method (over secure connection)
encodedForm = urlencode({ "j_username" : USER_NAME, "j_password" : PASSWORD})
# in this test case we used the port numbers, depending on the server, you can ignore these
urlform = "https://" + SERVER_NAME + HTTPS_PORT + "/wikis/j_security_check"
request = urllib2.Request(urlform, encodedForm)
opener.addheaders = [("User-agent","Mozilla/5.0")]
# Read the response from the server
loggedIn = opener.open(request).read()
We then URL-encode the authentication credentials which, together with the URL link to the j_security_check servlet, is used to construct a HTTP Request object. The HTTP Request object is created as a HTTP POST request because there is a second parameter in the constructor; otherwise, it would be a HTTP GET request.
Optionally, we can add a recommended header to the HTTP request, setting the User Agent to be Mozilla Firefox
compatible
.
The opener opens the request URL, and the contents of the response are stored in the loggedIn variable. Once the server responds, we can test whether the authentication succeeded. If the authentication failed, the response would be the Log-in page in Lotus Connections 3.0 or the redirect page to the Log-in page URL in Lotus Connections 2.5. We use this knowledge to test the response on these cases, as shown in listing 4.
Listing 4. Testing whether authentication succeeded
# Check if the response contains a redirection to the login page
# Or check if the response is the login page
if string.find(loggedIn, 'window.location.replace') == -1 and string.find(loggedIn, 'X-LConn-Login') == -1:
print 'Logged in'
else:
print 'Failed to log in'
exit()
You can see the results of running the code in the screen capture of the Python shell in figure 1.
Figure 1. Results of running the code
Getting the Wiki page Atom entry
After we've logged in, we can use the existing urllib2 infrastructure to open any URL on the Lotus Connections server that's accessible to the authenticated user, including his/her Wikis and Wiki pages. When getting Wiki pages we must first get the Wiki page Atom entry, which contains information about the Wiki page in which we are interested, including its URL.
So, a great use case with which to start would be to open a Wiki page Atom entry URL and get the HTML content of the selected Wiki page. To do this, first we need the Wiki page URL from the Atom API of Connections Wikis. We then set the Wiki name and page name substitutions of the URL and send a GET request to that URL (see listing 5).
Listing 5. Getting Wiki page Atom entry
# String template for the Wiki page entry
pageEntryUrl = "/wikis/basic/api/wiki/%s/page/%s/entry"
# SERVER_NAME, HTTP_PORT, WIKI_NAME and PAGE_NAME are the configuration parameters defined in the Setup section above.
# The quote function will replace all white spaces with %20 etc. correctly formatting the URL
url = "http://" + SERVER_NAME + HTTP_PORT + quote(pageEntryUrl % (WIKI_NAME, PAGE_NAME))
request = urllib2.Request(url)
entry = opener.open(request).read()
print entry
The server response should contain the Atom/XML entry of the Wiki page containing the page information (see figure 2).
Figure 2. Server response
Parsing the Wiki page entry and downloading its content
Once we have the Atom entry feed XML string, we can parse it by using the minidom parser. From the XML tree created by the parser we can harvest the content source URL and use it as the request URL for the page data. The content source URL is stored in the 'src' attribute of the 'content' element of the entry XML (see listing 6).
Listing 6. Parsing using the minidom parser
try:
xmldom = minidom.parseString(entry)
except:
print 'Error parsing entry XML'
raise
# there is only one element with a tag name 'content', get its 'src' attribute
url = xmldom.getElementsByTagName('content')[0].getAttribute("src")
request = urllib2.Request(url)
content = opener.open(request).read()
print content
The content of the Connections 3.0 Wiki page is returned as a HTML string, as shown in figure 3. Note, however, that in Lotus Connections 2.5 the content is returned as an XML string.
Figure 3. Content returned as HTML string
The content of the Wiki page in Lotus Connections 3.0 is returned as a HTML by default because the page source URL contains the parameter "convertTo=html". If we remove this parameter, the returned data will be XML data, just as in Lotus Connections 2.5.
Formatted as XML, it is easier to post the data back to the Wiki page since the update Wiki page API expects data in XML format. Therefore for the rest of this code sample, we use XML content (see listing 7).
Listing 7. Removing the convertTo=htmlparameter
request = urllib2.Request(url.replace("?convertTo=html",""))
content = opener.open(request).read()
print content
Figure 4 shows the response to this request.
Figure 4. Content returned as XML string
Create an XML Atom entry for posting the page content
The Atom entry for posting the Wiki content is the Wiki page template Atom XML that needs a few mandatory fields, that is, title, label, and the content. Optional nodes like visibility, propagation, and summary are ignored in this case. We can create the XML using the minidom object from the Python XML library (see listing 8).
Listing 8. Creating XML using minidom object
# create the XML document we populate
doc = minidom.Document()
# create the root entry tag
entry = doc.createElement("entry")
entry.setAttribute("xmlns","http://www.w3.org/2005/Atom")
doc.appendChild(entry)
category = doc.createElement("category")
category.setAttribute("term","page")
category.setAttribute("label","page")
category.setAttribute("scheme","tag:ibm.com,2006:td/type")
entry.appendChild(category)
# set the title and label of the page
titlenode = doc.createElement("title")
titletext = doc.createTextNode(PAGE_NAME)
titlenode.appendChild(titletext)
entry.appendChild(titlenode)
label = doc.createElement("label")
label.setAttribute("xmlns","urn:ibm.com/td")
labeltext = doc.createTextNode(PAGE_NAME)
label.appendChild(labeltext)
entry.appendChild(label)
# Some parts of the entry have been removed as optional
# like visibility, propagation, summary
# finally, set the content
contentnode = doc.createElement("content")
contentnode.setAttribute("type","text/html")
text = doc.createTextNode(content)
contentnode.appendChild(text)
entry.appendChild(contentnode)
print doc.toxml()
Figure 5 shows the XML content of the doc object printed to the screen.
Figure 5. The XML Atom entry of the Wiki page content
Update the Wiki page with the content
Once the Wiki page entry has been created with the new content, we can update the Wiki page by posting the XML to the Wiki page entry URL. To do this, we need only to set the HTTP method for updating an existing Wiki page to PUT and specify the content type to application/atom+xml. Posting to a URL will go through a secure connection, and we will specify the createVersion parameter to save the content as the next document version (see listing 9).
Listing 9. Updating the Wiki page
# add parameter createVersion to the URL
url = "https://" + SERVER_NAME + HTTPS_PORT + quote(pageEntryUrl % (WIKI_NAME, PAGE_NAME)) + "?createVersion=true"
request = urllib2.Request(url, doc.toxml())
# add header describing the posted content type
request.add_header('Content-Type', 'application/atom+xml')
# override the method from POST to PUT
request.get_method = lambda: 'PUT'
result = opener.open(request).read()
print result
Figure 6 shows the executing of the update to the Wiki page.
Figure 6. Executing of the update to the Wiki page
Conclusion
We have shown how easy it is to GET Atom entries from your server and POST/PUT content to Lotus Connections Wikis, using Python 2.x. Now with this knowledge you can read the official Lotus Connections API documentation and experiment a bit more with its possibilities. Using the simple techniques shown here, you should now be able to create your own interesting applications to GET and combine content from any of the Lotus Connections components, POST it back, or use it in an external application.
Even though our code was written in Python, the same basic principles would apply to any other programming language of your choice. Good luck and have fun with Lotus Connections!
Resources
developerWorks® Lotus Connections product page:
https://www.ibm.com/developerworks/lotus/products/connections/
Lotus Connections documentation:
https://www.ibm.com/developerworks/lotus/documentation/connections/
Lotus Connections discussion forum:
http://www-10.lotus.com/ldd/lcforum.nsf?OpenDatabase
About the authors
Thomas Delahunty is a Software Developer on the Connections Wiki team based at IBM's Dublin Software Lab (DSL). He has worked on Connections Wikis for the past year, before which he spent two years working for ISSL on a number of Customer sites, and three years at DSL working on Domino Portal integration tools. He co-presented a session at Lotusphere 2005 titled "AD301 - Integrating Domino Applications into WebSphere Portal and IBM Workplace."
Emil Varga is a Software Developer for Lotus Connections Wikis at IBM's DSL. He loves machine learning, Linux, social Web development, and programming in languages such as Python and JavaScript. He has a M.Sc. in Computer Science from the Faculty of Electrical Engineering, University of Belgrade, Serbia.