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

File AbstractDocumentTitleDisplayer.java

 

Coverage histogram

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

Code metrics

20
59
7
1
301
170
22
0.37
8.43
7
3.14

Classes

Class Line # Actions
AbstractDocumentTitleDisplayer 58 59 0% 22 9
0.8953488589.5%
 

Contributing tests

This file is covered by 16 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.io.StringReader;
23    import java.io.StringWriter;
24    import java.util.HashMap;
25    import java.util.Map;
26    import java.util.Stack;
27   
28    import javax.inject.Inject;
29    import javax.inject.Named;
30   
31    import org.apache.commons.lang3.StringUtils;
32    import org.slf4j.Logger;
33    import org.xwiki.bridge.DocumentAccessBridge;
34    import org.xwiki.bridge.DocumentModelBridge;
35    import org.xwiki.configuration.ConfigurationSource;
36    import org.xwiki.context.Execution;
37    import org.xwiki.model.EntityType;
38    import org.xwiki.model.ModelContext;
39    import org.xwiki.model.reference.DocumentReference;
40    import org.xwiki.model.reference.EntityReference;
41    import org.xwiki.model.reference.EntityReferenceProvider;
42    import org.xwiki.model.reference.EntityReferenceSerializer;
43    import org.xwiki.rendering.block.XDOM;
44    import org.xwiki.rendering.parser.ParseException;
45    import org.xwiki.rendering.parser.Parser;
46    import org.xwiki.rendering.util.ParserUtils;
47    import org.xwiki.security.authorization.AuthorizationManager;
48    import org.xwiki.security.authorization.Right;
49    import org.xwiki.velocity.VelocityEngine;
50    import org.xwiki.velocity.VelocityManager;
51   
52    /**
53    * Displays the title of a document.
54    *
55    * @version $Id: b09f692301d048a264a3c72d7d585ff997bf195b $
56    * @since 3.2M3
57    */
 
