Applicable: | This tutorial applies to Confluence 3.X only. |
Level of experience: | This is a beginner tutorial. This is a good tutorial to try if you have never developed an add-on before. |
Time estimate: | It should take you approximately 30 minutes to complete this tutorial. |
Status: | LEGACY This tutorial applies to Confluence versions that have reached end of life. |
This tutorial shows you how to write a simple macro for Confluence. It prints a greeting message on the page using a parameter supplied by the user.
Your completed plugin will consist of the following components:
When you are done, all these components will be packaged in a single JAR file.
About Pre-4.0 Macros
This tutorial shows how to build a pre-4.0 macro plugin. There are many differences between 3.x and 4.x plugins. A difference particularly relevant to macro development is the introduction of the XHTML-based output format. For a tutorial on creating 4.x version plugins, see Creating a New Confluence Macro.
Also, this plugin happens to work in both 3.x and 4.x versions of Confluence. Some legacy 3.x plugins will need to be modified in order to work in 4.x. For more information, see Plugin Development Upgrade FAQ for 4.0.
To complete this tutorial, you must already understand the basics of Java development: classes, interfaces, methods, how to use the compiler, and so on. You should also understand:
We encourage you to work through this tutorial. If you want to skip ahead or check your work when you are done, you can find the plugin source code on Atlassian Bitbucket. Bitbucket serves a public Git repository containing the tutorial's code. To clone the repository, issue the following command:
1 2git clone https://bitbucket.org/atlassian_tutorial/writing-macros-for-confluence-pre4
Alternatively, you can download the source using the get source option here: bitbucket.org/atlassian_tutorial/writing-macros-for-confluence-pre4
About these instructions
You can use any supported combination of OS and IDE to construct this plugin. These instructions were written using Eclipse Indigo on Windows 7. If you are using another combination, you should use the equivalent operations for your specific environment.
In this step, you'll use two atlas-
commands to generate stub code for your plugin and set up the stub code as an Eclipse project. The atlas-
commands are part of the Atlassian Plugin SDK, and automate much of the work of plugin development for you.
Open a terminal and navigate to your Eclipse workspace directory.
Enter the following command to create a Confluence plugin skeleton:
atlas-create-confluence-plugin
When prompted, enter the following information to identify your plugin:
group-id |
|
artifact-id |
|
version |
|
package |
|
Confirm your entries when prompted.
Change to the TutorialMacro
directory created by the previous step.
Run the command:
atlas-mvn eclipse:eclipse
Start Eclipse.
Select File > Import.
Eclipse starts the Import wizard.
Filter for Existing Projects into Workspace (or expand the General folder tree).
Press Next and enter the root directory of your workspace.
Your Atlassian plugin folder should appear under Projects.
Select your plugin and click Finish.
Eclipse imports your project.
It's a good idea to familiarise yourself with the initial project files. In this section, we'll modify information about your plugin and check the product version value. Open your plugin project in Eclipse and follow along with the next sections.
The POM (Project Object Model definition file) is located at the root of your project and declares the project dependencies and other information.
Add some metadata about your plugin.
Edit the pom.xml
file in the root folder of your plugin.
Add a description of your macro to the following <description>
element:
1 2<name>Confluence Macro Tutorial</name> <description>This is the com.atlassian.test:conf-test plugin for Atlassian Confluence.</description>
Save the file.
When you generated the stub files, a default Confluence version was included in your pom.xml
file. Take a moment and examine the Confluence dependency:
Open the pom.xml
file.
Scroll to the bottom of the file.
Find the <properties>
element.
This section lists the version of Confluence you are using. It should look something like this:
1 2<properties> <confluence.version>3.0.1</confluence.version> <confluence.data.version>3.0</confluence.data.version> </properties>
You have already generated the stubs for your plugin modules. Now you will write some code that will make your plugin do something. In this example, we are writing a simple Confluence macro that greets the user by name and displays the total number of spaces in the instance, along with the home page title and creator of a randomly chosen space. Once you're comfortable with the process, you can use the same steps to create a macro to do pretty much anything.
Before we implement the macro logic, we'll make some preliminary changes to the generated ExampleMacro
class.
In Eclipse or from the command line, open the ExampleMacro.java
file at the following directory path: src/main/java/com/example/plugins/tutorial/confluence/TutorialMacro
.
Replace the existing import statements with the following. (You may need to expand the import statements if they are collapsed in your source view.)
1 2import java.util.Map; import java.util.List; import java.util.Random; import com.atlassian.renderer.RenderContext; import com.atlassian.renderer.v2.macro.BaseMacro; import com.atlassian.renderer.v2.macro.MacroException; import com.atlassian.renderer.v2.RenderMode; import com.atlassian.confluence.pages.Page; import com.atlassian.confluence.spaces.SpaceManager; import com.atlassian.confluence.spaces.Space; import com.atlassian.confluence.user.AuthenticatedUserThreadLocal; import com.atlassian.confluence.renderer.radeox.macros.MacroUtils; import com.atlassian.confluence.util.velocity.VelocityUtils; import com.atlassian.user.User;
A macro is a Java class that implements the com.atlassian.renderer.v2.macro.Macro
interface, which lives in a renderer module shared between Atlassian applications. To simplify our task, we'll extend a convenience class that Confluence provides for macro authors, BaseMacro
.
Replace the existing ExampleMacro
class body with the following skeleton:
1 2public class ExampleMacro extends BaseMacro { // constructor and instance variables omitted public boolean isInline() { return false; } public boolean hasBody() { return false; } public RenderMode getBodyRenderMode() { return RenderMode.NO_RENDER; } public String execute(Map params, String body, RenderContext renderContext) throws MacroException { // body coming up } }
Here we see all four methods that a macro is required to implement:
isInline()
tells the macro how to display. If the macro output should stand apart from surrounding text (like, for example, the built-in {info} macro), this should return false
. If the output should be interleaved into the surrounding text (like a HTML <a>
tag, because usually occurs within some other tag like <p>
or <div>
), this should return true
. Since the example macro output will be a block of standalone HTML, return false
.hasBody()
is a Boolean value that indicates whether the macro returns body text. getBodyRenderMode()
specifies what rendering, if any, should be applied to the text returned from execute()
. If a macro generates wiki markup, returning RenderMode.ALL
or RenderMode.INLINE
would be appropriate. Since this example macro will render directly to HTML through a Velocity template, you don't want the renderer to mess with it. Return RenderMode.NO_RENDER
.execute()
is the crucial method in BaseMacro
. It returns a String
, which is just the macro-generated text. In short, this is where you tell your macro what to do.Like good software engineers, we'll separate our controller from our view and delegate the HTML rendering to a Velocity template, passing a model object from the former to the latter. In src/main/resources
, create a new folder named templates
and save the following Velocity template as tutorial-macro.vm
:
1 2<table border="1"> <tr> <td> #if ($greeting) <p><strong>$greeting</strong></p> #end #if ($totalSpaces == 0) <p>No spaces found in this installation!</p> #else <p>Of the $totalSpaces spaces in this installation, the random winner is...</p> <strong>$spaceName!</strong> <p>Its home page is named $homePageTitle, and it was created by $homePageCreator.</p> #end <p>Thanks for playing!</p> <p>(Everything in this border was generated by {tutorial-macro}.)</p> </td> </tr> </table>
The tokens that start with $
are Velocity references. They correspond to the objects inserted into the context by ExampleMacro
. At render time, Velocity replaces these references with the corresponding values.
Be careful of context parameter names
By default, Velocity doesn't distinguish between references in the context that have no value and those that don't exist at all. If your rendered template has stuff like $myVariable
, make sure you've spelled the reference exactly the same in both the template and the macro class.
Now that you have defined your template, return to your ExampleMacro
class skeleton and at the top, where you had previously omitted the constructor and instance variables, direct the macro to reference your Velocity template:
1 2private static final String MACRO_BODY_TEMPLATE = "templates/tutorial-macro.vm"; private final SpaceManager spaceManager; /** * Constructor. When the plugin containing the macro is activated, Confluence * will instantiate this class and automatically inject an implementation * of {@code SpaceManager}. * @param spaceManager the {@code SpaceManager} implementation to use */ public ExampleMacro(SpaceManager spaceManager) { this.spaceManager = spaceManager; }
Now refresh the project in Eclipse.
Now comes the fun part. At render time, macros are invoked through the execute()
method with the following parameters:
params
is a Map
of macro parameter names and values.body
contains the text inside the macro opening and closing tags. Macros that don't have a body (like ours) don't require closing tags, so this parameter will be null
in such cases.context
is the RenderContext
object which supplies information about the renderer
being used to render the current macro. Some macros can take advantage of this information, but this one will not need to.In summary, you want to create a Velocity context and place several things into it, including the value of the greeting
parameter (you make one yourself if none is supplied), the total number of spaces in the installation, a randomly chosen space, and the title and creator of that space's home page. The return
statement gets the rendered Velocity template and sends it back. Since our implementation of getBodyRenderMode()
returns RenderMode.NO_RENDER
, the HTML from Velocity will be inserted into the page with no further processing.
To make this happen, go to your ExampleMacro
class and replace the skeleton execute()
method with the following:
1 2public String execute(Map params, String body, RenderContext renderContext) throws MacroException { // create a Velocity context object as a model between the controller // (this macro) and the view (the Velocity template) Map<String, Object> context = MacroUtils.defaultVelocityContext(); // check if the user supplied a "greeting" parameter if (params.containsKey("greeting")) { context.put("greeting", params.get("greeting")); } else { // we'll construct one. get the currently logged in user and display // their name User user = AuthenticatedUserThreadLocal.getUser(); if (user != null) { context.put("greeting", "Hello " + user.getFullName()); } } // get all spaces in this installation @SuppressWarnings("unchecked") List<Space> spaces = spaceManager.getAllSpaces(); context.put("totalSpaces", spaces.size()); if (!spaces.isEmpty()) { // pick a space at random and find its home page Random random = new Random(); int randomSpaceIndex = random.nextInt(spaces.size()); Space randomSpace = spaces.get(randomSpaceIndex); context.put("spaceName", randomSpace.getName()); Page homePage = randomSpace.getHomePage(); context.put("homePageTitle", homePage.getTitle()); context.put("homePageCreator", homePage.getCreatorName()); } // render the Velocity template with the assembled context return VelocityUtils.getRenderedTemplate(MACRO_BODY_TEMPLATE, context); }
In pre-4.0 Confluence, the generated Notation Guide provided information on macros, including user-created macros. We can document our macro in the Notation Guide, again using a Velocity template.
Go to src/main/resources``/templates
and create a new file named tutorial-macro-help.vm
with this content:
1 2<tr> <td> {tutorial-macro} <br><br> {tutorial-macro:greeting=Hello from the macro!} </td> <td> <p>A macro provided by the Confluence Macro Tutorial.</p> <ul> <li>greeting - An optional message to show at the top of the macro.</li> </ul> </td> </tr>
After you create the template, refresh the package in Eclipse.
Now that the macro class is written, you need to register the plugin module in the plugin descriptor, atlassian-plugin.xml
, which is located at src/main/resources
.
The Atlassian plugin descriptor file describes your plugin to Confluence. Use the <plugin-info>
element to add a description and version number for your macro. Also add a <macro>
element that identifies the macro along with the parameters it takes.
When you're done, atlassian-plugin.xml
should look like this:
1 2<atlassian-plugin key="confluence-macro-tutorial-plugin" name="Confluence Macro Tutorial Plugin" plugins-version="2"> <plugin-info> <description>A sample plugin showing how to write Confluence macros.</description> <version>1.0</version> <vendor name="Atlassian" url="http://www.atlassian.com" /> </plugin-info> <!-- Registers the macro in a plugin module. --> <macro name="tutorial-macro" class="com.atlassian.plugins.tutorial.macro.TutorialMacro" key="tutorial-macro"> <description>Demonstrates Confluence dependency injection and Velocity output.</description> <!-- Provides help text for the Confluence notation guide. --> <resource type="velocity" name="help" location="templates/tutorial-macro-help.vm"> <param name="help-section" value="confluence"/> </resource> <!-- Specifies which macro browser category this macro should display in. --> <category name="confluence-content"/> <!-- Defines the parameters this macro may consume. --> <parameters> <parameter name="greeting" type="string"/> </parameters> </macro> </atlassian-plugin>
That's it! Now you can test your code. Open a command window and go to the plugin root folder (where the pom.xml
is located). Enter the atlas-run
command. When it's done, you should see something like the following:
1 2... [INFO] [talledLocalContainer] Tomcat 6.x started on port [1990] [INFO] confluence started successfully in 118s at http://LOCALHOST:1990/confluence [INFO] Type Ctrl-D to shutdown gracefully [INFO] Type Ctrl-C to exit
To use the macro:
In your favorite browser, open this Confluence instance and log in as admin
(the password is also admin
).
Add a page or edit an existing one.
In the edit screen, click the Edit/Insert macro icon.
Scroll down and select the macro named Macro tutorial.
Optionally, type a greeting in the greeting field and click Insert.
Alternatively, add this to the page in the edit screen:
1 2{tutorial-macro}
Save or preview the page.
You should see the rendered output:
Congratulations, that's it
Have a chocolate!
Rate this page: