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

File IncludeMacro.java

 
testIncludeMacro: Failed to lookup default document displayer.
testIncludeMacroWithNewContextShowsPassingOnRestrictedFlag: Failed to lookup default document displayer.
testIncludeMacroWhenIncludingDocumentWithRelativeReferences: Failed to lookup default document displayer.
testIncludeMacroWhenSectionSpecified: Failed to lookup default document displayer.
 

Coverage histogram

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

Code metrics

24
65
9
1
303
173
25
0.38
7.22
9
2.78

Classes

Class Line # Actions
IncludeMacro 64 65 0% 25 9
0.9081632590.8%
 

Contributing tests

This file is covered by 9 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.include;
21   
22    import java.util.Arrays;
23    import java.util.List;
24    import java.util.Map;
25    import java.util.Stack;
26   
27    import javax.inject.Inject;
28    import javax.inject.Named;
29    import javax.inject.Singleton;
30   
31    import org.xwiki.bridge.DocumentAccessBridge;
32    import org.xwiki.bridge.DocumentModelBridge;
33    import org.xwiki.component.annotation.Component;
34    import org.xwiki.display.internal.DocumentDisplayer;
35    import org.xwiki.display.internal.DocumentDisplayerParameters;
36    import org.xwiki.model.reference.DocumentReference;
37    import org.xwiki.model.reference.EntityReference;
38    import org.xwiki.model.reference.EntityReferenceResolver;
39    import org.xwiki.model.reference.EntityReferenceSerializer;
40    import org.xwiki.properties.BeanManager;
41    import org.xwiki.properties.PropertyException;
42    import org.xwiki.rendering.block.Block;
43    import org.xwiki.rendering.block.MacroBlock;
44    import org.xwiki.rendering.block.MacroMarkerBlock;
45    import org.xwiki.rendering.block.MetaDataBlock;
46    import org.xwiki.rendering.block.XDOM;
47    import org.xwiki.rendering.listener.MetaData;
48    import org.xwiki.rendering.macro.AbstractMacro;
49    import org.xwiki.rendering.macro.MacroExecutionException;
50    import org.xwiki.rendering.macro.include.IncludeMacroParameters;
51    import org.xwiki.rendering.macro.include.IncludeMacroParameters.Context;
52    import org.xwiki.rendering.transformation.MacroTransformationContext;
53    import org.xwiki.security.authorization.ContextualAuthorizationManager;
54    import org.xwiki.security.authorization.Right;
55   
56    /**
57    * @version $Id: b6441338330fa1097979510f441cbfeb1b7c02c4 $
58    * @since 1.5M2
59    */
60    // TODO: add support for others entity types (not only document and page). Mainly require more generic displayer API.
61    @Component
62    @Named("include")
63    @Singleton
 
