Last updated Mar 27, 2024

Writing macros for pre-4.0 versions of Confluence

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.

Overview

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:

  • Java classes encapsulating the plugin logic.
  • Resources for display of the plugin UI.
  • A plugin descriptor (XML file) to enable the plugin module in the Atlassian application.

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.

Prerequisite Knowledge

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:

Plugin Source

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
2
git 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.

Step 1. Create the plugin project

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.

  1. Open a terminal and navigate to your Eclipse workspace directory.

  2. 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

    com.atlassian.plugins.tutorial.macro

    artifact-id

    TutorialMacro

    version

    1.0-SNAPSHOT

    package

    com.atlassian.plugins.tutorial.macro

  3. Confirm your entries when prompted.

  4. Change to the TutorialMacro directory created by the previous step.

  5. Run the command:
    atlas-mvn eclipse:eclipse

  6. Start Eclipse.

  7. Select File > Import.
    Eclipse starts the Import wizard.

  8. Filter for Existing Projects into Workspace (or expand the General folder tree).

  9. Press Next and enter the root directory of your workspace.
    Your Atlassian plugin folder should appear under Projects.

  10. Select your plugin and click Finish.

Eclipse imports your project.

Step 2. Review and tweak the generated stub code

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.

Add plugin metadata to the POM

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.

  1. Edit the pom.xml file in the root folder of your plugin.

  2. 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>
    
  3. Save the file.

Verify your Confluence version

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:

  1. Open the pom.xml file.

  2. Scroll to the bottom of the file.

  3. 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>
    

Step 4. Write Java classes

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.

Update imports

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
2
import 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;

Build a skeleton to extend BaseMacro

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
2
public 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.

Create a Velocity template

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
2
private 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.

Tell the macro what to do

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
2
public 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);
}

Step 5. Register the Plugin Module in the Plugin Descriptor

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>

Step 6. Build, install and run the 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:

  1. In your favorite browser, open this Confluence instance and log in as admin (the password is also admin).

  2. Add a page or edit an existing one.

  3. In the edit screen, click the Edit/Insert macro icon.

  4. Scroll down and select the macro named Macro tutorial.

  5. 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}
    
  6. Save or preview the page.

You should see the rendered output:

Congratulations, that's it

Have a chocolate!

Rate this page: