1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.rendering.internal.macro.wikibridge

File DefaultWikiMacro.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart9.png
38% of files have more coverage

Code metrics

28
90
13
1
418
226
38
0.42
6.92
13
2.92

Classes

Class Line # Actions
DefaultWikiMacro 62 90 0% 38 13
0.9007633390.1%
 

Contributing tests

This file is covered by 19 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.macro.wikibridge;
21   
22    import java.util.Collections;
23    import java.util.HashMap;
24    import java.util.List;
25    import java.util.Map;
26   
27    import org.xwiki.component.manager.ComponentLookupException;
28    import org.xwiki.component.manager.ComponentManager;
29    import org.xwiki.context.Execution;
30    import org.xwiki.context.ExecutionContext;
31    import org.xwiki.model.reference.DocumentReference;
32    import org.xwiki.observation.ObservationManager;
33    import org.xwiki.rendering.block.Block;
34    import org.xwiki.rendering.block.MacroBlock;
35    import org.xwiki.rendering.block.MacroMarkerBlock;
36    import org.xwiki.rendering.block.MetaDataBlock;
37    import org.xwiki.rendering.block.ParagraphBlock;
38    import org.xwiki.rendering.block.XDOM;
39    import org.xwiki.rendering.internal.macro.script.NestedScriptMacroEnabled;
40    import org.xwiki.rendering.internal.transformation.MutableRenderingContext;
41    import org.xwiki.rendering.macro.Macro;
42    import org.xwiki.rendering.macro.MacroExecutionException;
43    import org.xwiki.rendering.macro.descriptor.MacroDescriptor;
44    import org.xwiki.rendering.macro.descriptor.ParameterDescriptor;
45    import org.xwiki.rendering.macro.parameter.MacroParameterException;
46    import org.xwiki.rendering.macro.wikibridge.WikiMacro;
47    import org.xwiki.rendering.macro.wikibridge.WikiMacroExecutionFinishedEvent;
48    import org.xwiki.rendering.macro.wikibridge.WikiMacroExecutionStartsEvent;
49    import org.xwiki.rendering.macro.wikibridge.WikiMacroParameters;
50    import org.xwiki.rendering.syntax.Syntax;
51    import org.xwiki.rendering.transformation.MacroTransformationContext;
52    import org.xwiki.rendering.transformation.RenderingContext;
53    import org.xwiki.rendering.transformation.Transformation;
54    import org.xwiki.rendering.transformation.TransformationContext;
55   
56    /**
57    * Default implementation of {@link WikiMacro}.
58    *
59    * @version $Id: 1440dd757d88eb981ee7a839c953593e5a3d0c17 $
60    * @since 2.0M1
61    */
 
