1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package org.xwiki.rendering.internal.transformation.macro

File MacroTransformation.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

22
64
13
3
344
204
28
0.44
4.92
4.33
2.15

Classes

Class Line # Actions
MacroTransformation 68 42 0% 14 4
0.9344262593.4%
MacroTransformation.MacroLookupExceptionElement 70 4 0% 3 0
1.0100%
MacroTransformation.PriorityMacroBlockMatcher 93 18 0% 11 0
1.0100%
 

Contributing tests

This file is covered by 447 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.rendering.internal.transformation.macro;
21   
22    import java.util.HashMap;
23    import java.util.LinkedList;
24    import java.util.List;
25    import java.util.Map;
26   
27    import javax.inject.Inject;
28    import javax.inject.Named;
29    import javax.inject.Singleton;
30   
31    import org.slf4j.Logger;
32    import org.xwiki.component.annotation.Component;
33    import org.xwiki.component.phase.Initializable;
34    import org.xwiki.component.phase.InitializationException;
35    import org.xwiki.properties.BeanManager;
36    import org.xwiki.rendering.block.Block;
37    import org.xwiki.rendering.block.MacroBlock;
38    import org.xwiki.rendering.block.MacroMarkerBlock;
39    import org.xwiki.rendering.block.match.BlockMatcher;
40    import org.xwiki.rendering.internal.transformation.MutableRenderingContext;
41    import org.xwiki.rendering.macro.Macro;
42    import org.xwiki.rendering.macro.MacroId;
43    import org.xwiki.rendering.macro.MacroLookupException;
44    import org.xwiki.rendering.macro.MacroManager;
45    import org.xwiki.rendering.macro.MacroNotFoundException;
46    import org.xwiki.rendering.syntax.Syntax;
47    import org.xwiki.rendering.transformation.AbstractTransformation;
48    import org.xwiki.rendering.transformation.MacroTransformationContext;
49    import org.xwiki.rendering.transformation.RenderingContext;
50    import org.xwiki.rendering.transformation.TransformationContext;
51    import org.xwiki.rendering.transformation.TransformationException;
52    import org.xwiki.rendering.util.ErrorBlockGenerator;
53   
54    /**
55    * Look for all {@link org.xwiki.rendering.block.MacroBlock} blocks in the passed {@link Block} and iteratively execute
56    * each Macro in the correct order. Macros can:
57    * <ul>
58    * <li>provide a hint specifying when they should run (priority)</li>
59    * <li>generate other Macros</li>
60    * </ul>
61    *
62    * @version $Id: 7a2bb1440d3728eef51a2c6f1d504cf37e2bff74 $
63    * @since 1.5M2
64    */
65    @Component
66    @Named("macro")
67    @Singleton
 
