The single biggest problem in communication is the illusion that it has taken place.
George Bernard Shaw
The recent Atlassian Codegeist competition afforded me an opportune moment to further explore new areas of Confluence plugin development. My current contracting assignment involves heavily customising both the theme and functionality of the Confluence platform - but Codegeist offered a chance to really experiment!
With the explosion of social networking applications and the rise of the flow, I decided to integrate the functionality of one of the most profilic flow tools in the netsphere, Twitter, with Confluence.
Twitter + wiki = Twikkir
Posing the simple question 'What are you doing now?", millions have taken to tweeting their response on Twitter to anybody who will listen. From a simple status update to a possible support circle to a tribe building ecosystem, Twitter has become another pillar of the fast-evolving web. In his blog, Rands notes:
I want to see how they see the world. This is why I follow people on Twitter. This is why they follow me.
As I commented on the Workstreamer blog, I think enterprise companies have witnessed social networks flourish outside of their control and domain. They are now looking to replicate these networks in their own space in order to captialise on the perceived benefits, create structure/control mechanisms and expose the data for processing. Granted, some of these integrations may be exploratory - but the enterprise is definitely looking to keep apace with these developments - and a Twitter-like tool is a main contender.
The actual plugin development proffered some interesting issues to solve - persistence of posted messages, an update mechanism for diffusing new messages and the display of messages amongst others.
The issue of persisting user posts was one of the first obstacles to tackle. Confluence offers various persistence mechanisms for storing data - in brief:
- Hibernate - Confluence uses the Hibernate framework to persist its objects. However, it is generally not recommended to modify the Hibernate files to incorporate persistence of custom objects.
- Content Properties - a mechanism to store a key value pair with an associated object (content, space, etc.) within Confluence
- Bandana - this Atlassian framework uses XStream to convert arbitrary Java objects into XML for storage.
I selected the Bandana framework as the solution - allowing content-unrelated data (Twikkir posts and users) to be stored in the Confluence database. Extremely simple to use, Bandana allows the persistence and retrieval of objects through keys in a global or space related context. In order to avoid class loader issues, as noted by David Peterson, it is necessary for plugins to set the classloader reference in the XStream object correctly:
// Create xstream reference
if(xstream == null)
xstream = new XStream();
// Persist data within the global Bandana context
String xml = getXStream().toXML(userSet);
bandanaManager.setValue(new ConfluenceBandanaContext(), Constants.BANDANA_CTX_KEY_TWIKKIR_USERS, xml);
String xmlString = (String) bandanaManager.getValue(new ConfluenceBandanaContext(), Constants.BANDANA_CTX_KEY_TWIKKIR_USERS);
I was eager to add some AJAX functionality to this plugin and, in doing so, explore some of the JS libraries offering some 'Web 2.0' goodness. My goal was to enable the asynchronous ability to post and receive new twikkir posts and avoid a total page reload.
var $jq = jQuery.noConflict();
All subsequent calls to jQuery were made through the variable $jq. jQuery really impressed me - the animated tab panels were generated by one line of code! I look forward to exploring jQuery further.
var request = createRequest();
var url = "/plugin/twikkir/postit.action";
request.open("POST", url, true);
request.onreadystatechange = function()
if(request.readyState == 4)
resp = request.responseXML;
document.getElementById("poststatus").innerHTML = getTagContent(resp, "poststatus");
postStatusImg.src = restImage.src;
document.getElementById("twikkirpost").value = null;
document.getElementById("charsleft").innerHTML = maxPostLen;
It should be noted that it is necessary to set the appropriate request type header in the request parameter (line 16 above). It is also necessary to set the appropriate response type in the associated action class - for example:
Further, the altassian-plugin.xml should also reflect the return type of the action - in this instance "velocity-xml":
<action name="postit" class="com.sidus.confluence.twikkir.action.PostItAction"> <result name="success" type="velocity-xml">/templates/sidus/twikkir/posted.vm</result> </action>
Another experiment took shape in the version control system used - Github - where you can find the source code behind the plugin.
Building the plugin proved to be an extremely useful learning exercise. The plugin certainly could be improved and there are many extension points:
- Send/receive tweets from Twitter
- Protect your twikkir feed
- Enable Confluence entities (pages/ blogposts, etc.) to send tweets
- Enable a command line interface to execute Confluence actions
- Build twikkir clouds
- Build twikkir connection data - inferring/suggesting potential relationships between users based on who is following who
The flexibility of the Confluence plugin system creates countless possibilities for extending the wiki platform with social networking integration and Twikkir is just one of these possibilities.
... and of course you can follow me @ keibro on Twitter.