1. Project Clover database Tue Dec 20 2016 21:24:09 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
59
8
3
319
184
23
0.39
7.38
2.67
2.88

Classes

Class Line # Actions
MacroTransformation 68 42 0% 14 4
0.9344262593.4%
MacroTransformation.MacroLookupExceptionElement 70 2 0% 1 0
1.0100%
MacroTransformation.PriorityMacroBlockMatcher 83 15 0% 8 1
0.9696%
 

Contributing tests

This file is covered by 412 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: 0fcf024c8ac78462f580b7753322b690807092bc $
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    public MacroBlock macroBlock;
73   
74    public MacroLookupException exception;
75   
 
76  13 toggle public MacroLookupExceptionElement(MacroBlock macroBlock, MacroLookupException exception)
77    {
78  13 this.macroBlock = macroBlock;
79  13 this.exception = exception;
80    }
81    }
82   
 
83    private class PriorityMacroBlockMatcher implements BlockMatcher
84    {
85    private final Syntax syntax;
86   
87    public MacroBlock block;
88   
89    public Macro<?> blockMacro;
90   
91    public List<MacroLookupExceptionElement> errors;
92   
93    // Cache known macros since getting them again and again from the ComponentManager might be expensive
94    private final Map<String, Macro<?>> knownMacros = new HashMap<>();
95   
 
96  26784 toggle PriorityMacroBlockMatcher(Syntax syntax)
97    {
98  26783 this.syntax = syntax;
99    }
100   
 
101  1669540 toggle @Override
102    public boolean match(Block block)
103    {
104  1669541 if (block instanceof MacroBlock) {
105  78494 MacroBlock macroBlock = (MacroBlock) block;
106   
107  78494 try {
108    // Try to find a known macros
109  78494 Macro<?> macro = this.knownMacros.get(macroBlock.getId());
110   
111    // If not found use the macro manager
112  78494 if (macro == null) {
113  21434 macro =
114    MacroTransformation.this.macroManager
115    .getMacro(new MacroId(macroBlock.getId(), this.syntax));
116   
117    // Cache the found macro for later
118  21422 this.knownMacros.put(macroBlock.getId(), macro);
119    }
120   
121    // Find higher priority macro
122  78482 if (this.block == null || this.blockMacro.compareTo(macro) > 0) {
123  17939 this.block = macroBlock;
124  17939 this.blockMacro = macro;
125    }
126    } catch (MacroLookupException e) {
127  13 if (this.errors == null) {
128  13 this.errors = new LinkedList<MacroLookupExceptionElement>();
129    }
130   
131  13 this.errors.add(new MacroLookupExceptionElement(macroBlock, e));
132    }
133    }
134   
135  1669541 return false;
136    }
137    }
138   
139    /**
140    * Number of times a macro can generate another macro before considering that we are in a loop. Such a loop can
141    * happen if a macro generates itself for example.
142    */
143    private int maxRecursions = 1000;
144   
145    /**
146    * Handles macro registration and macro lookups. Injected by the Component Manager.
147    */
148    @Inject
149    private MacroManager macroManager;
150   
151    /**
152    * Used to populate automatically macros parameters classes with parameters specified in the Macro Block.
153    */
154    @Inject
155    private BeanManager beanManager;
156   
157    /**
158    * Used to updated the rendering context.
159    */
160    @Inject
161    private RenderingContext renderingContext;
162   
163    /**
164    * The logger to log.
165    */
166    @Inject
167    private Logger logger;
168   
169    @Inject
170    private ErrorBlockGenerator errorBlockGenerator;
171   
172    /**
173    * Used to generate Macro error blocks when a Macro fails to execute.
174    */
175    private MacroErrorManager macroErrorManager;
176   
 
177  465 toggle @Override
178    public void initialize() throws InitializationException
179    {
180  465 this.macroErrorManager = new MacroErrorManager(this.errorBlockGenerator);
181    }
182   
 
183  7169 toggle @Override
184    public int getPriority()
185    {
186    // Make it one of the transformations that's executed first so that other transformations run on the executed
187    // macros.
188  7169 return 100;
189    }
190   
 
