Clover Coverage Report - XWiki Rendering - Parent POM 4.0-SNAPSHOT (Aggregated)
Coverage timestamp: Mon Mar 12 2012 18:03:13 CET
../../../../../../img/srcFileCovDistChart10.png 0% of files have more coverage
61   283   19   6.78
12   168   0.31   4.5
9     2.11  
2    
 
  MacroTransformation       Line # 69 58 0% 17 5 93.5% 0.9350649
  MacroTransformation.MacroHolder       Line # 95 3 0% 2 0 100% 1.0
 
  (115)
 
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.io.PrintWriter;
23    import java.io.StringWriter;
24    import java.util.ArrayList;
25    import java.util.Arrays;
26    import java.util.Collections;
27    import java.util.List;
28    import java.util.Map;
29   
30    import javax.inject.Inject;
31    import javax.inject.Named;
32    import javax.inject.Singleton;
33   
34    import org.slf4j.Logger;
35    import org.xwiki.component.annotation.Component;
36    import org.xwiki.properties.BeanManager;
37    import org.xwiki.rendering.block.Block;
38    import org.xwiki.rendering.block.FormatBlock;
39    import org.xwiki.rendering.block.GroupBlock;
40    import org.xwiki.rendering.block.MacroBlock;
41    import org.xwiki.rendering.block.MacroMarkerBlock;
42    import org.xwiki.rendering.block.VerbatimBlock;
43    import org.xwiki.rendering.block.WordBlock;
44    import org.xwiki.rendering.listener.Format;
45    import org.xwiki.rendering.macro.Macro;
46    import org.xwiki.rendering.macro.MacroId;
47    import org.xwiki.rendering.macro.MacroLookupException;
48    import org.xwiki.rendering.macro.MacroManager;
49    import org.xwiki.rendering.transformation.MacroTransformationContext;
50    import org.xwiki.rendering.syntax.Syntax;
51    import org.xwiki.rendering.transformation.AbstractTransformation;
52    import org.xwiki.rendering.transformation.TransformationContext;
53    import org.xwiki.rendering.transformation.TransformationException;
54   
55    /**
56    * Look for all {@link org.xwiki.rendering.block.MacroBlock} blocks in the passed {@link Block} and iteratively execute
57    * each Macro in the correct order. Macros can:
58    * <ul>
59    * <li>provide a hint specifying when they should run (priority)</li>
60    * <li>generate other Macros</li>
61    * </ul>
62    *
63    * @version $Id: 66d225115a1f04891537cf0dbc5d143b71c14b7a $
64    * @since 1.5M2
65    */
66    @Component
67    @Named("macro")
68    @Singleton
 
