Rate this page:
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.
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
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
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.
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.
TutorialMacro
directory created by the previous step.Run the command:
atlas-mvn eclipse:eclipse
Start Eclipse.
Select File > Import.
Eclipse starts the Import wizard.
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.
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:
pom.xml
file.Find the <properties>
element.
This section lists the version of Confluence you are using. It should look something like this:
1 2 3 4
<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 2 3 4 5 6 7 8 9 10 11 12 13 14 15
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;
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 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
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.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 namedtemplates
and save the following Velocity template as tutorial-macro.vm
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
<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.
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 3 4 5 6 7 8 9 10 11 12 13
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.
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 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
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);
}
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 3 4 5 6 7 8 9 10 11 12 13 14
<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 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
<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 3 4 5
...
[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:
admin
(the password is also admin
).Optionally, type a greeting in the greeting field and click Insert.
Alternatively, add this to the page in the edit screen:
1
{tutorial-macro}
Save or preview the page.
You should see the rendered output:
Rate this page: