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

File DocumentContentDisplayer.java

 

Coverage histogram

../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

22
60
11
1
368
191
33
0.55
5.45
11
3

Classes

Class Line # Actions
DocumentContentDisplayer 61 60 0% 33 7
0.924731292.5%
 

Contributing tests

This file is covered by 15 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.display.internal;
21   
22    import java.util.HashMap;
23    import java.util.Map;
24   
25    import javax.inject.Inject;
26    import javax.inject.Named;
27    import javax.inject.Singleton;
28   
29    import org.apache.commons.lang3.exception.ExceptionUtils;
30    import org.slf4j.Logger;
31    import org.xwiki.bridge.DocumentAccessBridge;
32    import org.xwiki.bridge.DocumentModelBridge;
33    import org.xwiki.component.annotation.Component;
34    import org.xwiki.context.Execution;
35    import org.xwiki.model.ModelContext;
36    import org.xwiki.model.reference.DocumentReference;
37    import org.xwiki.model.reference.EntityReference;
38    import org.xwiki.model.reference.EntityReferenceSerializer;
39    import org.xwiki.rendering.block.Block;
40    import org.xwiki.rendering.block.HeaderBlock;
41    import org.xwiki.rendering.block.XDOM;
42    import org.xwiki.rendering.block.match.BlockMatcher;
43    import org.xwiki.rendering.block.match.ClassBlockMatcher;
44    import org.xwiki.rendering.block.match.CompositeBlockMatcher;
45    import org.xwiki.rendering.listener.MetaData;
46    import org.xwiki.rendering.parser.ContentParser;
47    import org.xwiki.rendering.syntax.Syntax;
48    import org.xwiki.rendering.transformation.TransformationContext;
49    import org.xwiki.rendering.transformation.TransformationManager;
50    import org.xwiki.velocity.VelocityManager;
51   
52    /**
53    * Displays the content of a document.
54    *
55    * @version $Id: 3f280ab248f21c9b907309b288d47b7222cf778f $
56    * @since 3.2M3
57    */
58    @Component
59    @Named("content")
60    @Singleton
 