64    public class IncludeMacro extends AbstractMacro<IncludeMacroParameters>
65    {
66    /**
67    * The description of the macro.
68    */
69    private static final String DESCRIPTION = "Include other pages into the current page.";
70   
71    /**
72    * Used to access document content and check view access right.
73    */
74    @Inject
75    private DocumentAccessBridge documentAccessBridge;
76   
77    @Inject
78    private ContextualAuthorizationManager authorization;
79   
80    /**
81    * Used to transform the passed reference macro parameter into a complete {@link DocumentReference} one.
82    */
83    @Inject
84    @Named("macro")
85    private EntityReferenceResolver<String> macroEntityReferenceResolver;
86   
87    /**
88    * Used to serialize resolved document links into a string again since the Rendering API only manipulates Strings
89    * (done voluntarily to be independent of any wiki engine and not draw XWiki-specific dependencies).
90    */
91    @Inject
92    private EntityReferenceSerializer<String> defaultEntityReferenceSerializer;
93   
94    /**
95    * Used to display the content of the included document.
96    */
97    @Inject
98    @Named("configured")
99    private DocumentDisplayer documentDisplayer;
100   
101    @Inject
102    private BeanManager beans;
103   
104    /**
105    * A stack of all currently executing include macros with context=new for catching recursive inclusion.
106    */
107    private ThreadLocal<Stack<Object>> inclusionsBeingExecuted = new ThreadLocal<>();
108   
109    /**
110    * Default constructor.
111    */
 
112  40 toggle public IncludeMacro()
113    {
114  40 super("Include", DESCRIPTION, IncludeMacroParameters.class);
115   
116    // The include macro must execute first since if it runs with the current context it needs to bring
117    // all the macros from the included page before the other macros are executed.
118  40 setPriority(10);
119  40 setDefaultCategory(DEFAULT_CATEGORY_CONTENT);
120    }
121   
 
122  157 toggle @Override
123    public boolean supportsInlineMode()
124    {
125  157 return true;
126    }
127   
128    /**
129    * Allows overriding the Document Access Bridge used (useful for unit tests).
130    *
131    * @param documentAccessBridge the new Document Access Bridge to use
132    */
 
133  4 toggle public void setDocumentAccessBridge(DocumentAccessBridge documentAccessBridge)
134    {
135  4 this.documentAccessBridge = documentAccessBridge;
136    }
137   
138    /**
139    * Allows overriding the Document Displayer used (useful for unit tests).
140    *
141    * @param documentDisplayer the new Document Displayer to use
142    */
 
143  1 toggle public void setDocumentDisplayer(DocumentDisplayer documentDisplayer)
144    {
145  1 this.documentDisplayer = documentDisplayer;
146    }
147   
 
148  12923 toggle @Override
149    public List<Block> execute(IncludeMacroParameters parameters, String content, MacroTransformationContext context)
150    throws MacroExecutionException
151    {
152    // Step 1: Perform checks.
153  12927 if (parameters.getReference() == null) {
154  9 throw new MacroExecutionException(
155    "You must specify a 'reference' parameter pointing to the entity to include.");
156    }
157   
158  12916 EntityReference includedReference = resolve(context.getCurrentMacroBlock(), parameters);
159   
160  12916 checkRecursiveInclusion(context.getCurrentMacroBlock(), includedReference);
161   
162  12915 Context parametersContext = parameters.getContext();
163   
164    // Step 2: Retrieve the included document.
165  12910 DocumentModelBridge documentBridge;
166  12915 try {
167  12914 documentBridge = this.documentAccessBridge.getDocumentInstance(includedReference);
168    } catch (Exception e) {
169  0 throw new MacroExecutionException("Failed to load Document [" + includedReference + "]", e);
170    }
171   
172    // Step 3: Check right
173  12915 if (!this.authorization.hasAccess(Right.VIEW, documentBridge.getDocumentReference())) {
174  0 throw new MacroExecutionException(
175    String.format("Current user [%s] doesn't have view rights on document [%s]",
176    this.documentAccessBridge.getCurrentUserReference(), includedReference));
177    }
178   
179    // Step 4: Display the content of the included document.
180   
181    // Check the value of the "context" parameter.
182    //
183    // If CONTEXT_NEW then display the content in an isolated execution and transformation context.
184    //
185    // if CONTEXT_CURRENT then display the content without performing any transformations (we don't want any Macro
186    // to be executed at this stage since they should be executed by the currently running Macro Transformation.
187  12914 DocumentDisplayerParameters displayParameters = new DocumentDisplayerParameters();
188  12915 displayParameters.setContentTransformed(parametersContext == Context.NEW);
189  12914 displayParameters.setExecutionContextIsolated(displayParameters.isContentTransformed());
190  12914 displayParameters.setSectionId(parameters.getSection());
191  12913 displayParameters.setTransformationContextIsolated(displayParameters.isContentTransformed());
192  12914 displayParameters.setTransformationContextRestricted(context.getTransformationContext().isRestricted());
193  12916 displayParameters.setTargetSyntax(context.getTransformationContext().getTargetSyntax());
194  12915 displayParameters.setContentTranslated(true);
195   
196  12917 Stack<Object> references = this.inclusionsBeingExecuted.get();
197  149 if (parametersContext == Context.NEW) {
198  149 if (references == null) {
199  78 references = new Stack<>();
200  78 this.inclusionsBeingExecuted.set(references);
201    }
202  149 references.push(includedReference);
203    }
204   
205  12916 XDOM result;
206  12916 try {
207  12916 Test failure here result = this.documentDisplayer.display(documentBridge, displayParameters);
208    } catch (Exception e) {
209  7 Test failure here throw new MacroExecutionException(e.getMessage(), e);
210    } finally {
211  149 if (parametersContext == Context.NEW) {
212  149 references.pop();
213    }
214    }
215   
216    // Step 5: Wrap Blocks in a MetaDataBlock with the "source" meta data specified so that we know from where the
217    // content comes and "base" meta data so that reference are properly resolved
218  12909 MetaDataBlock metadata = new MetaDataBlock(result.getChildren(), result.getMetaData());
219  12908 String source = this.defaultEntityReferenceSerializer.serialize(includedReference);
220  12909 metadata.getMetaData().addMetaData(MetaData.SOURCE, source);
221  12909 if (parametersContext == Context.NEW) {
222  146 metadata.getMetaData().addMetaData(MetaData.BASE, source);
223    }
224   
225  12908 return Arrays.asList(metadata);
226    }
227   
228    /**
229    * Protect form recursive inclusion.
230    *
231    * @param currrentBlock the child block to check
232    * @param reference the reference of the document being included
233    * @throws MacroExecutionException recursive inclusion has been found
234    */
 
235  47557 toggle private void checkRecursiveInclusion(Block currrentBlock, EntityReference reference) throws MacroExecutionException
236    {
237    // Check for parent context=new macros
238  47560 Stack<Object> references = this.inclusionsBeingExecuted.get();
239  47562 if (references != null && references.contains(reference)) {
240  1 throw new MacroExecutionException("Found recursive inclusion of document [" + reference + "]");
241    }
242   
243    // Check for parent context=current macros
244  47561 Block parentBlock = currrentBlock.getParent();
245   
246  47558 if (parentBlock != null) {
247  8727 if (parentBlock instanceof MacroMarkerBlock) {
248  8727 MacroMarkerBlock parentMacro = (MacroMarkerBlock) parentBlock;
249   
250  8727 if (isRecursive(parentMacro, reference)) {
251  1 throw new MacroExecutionException("Found recursive inclusion of document [" + reference + "]");
252    }
253    }
254   
255  0 checkRecursiveInclusion(parentBlock, reference);
256    }
257    }
258   
259    /**
260    * Indicate if the provided macro is an include macro with the provided included document.
261    *
262    * @param parentMacro the macro block to check
263    * @param completeReference the document reference to compare to
264    * @return true if the documents are the same
265    */
266    // TODO: Add support for any kind of macro including content linked to a reference
 
267  8727 toggle private boolean isRecursive(MacroMarkerBlock parentMacro, EntityReference completeReference)
268    {
269  8726 if (parentMacro.getId().equals("include")) {
270  4095 IncludeMacroParameters macroParameters = getParameters(parentMacro.getParameters());
271   
272  4095 return completeReference.equals(this.macroEntityReferenceResolver.resolve(macroParameters.getReference(),
273    macroParameters.getType(), parentMacro));
274    }
275   
276  4631 return false;
277    }
278   
 
279  4094 toggle private IncludeMacroParameters getParameters(Map<String, String> values)
280    {
281  4095 IncludeMacroParameters parameters = new IncludeMacroParameters();
282   
283  4096 try {
284  4095 this.beans.populate(parameters, values);
285    } catch (PropertyException e) {
286    // It should not not happen since it's a macro that was already executed
287    }
288   
289  4096 return parameters;
290    }
291   
 
292  12915 toggle private EntityReference resolve(MacroBlock block, IncludeMacroParameters parameters) throws MacroExecutionException
293    {
294  12918 String reference = parameters.getReference();
295   
296  12915 if (reference == null) {
297  0 throw new MacroExecutionException(
298    "You must specify a 'reference' parameter pointing to the entity to include.");
299    }
300   
301  12914 return this.macroEntityReferenceResolver.resolve(reference, parameters.getType(), block);
302    }
303    }