68    public class MacroTransformation extends AbstractTransformation implements Initializable
69    {
 
70    private static class MacroLookupExceptionElement
71    {
72    private MacroBlock macroBlock;
73   
74    private MacroLookupException exception;
75   
 
76  212 toggle public MacroLookupExceptionElement(MacroBlock macroBlock, MacroLookupException exception)
77    {
78  212 this.macroBlock = macroBlock;
79  212 this.exception = exception;
80    }
81   
 
82  636 toggle public MacroBlock getMacroBlock()
83    {
84  636 return macroBlock;
85    }
86   
 
87  212 toggle public MacroLookupException getException()
88    {
89  212 return exception;
90    }
91    }
92   
 
93    private class PriorityMacroBlockMatcher implements BlockMatcher
94    {
95    private final Syntax syntax;
96   
97    private MacroBlock block;
98   
99    private Macro<?> blockMacro;
100   
101    private List<MacroLookupExceptionElement> errors;
102   
103    // Cache known macros since getting them again and again from the ComponentManager might be expensive
104    private final Map<String, Macro<?>> knownMacros = new HashMap<>();
105   
 
106  808288 toggle PriorityMacroBlockMatcher(Syntax syntax)
107    {
108  808168 this.syntax = syntax;
109    }
110   
 
111  808668 toggle public MacroBlock getBlock()
112    {
113  808718 return block;
114    }
115   
 
116  523090 toggle public Macro<?> getBlockMacro()
117    {
118  523101 return blockMacro;
119    }
120   
 
121  808774 toggle public List<MacroLookupExceptionElement> getErrors()
122    {
123  808821 return errors;
124    }
125   
 
126  39718655 toggle @Override
127    public boolean match(Block block)
128    {
129  39718983 if (block instanceof MacroBlock) {
130  883586 MacroBlock macroBlock = (MacroBlock) block;
131   
132  883608 try {
133    // Try to find a known macros
134  883618 Macro<?> macro = this.knownMacros.get(macroBlock.getId());
135   
136    // If not found use the macro manager
137  883494 if (macro == null) {
138  606044 macro =
139    MacroTransformation.this.macroManager
140    .getMacro(new MacroId(macroBlock.getId(), this.syntax));
141   
142    // Cache the found macro for later
143  605793 this.knownMacros.put(macroBlock.getId(), macro);
144    }
145   
146    // Find higher priority macro
147  883260 if (this.block == null || this.blockMacro.compareTo(macro) > 0) {
148  530426 this.block = macroBlock;
149  530459 this.blockMacro = macro;
150    }
151    } catch (MacroLookupException e) {
152  212 if (this.errors == null) {
153  112 this.errors = new LinkedList<MacroLookupExceptionElement>();
154    }
155   
156  212 this.errors.add(new MacroLookupExceptionElement(macroBlock, e));
157    }
158    }
159   
160  39718749 return false;
161    }
162    }
163   
164    /**
165    * Number of times a macro can generate another macro before considering that we are in a loop. Such a loop can
166    * happen if a macro generates itself for example.
167    */
168    private int maxRecursions = 1000;
169   
170    /**
171    * Handles macro registration and macro lookups. Injected by the Component Manager.
172    */
173    @Inject
174    private MacroManager macroManager;
175   
176    /**
177    * Used to populate automatically macros parameters classes with parameters specified in the Macro Block.
178    */
179    @Inject
180    private BeanManager beanManager;
181   
182    /**
183    * Used to updated the rendering context.
184    */
185    @Inject
186    private RenderingContext renderingContext;
187   
188    /**
189    * The logger to log.
190    */
191    @Inject
192    private Logger logger;
193   
194    @Inject
195    private ErrorBlockGenerator errorBlockGenerator;
196   
197    /**
198    * Used to generate Macro error blocks when a Macro fails to execute.
199    */
200    private MacroErrorManager macroErrorManager;
201   
 
202  486 toggle @Override
203    public void initialize() throws InitializationException
204    {
205  486 this.macroErrorManager = new MacroErrorManager(this.errorBlockGenerator);
206    }
207   
 
208  97501 toggle @Override
209    public int getPriority()
210    {
211    // Make it one of the transformations that's executed first so that other transformations run on the executed
212    // macros.
213  97677 return 100;
214    }
215   
 
216  285426 toggle @Override
217    public void transform(Block rootBlock, TransformationContext context) throws TransformationException
218    {
219    // Create a macro execution context with all the information required for macros.
220  285532 MacroTransformationContext macroContext = new MacroTransformationContext(context);
221  285207 macroContext.setTransformation(this);
222   
223    // Counter to prevent infinite recursion if a macro generates the same macro for example.
224  808690 for (int recursions = 0; recursions < this.maxRecursions;) {
225    // 1) Get highest priority macro
226  808667 PriorityMacroBlockMatcher priorityMacroBlockMatcher = new PriorityMacroBlockMatcher(context.getSyntax());
227  808522 rootBlock.getFirstBlock(priorityMacroBlockMatcher, Block.Axes.DESCENDANT);
228   
229    // 2) Apply macros lookup errors
230  808611 if (priorityMacroBlockMatcher.getErrors() != null) {
231  112 for (MacroLookupExceptionElement error : priorityMacroBlockMatcher.getErrors()) {
232  212 if (error.getException() instanceof MacroNotFoundException) {
233    // Macro cannot be found. Generate an error message instead of the macro execution result.
234    // TODO: make it internationalized
235  212 this.macroErrorManager.generateError(error.getMacroBlock(),
236    String.format("Unknown macro: %s.", error.getMacroBlock().getId()), String.format(
237    "The \"%s\" macro is not in the list of registered macros. Verify the spelling or "
238    + "contact your administrator.", error.getMacroBlock().getId()));
239    } else {
240    // TODO: make it internationalized
241  0 this.macroErrorManager.generateError(error.getMacroBlock(),
242    String.format("Invalid macro: %s", error.getMacroBlock().getId()), error.getException());
243    }
244    }
245    }
246   
247  808571 MacroBlock macroBlock = priorityMacroBlockMatcher.getBlock();
248   
249  808498 if (macroBlock == null) {
250    // Nothing left to do
251  285505 return;
252    }
253   
254  523047 Macro<?> macro = priorityMacroBlockMatcher.getBlockMacro();
255   
256  523098 boolean incrementRecursions = macroBlock.getParent() instanceof MacroMarkerBlock;
257   
258  522971 List<Block> newBlocks;
259  523058 try {
260    // 3) Verify if we're in macro inline mode and if the macro supports it. If not, send an error.
261  523050 if (macroBlock.isInline()) {
262  28301 macroContext.setInline(true);
263  28303 if (!macro.supportsInlineMode()) {
264    // The macro doesn't support inline mode, raise a warning but continue.
265    // The macro will not be executed and we generate an error message instead of the macro
266    // execution result.
267  5 this.macroErrorManager.generateError(macroBlock, String.format(
268    "The [%s] macro is a standalone macro and it cannot be used inline",
269    macroBlock.getId()),
270    "This macro generates standalone content. As a consequence you need to make sure to use a "
271    + "syntax that separates your macro from the content before and after it so that it's on a "
272    + "line by itself. For example in XWiki Syntax 2.0+ this means having 2 newline characters "
273    + "(a.k.a line breaks) separating your macro from the content before and after it.");
274  5 continue;
275    }
276    } else {
277  494687 macroContext.setInline(false);
278    }
279   
280    // 4) Execute the highest priority macro
281  523072 macroContext.setCurrentMacroBlock(macroBlock);
282  523018 ((MutableRenderingContext) this.renderingContext).setCurrentBlock(macroBlock);
283   
284    // Populate and validate macro parameters.
285  523056 Object macroParameters = macro.getDescriptor().getParametersBeanClass().newInstance();
286  523087 try {
287  523113 this.beanManager.populate(macroParameters, macroBlock.getParameters());
288    } catch (Throwable e) {
289    // One macro parameter was invalid.
290    // The macro will not be executed and we generate an error message instead of the macro
291    // execution result.
292  0 this.macroErrorManager.generateError(macroBlock,
293    String.format("Invalid macro parameters used for the \"%s\" macro", macroBlock.getId()), e);
294  0 continue;
295    }
296   
297  522970 newBlocks = ((Macro) macro).execute(macroParameters, macroBlock.getContent(), macroContext);
298    } catch (Throwable e) {
299    // The Macro failed to execute.
300    // The macro will not be executed and we generate an error message instead of the macro
301    // execution result.
302    // Note: We catch any Exception because we want to never break the whole rendering.
303  56 this.macroErrorManager.generateError(macroBlock,
304    String.format("Failed to execute the [%s] macro", macroBlock.getId()), e);
305  55 continue;
306    } finally {
307  523139 ((MutableRenderingContext) this.renderingContext).setCurrentBlock(null);
308    }
309   
310    // We wrap the blocks generated by the macro execution with MacroMarker blocks so that listeners/renderers
311    // who wish to know the group of blocks that makes up the executed macro can. For example this is useful for
312    // the XWiki Syntax renderer so that it can reconstruct the macros from the transformed XDOM.
313  523098 Block resultBlock = wrapInMacroMarker(macroBlock, newBlocks);
314   
315    // 5) Replace the MacroBlock by the Blocks generated by the execution of the Macro
316  523102 macroBlock.getParent().replaceChild(resultBlock, macroBlock);
317   
318  523091 if (incrementRecursions) {
319  190933 ++recursions;
320    }
321    }
322    }
323   
324    /**
325    * Wrap the output of a macro block with a {@link MacroMarkerBlock}.
326    *
327    * @param macroBlockToWrap the block that should be replaced
328    * @param newBlocks list of blocks to wrap
329    * @return the wrapper
330    */
 
331  523083 toggle private Block wrapInMacroMarker(MacroBlock macroBlockToWrap, List<Block> newBlocks)
332    {
333  523097 return new MacroMarkerBlock(macroBlockToWrap.getId(), macroBlockToWrap.getParameters(),
334    macroBlockToWrap.getContent(), newBlocks, macroBlockToWrap.isInline());
335    }
336   
337    /**
338    * @param maxRecursions the max numnber of recursion allowed before we stop transformations
339    */
 
340  2 toggle public void setMaxRecursions(int maxRecursions)
341    {
342  2 this.maxRecursions = maxRecursions;
343    }
344    }