Berichten met label plugin

Developing a Confluence Plugin

Introduction

Does your company use Confluence? If you’re looking to extend its possibilities, it may not seem obvious straight away which of the 30+ plugin module types to choose.

Say you want to add a form to Confluence in order to submit (and process) some data. Once the processing is done, the results should be displayed, if possible on the same page. If you’re interested in how to accomplish this, you probably want to keep on reading; it seems easy to create such a plugin, but there’s a few issues that you’re likely to run into.

In this blog I’ll describe the more troublesome/unexpected ones that we encountered and will explain how they were handled (or sometimes: worked around).

Plugin Module Types

First of all, let’s pick the plugin module types to use. In order to embed (HTML) content in a page, the plugin type to use is the Macro Plugin. This is not sufficient, though; for something to happen, the macro needs to display some form elements (e.g. text fields, select fields and a submit button) and when the form is submitted, the right action should take place. For this to happen, we used an XWork Action; the form data is sent to and handled by the Action.

First of all, you need to get a simple Macro Plugin up-and running. Besides creating an Archetype and some plugin-specific steps, most of the steps required to do this can be found here.

Following that, you need to setup an XWork Action (check the ‘XWork Action’ link above to find out more). Once that is done, create a velocity macro (.vm file) containing a form that submits its data to the Action:

    <form name="myForm" action="/confluence/plugins/actions/myaction.action" method="GET">
    	$action.inputText: <input type="text" name="myInput">
        <p>
        <input type="submit" value="Submit">
    </form>

Note that he action to be used (on a ’submit’ of the form) should be defined as the XWork Action ‘myaction’ in atlassian-plugin.xml.

After that, display the form by embedding (all text and controls defined in) the .vm file into the macro by updating its execute(…) method (used when the macro is rendered):

public String execute(Map params, String body, RenderContext renderContext) throws MacroException {
    Map context = MacroUtils.defaultVelocityContext();
    context.put("action", new MyAction());
    return VelocityUtils.getRenderedTemplate("templates/myaction.vm", context);
}

Note that the action that will handle the submitted form data is supplied through a “velocity context” map. Supplying it is only required if the action is queried when rendering the velocity macro (i.e. if you’re using elements in the .vm file that start with $action. – specifically, in this case: $action.inputText).

Also, it’s important to use only HTML body elements in the .vm file; a complete HTML syntax will result in anything on a page following the {macro} tag no longer being displayed! After this is done, it is possible to display / fill in the form (and submitting it to the XWork Action) through rendering the Macro that is wrapped around it.

Return from whence we came

As previously mentioned, we would like to ’stay’ in the same page. In order to achieve this, Confluence offers the possibility of a (server) redirect. Now, all we need to do is figure out from whence we came, since the macro can be embedded pretty much anywhere! Unfortunately, simply checking the request URL does not work (because of Confluence (pre)processing); a generic URL is returned (ending with /pages/viewpage.action). We solved it using a bit of a ’scrippety-trickery’ solution! Since we can’t find out the URL on the server-side, in the form that was submitted, we included a hidden element:

<input type="hidden" name="macroUrl">

and set it with the current URL by including a bit of JavaScript in the .vm file as well:

<script language="JavaScript">
	document.myForm.macroUrl.value = window.location
</script>

Don’t forget to add the matching getter and setter in the action that the velocity macro (form) posts to (in this case ‘MyAction’)! Once this is done, you can retrieve the Action value in the xwork/myaction part of the atlassian-plugin.xml configuration file (note that this is runtime info and not maven setting some properties!):

<action name="myaction" class="com.luminis.confluence.plugin.MyAction">
    <result name="success" type="redirect">${macroUrl}</result>
</action>

Persistent data

In order to store userspecific data, Confluence uses a table in which key-value combinations can be stored: the BANDANA table. Writing to and reading from this table is done through the BandanaManager.

To get hold of the correct manager objects, all you need to do is declare a variable and a setter for the variable and Spring will auto-inject the right managers:

    BandanaManager m_bandanaManager;
    public void setBandanaManager(BandanaManager bandanaManager) {
        m_bandanaManager = bandanaManager;
    }

If, at some point, you can’t wait for Spring to inject them, you can also look them up manually, e.g. for the PlatformTransactionManager:

    PlatformTransactionManager m_transactionManager;
    // ...
    Object o = ContainerManager.getComponent("transactionManager");
    if (o instanceof PlatformTransactionManager) {
        m_transactionManager = (PlatformTransactionManager)o;
    }

Finally, the BandanaManager uses a ‘context’ to get and set values:

    BandanaContext m_bandanaContext;
    // ...
    String rootKey = getClass().getPackage().getName();
    m_bandanaContext = new ConfluenceBandanaContext(rootKey);

As soon as the BandanaManager, the PlatformTransactionManager and the ConfluenceBandanaContext are available, it’s possible to store data into the BANDANA table:

    public void storeKeyValue(String key, Object val) {
        storeDataWithinTransaction(key, val);
        // Stopping and starting Confluence here is no problem.
        Object o = m_bandanaManager.getValue(m_bandanaContext, key);
        // o.equals(val) == true // assuming properly implemented 'equals()'
    }

    // Data should be stored within a transaction; use a Spring TransactionTemplate to wrap a transaction around the operation:
    public void storeDataWithinTransaction(final String key, final Object val) {
        TransactionTemplate tt = new TransactionTemplate(m_transactionManager);
        TransactionCallback callback = new TransactionCallback() {
            public Object doInTransaction(TransactionStatus transactionStatus) {
                m_bandanaManager.setValue(m_bandanaContext, key, val);
                return null;
            }
        };
        tt.execute(callback);
    }

Persistent ‘proprietary’ data

In case you want to define your own objects and store them as well, this will fail when using the BandanaManager, since it can’t find the class. Note that this is a tricky failure because after restarting Confluence, reading the value fails without any logging at all! Storing (new) values after that, however, does work. To make sure this does succeed, use XStream to convert the object to a(n XML) String before storing it:

    XStream m_xStream = new XStream();
    {
        m_xStream.setClassLoader(getClass().getClassLoader());
        m_xStream.alias("my-class", MyClass.class);
    }

    public void storeMyClass(MyClass mc) {
        String xmlStr = m_xStream.toXML(mc);
        storeKeyValue("specifickey", xmlStr);
    }

And convert it back from XML after reading it:

    public MyClass readMyClass() {
        String s = (String)m_bandanaManager.getValue(m_bandanaContext, "specifickey");
        return (MyClass)m_xStream.fromXML(s);
    }

, , , , , , , , ,

5 reacties

“top threads” plugin for JConsole

When working with large (server side) java application, sometimes it would be nice if you could look inside, to see what thread is taking up so much cpu time, and why. Something similar to the Unix top command, but then showing all threads in one (java) application, instead of all processes in the system.

When I was looking for such a monitoring application, I came accross the 2.0 version of MC4J that provides a “Thread Info” panel that displays threads together with CPU usage; exactly what I needed. Unfortunately, there is only an alpha release of this MC4J version, that is not yet perfectly stable. Moreover, the thread info panel doesn’t handle applications with large amounts of threads very well. As the source code of this version of MC4J is not (yet) publically available, this option turned out to be a dead end.

To my surprise, other applications with such functionality are hard to find. There are probably enough profiling applications that can do the job, but I wanted something simple, something JMX-based, that can used also to monitor applications running in production.

There is however something called JTop, which is a plugin for JConsole. It’s actually a demo for the new (since Java 6) JConsole plugin API, that does show CPU usage per thread. It’s fairly basic and only shows total CPU usage, which is not very usefull. You would expect that (after a year), somebody would have extended the demo to something more useful, but as I couldn’t find anything like that, I thought I should give it a try myself.

The result is a JConsole plugin that displays the top threads, sorted by cpu usage in the last sampling period. It also displays cpu usage history, and an average over the last 10 sampling periods.

To avoid ending up with an unresponsive user interface when monitoring applications with large number of threads, I took a few precautions. First of all, the plugin has it’s own refresh rate. It’s independent from the JConsole poll interval, which is 4 seconds by default. For applications with large amounts of threads, this is way too short: only retrieving all thread information can already take 4 or 5 seconds! Although you can change the JConsole poll interval with a command line option, I thought it would be more convenient to be able to change it from the monitoring panel. It’s default set to 10 seconds, which I think is reasonable in most cases. If you notice that cpu usage measurement takes too much of the application under test, just increase the sample period and see the RMI Connection thread that processes these request, sink in the list.

Another precaution was not to list all threads in the table. Displaying thousands of rows in a table is quite useless in any case, and I was afraid it would seriously harm performance. Eventually, diplaying that many rows turned out to be not much of a problem; I guess I still suffer from an prejudice with
respect to Swing performance…

Using MX4J also showed me that in a continuously refreshing table, it’s hard to select a thread in order to see it’s stacktrace. Therefore, in this plugin, tracing a thread is “sticky”: when you click a row in the table, the stacktrace of that thread is shown immediately and is refreshed with each new sample, until you deselect it or select another thread.

Even though having threads sorted by cpu usage is the logical thing to do, it’s not always convenient when you’re studying the results, as rows keeping moving with each refresh. To lock the rows to there current position, click the “fix order” button. The topmost rows (actually all rows with a non-zero cpu usage), will stay where they are. Rows that had a cpu usage of zero, but have a non-zero value in the next sampling periods, will appear just below these rows, to avoid that you oversee any thread that suddenly takes a large amount of cpu time.

You can run the plugin by downloading the jar file and passing it to JConsole with the plugin option:
jconsole -pluginpath topthreads.jar. When JConsole connects, it should have a seventh tab named “top threads”.

update: there’s a new version, see http://lsd.luminis.eu/new_version_topthreads_jconsole_plugin

, ,

18 reacties