61    public class DocumentContentDisplayer implements DocumentDisplayer
62    {
63    /**
64    * The context property which indicates if the current code was called from a template (only Velocity execution) or
65    * from a wiki page (wiki syntax rendering).
66    */
67    private static final String IS_IN_RENDERING_ENGINE = "isInRenderingEngine";
68   
69    /**
70    * The object used for logging.
71    */
72    @Inject
73    private Logger logger;
74   
75    /**
76    * The component used to serialize entity references.
77    */
78    @Inject
79    private EntityReferenceSerializer<String> defaultEntityReferenceSerializer;
80   
81    /**
82    * Used to get the current document reference and to change the context document.
83    */
84    @Inject
85    private DocumentAccessBridge documentAccessBridge;
86   
87    /**
88    * Execution context handler, needed for accessing the XWiki context map.
89    */
90    @Inject
91    private Execution execution;
92   
93    /**
94    * The object used to access the Velocity engine.
95    */
96    @Inject
97    private VelocityManager velocityManager;
98   
99    /**
100    * The component used to execute the transformations on the displayed content.
101    */
102    @Inject
103    private TransformationManager transformationManager;
104   
105    @Inject
106    private ModelContext modelContext;
107   
108    /**
109    * Used to get a parser for a specific syntax.
110    */
111    @Inject
112    private ContentParser parser;
113   
 
114  40045 toggle @Override
115    public XDOM display(DocumentModelBridge document, DocumentDisplayerParameters parameters)
116    {
117  40046 String nameSpace =
118  40047 defaultEntityReferenceSerializer.serialize(parameters.isContentTransformed()
119    && parameters.isTransformationContextIsolated() ? document.getDocumentReference()
120    : documentAccessBridge.getCurrentDocumentReference());
121   
122    // This tells display() methods that we are inside the rendering engine and thus that they can return wiki
123    // syntax and not HTML syntax (which is needed when outside the rendering engine, i.e. when we're inside
124    // templates using only Velocity for example).
125  40051 Map<Object, Object> xwikiContext = getXWikiContextMap();
126  40051 Object isInRenderingEngine = xwikiContext.put(IS_IN_RENDERING_ENGINE, true);
127   
128  40046 maybeOpenNameSpace(nameSpace, parameters.isTransformationContextIsolated(), isInRenderingEngine);
129   
130  40043 try {
131  40049 XDOM result =
132  40049 parameters.isExecutionContextIsolated() ? displayInIsolatedExecutionContext(document, nameSpace,
133    parameters) : display(document, nameSpace, parameters);
134  40045 return result;
135    } finally {
136    // Since we configure Velocity to have local macros (i.e. macros visible only to the local context), since
137    // Velocity caches the velocity macros in a local cache (we use key which is the absolute document
138    // reference) and since documents can include other documents or panels, we need to make sure we empty the
139    // local Velocity macro cache at the end of the rendering for the document as otherwise the local Velocity
140    // macro caches will keep growing as users create new pages.
141  40049 maybeCloseNameSpace(nameSpace, parameters.isTransformationContextIsolated(), isInRenderingEngine);
142   
143  40046 if (isInRenderingEngine != null) {
144  15955 xwikiContext.put(IS_IN_RENDERING_ENGINE, isInRenderingEngine);
145    } else {
146  24096 xwikiContext.remove(IS_IN_RENDERING_ENGINE);
147    }
148    }
149    }
150   
151    /**
152    * @return the XWiki context map
153    */
 
154  40044 toggle @SuppressWarnings("unchecked")
155    private Map<Object, Object> getXWikiContextMap()
156    {
157  40049 return (Map<Object, Object>) execution.getContext().getProperty("xwikicontext");
158    }
159   
160    /**
161    * Opens the specified name-space if the transformation context is isolated and we are not in the rendering engine.
162    *
163    * @param nameSpace the name-space to open
164    * @param transformationContextIsolated whether the transformation context is isolated
165    * @param isInRenderingEngine whether we are in the rendering engine
166    */
 
167  40048 toggle private void maybeOpenNameSpace(String nameSpace, boolean transformationContextIsolated, Object isInRenderingEngine)
168    {
169  40045 if (transformationContextIsolated && (isInRenderingEngine == null || isInRenderingEngine == Boolean.FALSE)) {
170  23280 try {
171    // Mark that we're starting to use a different Velocity macro name-space.
172  23283 velocityManager.getVelocityEngine().startedUsingMacroNamespace(nameSpace);
173  23279 logger.debug("Started using velocity macro namespace [{}].", nameSpace);
174    } catch (Exception e) {
175    // Failed to get the Velocity Engine and thus to clear Velocity Macro cache. Log this as a warning but
176    // continue since it's not absolutely critical.
177  1 logger.warn("Failed to notify Velocity Macro cache for opening the [{}] namespace. Reason = [{}]",
178    nameSpace, ExceptionUtils.getRootCauseMessage(e));
179    }
180    }
181    }
182   
183    /**
184    * Closes the specified name-space if the transformation context is isolated and we are not in the rendering engine.
185    *
186    * @param nameSpace the name-space to close
187    * @param transformationContextIsolated whether the transformation context is isolated
188    * @param isInRenderingEngine whether we are in the rendering engine
189    */
 
190  40045 toggle private void maybeCloseNameSpace(String nameSpace, boolean transformationContextIsolated,
191    Object isInRenderingEngine)
192    {
193    // Note that we check if we are in the rendering engine as this cleanup must be done only once after the
194    // document has been displayed but the display method can be called recursively. We know it's the initial entry
195    // point when isInRenderingEngine is false.
196  40045 if (transformationContextIsolated && (isInRenderingEngine == null || isInRenderingEngine == Boolean.FALSE)) {
197  23282 try {
198  23283 velocityManager.getVelocityEngine().stoppedUsingMacroNamespace(nameSpace);
199  23281 logger.debug("Stopped using velocity macro namespace [{}].", nameSpace);
200    } catch (Exception e) {
201    // Failed to get the Velocity Engine and thus to clear Velocity Macro cache. Log this as a warning but
202    // continue since it's not absolutely critical.
203  2 logger.warn("Failed to notify Velocity Macro cache for closing the [{}] namespace. Reason = [{}]",
204    nameSpace, e.getMessage());
205    }
206    }
207    }
208   
209    /**
210    * Displays the given document in a new execution context.
211    *
212    * @param document the document to display; this document is also put on the new execution context
213    * @param nameSpace the name-space to be used when performing the display transformations
214    * @param parameters the display parameters
215    * @return the result of displaying the given document
216    */
 
217  186 toggle private XDOM displayInIsolatedExecutionContext(DocumentModelBridge document, String nameSpace,
218    DocumentDisplayerParameters parameters)
219    {
220  186 Map<String, Object> backupObjects = new HashMap<>();
221  186 EntityReference currentWikiReference = this.modelContext.getCurrentEntityReference();
222  186 try {
223    // The following method call also clones the execution context.
224  186 documentAccessBridge.pushDocumentInContext(backupObjects, document);
225    // Make sure to synchronize the context wiki with the context document's wiki.
226  186 modelContext.setCurrentEntityReference(document.getDocumentReference().getWikiReference());
227  186 return display(document, nameSpace, parameters);
228    } catch (Exception e) {
229  0 throw new RuntimeException(e.getMessage(), e);
230    } finally {
231  186 documentAccessBridge.popDocumentFromContext(backupObjects);
232    // Also restore the context wiki.
233  186 this.modelContext.setCurrentEntityReference(currentWikiReference);
234    }
235    }
236   
237    /**
238    * Displays the given document in the current execution context.
239    *
240    * @param document the document to display
241    * @param nameSpace the name-space to be used when performing the display transformations
242    * @param parameters the display parameters
243    * @return the result of displaying the given document
244    */
 
245  40042 toggle protected XDOM display(DocumentModelBridge document, String nameSpace, DocumentDisplayerParameters parameters)
246    {
247    // This is a clone of the cached content that can be safely modified.
248  40043 XDOM content = getContent(document, parameters);
249   
250  40039 if (!parameters.isContentTransformed()) {
251  12760 return content;
252    }
253   
254    // Before executing the XDOM transformations make sure the references used by them (e.g. the 'reference'
255    // parameter of the Include macro) are resolved relative to the current document on the execution context.
256  27284 content.getMetaData().addMetaData(MetaData.BASE,
257    defaultEntityReferenceSerializer.serialize(documentAccessBridge.getCurrentDocumentReference()));
258   
259  27281 TransformationContext txContext =
260    new TransformationContext(content, document.getSyntax(), parameters.isTransformationContextRestricted());
261  27281 txContext.setId(nameSpace);
262  27282 try {
263  27284 transformationManager.performTransformations(content, txContext);
264    } catch (Exception e) {
265  0 throw new RuntimeException(e);
266    }
267   
268  27285 return content;
269    }
270   
271    /**
272    * Get the content to display (either the entire document content or the content of a specific section).
273    *
274    * @param document the source document
275    * @param parameters the display parameters
276    * @return the content as an XDOM tree
277    */
 
278  40044 toggle private XDOM getContent(DocumentModelBridge document, final DocumentDisplayerParameters parameters)
279    {
280  40046 XDOM content = parameters.isContentTranslated() ? getTranslatedContent(document) : document.getXDOM();
281   
282  40045 if (parameters.getSectionId() != null) {
283  273 HeaderBlock headerBlock =
284    content.getFirstBlock(new CompositeBlockMatcher(new ClassBlockMatcher(HeaderBlock.class),
285    new BlockMatcher()
286    {
 
287  1196 toggle @Override
288    public boolean match(Block block)
289    {
290  1196 return ((HeaderBlock) block).getId().equals(parameters.getSectionId());
291    }
292    }), Block.Axes.DESCENDANT);
293  273 if (headerBlock == null) {
294  0 throw new RuntimeException("Cannot find section [" + parameters.getSectionId() + "] in document ["
295    + this.defaultEntityReferenceSerializer.serialize(document.getDocumentReference()) + "]");
296    } else {
297  273 content = new XDOM(headerBlock.getSection().getChildren(), content.getMetaData());
298    }
299    }
300   
301  40040 return content;
302    }
303   
304    /**
305    * Get the translated content of the given document as XDOM tree. If the language of the given document matches the
306    * context language (meaning that the given document is the current translation) then we use the content of the
307    * given document (including the content changes that could have been made prior to calling this method). Otherwise
308    * we load the current translation from the database/cache and use its content.
309    *
310    * @param document the source document
311    * @return the translated content of the given document, as XDOM tree
312    */
 
313  13341 toggle private XDOM getTranslatedContent(DocumentModelBridge document)
314    {
315  13342 try {
316  13344 DocumentModelBridge translatedDocument =
317    documentAccessBridge.getTranslatedDocumentInstance(document.getDocumentReference());
318    // FIXME: This is not a reliable way to determine if the language of the given document matches the context
319    // language. For instance the given document can have "en" language set while the translated document
320    // returned by the document access bridge can have "" or "default" language set.
321  13341 if (!document.getRealLanguage().equals(translatedDocument.getRealLanguage())) {
322    // The language of the given document doesn't match the context language. Use the translated content.
323  11 if (document.getSyntax().equals(translatedDocument.getSyntax())) {
324    // Use getXDOM() because it caches the XDOM.
325  9 return translatedDocument.getXDOM();
326    } else {
327    // If the translated document has a different syntax then we have to parse its content using the
328    // syntax of the given document.
329  2 return parseContent(translatedDocument.getContent(), document.getSyntax(),
330    document.getDocumentReference());
331    }
332    }
333    } catch (Exception e) {
334    // Use the content of the given document.
335    }
336  13332 return document.getXDOM();
337    }
338   
339    /**
340    * Parses a string content.
341    *
342    * @param content the content to parse
343    * @param syntax the syntax of the given content
344    * @return the result of parsing the given content
345    */
 
346  2 toggle private XDOM parseContent(String content, Syntax syntax, DocumentReference source)
347    {
348  2 try {
349  2 return parser.parse(content, syntax, source);
350    } catch (Exception e) {
351  0 throw new RuntimeException(e);
352    }
353    }
354   
355    /**
356    * Makes the execution component available to the derived classes.
357    * <p>
358    * Note: The fact that this class uses the execution component is an implementation detail and derived classes
359    * shouldn't depend on it but it seems that {@code @Inject} annotation fails to inject the execution component in
360    * this class if a derived class redefines the same field.
361    *
362    * @return the execution component
363    */
 
364  0 toggle protected Execution getExecution()
365    {
366  0 return execution;
367    }
368    }