Access Keys:
Skip to content (Access Key - 0)

Dan Hardiker Blog from November, 2008

  2008/11/16
AtlasCamp + Atlassian Plugins v2
Last Changed by Dan Hardiker, Dec 15, 2008 11:47

It's been a while since I've blogged about anything developer related, for various reasons; one of which was attending the first Atlas Camp - which was totally awesome, and I choose that superlative carefully. Not many things have a profound effect on me, but this was one ... I'm still buzzing!

It was simply amazing being amongst some of the smartest people in the community and chatting to the people that I've got to know over the years but never met, or those who I've not seen in a while. There were many presentations, lots of debates, free flowing food & drink, and as much werewolf as you could take! The Atlassian's haven't yet started talking about next year's event, but count my name already in.

There are too many topics to discuss in one post, but the clear leader in the importance stakes was Don Brown's presentations on Atlassian Plugins v2 (which will make it's debut in Confluence v2.10) and the new Shared Access Layer or SAL.

Table of Contents

Atlassian Plugins v2

After seeing the Plugins v2 talk I was energised and found myself wanting to get started ... so to aid other developers, I've decided to take a few common plugin use cases and apply them in demo form to real world applications.

Disclaimer
What follows is not an indication of the direction any actual plugin will take when adopting Plugins v2 features. It is however a useful construct for testing out concepts so you guys can see what's possible and what limitations you may hit along the way.

What's the diff?

Atlassian Plugins v2 has been built so it will work just like v1 unless you leverage the extra features that come from v2 now being OSGi based. While it won't hurt to understand OSGi's inner workings, there is no requirement for you to ... in fact I only have a ruidemntary understanding of OSGi and I have even been able to submit patches to the PLUG project already!

Getting Setup

Making a plugin "v2 enabled" is about as simple as possible. Simply add the pluginsVersion="2" attribute to the <plugin ...> element at the root of your atlassian-plugin.xml and the alternative loading system is engaged. Clearly, the Atlassian's have kept the adoption barriers low - so lets get started.

You will need the following to use my examples and follow this guide:

  1. The m8 developer release of Confluence v2.10
  2. Specifically for the Scriptix stuff:
    1. Java 6 (that's what I'm building on, some of the jars may work on Java 5)
    2. My patch to atlassian-plugins-osgi-2.1.1.jar for PLUG-203 which you can find here

Theme Builder Extensions

From a Plugins v2 point of view - this is trivial. There are two parts to this:

  • A master Builder plugin (like the plugin that currently exists) which defines a new module type with some code which consumes the usages.
    atlassian-plugin.xml excerpt
    <module-type key="builder-strapon" class="com.adaptavist.builder.ext.BuilderStraponModuleDescriptor"/>
    
  • Extension plugins which provide normal confluence modulesĀ (macros, resources, i18n, whatever) but also provide meta-data for the master to consume.
    atlassian-plugin.xml excerpt
    <builder-strapon key="foo" name="my first strapon"/>
    
    <builder-strapon key="big-strapon" name="Strap Me On Scottie!">
        <description>I love strap-ons</description>
        <param name="configFile">builder/big-strapon/config.xml</param>
        <resource type="download" name="test.css" location="builder/big-strapon/test.css">
            <param name="content-type" value="text/css"/>
        </resource>
        <resource type="download" name="test.jpg" location="builder/big-strapon/test.jpg">
            <param name="content-type" value="image/jpg"/>            
        </resource>
    </builder-strapon>
    

I think I'll take a minute to pause while those amongst you with dirty minds go clean out your heads ... ready? Let's continue.

Using the module as a proxy for communicating the configuration and resources means that the BuilderStraponModuleDescriptor is merely a bean factory and also means there's no dependance on the master plugin.

Here are the classes:

BuilderStraponModuleDescriptor.java
public BuilderStrapon getModule() {
    synchronized (this) {

        // Create if needed
        if (strapon == null) {
            strapon = new BuilderStrapon();
            strapon.setKey( getCompleteKey() );
            strapon.setTitle( getName() );
            if (TextUtils.stringSet( getDescription() )) {
                strapon.setDescription( getDescription() );
            }            

            // Load the config file
            String configFile = (String) getParams().get("configFile");
            if (configFile != null) {
                if (pluginAccessor == null) pluginAccessor = (PluginAccessor) ContainerManager.getComponent("pluginAccessor");
                try {
                    InputStream is = pluginAccessor.getPlugin( getPluginKey() )
                            .getClassLoader().getResourceAsStream( configFile );
                    strapon.setConfiguration( new String( StreamUtils.getBytes( is ) ) );
                } catch (IOException e) {
                    log.error(e, e);
                }
            }
        }

    }
    // Return
    return strapon;
}
BuilderStrapon.java
public class BuilderStrapon {

