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

File DocumentContentDisplayer.java

 

Coverage histogram

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

Code metrics

22
60
11
1
367
190
33
0.55
5.45
11
3

Classes

Class Line # Actions
DocumentContentDisplayer 61 60 0% 33 16
0.82795782.8%
 

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.lang.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: d338f4f5c874665bd1db2d2909460572f59b8818 $
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  7320 toggle @Override
115    public XDOM display(DocumentModelBridge document, DocumentDisplayerParameters parameters)
116    {
117  7320 String nameSpace =
118  7320 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  7320 Map<Object, Object> xwikiContext = getXWikiContextMap();
126  7320 Object isInRenderingEngine = xwikiContext.put(IS_IN_RENDERING_ENGINE, true);
127   
128  7320 maybeOpenNameSpace(nameSpace, parameters.isTransformationContextIsolated(), isInRenderingEngine);
129   
130  7320 try {
131  7320 XDOM result =
132  7320 parameters.isExecutionContextIsolated() ? displayInIsolatedExecutionContext(document, nameSpace,
133    parameters) : display(document, nameSpace, parameters);
134  7320 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  7320 maybeCloseNameSpace(nameSpace, parameters.isTransformationContextIsolated(), isInRenderingEngine);
142   
143  7320 if (isInRenderingEngine != null) {
144  1208 xwikiContext.put(IS_IN_RENDERING_ENGINE, isInRenderingEngine);
145    } else {
146  6112 xwikiContext.remove(IS_IN_RENDERING_ENGINE);
147    }
148    }
149    }
150   
151    /**
152    * @return the XWiki context map
153    */
 
154  7320 toggle @SuppressWarnings("unchecked")
155    private Map<Object, Object> getXWikiContextMap()
156    {
157  7320 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  7320 toggle private void maybeOpenNameSpace(String nameSpace, boolean transformationContextIsolated, Object isInRenderingEngine)
168    {
169  7320 if (transformationContextIsolated && (isInRenderingEngine == null || isInRenderingEngine == Boolean.FALSE)) {
170  6001 try {
171    // Mark that we're starting to use a different Velocity macro name-space.
172  6001 velocityManager.getVelocityEngine().startedUsingMacroNamespace(nameSpace);
173  6000 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  7320 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  7320 if (transformationContextIsolated && (isInRenderingEngine == null || isInRenderingEngine == Boolean.FALSE)) {
197  6001 try {
198  6001 velocityManager.getVelocityEngine().stoppedUsingMacroNamespace(nameSpace);
199  6000 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  1 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  8 toggle private XDOM displayInIsolatedExecutionContext(DocumentModelBridge document, String nameSpace,
218    DocumentDisplayerParameters parameters)
219    {
220  8 Map<String, Object> backupObjects = new HashMap<String, Object>();
221  8 EntityReference currentWikiReference = this.modelContext.getCurrentEntityReference();
222  8 try {
223    // The following method call also clones the execution context.
224  8 documentAccessBridge.pushDocumentInContext(backupObjects, document);
225    // Make sure to synchronize the context wiki with the context document's wiki.
226  8 modelContext.setCurrentEntityReference(document.getDocumentReference().getWikiReference());
227  8 return display(document, nameSpace, parameters);
228    } catch (Exception e) {
229  0 throw new RuntimeException(e.getMessage(), e);
230    } finally {
231  8 documentAccessBridge.popDocumentFromContext(backupObjects);
232    // Also restore the context wiki.
233  8 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  7320 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  7320 XDOM content = getContent(document, parameters);
249   
250  7320 if (!parameters.isContentTransformed()) {
251  955 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  6364 content.getMetaData().addMetaData(MetaData.BASE,
257    defaultEntityReferenceSerializer.serialize(documentAccessBridge.getCurrentDocumentReference()));
258   
259  6365 TransformationContext txContext =
260    new TransformationContext(content, document.getSyntax(), parameters.isTransformationContextRestricted());
261  6364 txContext.setId(nameSpace);
262  6364 try {
263  6365 transformationManager.performTransformations(content, txContext);
264    } catch (Exception e) {
265  0 throw new RuntimeException(e);
266    }
267   
268  6365 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  7320 toggle private XDOM getContent(DocumentModelBridge document, final DocumentDisplayerParameters parameters)
279    {
280  7320 XDOM content = parameters.isContentTranslated() ? getTranslatedContent(document) : document.getXDOM();
281   
282  7320 if (parameters.getSectionId() != null) {
283  0 HeaderBlock headerBlock =
284    content.getFirstBlock(new CompositeBlockMatcher(new ClassBlockMatcher(HeaderBlock.class),
285    new BlockMatcher()
286    {
 
287  0 toggle @Override
288    public boolean match(Block block)
289    {
290  0 return ((HeaderBlock) block).getId().equals(parameters.getSectionId());
291    }
292    }), Block.Axes.DESCENDANT);
293  0 if (headerBlock == null) {
294  0 throw new RuntimeException("Cannot find section [" + parameters.getSectionId() + "] in document ["
295    + this.defaultEntityReferenceSerializer.serialize(document.getDocumentReference()) + "]");
296    } else {
297  0 content = new XDOM(headerBlock.getSection().getChildren(), content.getMetaData());
298    }
299    }
300   
301  7320 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  175 toggle private XDOM getTranslatedContent(DocumentModelBridge document)
314    {
315  175 try {
316  175 DocumentModelBridge translatedDocument = documentAccessBridge.getDocument(document.getDocumentReference());
317    // FIXME: This is not a reliable way to determine if the language of the given document matches the context
318    // language. For instance the given document can have "en" language set while the translated document
319    // returned by the document access bridge can have "" or "default" language set.
320  175 if (!document.getRealLanguage().equals(translatedDocument.getRealLanguage())) {
321    // The language of the given document doesn't match the context language. Use the translated content.
322  2 if (document.getSyntax().equals(translatedDocument.getSyntax())) {
323    // Use getXDOM() because it caches the XDOM.
324  0 return translatedDocument.getXDOM();
325    } else {
326    // If the translated document has a different syntax then we have to parse its content using the
327    // syntax of the given document.
328  2 return parseContent(translatedDocument.getContent(), document.getSyntax(),
329    document.getDocumentReference());
330    }
331    }
332    } catch (Exception e) {
333    // Use the content of the given document.
334    }
335  173 return document.getXDOM();
336    }
337   
338    /**
339    * Parses a string content.
340    *
341    * @param content the content to parse
342    * @param syntax the syntax of the given content
343    * @return the result of parsing the given content
344    */
 
345  2 toggle private XDOM parseContent(String content, Syntax syntax, DocumentReference source)
346    {
347  2 try {
348  2 return parser.parse(content, syntax, source);
349    } catch (Exception e) {
350  0 throw new RuntimeException(e);
351    }
352    }
353   
354    /**
355    * Makes the execution component available to the derived classes.
356    * <p>
357    * Note: The fact that this class uses the execution component is an implementation detail and derived classes
358    * shouldn't depend on it but it seems that {@code @Inject} annotation fails to inject the execution component in
359    * this class if a derived class redefines the same field.
360    *
361    * @return the execution component
362    */
 
363  0 toggle protected Execution getExecution()
364    {
365  0 return execution;
366    }
367    }