Show last authors
1 {{box cssClass="floatinginfobox" title="**Contents**"}}
2 {{toc/}}
3 {{/box}}
4
5 This tutorial explains how to create an XWiki Rendering Macro in Java.
6
7 Pre-requisites:
8
9 * You must be using JDK 1.5 or above.
10 * You must be using XWiki Rendering 3.0 or above.
11 * (optional) You need to have [[Maven installed>>http://maven.apache.org/]] if you want to use the Maven Archetype we're offering to easily create Macro projects using Maven.
12 * You should understand XWiki's Rendering [[Architecture>>Architecture]].
13
14 = General Principles =
15
16 Start by understanding the [[Macro execution process>>Architecture]].
17
18 In order to implement a new Macro you'll need to write 2 classes:
19
20 * One that is a pure Java Bean and that represents the parameters allowed for that macro, including mandatory parameters, default values, parameter descriptions. An instance of this class will be automagically populated when the user calls the macro in wiki syntax.
21 * 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:(((
22 {{code language="java"}}
23 /**
24 * @return true if the macro can be inserted in some existing content such as a paragraph, a list item etc. For
25 * example if I have <code>== hello {{velocity}}world{{/velocity}}</code> then the Velocity macro must
26 * support the inline mode and not generate a paragraph.
27 */
28 boolean supportsInlineMode();
29
30 /**
31 * @param parameters the macro parameters in the form of a bean defined by the {@link Macro} implementation
32 * @param content the content of the macro
33 * @param context the context of the macros transformation process
34 * @return the result of the macro execution as a list of Block elements
35 * @throws MacroExecutionException error when executing the macro
36 */
37 List<Block> execute(P parameters, String content, MacroTransformationContext context)
38 throws MacroExecutionException;
39 {{/code}}
40 )))
41
42 Then you'll need to register your Macro class with the [[Component Manager>>platform:DevGuide.WritingComponents]] so that it can be called from a wiki page. You register with a Macro.class role and a hint corresponding to the macro name. For example if you've registered your macro under the ##mymacro## hint, you'll be able to call:
43
44 {{code language="none"}}
45 {{mymacro .../}}
46 {{/code}}
47
48 In addition you can register your Macro for all syntaxes or only for a given syntax. In order to register only for a given syntax you must use a hint in the format ##macroname/syntaxid##. For example: ##mymacro/xwiki/2.0##.
49
50 == Security considerations ==
51
52 The Macro Transformation Context can be set in "Restricted Mode". When set, this parameter indicates that rendering is performed in a context where:
53
54 * modifications to the database or other privileged operations should not be performed
55 * expensive computations should not be performed
56 * any potentially dangerous operations should not be performed (like executing scripts)
57
58 Your macro should respect this parameter (by checking ##macroContext.getTransformationContext().isRestricted()##) and either not execute at all or execute in a restricted mode if the above is a concern for your macro.
59
60 = Implementing a Macro =
61
62 Here are detailed steps explaining how you can create a macro and deploy it.
63
64 == Creating a Macro using Maven ==
65
66 In order for this job to go as smooth as possible we have created a [[Maven Archetype>>http://maven.apache.org/archetype/maven-archetype-plugin/]] to help you create a simple macro module with a single command.
67
68 After you've [[installed Maven>>http://maven.apache.org/]], open a shell prompt an type:
69 {{code language="none"}}mvn archetype:generate{{/code}}
70
71 This will list all archetypes available on Maven Central. If instead you wish to directly use the XWiki Rendering Macro Archetype, you can directly type (update the version to use the version you wish to use):
72
73 {{code language="none"}}
74 mvn archetype:generate \
75 -DarchetypeArtifactId=xwiki-rendering-archetype-macro \
76 -DarchetypeGroupId=org.xwiki.rendering \
77 -DarchetypeVersion=3.2
78 {{/code}}
79
80 Then follow the instructions. For example:
81
82 {{code language="none"}}
83 [email protected] $ mvn archetype:generate
84 [INFO] Scanning for projects...
85 [INFO]
86 [INFO] ------------------------------------------------------------------------
87 [INFO] Building Maven Stub Project (No POM) 1
88 [INFO] ------------------------------------------------------------------------
89 [INFO]
90 [INFO] >>> maven-archetype-plugin:2.0:generate (default-cli) @ standalone-pom >>>
91 [INFO]
92 [INFO] <<< maven-archetype-plugin:2.0:generate (default-cli) @ standalone-pom <<<
93 [INFO]
94 [INFO] --- maven-archetype-plugin:2.0:generate (default-cli) @ standalone-pom ---
95 [INFO] Generating project in Interactive mode
96 [INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
97 Choose archetype:
98 ...
99 466: remote -> org.xwiki.rendering:xwiki-rendering-archetype-macro (Make it easy to create a maven project for creating XWiki Rendering Macros.)
100 ...
101 Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 143: 466
102 Choose version:
103 1: 3.2-milestone-3
104 2: 3.2-rc-1
105 3: 3.2
106 Choose a number: 3:
107 Define value for property 'groupId': : com.acme
108 Define value for property 'artifactId': : example
109 Define value for property 'version': 1.0-SNAPSHOT: :
110 Define value for property 'package': com.acme: :
111 Confirm properties configuration:
112 groupId: com.acme
113 artifactId: example
114 version: 1.0-SNAPSHOT
115 package: com.acme
116 Y: : Y
117 [INFO] ------------------------------------------------------------------------
118 [INFO] BUILD SUCCESS
119 [INFO] ------------------------------------------------------------------------
120 [INFO] Total time: 12.248s
121 [INFO] Finished at: Fri Mar 11 14:54:46 CET 2011
122 [INFO] Final Memory: 7M/81M
123 [INFO] ------------------------------------------------------------------------
124 {{/code}}
125
126 Then go in the created directory (##example## in our example above) and run {{code}}mvn install{{/code}} to build your macro.
127
128 If you are a XWiki developer or you just want to see [[more examples of implemented macros>>Main.Macros]], you can check the {{scm project="xwiki-rendering" path="xwiki-rendering-macros"}}source code for the Macros in our SCM{{/scm}}.
129
130 Now, let's take a moment and examine the newly generated project. Navigating in the project's folder, we will see the following structure:
131
132 * ##pom.xml## - the project's POM file.
133 * ##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.
134 * ##src/main/java/.../internal/ExampleMacro.java## - the macro itself.
135 * ##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##).
136 * ##src/test/java/.../IntegrationTests.java## - JUnit Test Suite to run rendering tests for the Macro.
137 * ##src/test/resources/example1.test## - a test file for testing the Macro. It tests that the macro works when standalone.
138 * ##src/test/resources/example2.test## - a test file for testing the Macro. It tests that the macro works when inline.
139
140 == Macro Code ==
141
142 Here's the content of our generated ##ExampleMacro.java##.
143
144 {{code language="java"}}
145 package com.acme.internal;
146
147 import javax.inject.Named;
148
149 import java.util.List;
150 import java.util.Arrays;
151
152 import org.xwiki.component.annotation.Component;
153 import org.xwiki.rendering.block.Block;
154 import org.xwiki.rendering.block.WordBlock;
155 import org.xwiki.rendering.block.ParagraphBlock;
156 import org.xwiki.rendering.macro.AbstractMacro;
157 import org.xwiki.rendering.macro.MacroExecutionException;
158 import com.acme.ExampleMacroParameters;
159 import org.xwiki.rendering.transformation.MacroTransformationContext;
160
161 /**
162 * Example Macro.
163 */
164 @Component
165 @Named("example")
166 public class ExampleMacro extends AbstractMacro<ExampleMacroParameters>
167 {
168 /**
169 * The description of the macro.
170 */
171 private static final String DESCRIPTION = "Example Macro";
172
173 /**
174 * Create and initialize the descriptor of the macro.
175 */
176 public ExampleMacro()
177 {
178 super("Example", DESCRIPTION, ExampleMacroParameters.class);
179 }
180
181 /**
182 * {@inheritDoc}
183 *
184 * @see org.xwiki.rendering.macro.Macro#execute(Object, String, MacroTransformationContext)
185 */
186 public List<Block> execute(ExampleMacroParameters parameters, String content, MacroTransformationContext context)
187 throws MacroExecutionException
188 {
189 List<Block> result;
190
191 List<Block> wordBlockAsList = Arrays.<Block>asList(new WordBlock(parameters.getParameter()));
192
193 // Handle both inline mode and standalone mode.
194 if (context.isInline()) {
195 result = wordBlockAsList;
196 } else {
197 // Wrap the result in a Paragraph Block since a WordBlock is an inline element and it needs to be
198 // inside a standalone block.
199 result = Arrays.<Block>asList(new ParagraphBlock(wordBlockAsList));
200 }
201
202 return result;
203 }
204
205 /**
206 * {@inheritDoc}
207 *
208 * @see org.xwiki.rendering.macro.Macro#supportsInlineMode()
209 */
210 public boolean supportsInlineMode()
211 {
212 return true;
213 }
214 }
215 {{/code}}
216
217 As explained in the [[Rendering Architecture>>Architecture]], the Macro's ##execute## method returns a list of Blocks. In the case of this simple macro it simply return a Word Block with the value if the ##parameter## parameter (i.e. ##hello## if the macro is called with ##~{~{example parameter="hello"/}}##).
218
219 And here's the ##ExampleMacroParameters.java## file:
220
221 {{code language="java"}}
222 package com.acme;
223
224 import org.xwiki.properties.annotation.PropertyMandatory;
225 import org.xwiki.properties.annotation.PropertyDescription;
226
227 /**
228 * Parameters for the {@link com.acme.internal.ExampleMacro} Macro.
229 */
230 public class ExampleMacroParameters
231 {
232 /**
233 * @see {@link #getParameter()}
234 */
235 private String parameter;
236
237 /**
238 * @return the example parameter
239 */
240 public String getParameter()
241 {
242 return this.parameter;
243 }
244
245 /**
246 * @param parameter the example parameter
247 */
248 @PropertyMandatory
249 @PropertyDescription("Example parameter")
250 public void setParameter(String parameter)
251 {
252 this.parameter = parameter;
253 }
254 }
255 {{/code}}
256
257 == Testing the Macro ==
258
259 The XWiki Rendering system has a pretty advanced Test framework to make it easy to test macros. Here is the test declaration for ##example1.test##:
260
261 {{code language="none"}}
262 .runTransformations
263 .#-----------------------------------------------------
264 .input|xwiki/2.0
265 .# Test the macro in standalone mode
266 .#-----------------------------------------------------
267 {{example parameter="hello"/}}
268 .#-----------------------------------------------------
269 .expect|xhtml/1.0
270 .#-----------------------------------------------------
271 <p>hello</p>
272 .#-----------------------------------------------------
273 .expect|event/1.0
274 .#-----------------------------------------------------
275 beginDocument
276 beginMacroMarkerStandalone [example] [parameter=hello]
277 beginParagraph
278 onWord [hello]
279 endParagraph
280 endMacroMarkerStandalone [example] [parameter=hello]
281 endDocument
282 {{/code}}
283
284 This instructs the test framework to execute the macro with the given input and to compare to all specified outputs (defined using the ##expect## keyword). In this example we're inputting XWiki Syntax 2.0 and comparing the result against XHTML 1.0 and against the internal events generated by the parser. These events are the pivot format used internally by the XWiki Rendering system. All the Renderers take those events to generate some output.
285
286 Note that the ##.runTransformations## directives simply tells the test framework to execute the Macro Transformation on the XDOM generated by the input.
287
288 And here's the second test ##example2.test##, this time testing the macro when used in inline mode:
289
290 {{code language="none"}}
291 .runTransformations
292 .#-----------------------------------------------------
293 .input|xwiki/2.0
294 .# Test the macro in inline mode
295 .#-----------------------------------------------------
296 This is inline {{example parameter="hello"/}}
297 .#-----------------------------------------------------
298 .expect|xhtml/1.0
299 .#-----------------------------------------------------
300 <p>This is inline hello</p>
301 .#-----------------------------------------------------
302 .expect|event/1.0
303 .#-----------------------------------------------------
304 beginDocument
305 beginParagraph
306 onWord [This]
307 onSpace
308 onWord [is]
309 onSpace
310 onWord [inline]
311 onSpace
312 beginMacroMarkerInline [example] [parameter=hello]
313 onWord [hello]
314 endMacroMarkerInline [example] [parameter=hello]
315 endParagraph
316 endDocument
317 {{/code}}
318
319 Finally this is how the ##IntegrationTests.java## file looks like:
320
321 {{code language="java"}}
322 package com.acme;
323
324 import org.junit.runner.RunWith;
325 import org.xwiki.rendering.test.integration.RenderingTestSuite;
326
327 /**
328 * Run all tests found in {@code *.test} files located in the classpath. These {@code *.test} files must follow the
329 * conventions described in {@link org.xwiki.rendering.test.integration.TestDataParser}.
330 */
331 @RunWith(RenderingTestSuite.class)
332 public class IntegrationTests
333 {
334 }
335 {{/code}}
336
337 = Deploying the Macro =
338
339 Now that we have a functioning Macro let's build it and deploy it:
340
341 * To build the macro issue {{code}}mvn install{{/code}}. This generates a Macro JAR in the ##target## directory of your project.
342 * To use it simply make that JAR available in your runtime classpath. There is various way to do it, in a XWiki instance it usually mean one of the following:
343 ** copy the jar file in ##WEB-INF/lib##
344 ** deploy the JAR in a Maven of XWiki repository and then install it using Extension Manager (if it's not https://nexus.xwiki.org or http://extension.xwiki.org make sure you indicate your repository in ##WEB-INF/xwiki.properties##)
345
346 You can now use the following as an input to the XWiki Rendering Parser (in a XWiki instance you can put it in a page content for example):
347 {{code language="none"}}{{example parameter="hello"/}}{{/code}}
348
349 Enjoy!
350
351 = Tips =
352
353 == Parsing Macro Content in Wiki Syntax ==
354
355 If your Macro content contains wiki syntax and you wish to parse it to generate XDOM blocks there's a very easy to do so. You just need to get injected a ##MacroContentParser## as shown in this example:
356
357 {{code language="java"}}
358 ...
359 import org.xwiki.rendering.macro.MacroContentParser;
360 ...
361 public class MyMacro extends AbstractMacro<MyMacroParameters>
362 {
363 ...
364 @Inject
365 private MacroContentParser contentParser;
366 ...
367 public List<Block> execute(MyMacroParameters parameters, String content, MacroTransformationContext context)
368 throws MacroExecutionException
369 {
370 // Parse macro content here
371 List<Block> blocks = this.contentParser.parse(content, context, true, context.isInline()).getChildren();
372
373 return blocks;
374 }
375 }
376 {{/code}}
377
378 == Finding the location of the Macro ==
379
380 You could be interested in finding the reference to the document where your Macro has been called. This can be achieved like this:
381
382 {{code language="java"}}
383 @Inject
384 private DocumentReferenceResolver<String> documentReferenceResolver;
385 ...
386 public List<Block> execute(ChartMacroParameters macroParams, String content, MacroTransformationContext context)
387 throws MacroExecutionException
388 {
389 String source = extractSourceContentReference(context.getCurrentMacroBlock());
390 DocumentReference reference = this.documentReferenceResolver.resolve(source);
391 ...
392 }
393 ...
394 private String extractSourceContentReference(Block source)
395 {
396 String contentSource = null;
397 MetaDataBlock metaDataBlock =
398 source.getFirstBlock(new MetadataBlockMatcher(MetaData.SOURCE), Block.Axes.ANCESTOR);
399 if (metaDataBlock != null) {
400 contentSource = (String) metaDataBlock.getMetaData().getMetaData(MetaData.SOURCE);
401 }
402 return contentSource;
403 }
404 {{/code}}
405
406 == Inline editing content and parameter ==
407
408 {{warning}}
409 Inline editing of content has been introduced in XWiki 10.10RC1 whereas inline editing of parameter has been introduced in XWiki 11.1RC1.
410 {{/warning}}
411
412 You might be interested in declaring some part of the macro content or some parameter as inline editable: the user will then be able to edit those parts of the content directly in the [[wysiwyg editor>>extensions:Extension.CKEditor Integration.WebHome]].
413
414 In order to make this available you need to specify 2 information when declaring the macro:
415
416 1. the type of the macro content or of the parameter
417 1. the parts of the macro that can be editable inline
418
419 === Specify the type of the macro content ===
420
421 You need to specify it in the constructor of the {{code}}DefaultContentDescriptor{{/code}}:
422
423 {{code language="java"}}
424 public DefaultContentDescriptor(String description, boolean mandatory, Type type)
425 {{/code}}
426
427 The type of a content which can be editable inline, is {{code}}List<Block>{{/code}}.
428 In order to simplify its declaration, we created a constant that can be immediately used:
429
430 {{code language="java"}}
431 new DefaultContentDescriptor("Content of the message", true, Block.LIST_BLOCK_TYPE));
432 {{/code}}
433
434 === Specify the type of the macro parameter ===
435
436 You can use a dedicated annotation to use on the parameter:
437
438 {{code language="java"}}
439 @PropertyDisplayType({ List.class, Block.class })
440 {{/code}}
441
442 === Specify the parts of the macro that can be editable inline ===
443
444 When implementing the the Macro's {{code}}execute{{/code}} method, you can specify which parts of the macro will be editable inline, by specifying some metadata.
445
446 For example, if you want to declare a block containing a logo which is always the same and a block which will be editable inline you can specify it like this:
447
448 {{code language="java"}}
449 ResourceReference imageReference = // declare the reference to the image logo
450 Block logoBlock = new ImageBlock(imageReference, true);
451 List<Block> content = this.contentParser.parse(content, context, false, context.isInline()).getChildren(); // parse the existing content and get its children blocks
452 Block editableContent = new MetadataBlock(content, getNonGeneratedContentMetadata()); // specifies the right metadata in order to make the content editable inline
453 return Arrays.asList(logoBlock, editableContent);
454 {{/code}}
455
456 Please note that you can also specify which syntax will be used inside the macro content, by specifying a metadata containing the id of the syntax to use.
457
458 When dealing with macro parameters, you can use the same kind of utility method, except that you need to specify the name of the parameter. For example, if you're handling a parameter named ##title##, you can write something like that:
459
460 {{code language="java"}}
461 new MetaDataBlock(titleBlock, getNonGeneratedContentMetaData("title"));
462 {{/code}}
463
464 Note: the ##getNonGeneratedContentMetadata()## method is a helper available in the ##AbstractMacro##. It simply returns a ##MetaData## object with a metadata named ##non-generated-content##. You could do the same manually with:
465
466 {{code language="java"}}
467 MetaData metaData = new MetaData();
468 metaData.addMetaData(MetaData.NON_GENERATED_CONTENT, "string value representing the type, e.g. java.util.List<org.xwiki.rendering.block.Block>");
469 {{/code}}
  • Powered by XWiki 11.10.2-node2. Hosted and managed by XWiki SAS

Get Connected