    private String key;
    private String title;
    private String description;
    private String configuration;

    // Accessors

The code for listing all the modules is fairly trivial too:

// Get the modules
List<BuilderStrapon> builderStrapons = new ArrayList<BuilderStrapon>();
for (BuilderStraponModuleDescriptor each :
        pluginAccessor.getEnabledModuleDescriptorsByClass( BuilderStraponModuleDescriptor.class )) {

    builderStrapons.add( each.getModule() );
            
}

Give it a go!

You can find the source code here:

  1. https://svn.atlassian.com/svn/public/contrib/confluence/v2demo/demo-builder-master/trunk/
  2. https://svn.atlassian.com/svn/public/contrib/confluence/v2demo/demo-builder-strapon/trunk/

If you want to get started quickly, then just install the following jar's in order:

  1. https://svn.atlassian.com/svn/public/contrib/confluence/v2demo/demo-builder-master/jars/demo-builder-master-0.1-SNAPSHOT.jar
  2. https://svn.atlassian.com/svn/public/contrib/confluence/v2demo/demo-builder-strapon/jars/demo-builder-strapon-0.1-SNAPSHOT.jar

Place the macro {builder-strapon-lister} onto a page and you should see a listing from inside of the Builder Master, showing the 2 extension modules defined in the Builder Strapon.

Possibilities

There are a few end goals which this could help to accomplish:

  1. Pre-made Builder theme's placed into the Plugin Repository, effectively creating a Builder Theme Library
  2. Enabling standard Confluence themes to provide an equivalent Builder configuration, so it can be used as a read-only base for on-the-fly customisation
  3. Exporting of on-the-fly Builder themes into a generated plugin jar, making for easier deployment

NOTE: the above are just possible, this is in no way a suggestion they'll emerge.

PLUG-200 - Better Pluggable Module Types

Fixed!
This made it into Confluence v2.10 - great going Atlassian!

Unfortunately if you were to uninstall/reinstall the Builder Master, or if you were to install the extension before the master, then it won't be picked up. I've come up with a suggestion for how this can be improved - although I don't have an implementation/patch for this one. Hopefully it'll make it into a future version of Atlassian Plugins, but I doubt it will be resolved in time for Confluence v2.10's immanent release.

Scriptix Extensions

With the Builder strapons, you saw a simple way of passing around metadata through the XML plugin descriptor - however some plugins would have more ambitious intentions and require more advanced integration. Here I'll demonstrate how Scriptix could:

  1. Allow languages to be bundled into an extension plugin for Scriptix to consume (so you could upload Groovy, and then start using it)
  2. Have Scriptix publish a neutral Scripting API, keeping it's security constraints
  3. Allow other plugins consume Scriptix's neutral API - not using the javax.script API means that there's no dependence on Java 6 with the consuming plugin, the dependence is on Scriptix instead

I'm not entirely sure if you can have a loose dependency, along the lines of "wire up ScriptixManager if it's installed (or gets installed after me) - later set it to null if it gets uninstalled" ... that would be ideal, as then plugins could enable scripting if it's available and remove that avenue if not.

Pluggable Languages

In order to achieve this, I decided to create an abstract class called ScriptixLanguage in the demo Scriptix master plugin. This looks like:

ScriptixLanguage.java
public abstract class ScriptixLanguage {

    private String key;
    private String name;

    // Accessors

	...
	
    // API

    public abstract ScriptEngine getScriptEngine();

    public abstract String getHelloWorld();

    public abstract String getHelloWorldEvalOutput();

}

The module type definition looks similar to Builder master's:

<module-type key="scriptix-language" class="com.adaptavist.scriptix.ext.ScriptixLanguageModuleDescriptor"/>

However, the descriptor looks a little different:

ScriptixLanguageModuleDescriptor.java
public ScriptixLanguage getModule() {
    synchronized (this) {
        if (language == null) {

            // Instanciate
            try {
                language = getModuleClass().newInstance();
            } catch (Throwable t) {
                language = new BrokenScriptixLanguage(t);
            }

            // Typical configuration
            language.setKey( getCompleteKey() );
            language.setName( getName() );

        }
    }
    // Return
    return language;
}

Using this from a language extension plugin means we have to do two things:

  1. Bundle the scripting libraries and javax.script interface
  2. Provide a concrete implementation of ScriptixLanguage

For a Groovy example, both of the above have the following pom.xml specifics:

<dependencies>
    ...
    <!-- Scriptix Master Dependency -->
    <dependency>
        <groupId>com.adaptavist.scriptix</groupId>
        <artifactId>demo-scriptix-master</artifactId>
        <version>0.1-SNAPSHOT</version>
        <scope>provided</scope>
    </dependency>
    <!-- Languages -->
    <dependency>
        <groupId>org.codehaus.groovy</groupId>
        <artifactId>groovy</artifactId>
        <version>1.5.7</version>
    </dependency>
    <dependency>
        <groupId>javax.scripting</groupId>
        <artifactId>groovy-engine</artifactId>
        <version>20080611</version>
    </dependency>
	...
</dependencies>
...
<repositories>
    ...
    <!-- repository for groovy-engine -->
    <repository>
        <id>internet2-m2-repository</id>
        <name>Internet2 Maven 2 Repository</name>
        <url>http://shibboleth.internet2.edu/downloads/maven2</url>
    </repository>
</repositories>

In the atlassian-plugin.xml:

<scriptix-language key="groovy" name="Groovy" class="com.adaptavist.scriptix.local.GroovyLanguage"/>

and the java:

GroovyLangauge.java
public class GroovyLanguage extends ScriptixLanguage {

    private ScriptEngine engine;

    public ScriptEngine getScriptEngine() {
        synchronized (this) {
            if (engine == null) {

                ScriptEngineManager manager = new ScriptEngineManager( getClass().getClassLoader() );
                engine = manager.getEngineByName("groovy");

            }
        }
        return engine;
    }

    public String getHelloWorld() {
        return "(1..10).sum()";
    }

    public String getHelloWorldEvalOutput() {
        return "55";
    }

}

Using the Pluggable Languages

To test the process, I wrote a LanguageListerMacro which simply puts the ScriptixManager onto the Velocity context and then ultimately executes the Hello World script from each of the available languages found - both of these found in the ScriptixManager's API. There is also a 3rd which will be used later.

ScriptixManagerImpl.java
public List<ScriptixLanguage> getAvailableLanguages() {
    if (pluginAccessor == null) throw new NullPointerException("Plugin Accessor not autowired!");

    // Get the modules
    List<ScriptixLanguage> scriptixLanguages = new ArrayList<ScriptixLanguage>();
    for (ScriptixLanguageModuleDescriptor each :
            pluginAccessor.getEnabledModuleDescriptorsByClass( ScriptixLanguageModuleDescriptor.class )) {

        scriptixLanguages.add( each.getModule() );

    }

    return scriptixLanguages;
}

public ScriptixLanguage getAvailableLanguageByModuleKey(String moduleKey) {
    if (!TextUtils.stringSet( moduleKey )) return null;

    for (ScriptixLanguage each : getAvailableLanguages()) {
        if (moduleKey.equals( each.getKey() )) return each;
    }

    return null;
}

// NOTE: For this to work PLUG-203 needs fixing / patching
public String executeScript(ScriptixLanguage language, String script) throws ScriptixException {
    if (language == null) throw new NullPointerException("You must provide a language");

    try {

        ScriptEngine engine = language.getScriptEngine();
        Object result = engine.eval( script );

        return String.valueOf( result );

    } catch (ScriptException e) {

        log.error(e, e);
        throw new ScriptixException(e.getMessage(), e);
        
    }
}

Just placing {scriptix-language-lister} on a page will get you going.

Publishing the ScriptixManager Service

The following atlassian-plugin.xml module publicises the Scriptix Manager API, specifically the public="true" means it's consumable outside of the defining plugin:

<component key="scriptixManager" class="com.adaptavist.scriptix.ext.ScriptixManagerImpl" public="true">
    <interface>com.adaptavist.scriptix.ext.ScriptixManager</interface>
</component>

That's it! Couldn't be simpler.

In order to compile a consumer, you will need to add the code dependency - so the Scriptix Master, in this case, will need to be installed to a Maven 2 repository so it's on the class path when building.

Consuming the ScriptixManager Service

To show how a 3rd party plugin could consume the ScriptixManager service, I wrote the demo Scriptix User plugin. It's simpler than you'd expect with the following module in atlassian-plugin.xml:

<component-import key="scriptixManager"> 
    <description>Consumes the Scriptix Manager service.</description>
    <interface>com.adaptavist.scriptix.ext.ScriptixManager</interface>
</component-import>

Of course, there's the Maven 2 dependency on the demo Scriptix Master - so I moved onto creating a ListLanguagesMacro, however that turned out to be too trivial to detail here, so I progressed onto the ExecuteScriptMacro quicker:

public String execute(Map params, String body, RenderContext renderContext) throws MacroException {
    try {

        String moduleKey = (String) params.get( RAW_PARAMS_KEY );
        ScriptixLanguage language = scriptixManager.getAvailableLanguageByModuleKey( moduleKey );

        if (language == null) throw new MacroException("Unable to find an available language by module key: "+ moduleKey);

        return scriptixManager.executeScript(language, body);

    } catch (ScriptixException e) {

        log.error(e, e);
        throw new MacroException(e.getMessage(), e);

    }
}

Give it a go!

You can find the source code here:

  1. https://svn.atlassian.com/svn/public/contrib/confluence/v2demo/demo-scriptix-master/trunk/
  2. https://svn.atlassian.com/svn/public/contrib/confluence/v2demo/demo-scriptix-language/trunk/
  3. https://svn.atlassian.com/svn/public/contrib/confluence/v2demo/demo-scriptix-user/trunk/

If you want to get started quickly, then first see the PLUG-203 patching instructions below and then install the following jar's in order:

  1. https://svn.atlassian.com/svn/public/contrib/confluence/v2demo/demo-scriptix-master/jars/demo-scriptix-master-0.1-SNAPSHOT.jar
  2. https://svn.atlassian.com/svn/public/contrib/confluence/v2demo/demo-scriptix-language/jars/demo-scriptix-language-0.1-SNAPSHOT.jar
  3. https://svn.atlassian.com/svn/public/contrib/confluence/v2demo/demo-scriptix-user/jars/demo-scriptix-user-0.1-SNAPSHOT.jar

Then place the following markup on a page:

h3. Language Lister
Here is a list of languages, as provided by the Scriptix Master plugin.
You will only see languages listed if you have the Scriptix Language plugin installed _after_ the Scriptix Master plugin.

*Result*
{quote}
{scriptix-language-lister}
{quote}

h3. Another Language Lister
Here is another list of languages, using a similar method to above, but in the Scriptix User plugin.
This is accessing scriptix over a component import - this is how I expect 3rd party plugin authors will come to utilise the power of scripting languages.

*Result*
{quote}
{scriptix-languages}
{quote}

h3. A script execution
Here is an execution of the following groovy script, using the module key {{com.adaptavist.scriptix.language:groovy}}
(requires Scriptix Language to be installed):

{code}"Here is a sum: "+ [1776, -1, 33, 99, 0, 888].sum(){code}

*Result*
{quote}
{scriptix-exec:com.adaptavist.scriptix.language:groovy}
"Here is a sum: "+ [1776, -1, 33, 99, 0, 888].sum()
{scriptix-exec}
{quote}

PLUG-203 - Extend supported JDK packages to take into account the JDK version

In order to take into account the JDK version to support Java 6 libraries (such as javax.script) I created a patch which you can install over the top of the packaged jar in WEB-INF/lib. You can download it here: https://svn.atlassian.com/svn/public/contrib/confluence/v2demo/support-lib/PLUG-203/atlassian-plugins-osgi-2.1.1-PLUG203.jar

Get Stuck In

I've enjoyed my foray into Plugins v2 and the future is indeed bright. I am also reassured that the Plugins v1 system will remain in place for a good while yet to ensure that the things that we have to hack into are still hackable. I am expecting that as time goes on, more and more of Confluence will become exposed and pluggable.

Bring it on!

Posted at 16 Nov @ 9:51 PM by Dan Hardiker 2 Comments
  2008/11/30
The Sensitive Man
Labels: humor

A woman meets a man in a bar. They talk; they connect; they end up leaving together..

They get back to his place and, as he shows her around his apartment, she notices that one wall of his bedroom is completely filled with soft, sweet, cuddly teddy bears. There are three shelves in the bedroom with hundreds and hundreds of cute, cuddly teddy bears carefully placed in rows covering the entire wall!

It was obvious that he had taken quite some time to lovingly arrange them and she was immediately touched by the amount of thought he had put into organizing the display. There were small bears all along the bottom shelf, medium-sized bears covering the length of the middle shelf, and enormous bears running all the way along the top shelf.

She found it strange for an obviously masculine guy to have such a large collection of Teddy Bears, but was quite impressed by his sensitive side, so didn't mention this to him.

They share a bottle of wine and continue talking and, after awhile, she finds herself thinking, 'Oh my God! Maybe, this guy could be the one! Maybe he could be the future father of my children?'

She turns to him and kisses him lightly on the lips.

He responds warmly.

They continue to kiss and the passion builds.

He romantically lifts her in his arms and carries her into his bedroom where they rip off each other's clothes and make hot, steamy love. She is so overwhelmed that she responds with more passion, more creativity, more heat than she has ever known. After an intense, explosive night of raw passion with this sensitive guy, they are lying there together in the afterglow.

The woman rolls over, gently strokes his chest and asks coyly, 'Well, how was it?'

The guy gently smiles at her, strokes her cheek, looks deeply into her eyes, and says:

'Help yourself to any prize from the middle shelf'

Posted at 30 Nov @ 11:40 AM by Dan Hardiker 0 Comments
Toggle Sidebar
Adaptavist Theme Builder Powered by Atlassian Confluence