191  9345 toggle @Override
192    public void transform(Block rootBlock, TransformationContext context) throws TransformationException
193    {
194    // Create a macro execution context with all the information required for macros.
195  9345 MacroTransformationContext macroContext = new MacroTransformationContext(context);
196  9345 macroContext.setTransformation(this);
197   
198    // Counter to prevent infinite recursion if a macro generates the same macro for example.
199  26784 for (int recursions = 0; recursions < this.maxRecursions;) {
200    // 1) Get highest priority macro
201  26783 PriorityMacroBlockMatcher priorityMacroBlockMatcher = new PriorityMacroBlockMatcher(context.getSyntax());
202  26784 rootBlock.getFirstBlock(priorityMacroBlockMatcher, Block.Axes.DESCENDANT);
203   
204    // 2) Apply macros lookup errors
205  26782 if (priorityMacroBlockMatcher.errors != null) {
206  13 for (MacroLookupExceptionElement error : priorityMacroBlockMatcher.errors) {
207  13 if (error.exception instanceof MacroNotFoundException) {
208    // Macro cannot be found. Generate an error message instead of the macro execution result.
209    // TODO: make it internationalized
210  13 this.macroErrorManager.generateError(error.macroBlock,
211    String.format("Unknown macro: %s.", error.macroBlock.getId()), String.format(
212    "The \"%s\" macro is not in the list of registered macros. Verify the spelling or "
213    + "contact your administrator.", error.macroBlock.getId()));
214    } else {
215    // TODO: make it internationalized
216  0 this.macroErrorManager.generateError(error.macroBlock,
217    String.format("Invalid macro: %s", error.macroBlock.getId()), error.exception);
218    }
219    }
220    }
221   
222  26783 MacroBlock macroBlock = priorityMacroBlockMatcher.block;
223   
224  26783 if (macroBlock == null) {
225    // Nothing left to do
226  9343 return;
227    }
228   
229  17439 Macro<?> macro = priorityMacroBlockMatcher.blockMacro;
230   
231  17440 boolean incrementRecursions = macroBlock.getParent() instanceof MacroMarkerBlock;
232   
233  17440 List<Block> newBlocks;
234  17440 try {
235    // 3) Verify if we're in macro inline mode and if the macro supports it. If not, send an error.
236  17440 if (macroBlock.isInline()) {
237  4658 macroContext.setInline(true);
238  4658 if (!macro.supportsInlineMode()) {
239    // The macro doesn't support inline mode, raise a warning but continue.
240    // The macro will not be executed and we generate an error message instead of the macro
241    // execution result.
242  5 this.macroErrorManager.generateError(macroBlock, String.format(
243    "The [%s] macro is a standalone macro and it cannot be used inline",
244    macroBlock.getId()),
245    "This macro generates standalone content. As a consequence you need to make sure to use a "
246    + "syntax that separates your macro from the content before and after it so that it's on a "
247    + "line by itself. For example in XWiki Syntax 2.0+ this means having 2 newline characters "
248    + "(a.k.a line breaks) separating your macro from the content before and after it.");
249  5 continue;
250    }
251    } else {
252  12782 macroContext.setInline(false);
253    }
254   
255    // 4) Execute the highest priority macro
256  17435 macroContext.setCurrentMacroBlock(macroBlock);
257  17435 ((MutableRenderingContext) this.renderingContext).setCurrentBlock(macroBlock);
258   
259    // Populate and validate macro parameters.
260  17435 Object macroParameters = macro.getDescriptor().getParametersBeanClass().newInstance();
261  17435 try {
262  17434 this.beanManager.populate(macroParameters, macroBlock.getParameters());
263    } catch (Throwable e) {
264    // One macro parameter was invalid.
265    // The macro will not be executed and we generate an error message instead of the macro
266    // execution result.
267  0 this.macroErrorManager.generateError(macroBlock,
268    String.format("Invalid macro parameters used for the \"%s\" macro", macroBlock.getId()), e);
269  0 continue;
270    }
271   
272  17435 newBlocks = ((Macro) macro).execute(macroParameters, macroBlock.getContent(), macroContext);
273    } catch (Throwable e) {
274    // The Macro failed to execute.
275    // The macro will not be executed and we generate an error message instead of the macro
276    // execution result.
277    // Note: We catch any Exception because we want to never break the whole rendering.
278  13 this.macroErrorManager.generateError(macroBlock,
279    String.format("Failed to execute the [%s] macro", macroBlock.getId()), e);
280  13 continue;
281    } finally {
282  17440 ((MutableRenderingContext) this.renderingContext).setCurrentBlock(null);
283    }
284   
285    // We wrap the blocks generated by the macro execution with MacroMarker blocks so that listeners/renderers
286    // who wish to know the group of blocks that makes up the executed macro can. For example this is useful for
287    // the XWiki Syntax renderer so that it can reconstruct the macros from the transformed XDOM.
288  17422 Block resultBlock = wrapInMacroMarker(macroBlock, newBlocks);
289   
290    // 5) Replace the MacroBlock by the Blocks generated by the execution of the Macro
291  17422 macroBlock.getParent().replaceChild(resultBlock, macroBlock);
292   
293  17422 if (incrementRecursions) {
294  2721 ++recursions;
295    }
296    }
297    }
298   
299    /**
300    * Wrap the output of a macro block with a {@link MacroMarkerBlock}.
301    *
302    * @param macroBlockToWrap the block that should be replaced
303    * @param newBlocks list of blocks to wrap
304    * @return the wrapper
305    */
 
306  17422 toggle private Block wrapInMacroMarker(MacroBlock macroBlockToWrap, List<Block> newBlocks)
307    {
308  17420 return new MacroMarkerBlock(macroBlockToWrap.getId(), macroBlockToWrap.getParameters(),
309    macroBlockToWrap.getContent(), newBlocks, macroBlockToWrap.isInline());
310    }
311   
312    /**
313    * @param maxRecursions the max numnber of recursion allowed before we stop transformations
314    */
 
315  2 toggle public void setMaxRecursions(int maxRecursions)
316    {
317  2 this.maxRecursions = maxRecursions;
318    }
319    }