69    public class MacroTransformation extends AbstractTransformation
70    {
71    /**
72    * Number of macro executions allowed when rendering the current content before considering that we are in a loop.
73    * Such a loop can happen if a macro generates itself for example.
74    */
75    private int maxMacroExecutions = 1000;
76   
77    /**
78    * Handles macro registration and macro lookups. Injected by the Component Manager.
79    */
80    @Inject
81    private MacroManager macroManager;
82   
83    /**
84    * Used to populate automatically macros parameters classes with parameters specified in the Macro Block.
85    */
86    @Inject
87    private BeanManager beanManager;
88   
89    /**
90    * The logger to log.
91    */
92    @Inject
93    private Logger logger;
94   
 
95    private class MacroHolder implements Comparable<MacroHolder>
96    {
97    Macro< ? > macro;
98   
99    MacroBlock macroBlock;
100   
 
101  1162 toggle public MacroHolder(Macro< ? > macro, MacroBlock macroBlock)
102    {
103  1162 this.macro = macro;
104  1162 this.macroBlock = macroBlock;
105    }
106   
 
107  22 toggle @Override
108    public int compareTo(MacroHolder holder)
109    {
110  22 return this.macro.compareTo(holder.macro);
111    }
112    }
113   
 
114  0 toggle @Override
115    public int getPriority()
116    {
117    // Make it one of the transformations that's executed first so that other transformations run on the executed
118    // macros.
119  0 return 100;
120    }
121   
 
122  132 toggle @Override
123    public void transform(Block rootBlock, TransformationContext context) throws TransformationException
124    {
125    // Create a macro execution context with all the information required for macros.
126  132 MacroTransformationContext macroContext = new MacroTransformationContext(context);
127  132 macroContext.setTransformation(this);
128   
129    // Counter to prevent infinite recursion if a macro generates the same macro for example.
130  132 int executions = 0;
131  132 List<MacroBlock> macroBlocks = rootBlock.getChildrenByType(MacroBlock.class, true);
132  1277 while (!macroBlocks.isEmpty() && executions < this.maxMacroExecutions) {
133  1145 transformOnce(rootBlock, macroContext, context.getSyntax());
134   
135    // TODO: Make this less inefficient by caching the blocks list.
136  1145 macroBlocks = rootBlock.getChildrenByType(MacroBlock.class, true);
137  1145 executions++;
138    }
139    }
140   
 
141  1145 toggle private void transformOnce(Block rootBlock, MacroTransformationContext context, Syntax syntax)
142    {
143    // 1) Get highest priority macro to execute
144  1145 MacroHolder macroHolder = getHighestPriorityMacro(rootBlock, syntax);
145  1145 if (macroHolder == null) {
146  5 return;
147    }
148   
149    // 2) Verify if we're in macro inline mode and if the macro supports it. If not, send an error.
150  1140 if (macroHolder.macroBlock.isInline()) {
151  41 context.setInline(true);
152  41 if (!macroHolder.macro.supportsInlineMode()) {
153    // The macro doesn't support inline mode, raise a warning but continue.
154    // The macro will not be executed and we generate an error message instead of the macro
155    // execution result.
156  5 generateError(macroHolder.macroBlock, "Not an inline macro",
157    "This macro can only be used by itself on a new line");
158  5 this.logger.debug("The [" + macroHolder.macroBlock.getId() + "] macro doesn't support inline mode.");
159  5 return;
160    }
161    } else {
162  1099 context.setInline(false);
163    }
164   
165    // 3) Execute the highest priority macro
166  1135 List<Block> newBlocks;
167  1135 try {
168  1135 context.setCurrentMacroBlock(macroHolder.macroBlock);
169   
170    // Populate and validate macro parameters.
171  1135 Object macroParameters;
172  1135 try {
173  1135 macroParameters = macroHolder.macro.getDescriptor().getParametersBeanClass().newInstance();
174  1135 this.beanManager.populate(macroParameters, macroHolder.macroBlock.getParameters());
175    } catch (Throwable e) {
176    // One macro parameter was invalid.
177    // The macro will not be executed and we generate an error message instead of the macro
178    // execution result.
179  0 generateError(macroHolder.macroBlock, "Invalid macro parameters used for the \""
180    + macroHolder.macroBlock.getId() + "\" macro", e);
181  0 this.logger.debug(
182    "Invalid macro parameter for the [" + macroHolder.macroBlock.getId() + "] macro. Internal error: ["
183    + e.getMessage() + "]");
184   
185  0 return;
186    }
187   
188  1135 newBlocks =
189    ((Macro<Object>) macroHolder.macro).execute(macroParameters, macroHolder.macroBlock.getContent(),
190    context);
191    } catch (Throwable e) {
192    // The Macro failed to execute.
193    // The macro will not be executed and we generate an error message instead of the macro
194    // execution result.
195    // Note: We catch any Exception because we want to never break the whole rendering.
196  10 generateError(macroHolder.macroBlock, "Failed to execute the [" + macroHolder.macroBlock.getId()
197    + "] macro", e);
198  10 this.logger.debug(
199    "Failed to execute the [" + macroHolder.macroBlock.getId() + "]macro. Internal error ["
200    + e.getMessage() + "]");
201   
202  10 return;
203    }
204   
205    // We wrap the blocks generated by the macro execution with MacroMarker blocks so that listeners/renderers
206    // who wish to know the group of blocks that makes up the executed macro can. For example this is useful for
207    // the XWiki Syntax renderer so that it can reconstruct the macros from the transformed XDOM.
208  1125 Block resultBlock = wrapInMacroMarker(macroHolder.macroBlock, newBlocks);
209   
210    // 4) Replace the MacroBlock by the Blocks generated by the execution of the Macro
211  1125 macroHolder.macroBlock.getParent().replaceChild(resultBlock, macroHolder.macroBlock);
212    }
213   
214    /**
215    * @return the macro with the highest priority for the passed syntax or null if no macro is found
216    */
 
217  1145 toggle private MacroHolder getHighestPriorityMacro(Block rootBlock, Syntax syntax)
218    {
219  1145 List<MacroHolder> macroHolders = new ArrayList<MacroHolder>();
220   
221    // 1) Sort the macros by priority to find the highest priority macro to execute
222  1145 for (MacroBlock macroBlock : rootBlock.getChildrenByType(MacroBlock.class, true)) {
223  1167 try {
224  1167 Macro< ? > macro = this.macroManager.getMacro(new MacroId(macroBlock.getId(), syntax));
225  1162 macroHolders.add(new MacroHolder(macro, macroBlock));
226    } catch (MacroLookupException e) {
227    // Macro cannot be found. Generate an error message instead of the macro execution result.
228    // TODO: make it internationalized
229  5 generateError(macroBlock, "Unknown macro: " + macroBlock.getId(), "The \"" + macroBlock.getId()
230    + "\" macro is not in the list of registered macros. Verify the "
231    + "spelling or contact your administrator.");
232  5 this.logger.debug("Failed to locate the [" + macroBlock.getId() + "] macro. Ignoring it.");
233    }
234    }
235   
236    // Sort the Macros by priority
237  1145 Collections.sort(macroHolders);
238   
239  1145 return macroHolders.size() > 0 ? macroHolders.get(0) : null;
240    }
241   
242    /**
243    * Wrap the output of a macro block with a {@link MacroMarkerBlock}.
244    *
245    * @param macroBlockToWrap the block that should be replaced
246    * @param newBlocks list of blocks to wrap
247    * @return the wrapper
248    */
 
249  1145 toggle private Block wrapInMacroMarker(MacroBlock macroBlockToWrap, List<Block> newBlocks)
250    {
251  1145 return new MacroMarkerBlock(macroBlockToWrap.getId(), macroBlockToWrap.getParameters(), macroBlockToWrap
252    .getContent(), newBlocks, macroBlockToWrap.isInline());
253    }
254   
 
255  20 toggle private void generateError(MacroBlock macroToReplace, String message, String description)
256    {
257  20 List<Block> errorBlocks = new ArrayList<Block>();
258   
259  20 Map<String, String> errorBlockParams = Collections.singletonMap("class", "xwikirenderingerror");
260  20 Map<String, String> errorDescriptionBlockParams =
261    Collections.singletonMap("class", "xwikirenderingerrordescription hidden");
262   
263  20 Block descriptionBlock = new VerbatimBlock(description, macroToReplace.isInline());
264   
265  20 if (macroToReplace.isInline()) {
266  15 errorBlocks.add(new FormatBlock(Arrays.<Block> asList(new WordBlock(message)), Format.NONE,
267    errorBlockParams));
268  15 errorBlocks.add(new FormatBlock(Arrays.asList(descriptionBlock), Format.NONE, errorDescriptionBlockParams));
269    } else {
270  5 errorBlocks.add(new GroupBlock(Arrays.<Block> asList(new WordBlock(message)), errorBlockParams));
271  5 errorBlocks.add(new GroupBlock(Arrays.asList(descriptionBlock), errorDescriptionBlockParams));
272    }
273   
274  20 macroToReplace.getParent().replaceChild(wrapInMacroMarker(macroToReplace, errorBlocks), macroToReplace);
275    }
276   
 
277  10 toggle private void generateError(MacroBlock macroToReplace, String message, Throwable throwable)
278    {
279  10 StringWriter writer = new StringWriter();
280  10 throwable.printStackTrace(new PrintWriter(writer));
281  10 generateError(macroToReplace, message, writer.getBuffer().toString());
282    }
283    }