Confluence 2.10 has reached end of life
Check out the [latest version] of the documentation
Overview
To flesh out the example macros, and to learn a bit about the process myself, I wrote a macro to insert World of Warcraft item links into any Confluence page. If you're not familiar with World of Warcraft (or WoW for those in-the-know) it's a MMORPG with millions of players world wide. What better way to show of some CSS and JavaScript integration with Confluence!
First a quick overview of what the macro is trying to do. If you've ever played the game or read any of the many WoW community web sites you would be familiar with item links. Each item in WoW has several properties that describe its use, its impossible to memorize the thousands of different items, so these web sites use javascript to add a popup to display item's details. When you move the mouse over the link the popup appears, showing you the details of the item. This example shows a link to the Skullsplitter Helm.
The macro works by sending a message to Allakhazam's XML interface which is validated and parsed into an object. The Macro then uses a velocity template to generate a small snippet of HTML and with some jQuery JavaScript wizardry, produces a popup.
The Plugin
The World of Warcraft plugin consists of two parts: The Macro, and The Web Resources.
The Macro
The heart of any macro is the execute
method. This method is responsible for returning the result of the macro's function, either in HTML or in WikiMarkup.
This macro goes through a very predictable process:
- Validate and interpret the parameters
- Connect to Allakhazam and ask for the item details
- Use velocity to render the output
For the complete source take a look here.
Validate The Input
We have some very simple requirements for input. We only have one parameter which is the item id. To confirm with what Allakhazam uses, I chose to call this parameter witem
. I also wanted to allow the user to supply the parameter without a name. The process to do this is described briefly here.
String witemString = (String) params.get("0"); if (witemString == null) { witemString = (String) params.get("witem"); } if (witemString == null) { return "No witem specified."; } int witem; try { witem = Integer.parseInt(witemString); } catch (NumberFormatException e) { return "witem specified is not a number: "+witemString; }
This code shows the process to check for the named and unnamed parameters (using the unnamed as preference). The string value is then validated by trying to convert to an integer.
Connect to Allakhazam
Now that we have a valid integer, that is hopefully valid item id, we use the HttpRetrievalService
to send a HTTP request to the Allakhazam website.
HttpResponse response = httpRetrievalService. get("http://wow.allakhazam.com/cluster/item-xml.pl?witem=" + witem); if (response.getStatusCode() != 200) { return "error " + response.getStatusCode() + " loading item"; }
For the macro to have access to the HttpRetrievalService
all we need is a setter method named appropriately. Confluence will call this method at the appropriate time, you can do this to inject any of the Confluence components.
private HttpRetrievalService httpRetrievalService; public void setHttpRetrievalService(HttpRetrievalService httpRetrievalService) { this.httpRetrievalService = httpRetrievalService; }
The HttpRetrievalService
takes care of the http connection for us and will time out according to the settings in the Administration section of the Confluence system. The retrieval service will give us an InputStream
as the result so we need to read that and create an object which we can actually use.
The result from Allakhazam is in XML format, and its usually pretty small. I chose to use a DOM parser process the XML then a series of XPath queries to extract the details we wanted:
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = docFactory.newDocumentBuilder(); Document document = builder.parse(in); XPathFactory xpathFactory = XPathFactory.newInstance(); XPath xpath = xpathFactory.newXPath(); final String nameString = (String) xpath.evaluate("//wowitem/name1", document, XPathConstants.STRING); final String htmlString = (String) xpath.evaluate("//wowitem/display_html", document, XPathConstants.STRING); final String qualityString = (String) xpath.evaluate("//wowitem/quality", document, XPathConstants.STRING); item.setName( StringUtils.isEmpty(nameString) ? UNKNOWN_ITEM : nameString); item.setHtml( StringUtils.isEmpty(htmlString) ? "" : htmlString); if (StringUtils.isNotEmpty(qualityString)) { int quality = Integer.parseInt(qualityString); item.setQuality(quality); } else { item.setQuality(0); }
Allakhazam's XML format includes item details that match other locales, an extension to this plugin could use this information to provide the popup in the current user's locale!
Render the Output
This macro uses velocity to render the output. This is helped using the VelocityUtils
class which provides easy to use methods for accessing the Velocity subsystem.
VelocityContext contextMap = new VelocityContext(MacroUtils.defaultVelocityContext()); contextMap.put(BODY_FIELD, item.getHtml()); contextMap.put(NAME_FIELD, item.getName()); contextMap.put(LINK_FIELD, "http://wow.allakhazam.com/db/item.html?witem=" + witem); contextMap.put(QUALITY_FIELD, item.getQualityName()); return VelocityUtils.getRenderedTemplate(TEMPLATE_NAME, contextMap);
We first create a context map by calling MacroUtils.defaultVelocityContext())
. This creates a Map
of some useful components for our template rendering. Creating a context like this is important if you want to access macro's and other components supplied by Confluence. This example then places this map into a VeloctyContext
object to provide type safety on the put methods.
The template used by this macro is extremely simple.
<!-- #requireResource("confluence.web.resources:jquery") #requireResource("com.atlassian.confluence.plugins.wow-plugin:resources") --> <div class="wow"><a class="wow-link wowitemtitle $qualityName" href="$link">$itemName</a>$body</div>
The references to $qualityName, $link, $itemName, and $body are resolved by Velocity as the template is processed. They are looked up in the context we supplied in the macro.
The two #requireResource calls tell Confluence to include the required resources in the page.
The first call tells Confluence to include jQuery. jQuery is actually available on every Confluence page, but since our macro uses it, we need to make sure Confluence loads jQuery first. We do that by supplying an explicit dependency in the order in which we need it.
These resources are configured in the atlassian-plugin.xml
file inside the plugin.
<web-resource key="resources" name="WoW Resources" i18n-name-key="com.atlassian.confluence.plugins.wow-plugin.resources"> <resource type="download" name="wow.css" location="wow.css"/> <resource type="download" name="wow.js" location="wow.js"/> </web-resource>
This snippet of the configuration shows the definition of the resources this macro uses. Confluence will use the extension of the name attribute to work out how to link in the resource (ie: link or script tag).
We have two resources, one for the CSS and one for our JavaScript.
Resources
The web resources configured in the previous section contain the CSS formating and JavaScript behavior of the macro. The CSS file is simple enough and can be seen here. Most of this CSS was taken from the Allakhazam web site then customized to work within Confluence. To do this I added a parent div tag to reduce the scope of the css selectors.
The JavaScript code uses jQuery to provide mouse over and popup functionality over the item link:
jQuery(function ($) { $(".wow-link").hover(function () { $(this).parent().find(".wowitem").show(); }, function () { $(this).parent().find(".wowitem").hide(); }); $(".wow-link").mousemove(function(e) { $(this).parent().find(".wowitem").css("left", e.pageX).css("top", e.pageY); }); });
First, we a hook into the hover event on any element with the wow-link
class. As the mouse enters over the link, the next sibling with the class wowitem
will be shown. As the mouse leaves, its hidden. This turns the item information section on an off.
Another hook is added on the mouse move event while the pointer is over the link. This hook is used to move the popup with the mouse pointer.
The Result
You can download this plugin from here and install it through the Administration section of your 2.8.x Confluence instance.
The source is available here.
Compiling the Source
The general details of compiling a plugin applies to this plugin, so follow the instructions there.
For the impatient:
- Install JDK 1.5 or later
- Install Maven 2
- Create a settings.xml file (a good start is this one)
- Checkout the trunk or a tag of the source
- use maven to compile it: mvn clean package