62    public class DefaultWikiMacro implements WikiMacro, NestedScriptMacroEnabled
63    {
64    /**
65    * The key under which macro context will be available in the XWikiContext for scripts.
66    */
67    private static final String MACRO_KEY = "macro";
68   
69    /**
70    * Macro hint for {@link Transformation} component. Same as MACRO_KEY (Check style fix).
71    */
72    private static final String MACRO_HINT = MACRO_KEY;
73   
74    /**
75    * The key under which macro body will be available inside macro context.
76    */
77    private static final String MACRO_CONTENT_KEY = "content";
78   
79    /**
80    * The key under which macro parameters will be available inside macro context.
81    */
82    private static final String MACRO_PARAMS_KEY = "params";
83   
84    /**
85    * The key under which macro transformation context will be available inside macro context.
86    */
87    private static final String MACRO_CONTEXT_KEY = "context";
88   
89    /**
90    * The key under which macro can directly return the resulting {@link List} of {@link Block}.
91    */
92    private static final String MACRO_RESULT_KEY = "result";
93   
94    /**
95    * Event sent before wiki macro execution.
96    */
97    private static final WikiMacroExecutionStartsEvent STARTEXECUTION_EVENT = new WikiMacroExecutionStartsEvent();
98   
99    /**
100    * Event sent after wiki macro execution.
101    */
102    private static final WikiMacroExecutionFinishedEvent ENDEXECUTION_EVENT = new WikiMacroExecutionFinishedEvent();
103   
104    /**
105    * The {@link MacroDescriptor} for this macro.
106    */
107    private MacroDescriptor descriptor;
108   
109    /**
110    * Document which contains the definition of this macro.
111    */
112    private DocumentReference macroDocumentReference;
113   
114    /**
115    * User to be used to check rights for the macro.
116    */
117    private DocumentReference macroAuthor;
118   
119    /**
120    * Whether this macro supports inline mode or not.
121    */
122    private boolean supportsInlineMode;
123   
124    /**
125    * Macro content.
126    */
127    private XDOM content;
128   
129    /**
130    * Syntax id.
131    */
132    private Syntax syntax;
133   
134    /**
135    * The component manager used to lookup other components.
136    */
137    private ComponentManager componentManager;
138   
139    /**
140    * Constructs a new {@link DefaultWikiMacro}.
141    *
142    * @param macroDocumentReference the name of the document which contains the definition of this macro
143    * @param macroAuthor the author of the wiki macro
144    * @param supportsInlineMode says if macro support inline mode or not
145    * @param descriptor the {@link MacroDescriptor} describing this macro.
146    * @param macroContent macro content to be evaluated.
147    * @param syntax syntax of the macroContent source.
148    * @param componentManager {@link ComponentManager} component used to look up for other components.
149    * @since 2.3M1
150    */
 
151  248 toggle public DefaultWikiMacro(DocumentReference macroDocumentReference, DocumentReference macroAuthor,
152    boolean supportsInlineMode, MacroDescriptor descriptor, XDOM macroContent, Syntax syntax,
153    ComponentManager componentManager)
154    {
155  248 this.macroDocumentReference = macroDocumentReference;
156  248 this.macroAuthor = macroAuthor;
157  248 this.supportsInlineMode = supportsInlineMode;
158  248 this.descriptor = descriptor;
159  248 this.content = macroContent;
160  248 this.syntax = syntax;
161  248 this.componentManager = componentManager;
162    }
163   
 
164  154 toggle @Override
165    public List<Block> execute(WikiMacroParameters parameters, String macroContent, MacroTransformationContext context)
166    throws MacroExecutionException
167    {
168  154 validate(parameters, macroContent);
169   
170    // Parse the wiki macro content.
171  154 XDOM xdom = prepareWikiMacroContent(context);
172   
173    // Prepare macro context.
174  154 Map<String, Object> macroBinding = new HashMap<String, Object>();
175  154 macroBinding.put(MACRO_PARAMS_KEY, parameters);
176  154 macroBinding.put(MACRO_CONTENT_KEY, macroContent);
177  154 macroBinding.put(MACRO_CONTEXT_KEY, context);
178  154 macroBinding.put(MACRO_RESULT_KEY, null);
179   
180    // Extension point to add more wiki macro bindings
181  154 try {
182  154 List<WikiMacroBindingInitializer> bindingInitializers =
183    this.componentManager.getInstanceList(WikiMacroBindingInitializer.class);
184   
185  154 for (WikiMacroBindingInitializer bindingInitializer : bindingInitializers) {
186  146 bindingInitializer.initialize(this.macroDocumentReference, parameters, macroContent, context,
187    macroBinding);
188    }
189    } catch (ComponentLookupException e) {
190    // TODO: we should probably log something but that should never happen
191    }
192   
193    // Execute the macro
194  154 ObservationManager observation = null;
195  154 try {
196  154 observation = this.componentManager.getInstance(ObservationManager.class);
197    } catch (ComponentLookupException e) {
198    // TODO: maybe log something
199    }
200   
201    // Get XWiki context
202  154 Map<String, Object> xwikiContext = null;
203  154 try {
204  154 Execution execution = this.componentManager.getInstance(Execution.class);
205  154 ExecutionContext econtext = execution.getContext();
206  154 if (econtext != null) {
207  154 xwikiContext = (Map<String, Object>) execution.getContext().getProperty("xwikicontext");
208    }
209    } catch (ComponentLookupException e) {
210    // TODO: maybe log something
211    }
212   
213  154 try {
214  154 Transformation macroTransformation = this.componentManager.getInstance(Transformation.class, MACRO_HINT);
215   
216  154 if (xwikiContext != null) {
217    // Place macro context inside xwiki context ($xcontext.macro).
218  154 xwikiContext.put(MACRO_KEY, macroBinding);
219    }
220   
221  154 MacroBlock wikiMacroBlock = context.getCurrentMacroBlock();
222   
223  154 MacroMarkerBlock wikiMacroMarker =
224    new MacroMarkerBlock(wikiMacroBlock.getId(), wikiMacroBlock.getParameters(),
225    wikiMacroBlock.getContent(), xdom.getChildren(), wikiMacroBlock.isInline());
226   
227    // Make sure to use provided metadatas
228  154 MetaDataBlock metaDataBlock =
229    new MetaDataBlock(Collections.<Block> singletonList(wikiMacroMarker), xdom.getMetaData());
230   
231    // Make sure the context XDOM contains the wiki macro content
232  154 wikiMacroBlock.getParent().replaceChild(metaDataBlock, wikiMacroBlock);
233   
234    // "Emulate" the fact that wiki macro block is still part of the XDOM (what is in the XDOM is a
235    // MacroMarkerBlock and MacroTransformationContext current macro block only support MacroBlock so we can't
236    // switch it without breaking some APIs)
237  154 wikiMacroBlock.setParent(metaDataBlock.getParent());
238  154 wikiMacroBlock.setNextSiblingBlock(metaDataBlock.getNextSibling());
239  154 wikiMacroBlock.setPreviousSiblingBlock(metaDataBlock.getPreviousSibling());
240   
241  154 try {
242  154 if (observation != null) {
243  154 observation.notify(STARTEXECUTION_EVENT, this, macroBinding);
244    }
245   
246    // Perform internal macro transformations.
247  154 TransformationContext txContext = new TransformationContext(context.getXDOM(), this.syntax);
248  154 txContext.setId(context.getId());
249   
250  154 RenderingContext renderingContext = componentManager.getInstance(RenderingContext.class);
251  154 ((MutableRenderingContext) renderingContext).transformInContext(macroTransformation, txContext,
252    wikiMacroMarker);
253    } finally {
254    // Restore context XDOM to its previous state
255  154 metaDataBlock.getParent().replaceChild(wikiMacroBlock, metaDataBlock);
256    }
257   
258  154 return extractResult(wikiMacroMarker.getChildren(), macroBinding, context);
259    } catch (Exception ex) {
260  0 throw new MacroExecutionException("Error while performing internal macro transformations", ex);
261    } finally {
262  154 if (xwikiContext != null) {
263  154 xwikiContext.remove(MACRO_KEY);
264    }
265   
266  154 if (observation != null) {
267  154 observation.notify(ENDEXECUTION_EVENT, this);
268    }
269    }
270    }
271   
272    /**
273    * Extract result of the wiki macro execution.
274    *
275    * @param blocks the wiki macro content
276    * @param macroContext the wiki macro context
277    * @param context the macro execution context
278    * @return the result
279    */
 
280  154 toggle private List<Block> extractResult(List<Block> blocks, Map<String, Object> macroContext,
281    MacroTransformationContext context)
282    {
283  154 Object resultObject = macroContext.get(MACRO_RESULT_KEY);
284   
285  154 List<Block> result;
286  154 if (resultObject != null && resultObject instanceof List) {
287  2 result = (List<Block>) macroContext.get(MACRO_RESULT_KEY);
288    } else {
289  152 result = blocks;
290    // If in inline mode remove any top level paragraph.
291  152 if (context.isInline()) {
292  2 removeTopLevelParagraph(result);
293    }
294    }
295   
296  154 return result;
297    }
298   
299    /**
300    * Removes any top level paragraph since for example for the following use case we don't want an extra paragraph
301    * block: <code>= hello {{velocity}}world{{/velocity}}</code>.
302    *
303    * @param blocks the blocks to check and convert
304    */
 
305  2 toggle private void removeTopLevelParagraph(List<Block> blocks)
306    {
307    // Remove any top level paragraph so that the result of a macro can be used inline for example.
308    // We only remove the paragraph if there's only one top level element and if it's a paragraph.
309  2 if ((blocks.size() == 1) && blocks.get(0) instanceof ParagraphBlock) {
310  1 Block paragraphBlock = blocks.remove(0);
311  1 blocks.addAll(0, paragraphBlock.getChildren());
312    }
313    }
314   
315    /**
316    * Clone and filter wiki macro content depending of the context.
317    *
318    * @param context the macro execution context
319    * @return the cleaned wiki macro content
320    */
 
321  154 toggle private XDOM prepareWikiMacroContent(MacroTransformationContext context)
322    {
323  154 XDOM xdom = this.content.clone();
324   
325    // Macro code segment is always parsed into a separate xdom document. Now if this code segment starts with
326    // another macro block, it will always be interpreted as a block macro regardless of the current wiki macro's
327    // context (because as far as the nested macro is concerned, it starts on a new line). This will introduce
328    // unnecessary paragraph elements when the wiki macro is used inline, so we need to force such opening macro
329    // blocks to behave as inline macros if the wiki macro is used inline.
330  154 if (context.isInline()) {
331  3 List<Block> children = xdom.getChildren();
332  3 if (children.size() > 0 && children.get(0) instanceof MacroBlock) {
333  2 MacroBlock old = (MacroBlock) children.get(0);
334  2 MacroBlock replacement = new MacroBlock(old.getId(), old.getParameters(), old.getContent(), true);
335  2 xdom.replaceChild(replacement, old);
336    }
337    }
338   
339  154 return xdom;
340    }
341   
342    /**
343    * Check validity of the given macro parameters and content.
344    *
345    * @param parameters the macro parameters
346    * @param macroContent the macro content
347    * @throws MacroExecutionException given parameters of content is invalid
348    */
 
349  154 toggle private void validate(WikiMacroParameters parameters, String macroContent) throws MacroExecutionException
350    {
351    // First verify that all mandatory parameters are provided.
352    // Note that we currently verify automatically mandatory parameters in Macro Transformation but for the moment
353    // this is only checked for Java-based macros. Hence why we need to check here too.
354  154 Map<String, ParameterDescriptor> parameterDescriptors = getDescriptor().getParameterDescriptorMap();
355  154 for (String parameterName : parameterDescriptors.keySet()) {
356  1225 ParameterDescriptor parameterDescriptor = parameterDescriptors.get(parameterName);
357  1225 Object parameterValue = parameters.get(parameterName);
358  1225 if (parameterDescriptor.isMandatory() && (null == parameterValue)) {
359  0 throw new MacroParameterException(String.format("Parameter [%s] is mandatory", parameterName));
360    }
361   
362    // Set default parameter value if applicable.
363  1225 Object parameterDefaultValue = parameterDescriptor.getDefaultValue();
364  1225 if (parameterValue == null && parameterDefaultValue != null) {
365  285 parameters.set(parameterName, parameterDefaultValue);
366    }
367    }
368   
369    // Verify the a macro content is not empty if it was declared mandatory.
370  154 if (getDescriptor().getContentDescriptor() != null && getDescriptor().getContentDescriptor().isMandatory()) {
371  0 if (macroContent == null || macroContent.length() == 0) {
372  0 throw new MacroExecutionException("Missing macro content: this macro requires content (a body)");
373    }
374    }
375    }
376   
 
377  1209 toggle @Override
378    public MacroDescriptor getDescriptor()
379    {
380  1209 return this.descriptor;
381    }
382   
 
383  1683 toggle @Override
384    public int getPriority()
385    {
386  1683 return 1000;
387    }
388   
 
389  1 toggle @Override
390    public String getId()
391    {
392  1 return this.descriptor.getId().getId();
393    }
394   
 
395  23 toggle @Override
396    public DocumentReference getDocumentReference()
397    {
398  23 return this.macroDocumentReference;
399    }
400   
 
401  641 toggle @Override
402    public DocumentReference getAuthorReference()
403    {
404  641 return this.macroAuthor;
405    }
406   
 
407  1633 toggle @Override
408    public int compareTo(Macro< ? > macro)
409    {
410  1633 return getPriority() - macro.getPriority();
411    }
412   
 
413  4 toggle @Override
414    public boolean supportsInlineMode()
415    {
416  4 return this.supportsInlineMode;
417    }
418    }