The 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 descriptionhascode.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 GitHub.org or if you have Mercurial installed just checkout like this
hg clone http://github.com/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
Article Updates
-
2015-03-03: Table of contents added.