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

File IncludeMacro.java

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

Coverage histogram

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

Code metrics

26
62
8
1
283
160
24
0.39
7.75
8
3

Classes

Class Line # Actions
IncludeMacro 58 62 0% 24 14
0.854166785.4%
 

Contributing tests

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