58    public abstract class AbstractDocumentTitleDisplayer implements DocumentDisplayer
59    {
60    /**
61    * The key used to store on the XWiki context map the stack of references to documents whose titles are currently
62    * being evaluated (in the current execution context). This stack is used to prevent infinite recursion, which can
63    * happen if the title displayer is called on the current document from the title field or from a script within the
64    * first content heading.
65    */
66    private static final String DOCUMENT_REFERENCE_STACK_KEY = "internal.displayer.title.documentReferenceStack";
67   
68    /**
69    * The object used for logging.
70    */
71    @Inject
72    private Logger logger;
73   
74    /**
75    * The component used to parse the rendered title into an XDOM.
76    */
77    @Inject
78    @Named("plain/1.0")
79    private Parser plainTextParser;
80   
81    /**
82    * The component used to get the Velocity Engine and the Velocity Context needed to evaluate the Velocity script
83    * from the document title.
84    */
85    @Inject
86    private VelocityManager velocityManager;
87   
88    /**
89    * The component used to get the current document reference.
90    */
91    @Inject
92    private DocumentAccessBridge documentAccessBridge;
93   
94    /**
95    * The component used to serialize entity references.
96    */
97    @Inject
98    private EntityReferenceSerializer<String> defaultEntityReferenceSerializer;
99   
100    /**
101    * Execution context handler, needed for accessing the XWiki context map.
102    */
103    @Inject
104    private Execution execution;
105   
106    @Inject
107    @Named("xwikicfg")
108    private ConfigurationSource xwikicfg;
109   
110    @Inject
111    private AuthorizationManager authorizationManager;
112   
113    /**
114    * Used to get the default document reference, which normally is used to represent the home page of a space.
115    *
116    * @see #getStaticTitle(DocumentModelBridge)
117    */
118    @Inject
119    private EntityReferenceProvider defaultEntityReferenceProvider;
120   
121    @Inject
122    private ModelContext modelContext;
123   
124    /**
125    * Used to emulate an in-line parsing.
126    */
127    private ParserUtils parserUtils = new ParserUtils();
128   
 
129  19891 toggle @Override
130    public XDOM display(DocumentModelBridge document, DocumentDisplayerParameters parameters)
131    {
132    // Protect against infinite recursion which can happen for instance if the document title displayer is called on
133    // the current document from the title field or from a script within the first content heading.
134  19889 Map<Object, Object> xwikiContext = getXWikiContextMap();
135  19890 @SuppressWarnings("unchecked")
136    Stack<DocumentReference> documentReferenceStack =
137    (Stack<DocumentReference>) xwikiContext.get(DOCUMENT_REFERENCE_STACK_KEY);
138  19891 if (documentReferenceStack == null) {
139  8232 documentReferenceStack = new Stack<DocumentReference>();
140  8232 xwikiContext.put(DOCUMENT_REFERENCE_STACK_KEY, documentReferenceStack);
141  11658 } else if (documentReferenceStack.contains(document.getDocumentReference())) {
142  0 logger.warn("Infinite recursion detected while displaying the title of [{}]. "
143    + "Using the document name as title.", document.getDocumentReference());
144  0 return getStaticTitle(document);
145    }
146   
147  19889 documentReferenceStack.push(document.getDocumentReference());
148  19890 try {
149  19890 return displayTitle(document, parameters);
150    } finally {
151  19886 documentReferenceStack.pop();
152    }
153    }
154   
 
155  19889 toggle private XDOM displayTitle(DocumentModelBridge document, DocumentDisplayerParameters parameters)
156    {
157    // 1. Try to use the title provided by the user.
158  19886 String rawTitle = document.getTitle();
159  19891 if (!StringUtils.isEmpty(rawTitle)) {
160  5087 try {
161  5087 String title = rawTitle;
162    // Evaluate the title only if the document has script rights, otherwise use the raw title.
163  5087 if (authorizationManager.hasAccess(Right.SCRIPT, document.getContentAuthorReference(),
164    document.getDocumentReference())) {
165  5031 title = evaluateTitle(rawTitle, document.getDocumentReference(), parameters);
166    }
167  5086 return parseTitle(title);
168    } catch (Exception e) {
169  1 logger.warn("Failed to interpret title of document [{}].", document.getDocumentReference(), e);
170    }
171    }
172   
173    // 2. Try to extract the title from the document content.
174  14805 if ("1".equals(this.xwikicfg.getProperty("xwiki.title.compatibility", "0"))) {
175  9 try {
176  9 XDOM title = extractTitleFromContent(document, parameters);
177  9 if (title != null) {
178  8 return title;
179    }
180    } catch (Exception e) {
181  0 logger.warn("Failed to extract title from content of document [{}].", document.getDocumentReference(),
182    e);
183    }
184    }
185   
186    // 3. The title was not specified or its evaluation failed. Use the document name as a fall-back.
187  14796 return getStaticTitle(document);
188    }
189   
190    /**
191    * Parses the given title as plain text and returns the generated XDOM.
192    *
193    * @param title the title to be parsed
194    * @return the XDOM generated from parsing the title as plain text
195    */
 
196  19878 toggle protected XDOM parseTitle(String title)
197    {
198  19878 try {
199  19880 XDOM xdom = plainTextParser.parse(new StringReader(title));
200  19881 parserUtils.removeTopLevelParagraph(xdom.getChildren());
201  19882 return xdom;
202    } catch (ParseException e) {
203  0 throw new RuntimeException(e);
204    }
205    }
206   
207    /**
208    * Evaluates the Velocity script from the specified title.
209    *
210    * @param title the title to evaluate
211    * @param documentReference a reference to the document whose title is evaluated
212    * @param parameters display parameters
213    * @return the result of evaluating the Velocity script from the given title
214    */
 
215  5031 toggle protected String evaluateTitle(String title, DocumentReference documentReference,
216    DocumentDisplayerParameters parameters)
217    {
218  5030 StringWriter writer = new StringWriter();
219  5029 String namespace =
220  5030 defaultEntityReferenceSerializer.serialize(parameters.isTransformationContextIsolated() ? documentReference
221    : documentAccessBridge.getCurrentDocumentReference());
222   
223    // Get the velocity engine
224  5033 VelocityEngine velocityEngine;
225  5033 try {
226  5032 velocityEngine = this.velocityManager.getVelocityEngine();
227    } catch (Exception e) {
228  0 throw new RuntimeException(e);
229    }
230   
231    // Execute Velocity code
232  5033 Map<String, Object> backupObjects = null;
233  5032 boolean canPop = false;
234  5033 EntityReference currentWikiReference = this.modelContext.getCurrentEntityReference();
235  5033 try {
236  5033 if (parameters.isExecutionContextIsolated()) {
237  4163 backupObjects = new HashMap<String, Object>();
238    // The following method call also clones the execution context.
239  4163 documentAccessBridge.pushDocumentInContext(backupObjects, documentReference);
240    // Pop the document from the context only if the push was successful!
241  4163 canPop = true;
242    // Make sure to synchronize the context wiki with the context document's wiki.
243  4163 modelContext.setCurrentEntityReference(documentReference.getWikiReference());
244    }
245  5033 velocityEngine
246    .evaluate(velocityManager.getVelocityContext(), writer, namespace, title);
247    } catch (Exception e) {
248  1 throw new RuntimeException(e);
249    } finally {
250  5033 if (canPop) {
251  4163 documentAccessBridge.popDocumentFromContext(backupObjects);
252    // Also restore the context wiki.
253  4163 this.modelContext.setCurrentEntityReference(currentWikiReference);
254    }
255    }
256  5031 return writer.toString();
257    }
258   
259    /**
260    * @return the XWiki context map
261    */
 
262  19889 toggle @SuppressWarnings("unchecked")
263    private Map<Object, Object> getXWikiContextMap()
264    {
265  19888 return (Map<Object, Object>) execution.getContext().getProperty("xwikicontext");
266    }
267   
268    /**
269    * Extracts the title from the document content.
270    *
271    * @param document the document to extract the title from
272    * @param parameters display parameters
273    * @return the title XDOM
274    * @deprecated since 7.0M1
275    */
276    @Deprecated
277    protected abstract XDOM extractTitleFromContent(DocumentModelBridge document,
278    DocumentDisplayerParameters parameters);
279   
280    /**
281    * @param document an XWiki document
282    * @return the title used as a fall-back when the dynamic title cannot be evaluated
283    */
 
284  14794 toggle private XDOM getStaticTitle(DocumentModelBridge document)
285    {
286  14795 String documentName = document.getDocumentReference().getName();
287  14797 if (defaultEntityReferenceProvider.getDefaultReference(EntityType.DOCUMENT).getName().equals(documentName)) {
288    // This document represents a space (it is the home page of a space). Use the space name instead.
289  7459 documentName = document.getDocumentReference().getParent().getName();
290    }
291  14797 return parseTitle(documentName);
292    }
293   
294    /**
295    * @return the object used for logging
296    */
 
297  0 toggle protected Logger getLogger()
298    {
299  0 return logger;
300    }
301    }