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. See the [[Macro Transformation reference documentation>>extensions:Extension.RenderingMacroTransformation.WebHome]] for additional details.
6
7 Pre-requisites:
8
9 * You must be using JDK 1.8 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 /*
146 * See the NOTICE file distributed with this work for additional
147 * information regarding copyright ownership.
148 *
149 * This is free software; you can redistribute it and/or modify it
150 * under the terms of the GNU Lesser General Public License as
151 * published by the Free Software Foundation; either version 2.1 of
152 * the License, or (at your option) any later version.
153 *
154 * This software is distributed in the hope that it will be useful,
155 * but WITHOUT ANY WARRANTY; without even the implied warranty of
156 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
157 * Lesser General Public License for more details.
158 *
159 * You should have received a copy of the GNU Lesser General Public
160 * License along with this software; if not, write to the Free
161 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
162 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
163 */
164 package com.acme.internal;
165
166 import java.util.Collections;
167 import java.util.List;
168
169 import javax.inject.Named;
170
171 import org.xwiki.component.annotation.Component;
172 import org.xwiki.rendering.block.Block;
173 import org.xwiki.rendering.block.ParagraphBlock;
174 import org.xwiki.rendering.block.WordBlock;
175 import org.xwiki.rendering.macro.AbstractMacro;
176 import org.xwiki.rendering.transformation.MacroTransformationContext;
177
178 import static java.util.Collections.singleton;
179
180 /**
181 * Example Macro.
182 *
183 * @version $Id$
184 * @since X.Y.X
185 */
186 @Component
187 @Named("example")
188 public class ExampleMacro extends AbstractMacro<ExampleMacroParameters>
189 {
190 /**
191 * The description of the macro.
192 */
193 private static final String DESCRIPTION = "Example Macro";
194
195 /**
196 * Create and initialize the descriptor of the macro.
197 */
198 public ExampleMacro()
199 {
200
201 super("Example", DESCRIPTION, ExampleMacroParameters.class);
202 setDefaultCategories(singleton("Tutorial"));
203 }
204
205 @Override
206 public List<Block> execute(ExampleMacroParameters parameters, String content, MacroTransformationContext context)
207 {
208 List<Block> result;
209
210 List<Block> wordBlockAsList = Collections.singletonList(new WordBlock(parameters.getParameter()));
211
212 // Handle both inline mode and standalone mode.
213 if (context.isInline()) {
214 result = wordBlockAsList;
215 } else {
216 // Wrap the result in a Paragraph Block since a WordBlock is an inline element and it needs to be
217 // inside a standalone block.
218 result = Collections.singletonList(new ParagraphBlock(wordBlockAsList));
219 }
220
221 return result;
222 }
223
224 @Override
225 public boolean supportsInlineMode()
226 {
227 return true;
228 }
229 }
230
231 {{/code}}
232
233 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"/}}##).
234
235 And here's the ##ExampleMacroParameters.java## file:
236
237 {{code language="java"}}
238 package com.acme;
239
240 import org.xwiki.properties.annotation.PropertyMandatory;
241 import org.xwiki.properties.annotation.PropertyDescription;
242
243 /**
244 * Parameters for the {@link com.acme.internal.ExampleMacro} Macro.
245 */
246 public class ExampleMacroParameters
247 {
248 /**
249 * @see {@link #getParameter()}
250 */
251 private String parameter;
252
253 /**
254 * @return the example parameter
255 */
256 public String getParameter()
257 {
258 return this.parameter;
259 }
260
261 /**
262 * @param parameter the example parameter
263 */
264 @PropertyMandatory
265 @PropertyDescription("Example parameter")
266 public void setParameter(String parameter)
267 {
268 this.parameter = parameter;
269 }
270 }
271 {{/code}}
272
273 == Testing the Macro ==
274
275 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##:
276
277 {{code language="none"}}
278 .runTransformations
279 .#-----------------------------------------------------
280 .input|xwiki/2.0
281 .# Test the macro in standalone mode
282 .#-----------------------------------------------------
283 {{example parameter="hello"/}}
284 .#-----------------------------------------------------
285 .expect|xhtml/1.0
286 .#-----------------------------------------------------
287 <p>hello</p>
288 .#-----------------------------------------------------
289 .expect|event/1.0
290 .#-----------------------------------------------------
291 beginDocument
292 beginMacroMarkerStandalone [example] [parameter=hello]
293 beginParagraph
294 onWord [hello]
295 endParagraph
296 endMacroMarkerStandalone [example] [parameter=hello]
297 endDocument
298 {{/code}}
299
300 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.
301
302 Note that the ##.runTransformations## directives simply tells the test framework to execute the Macro Transformation on the XDOM generated by the input.
303
304 And here's the second test ##example2.test##, this time testing the macro when used in inline mode:
305
306 {{code language="none"}}
307 .runTransformations
308 .#-----------------------------------------------------
309 .input|xwiki/2.0
310 .# Test the macro in inline mode
311 .#-----------------------------------------------------
312 This is inline {{example parameter="hello"/}}
313 .#-----------------------------------------------------
314 .expect|xhtml/1.0
315 .#-----------------------------------------------------
316 <p>This is inline hello</p>
317 .#-----------------------------------------------------
318 .expect|event/1.0
319 .#-----------------------------------------------------
320 beginDocument
321 beginParagraph
322 onWord [This]
323 onSpace
324 onWord [is]
325 onSpace
326 onWord [inline]
327 onSpace
328 beginMacroMarkerInline [example] [parameter=hello]
329 onWord [hello]
330 endMacroMarkerInline [example] [parameter=hello]
331 endParagraph
332 endDocument
333 {{/code}}
334
335 Finally this is how the ##IntegrationTests.java## file looks like:
336
337 {{code language="java"}}
338 package com.acme;
339
340 import org.junit.runner.RunWith;
341 import org.xwiki.rendering.test.integration.RenderingTestSuite;
342
343 /**
344 * Run all tests found in {@code *.test} files located in the classpath. These {@code *.test} files must follow the
345 * conventions described in {@link org.xwiki.rendering.test.integration.TestDataParser}.
346 */
347 @RunWith(RenderingTestSuite.class)
348 public class IntegrationTests
349 {
350 }
351 {{/code}}
352
353 = Deploying the Macro =
354
355 Now that we have a functioning Macro let's build it and deploy it:
356
357 * To build the macro issue {{code}}mvn install{{/code}}. This generates a Macro JAR in the ##target## directory of your project.
358 * 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:
359 ** copy the jar file in ##WEB-INF/lib##
360 ** 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##)
361
362 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):
363 {{code language="none"}}{{example parameter="hello"/}}{{/code}}
364
365 Enjoy!
366
367 = Tips =
368
369 == Parsing Macro Content in Wiki Syntax ==
370
371 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:
372
373 {{code language="java"}}
374 ...
375 import org.xwiki.rendering.macro.MacroContentParser;
376 ...
377 public class MyMacro extends AbstractMacro<MyMacroParameters>
378 {
379 ...
380 @Inject
381 private MacroContentParser contentParser;
382 ...
383 public List<Block> execute(MyMacroParameters parameters, String content, MacroTransformationContext context)
384 throws MacroExecutionException
385 {
386 // Parse macro content here
387 List<Block> blocks = this.contentParser.parse(content, context, true, context.isInline()).getChildren();
388
389 return blocks;
390 }
391 }
392 {{/code}}
393
394 == Finding the location of the Macro ==
395
396 You could be interested in finding the reference to the document where your Macro has been called. This can be achieved like this:
397
398 {{code language="java"}}
399 @Inject
400 private DocumentReferenceResolver<String> documentReferenceResolver;
401 ...
402 public List<Block> execute(ChartMacroParameters macroParams, String content, MacroTransformationContext context)
403 throws MacroExecutionException
404 {
405 String source = extractSourceContentReference(context.getCurrentMacroBlock());
406 DocumentReference reference = this.documentReferenceResolver.resolve(source);
407 ...
408 }
409 ...
410 private String extractSourceContentReference(Block source)
411 {
412 String contentSource = null;
413 MetaDataBlock metaDataBlock =
414 source.getFirstBlock(new MetadataBlockMatcher(MetaData.SOURCE), Block.Axes.ANCESTOR);
415 if (metaDataBlock != null) {
416 contentSource = (String) metaDataBlock.getMetaData().getMetaData(MetaData.SOURCE);
417 }
418 return contentSource;
419 }
420 {{/code}}
421
422 == Inline editing content and parameter ==
423
424 {{warning}}
425 Inline editing of content has been introduced in XWiki 10.10RC1 whereas inline editing of parameter has been introduced in XWiki 11.1RC1.
426 {{/warning}}
427
428 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]].
429
430 In order to make this available you need to specify 2 information when declaring the macro:
431
432 1. the type of the macro content or of the parameter
433 1. the parts of the macro that can be editable inline
434
435 === Specify the type of the macro content ===
436
437 You need to specify it in the constructor of the {{code}}DefaultContentDescriptor{{/code}}:
438
439 {{code language="java"}}
440 public DefaultContentDescriptor(String description, boolean mandatory, Type type)
441 {{/code}}
442
443 The type of a content which can be editable inline, is {{code}}List<Block>{{/code}}.
444 In order to simplify its declaration, we created a constant that can be immediately used:
445
446 {{code language="java"}}
447 new DefaultContentDescriptor("Content of the message", true, Block.LIST_BLOCK_TYPE));
448 {{/code}}
449
450 === Specify the type of the macro parameter ===
451
452 You can use a dedicated annotation to use on the parameter:
453
454 {{code language="java"}}
455 @PropertyDisplayType({ List.class, Block.class })
456 {{/code}}
457
458 === Specify the parts of the macro that can be editable inline ===
459
460 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.
461
462 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:
463
464 {{code language="java"}}
465 ResourceReference imageReference = // declare the reference to the image logo
466 Block logoBlock = new ImageBlock(imageReference, true);
467 List<Block> content = this.contentParser.parse(content, context, false, context.isInline()).getChildren(); // parse the existing content and get its children blocks
468 Block editableContent = new MetadataBlock(content, getNonGeneratedContentMetadata()); // specifies the right metadata in order to make the content editable inline
469 return Arrays.asList(logoBlock, editableContent);
470 {{/code}}
471
472 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.
473
474 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:
475
476 {{code language="java"}}
477 new MetaDataBlock(titleBlock, getNonGeneratedContentMetaData("title"));
478 {{/code}}
479
480 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:
481
482 {{code language="java"}}
483 MetaData metaData = new MetaData();
484 metaData.addMetaData(MetaData.NON_GENERATED_CONTENT, "string value representing the type, e.g. java.util.List<org.xwiki.rendering.block.Block>");
485 {{/code}}
486
487 == Backlinks and links refactoring in macros ==
488
489 {{version since="13.4.3,13.7RC1"}}
490 Links are automatically extracted from content of macros which are using the Wiki macro content type to create backlinks, and those links are then automatically refactored when a page is moved or renamed. It's also possible for Java macros to provide a dedicated implementation of the [[MacroRefactoring component>>https://github.com/xwiki/xwiki-platform/blob/master/xwiki-platform-core/xwiki-platform-rendering/xwiki-platform-rendering-xwiki/src/main/java/org/xwiki/rendering/macro/MacroRefactoring.java]] to allow extracting links for specific parameters for example, or to process differently the macro content to extract links.
491 {{/version}}
  • Powered by XWiki 14.4.6-node1. Hosted and managed by XWiki SAS

Get Connected