How to build a Confluence Macro Plugin
April 13th, 2010 by Micha KopsThe goal is to build a small macro plugin deployable via the Confluence plugin API rendering some spaces.
Please note that I am going to build the plugin using just Maven and not the Atlassian Maven Wrapper called the “Atlassian Plugin SDK” – more information about that is available at the Atlassian website.
The macro output will be rendered using a Velocity template and all messages are stored for i18n in properties files bundled with the plugin.
If you need to set up an instance of Confluence first, head over to this article.
Steps
- Create a new project structure using archetypes
mvn archetype:generate -DarchetypeCatalog=http://svn.atlassian.com/svn/public/atlassian/maven-plugins/archetype-catalog
- This should show some output like this:
Choose archetype: 1: http://svn.atlassian.com/svn/public/atlassian/maven-plugins/archetype-catalog -> bamboo-plugin-archetype (Archetype for a new Atlassian Bamboo plugin project) 2: http://svn.atlassian.com/svn/public/atlassian/maven-plugins/archetype-catalog -> confluence-plugin-archetype (Archetype for a new Atlassian Confluence plugin project) 3: http://svn.atlassian.com/svn/public/atlassian/maven-plugins/archetype-catalog -> confluence-plugin2-archetype (Archetype for a new Atlassian Confluence plugin 2 project (OSGi-based plugin)) 4: http://svn.atlassian.com/svn/public/atlassian/maven-plugins/archetype-catalog -> crowd-plugin-archetype (Archetype for a new Atlassian Crowd plugin project) 5: http://svn.atlassian.com/svn/public/atlassian/maven-plugins/archetype-catalog -> crucible-plugin-archetype (Archetype for a new Atlassian Fisheye or Atlassian Crucible plugin project) 6: http://svn.atlassian.com/svn/public/atlassian/maven-plugins/archetype-catalog -> jira-plugin-archetype (Archetype for a new Atlassian JIRA plugin project) Choose a number: (1/2/3/4/5/6):
- Be sure to select confluence-plugin2-archetype as we want to build an OSGi plugin for Confluence!
- Enter the groupId, artifactId, version and optional the package name and confirm your selection
Define value for groupId: : com.hascode.confluence Define value for artifactId: : macro-tutorial Define value for version: 1.0-SNAPSHOT: : 0.1 Define value for package: com.hascode.confluence: : Confirm properties configuration: groupId: com.hascode.confluence artifactId: macro-tutorial version: 0.1 package: com.hascode.confluence Y: :
- Edit the project name in the pom.xml and adjust the Confluence version used – the file should look like this:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <parent> <groupId>com.atlassian.confluence.plugin.base</groupId> <artifactId>confluence-plugin-base</artifactId> <version>25</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.hascode.confluence</groupId> <artifactId>macro-tutorial</artifactId> <version>0.1</version> <name>hasCode.com - Confluence Macro Tutorial</name> <packaging>atlassian-plugin</packaging> <properties> <atlassian.plugin.key>com.hascode.confluence.macro-tutorial</atlassian.plugin.key> <!-- Confluence version --> <atlassian.product.version>2.9</atlassian.product.version> <!-- Confluence plugin functional test library version --> <atlassian.product.test-lib.version>1.4.1</atlassian.product.test-lib.version> <!-- Confluence data version --> <atlassian.product.data.version>2.9</atlassian.product.data.version> </properties> </project>
- Now edit the plugin deployment descriptor named atlassian-plugin.xml in src/main/resources as described in the Macro Module Documentation and add a reference to the properties file used for i18n
<atlassian-plugin key="${atlassian.plugin.key}" name="hasCode.com Macro Tutorial" pluginsVersion="2"> <resource type="i18n" name="i18n" location="com.hascode.confluence.macro.messages"/> <plugin-info> <description key="hascode.plugin.description"/> <version>${project.version}</version> <vendor name="hasCode.com" url="https://www.hascode.com"/> </plugin-info> <macro name="DemoSpacesMacro" key="hascode.plugin.macro.name" class="com.hascode.confluence.macro.SpacesMacro"/> </atlassian-plugin>
- Now create the directory structure for the property files and create a file named messages.properties in the created directory src/main/resources/com/hascode/confluence/macro
mkdir -p src/main/resources/com/hascode/confluence/macro
- Add the two keys needed to the messages.properties
hascode.plugin.description=This is a plugin description hascode.plugin.macro.name=Spaces Overview Macro
- Add a package named com.hascode.confluence.macro and add a class SpacesMacro
package com.hascode.confluence.macro; import java.util.Map; import com.atlassian.renderer.RenderContext; import com.atlassian.renderer.v2.RenderMode; import com.atlassian.renderer.v2.macro.BaseMacro; import com.atlassian.renderer.v2.macro.MacroException; public class SpacesMacro extends BaseMacro implements com.atlassian.renderer.v2.macro.Macro { /* * (non-Javadoc) * * @see com.atlassian.renderer.v2.macro.Macro#execute(java.util.Map, * java.lang.String, com.atlassian.renderer.RenderContext) */ public String execute(Map params, String body, RenderContext renderContext) throws MacroException { return "TEST"; } /* * (non-Javadoc) * * @see com.atlassian.renderer.v2.macro.Macro#getBodyRenderMode() */ public RenderMode getBodyRenderMode() { return RenderMode.ALL; } /* * (non-Javadoc) * * @see com.atlassian.renderer.v2.macro.Macro#hasBody() */ public boolean hasBody() { return false; } /* * (non-Javadoc) * * @see com.atlassian.renderer.v2.macro.Macro#isInline() */ public boolean isInline() { return false; } /* * (non-Javadoc) * * @see * com.atlassian.renderer.v2.macro.Macro#suppressMacroRenderingDuringWysiwyg * () */ public boolean suppressMacroRenderingDuringWysiwyg() { return false; } /* * (non-Javadoc) * * @seecom.atlassian.renderer.v2.macro.Macro# * suppressSurroundingTagDuringWysiwygRendering() */ public boolean suppressSurroundingTagDuringWysiwygRendering() { return false; } }
- This macro returns a simple string so now add templating, logging and functionality
- First create a directory to put the template into in src/main/resources
mkdir -p src/main/resources/template
- Create a template named macro.vm in src/main/resources/template
Spaces Macro $output
- Change the class SpacesMacro to this
package com.hascode.confluence.macro; import java.util.Map; import com.atlassian.confluence.renderer.radeox.macros.MacroUtils; import com.atlassian.confluence.util.velocity.VelocityUtils; import com.atlassian.renderer.RenderContext; import com.atlassian.renderer.v2.RenderMode; import com.atlassian.renderer.v2.macro.BaseMacro; import com.atlassian.renderer.v2.macro.MacroException; public class SpacesMacro extends BaseMacro implements com.atlassian.renderer.v2.macro.Macro { private static final String MACRO_TEMPLATE = "template/macro.vm"; /* * (non-Javadoc) * * @see com.atlassian.renderer.v2.macro.Macro#execute(java.util.Map, * java.lang.String, com.atlassian.renderer.RenderContext) */ public String execute(Map params, String body, RenderContext renderContext) throws MacroException { Map ctx = MacroUtils.defaultVelocityContext(); ctx.put("output", "Test"); return VelocityUtils.getRenderedTemplate(MACRO_TEMPLATE, ctx); } [..] }
- Now add some logging:
package com.hascode.confluence.macro; import java.util.Map; import org.apache.log4j.Logger; [..] public class SpacesMacro extends BaseMacro implements com.atlassian.renderer.v2.macro.Macro { public static final String LOGGER_KEY = "com.hascode.confluence.macro"; private static final String MACRO_TEMPLATE = "template/macro.vm"; private static final Logger log = Logger.getLogger(LOGGER_KEY); /* * (non-Javadoc) * * @see com.atlassian.renderer.v2.macro.Macro#execute(java.util.Map, * java.lang.String, com.atlassian.renderer.RenderContext) */ public String execute(Map params, String body, RenderContext renderContext) throws MacroException { log.debug("macro execution triggered with params " + params.toString()); Map ctx = MacroUtils.defaultVelocityContext(); ctx.put("output", "Test"); return VelocityUtils.getRenderedTemplate(MACRO_TEMPLATE, ctx); } }
- Now enable logging in your Confluence installation .. go Browse > Confluence Admin > Logging and Profiling and add a new logging entry with the key specified in LOGGER_KEY, com.hascode.confluence.macro and your desired log level
- After the plugin deployment and eventually a server restart you might enjoy your logging in <confluence-installation>/logs/catalina.out (the location might differ)
- Now adding some functionality the macro class finally looks like this:
package com.hascode.confluence.macro; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import com.atlassian.confluence.renderer.radeox.macros.MacroUtils; import com.atlassian.confluence.spaces.Space; import com.atlassian.confluence.spaces.SpaceManager; import com.atlassian.confluence.util.velocity.VelocityUtils; import com.atlassian.renderer.RenderContext; import com.atlassian.renderer.v2.RenderMode; import com.atlassian.renderer.v2.macro.BaseMacro; import com.atlassian.renderer.v2.macro.MacroException; public class SpacesMacro extends BaseMacro implements com.atlassian.renderer.v2.macro.Macro { public static final String LOGGER_KEY = "com.hascode.confluence.macro"; private static final String MACRO_TEMPLATE = "template/macro.vm"; private static final Logger log = Logger.getLogger(LOGGER_KEY); private SpaceManager spaceManager; /* * (non-Javadoc) * * @see com.atlassian.renderer.v2.macro.Macro#execute(java.util.Map, * java.lang.String, com.atlassian.renderer.RenderContext) */ public String execute(Map params, String body, RenderContext renderContext) throws MacroException { log.debug("macro execution triggered with params " + params.toString()); Map ctx = MacroUtils.defaultVelocityContext(); ctx.put("spaces", this.findSpaces()); return VelocityUtils.getRenderedTemplate(MACRO_TEMPLATE, ctx); } /* * (non-Javadoc) * * @see com.atlassian.renderer.v2.macro.Macro#getBodyRenderMode() */ public RenderMode getBodyRenderMode() { return RenderMode.ALL; } /* * (non-Javadoc) * * @see com.atlassian.renderer.v2.macro.Macro#hasBody() */ public boolean hasBody() { return false; } /* * (non-Javadoc) * * @see com.atlassian.renderer.v2.macro.Macro#isInline() */ public boolean isInline() { return false; } /* * (non-Javadoc) * * @see * com.atlassian.renderer.v2.macro.Macro#suppressMacroRenderingDuringWysiwyg * () */ public boolean suppressMacroRenderingDuringWysiwyg() { return false; } /* * (non-Javadoc) * * @seecom.atlassian.renderer.v2.macro.Macro# * suppressSurroundingTagDuringWysiwygRendering() */ public boolean suppressSurroundingTagDuringWysiwygRendering() { return false; } @SuppressWarnings("unchecked") private List findSpaces() { return spaceManager.getAllSpaces(); } /** * set by IoC container * * @param spaceManager * the space manager */ public void setSpaceManager(SpaceManager spaceManager) { this.spaceManager = spaceManager; } }
- What happens here – the spaceManager is set by dependency injection via Spring (setter-injection) – the spaceManager is used to pull a (ugly,nongeneric) list of spaces. this list is passed to the velocity template
- Non adjust the velocity template macro.vm to this:
Spaces Macro<br/> #foreach ($space in $spaces) - $space.getName()<br/> #end
- Now build and deploy via the confluence administration area, just upload target/macro-tutorial-0.1.jar in the plugin section :
mvn package
- A faster method to build and deploy the plugin without using the file upload is to use the following goal:
mvn atlassian-pdk:install
- To make this work, add the following entry to your ~/.m2/settings.xml
<settings> [..] <profiles> <profile> <properties> <downloadSources>true</downloadSources> <downloadJavadocs>true</downloadJavadocs> <atlassian.pdk.server.url>http://localhost:8080/</atlassian.pdk.server.url> <atlassian.pdk.server.username>youradminusername</atlassian.pdk.server.username> <atlassian.pdk.server.password>youradminpassword</atlassian.pdk.server.password> </properties> </profile> [..] </profiles> [..]
- Another alternative if you do not have a running installation of confluence, use the following command to get Confluence via Maven and deploy hte plugin in one step – the URL is http://localhost:1990/confluence
mvn -Pplugin-debug
- Head over to the plugin section in your confluence and check if the plugin deployment was successful
- You should now be able to use the Macro in a Confluence Page
{DemoSpacesMacro}
- Here some screenshots of the deployed plugin in the Confluence administration area and the rendered macro in a page in my testing environment with three existing spaces
Sources Download
- You’re able to download the sources from Bitbucket.org or if you have Mercurial installed just checkout like this
hg clone http://bitbucket.org/hascode/hascode-tutorials
Troubleshooting
- “Unable to find resource ‘tangosol-coherence:coherence:jar:3.3“ – Oracle removed the libraries for the Tangosol/Coherence Cache from the Maven repositories so if you’re not going to use these directly just add this exclude rule to your pom.xml
<dependency> <groupId>com.atlassian.confluence</groupId> <artifactId>confluence</artifactId> <version>${confluence.version}</version> <scope>provided</scope> <exclusions> <exclusion> <groupId>tangosol-coherence</groupId> <artifactId>coherence</artifactId> </exclusion> <exclusion> <groupId>tangosol-coherence</groupId> <artifactId>tangosol</artifactId> </exclusion> </exclusions> </dependency>
- “Unable to find resource seraph ..” - add another exclusion to your pom.xml
<dependency> <groupId>com.atlassian.confluence</groupId> <artifactId>confluence</artifactId> <version>${confluence.version}</version> <scope>provided</scope> <exclusions> <exclusion> <groupId>seraph</groupId> <artifactId>seraph</artifactId> </exclusion> </exclusions> </dependency>
- “The plugin ‘org.twdata.maven:maven-cli-plugin’ does not exist or no valid version could be found” or other dependencies not resolvable.. – add Atlassian’s Maven repositories to your pom.xml
<repositories> <repository> <id>atlassian-public</id> <url>https://maven.atlassian.com/repository/public</url> <snapshots> <enabled>true</enabled> </snapshots> <releases> <enabled>true</enabled> </releases> </repository> <repository> <id>atlassian-m1-repository</id> <url>https://maven.atlassian.com/maven1</url> <layout>legacy</layout> </repository> <repository> <id>atlassian-unknown</id> <name>atlassian-unknown</name> <url>http://repository.atlassian.com/</url> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>atlassian-public</id> <url>https://maven.atlassian.com/repository/public</url> <snapshots> <enabled>true</enabled> </snapshots> <releases> <enabled>true</enabled> </releases> </pluginRepository>
Resources
- Quick Confluence Installation
- Atlassian Plugin Archetypes
- Atlassian Plugin SDK
- Atlassian Maven Repositories
- Atlassian JavaDocs
- Atlassian Documentation: Development without the Atlassian Plugin SDK
- Writing Confluence Macros Tutorial from Atlassian
- Macro Module
- Velocity User Guide
Article Updates
- 2015-03-03: Table of contents added.
Tags: Atlassian, Confluence, dependency injection, macro, maven, renderer, velocity
May 4th, 2010 at 6:06 am
This is really useful. Any idean how to hook up the licensing module with the confluence plug-in?
thanks,
Hotts
May 6th, 2010 at 3:10 pm
You could take a look at the Confluence LicenseManager class its a good starting point to get license information and to get to the LicenseRegistry.
There are also some helping methods available in the velocity context in generalUtil and action to check license details from the Confluence License.
Also there is a nice License Creator Plugin from Customware.net but I haven’t tested it yet -> http://www.customware.net/repository/display/AtlassianPlugins/Creating+a+Licensed+Plugin
Finally you could take a look at this article: http://www.hascode.com/2010/05/snippets-getting-license-information-from-the-confluence-api/
December 2nd, 2010 at 4:53 am
hi..this was really useful..but is there any other way to built your macro.I mean without using maven.can’t we simply use java and xml.? If there is nay way then please tell me the procedure.
December 2nd, 2010 at 6:42 am
It is possible to create the macro without using maven but that means searching all referenced libraries by hand .. no automatic deployment process .. no versioning of your dependencies .. no project archetypes .. no embedded confluence developer instance ..
of course you might compile everything without maven and package it into a jar .. everything in src/main/resources goes into the jar’s root, the compiled class files into /com/package/Yourclass.class etc .. but I highly recommend to use Maven.
Perhaps you’re more comfortable with Atlassian’s Plugin SDK .. it’s a wrapper for Maven and allows you to create, build and deploy your plugins using customized commands as atlas-create-confluence-plugin
There is some detailed information on the Plugin SDK at Atlassian’s Documentation Wiki
December 3rd, 2010 at 4:39 am
thanks micha :: i have built without using maven but the problem is that when i upload my .jar file into confluence. Its is showing an error like:-Could not find a handler capable of installing the plugin at ag-supporrt.jar. Check that the file is a valid plugin.
Is there some i am missing?
1.class file.
2. xml file.
3 velocity file.
i am not sure that i am going in a right way.please ellaborate more if you can.
coz i am not being able to find any thing from internet…
with regards–kshitij
December 3rd, 2010 at 7:56 am
Seems like the Maven plugin creates some additional information in META-INF:
$ jar -tvf macro-tutorial-0.1.jar
0 Fri Dec 03 08:10:02 CET 2010 META-INF/
124 Fri Dec 03 08:10:00 CET 2010 META-INF/MANIFEST.MF
0 Fri Dec 03 08:09:46 CET 2010 com/
0 Fri Dec 03 08:09:46 CET 2010 com/hascode/
0 Fri Dec 03 08:09:46 CET 2010 com/hascode/confluence/
0 Fri Dec 03 08:09:58 CET 2010 com/hascode/confluence/macro/
0 Fri Dec 03 08:09:46 CET 2010 template/
104 Fri Dec 03 08:09:46 CET 2010 com/hascode/confluence/macro/messages.properties
2798 Fri Dec 03 08:09:58 CET 2010 com/hascode/confluence/macro/SpacesMacro.class
75 Fri Dec 03 08:09:46 CET 2010 template/macro.vm
452 Fri Dec 03 08:09:46 CET 2010 atlassian-plugin.xml
0 Fri Dec 03 08:10:02 CET 2010 META-INF/maven/
0 Fri Dec 03 08:10:02 CET 2010 META-INF/maven/com.hascode.confluence/
0 Fri Dec 03 08:10:02 CET 2010 META-INF/maven/com.hascode.confluence/macro-tutorial/
2306 Fri Dec 03 07:56:26 CET 2010 META-INF/maven/com.hascode.confluence/macro-tutorial/pom.xml
119 Fri Dec 03 08:10:02 CET 2010 META-INF/maven/com.hascode.confluence/macro-tutorial/pom.properties
I have put the sources for this tutorial on Bitbucket and have also uploaded a built plugin-jar-file here for you – you could compare this one to your approach
December 12th, 2010 at 2:51 pm
Thanks a lot – I got it running without using Maven. I’m going to try it with Maven the next time – it seems to work easier with it ..
September 13th, 2011 at 2:47 pm
Hey Micha,
the URI for the archetypes changed, its now ‘http://svn.atlassian.com/svn/public/atlassian/maven-plugins/archetype-catalog’.
September 13th, 2011 at 4:01 pm
thanks mate! does the new archetype work only with atlassian’s plugin sdk or does it work also with a normal working maven instance?
September 13th, 2011 at 7:23 pm
Mmmmm, i don’t know, get: ‘* or one of its dependencies could not be resolved’. I like it…