Writing a Rendering Macro in Java
Contents
Writing a Rendering Macro in Java
This tutorial explains how to create XWiki Macros using XWiki's Rendering Architecture and in Java. Pre-requisites:- You must be using JDK 1.5 or above.
- You must be using XWiki Core 1.8.4 or above.
- (optional) You need to have Maven installed if you want to use the Maven Archetype we're offering to easily create Macro projects using Maven.
- You should understand XWiki's Rendering Architecture.
General Principles
Start by understanding the Macro execution process. In order to implement a new Macro you'll need to write 2 classes:- One that is a pure Java Bean and that represents the parameters allowed for that macro, including mandatory parameters, default values, parameter descriptions. An intance of this class will be automagically populated when the user calls the macro in wiki syntax.
- Another one that is the Macro itself. This class should implement the Macro interface. However we recommend extending AbstractMacro which does some heavy lifting for you. By doing so you'll only have to implement 2 methods:
/** * @return true if the macro can be inserted in some existing content such as a paragraph, a list item etc. For * example if I have <code>== hello {{velocity}}world{{/velocity}}</code> then the Velocity macro must * support the inline mode and not generate a paragraph. */ boolean supportsInlineMode(); /** * @param parameters the macro parameters in the form of a bean defined by the {@link Macro} implementation * @param content the content of the macro * @param context the context of the macros transformation process * @return the result of the macro execution as a list of Block elements * @throws MacroExecutionException error when executing the macro */ List<Block> execute(P parameters, String content, MacroTransformationContext context) throws MacroExecutionException;
{{mymacro .../}}Implementing a Macro
Here are detailed steps explaining how you can create a macro and deploy it.Creating a Macro using Maven
In order for this job to go as smooth as possible, and since XWiki's build is based on Maven, we have created a Maven Archetype to help you create a simple macro module with a single command. After you've installed Maven, open a shell prompt an type:mvn archetype:generate -DarchetypeCatalog=http://svn.xwiki.org/svnroot/xwiki/platform/xwiki-tools/trunk/xwiki-archetype-macro/archetype-catalog.xml
vmassol@test $ mvn archetype:generate -DarchetypeCatalog=http://svn.xwiki.org/svnroot/xwiki/platform/xwiki-tools/trunk/xwiki-archetype-macro/archetype-catalog.xml [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'archetype'. [INFO] ------------------------------------------------------------------------ [INFO] Building Maven Default Project [INFO] task-segment: [archetype:generate] (aggregator-style) [INFO] ------------------------------------------------------------------------ [INFO] Preparing archetype:generate [INFO] No goals needed for project - skipping [INFO] Setting property: classpath.resource.loader.class => 'org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader'. [INFO] Setting property: velocimacro.messages.on => 'false'. [INFO] Setting property: resource.loader => 'classpath'. [INFO] Setting property: resource.manager.logwhenfound => 'false'. [INFO] [archetype:generate] [INFO] Generating project in Interactive mode [INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0) Choose archetype: 1: http://svn.xwiki.org/svnroot/xwiki/platform/xwiki-tools/trunk/xwiki-archetype-macro/archetype-catalog.xml -> xwiki-archetype-macro (Make it easy to create a maven project for creating XWiki Rendering Macros.) Choose a number: (1): 1 [INFO] snapshot com.xpn.xwiki.platform.tools:xwiki-archetype-macro:1.0-SNAPSHOT: checking for updates from xwiki-archetype-macro-repo Define value for groupId: : com.acme Define value for artifactId: : example Define value for version: 1.0-SNAPSHOT: : Define value for package: com.acme: : Confirm properties configuration: groupId: com.acme artifactId: example version: 1.0-SNAPSHOT package: com.acme Y: : Y [INFO] ---------------------------------------------------------------------------- [INFO] Using following parameters for creating OldArchetype: xwiki-archetype-macro:1.0-SNAPSHOT [INFO] ---------------------------------------------------------------------------- [INFO] Parameter: groupId, Value: com.acme [INFO] Parameter: packageName, Value: com.acme [INFO] Parameter: basedir, Value: /private/tmp/test [INFO] Parameter: package, Value: com.acme [INFO] Parameter: version, Value: 1.0-SNAPSHOT [INFO] Parameter: artifactId, Value: example [INFO] ********************* End of debug info from resources from generated POM *********************** [INFO] OldArchetype created in dir: /private/tmp/test/example [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ [INFO] Total time: 22 seconds [INFO] Finished at: Sat Jun 13 09:50:20 CEST 2009 [INFO] Final Memory: 8M/15M [INFO] ------------------------------------------------------------------------ vmassol@test $
- pom.xml - the project's POM file.
- src/main/java/.../ExampleMacroParameters.java - a simple bean representing the Macro's parameters. This bean contains annotations to tell the Macro engine the parameter that are mandatory, the list of allowed values, parameter descriptions, etc.
- src/main/java/.../internal/ExampleMacro.java - the macro itself.
- src/main/resources/META-INF/components.txt - the list of component implementations. Since our Macro is a component it needs to be listed there. Each component must have its full name written on a separate line (e.g. com.acme.internal.ExampleMacro).
- src/test/java/.../RenderingTests.java - JUnit Test Suite to run rendering tests for the Macro.
- src/test/resources/example1.test - a test file for testing the Macro. It contains information describing what the macro output should be for specific inputs.
Macro Code
Here's the content of our generated ExampleMacro.java.package com.acme.internal; import java.util.Collections; import java.util.List; import org.xwiki.component.annotation.Component; import org.xwiki.rendering.block.Block; import org.xwiki.rendering.block.WordBlock; import org.xwiki.rendering.block.ParagraphBlock; import org.xwiki.rendering.macro.AbstractMacro; import org.xwiki.rendering.macro.MacroExecutionException; import com.acme.ExampleMacroParameters; import org.xwiki.rendering.transformation.MacroTransformationContext; /** * Example Macro. */ @Component("example") public class ExampleMacro extends AbstractMacro<ExampleMacroParameters> { /** * The description of the macro. */ private static final String DESCRIPTION = "Example Macro"; /** * Create and initialize the descriptor of the macro. */ public ExampleMacro() { super(new DefaultMacroDescriptor(DESCRIPTION, ExampleMacroParameters.class)); } /** * {@inheritDoc} * * @see org.xwiki.rendering.macro.Macro#execute(Object, String, MacroTransformationContext) */ public List<Block> execute(ExampleMacroParameters parameters, String content, MacroTransformationContext context) throws MacroExecutionException { return Collections.singletonList((Block) new WordBlock(parameters.getParameter())); } /** * {@inheritDoc} * * @see org.xwiki.rendering.macro.Macro#supportsInlineMode() */ public boolean supportsInlineMode() { return true; } }
package com.acme; import org.xwiki.properties.annotation.PropertyMandatory; import org.xwiki.properties.annotation.PropertyDescription; /** * Parameters for the {@link com.acme.internal.ExampleMacro} Macro. */ public class ExampleMacroParameters { /** * @see {@link #getParameter()} */ private String parameter; /** * @return the example parameter */ public String getParameter() { return this.parameter; } /** * @param parameter the example parameter */ @PropertyMandatory @PropertyDescription("Example parameter") public void setParameter(String parameter) { this.parameter = parameter; } }
Testing the Macro
The XWiki Rendering system has a pretty advanced Test framework to make it easy to test macros. Here is the test declaration for our Macro (example1.test):.#-----------------------------------------------------
.input|xwiki/2.0
.#-----------------------------------------------------
{{example parameter="hello"/}}
.#-----------------------------------------------------
.expect|xhtml/1.0
.#-----------------------------------------------------
<!--startmacro:example|-|parameter="hello"-->hello<!--stopmacro-->
.#-----------------------------------------------------
.expect|event/1.0
.#-----------------------------------------------------
beginDocument
beginMacroMarkerStandalone [example] [parameter=hello]
onWord [hello]
endMacroMarkerStandalone [example] [parameter=hello]
endDocumentpackage com.acme; import junit.framework.Test; import junit.framework.TestCase; import org.xwiki.rendering.scaffolding.RenderingTestSuite; import org.xwiki.rendering.scaffolding.RenderingTestCase; import org.xwiki.test.ComponentManagerTestSetup; /** * All Rendering integration tests defined in text files using a special format. */ public class RenderingTests extends TestCase { public static Test suite() throws Exception { RenderingTestSuite suite = new RenderingTestSuite("Test the Example macro"); suite.addTestsFromResource("example1", true); return new ComponentManagerTestSetup(suite); } }
Deploying the Macro
Now that we have a functioning Macro let's build it and deploy it to a XWiki Enterprise instance:- To build the macro issue mvn install. This generates a Macro JAR in the target directory of your project.
- To install it into a XWiki Enterprise instance, just copy that JAR file in XE_WAR_HOME/WEB-INF/lib where XE_WAR_HOME is where the XWiki Enterprise WAR is deployed.
{{example parameter="hello"/}}