1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.render

File DefaultVelocityManager.java

 

Coverage histogram

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

Code metrics

28
74
9
1
363
212
26
0.35
8.22
9
2.89

Classes

Class Line # Actions
DefaultVelocityManager 81 74 0% 26 13
0.882882988.3%
 

Contributing tests

This file is covered by 24 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 com.xpn.xwiki.render;
21   
22    import java.io.Reader;
23    import java.io.Writer;
24    import java.util.Arrays;
25    import java.util.HashMap;
26    import java.util.HashSet;
27    import java.util.List;
28    import java.util.Map;
29    import java.util.Properties;
30    import java.util.Set;
31   
32    import javax.inject.Inject;
33    import javax.inject.Provider;
34    import javax.inject.Singleton;
35    import javax.script.ScriptContext;
36   
37    import org.apache.commons.io.output.NullWriter;
38    import org.apache.velocity.VelocityContext;
39    import org.apache.velocity.runtime.RuntimeConstants;
40    import org.apache.velocity.runtime.RuntimeSingleton;
41    import org.slf4j.Logger;
42    import org.xwiki.component.annotation.Component;
43    import org.xwiki.component.phase.Initializable;
44    import org.xwiki.component.phase.InitializationException;
45    import org.xwiki.context.Execution;
46    import org.xwiki.context.ExecutionContext;
47    import org.xwiki.observation.EventListener;
48    import org.xwiki.observation.ObservationManager;
49    import org.xwiki.observation.event.Event;
50    import org.xwiki.script.ScriptContextManager;
51    import org.xwiki.security.authorization.AuthorExecutor;
52    import org.xwiki.skin.Skin;
53    import org.xwiki.skin.SkinManager;
54    import org.xwiki.template.Template;
55    import org.xwiki.template.TemplateManager;
56    import org.xwiki.template.event.TemplateDeletedEvent;
57    import org.xwiki.template.event.TemplateEvent;
58    import org.xwiki.template.event.TemplateUpdatedEvent;
59    import org.xwiki.velocity.VelocityConfiguration;
60    import org.xwiki.velocity.VelocityEngine;
61    import org.xwiki.velocity.VelocityFactory;
62    import org.xwiki.velocity.VelocityManager;
63    import org.xwiki.velocity.XWikiVelocityException;
64    import org.xwiki.velocity.XWikiWebappResourceLoader;
65    import org.xwiki.velocity.internal.VelocityExecutionContextInitializer;
66   
67    import com.xpn.xwiki.XWikiContext;
68    import com.xpn.xwiki.api.DeprecatedContext;
69   
70    /**
71    * Note: This class should be moved to the Velocity module. However this is not possible right now since we need to
72    * populate the Velocity Context with XWiki objects that are located in the Core (such as the XWiki object for example)
73    * and since the Core needs to call the Velocity module this would cause a circular dependency.
74    *
75    * @version $Id: 92a0b566350dccacf76f54fe0c8eacee2b7125f2 $
76    * @since 1.5M1
77    */
78    @Component
79    @Singleton
80    // TODO: refactor to move it in xwiki-commons, the dependencies on the model are actually quite minor
 
81    public class DefaultVelocityManager implements VelocityManager, Initializable
82    {
83    /**
84    * The name of the Velocity configuration property that specifies the ResourceLoader name that Velocity should use
85    * when locating templates.
86    */
87    private static final String RESOURCE_LOADER = "resource.loader";
88   
89    /**
90    * The name of the Velocity configuration property that specifies the ResourceLoader class to use to locate Velocity
91    * templates.
92    */
93    private static final String RESOURCE_LOADER_CLASS = "xwiki.resource.loader.class";
94   
95    private static final String VELOCITYENGINE_CACHEKEY_NAME = "velocity.engine.key";
96   
97    private static final List<Event> EVENTS =
98    Arrays.<Event>asList(new TemplateUpdatedEvent(), new TemplateDeletedEvent());
99   
100    /**
101    * Used to access the current {@link org.xwiki.context.ExecutionContext}.
102    */
103    @Inject
104    private Execution execution;
105   
106    /**
107    * Used to access the current {@link XWikiContext}.
108    */
109    @Inject
110    private Provider<XWikiContext> xcontextProvider;
111   
112    /**
113    * Used to get the current script context.
114    */
115    @Inject
116    private ScriptContextManager scriptContextManager;
117   
118    @Inject
119    private VelocityFactory velocityFactory;
120   
121    @Inject
122    private VelocityConfiguration velocityConfiguration;
123   
124    /**
125    * Accessing it trough {@link Provider} since {@link TemplateManager} depends on {@link VelocityManager}.
126    */
127    @Inject
128    private Provider<TemplateManager> templates;
129   
130    @Inject
131    private SkinManager skinManager;
132   
133    @Inject
134    private ObservationManager observation;
135   
136    @Inject
137    private AuthorExecutor authorExecutor;
138   
139    @Inject
140    private Logger logger;
141   
142    /**
143    * Binding that should stay on Velocity side only.
144    */
145    private final Set<String> reservedBindings = new HashSet<>();
146   
 
147  84 toggle @Override
148    public void initialize() throws InitializationException
149    {
150  84 this.observation.addListener(new EventListener()
151    {
 
152  0 toggle @Override
153    public void onEvent(Event event, Object source, Object data)
154    {
155  0 if (event instanceof TemplateEvent) {
156  0 TemplateEvent templateEvent = (TemplateEvent) event;
157   
158  0 DefaultVelocityManager.this.velocityFactory.removeVelocityEngine(templateEvent.getId());
159    }
160    }
161   
 
162  332 toggle @Override
163    public String getName()
164    {
165  332 return DefaultVelocityManager.class.getName();
166    }
167   
 
168  83 toggle @Override
169    public List<Event> getEvents()
170    {
171  83 return EVENTS;
172    }
173    });
174   
175    // Set reserved bindings
176   
177    // "context" is a reserved binding in JSR223 world
178  84 this.reservedBindings.add("context");
179   
180    // Macros directive
181  84 this.reservedBindings.add("macro");
182    // Foreach directive
183  84 this.reservedBindings.add("foreach");
184  84 this.reservedBindings.add(this.velocityConfiguration.getProperties().getProperty(RuntimeConstants.COUNTER_NAME,
185    RuntimeSingleton.getString(RuntimeConstants.COUNTER_NAME)));
186  84 this.reservedBindings.add(this.velocityConfiguration.getProperties().getProperty(RuntimeConstants.HAS_NEXT_NAME,
187    RuntimeSingleton.getString(RuntimeConstants.HAS_NEXT_NAME)));
188    // Evaluate directive
189  84 this.reservedBindings.add("evaluate");
190    // TryCatch directive
191  84 this.reservedBindings.add("exception");
192  84 this.reservedBindings.add("try");
193    // Default directive
194  84 this.reservedBindings.add("define");
195    // The name of the context variable used for the template-level scope
196  84 this.reservedBindings.add("template");
197    }
198   
 
199  109396 toggle @Override
200    public VelocityContext getVelocityContext()
201    {
202  109383 ScriptVelocityContext velocityContext;
203   
204    // Make sure the velocity context support ScriptContext synchronization
205  109380 VelocityContext currentVelocityContext = getCurrentVelocityContext();
206  109377 if (currentVelocityContext instanceof ScriptVelocityContext) {
207  97964 velocityContext = (ScriptVelocityContext) currentVelocityContext;
208    } else {
209  11413 velocityContext = new ScriptVelocityContext(currentVelocityContext, this.reservedBindings);
210  11404 this.execution.getContext().setProperty(VelocityExecutionContextInitializer.VELOCITY_CONTEXT_ID,
211    velocityContext);
212    }
213   
214    // Synchronize with ScriptContext
215  109376 ScriptContext scriptContext = this.scriptContextManager.getScriptContext();
216  109365 velocityContext.setScriptContext(scriptContext);
217   
218    // Velocity specific bindings
219  109371 XWikiContext xcontext = this.xcontextProvider.get();
220    // Add the "context" binding which is deprecated since 1.9.1.
221  109383 velocityContext.put("context", new DeprecatedContext(xcontext));
222   
223  109362 return velocityContext;
224    }
225   
 
226  117964 toggle @Override
227    public VelocityContext getCurrentVelocityContext()
228    {
229    // The Velocity Context is set in VelocityExecutionContextInitializer, when the XWiki Request is initialized
230    // so we are guaranteed it is defined when this method is called.
231  117971 return (VelocityContext) this.execution.getContext()
232    .getProperty(VelocityExecutionContextInitializer.VELOCITY_CONTEXT_ID);
233    }
234   
235    /**
236    * @return the key used to cache the Velocity Engines. We have one Velocity Engine per skin which has a macros.vm
237    * file on the filesystem. Right now we don't support macros.vm defined in custom skins in wiki pages.
238    */
 
239  161249 toggle private Template getVelocityEngineMacrosTemplate()
240    {
241  161253 Template template = null;
242  161254 Map<String, Template> templateCache = null;
243   
244  161250 Skin currentSkin = this.skinManager.getCurrentSkin(true);
245   
246    // Generating this key is very expensive so we cache it in the context
247  161277 ExecutionContext econtext = this.execution.getContext();
248  161276 if (econtext != null) {
249  161278 templateCache = (Map<String, Template>) econtext.getProperty(VELOCITYENGINE_CACHEKEY_NAME);
250  161278 if (templateCache == null) {
251  11017 templateCache = new HashMap<>();
252  11018 econtext.setProperty(VELOCITYENGINE_CACHEKEY_NAME, templateCache);
253    } else {
254  150255 template = templateCache.get(currentSkin.getId());
255    }
256    }
257   
258  161278 if (template == null) {
259  11089 template = this.templates.get().getTemplate("macros.vm");
260   
261  11085 if (templateCache != null) {
262  11075 templateCache.put(currentSkin.getId(), template);
263    }
264    }
265   
266  161267 return template;
267    }
268   
269    /**
270    * @return the Velocity Engine corresponding to the current execution context. More specifically returns the
271    * Velocity Engine for the current skin since each skin has its own Velocity Engine so that each skin can
272    * have global velocimacros defined
273    * @throws XWikiVelocityException in case of an error while creating a Velocity Engine
274    */
 
275  161262 toggle @Override
276    public VelocityEngine getVelocityEngine() throws XWikiVelocityException
277    {
278    // Note: For improved performance we cache the Velocity Engines in order not to
279    // recreate them all the time. The key we use is the location to the skin's macro.vm
280    // file since caching on the skin would create more Engines than needed (some skins
281    // don't have a macros.vm file and some skins inherit from others).
282   
283    // Create a Velocity context using the Velocity Manager associated to the current skin's
284    // macros.vm
285   
286    // Get the location of the skin's macros.vm file
287  161259 XWikiContext xcontext = this.xcontextProvider.get();
288   
289  161273 final Template template;
290  161271 if (xcontext != null && xcontext.getWiki() != null) {
291  161262 template = getVelocityEngineMacrosTemplate();
292    } else {
293  0 template = null;
294    }
295   
296  161259 String cacheKey = template != null ? template.getId() : "default";
297   
298    // Get the Velocity Engine to use
299  161257 VelocityEngine velocityEngine = this.velocityFactory.getVelocityEngine(cacheKey);
300  161260 if (velocityEngine == null) {
301    // Note 1: This block is synchronized to prevent threads from creating several instances of
302    // Velocity Engines (for the same skin).
303    // Note 2: We do this instead of marking the whole method as synchronized since it seems this method is
304    // called quite often and we would incur the synchronization penalty. Ideally the engine should be
305    // created only when a new skin is created and not be on the main execution path.
306  51 synchronized (this) {
307  51 velocityEngine = this.velocityFactory.getVelocityEngine(cacheKey);
308  51 if (velocityEngine == null) {
309    // Gather the global Velocity macros that we want to have. These are skin dependent.
310  48 Properties properties = new Properties();
311   
312    // If the user hasn't specified any custom Velocity Resource Loader to use, use the XWiki Resource
313    // Loader
314  48 if (!this.velocityConfiguration.getProperties().containsKey(RESOURCE_LOADER)) {
315  48 properties.setProperty(RESOURCE_LOADER, "xwiki");
316  48 properties.setProperty(RESOURCE_LOADER_CLASS, XWikiWebappResourceLoader.class.getName());
317    }
318   
319  48 if (xcontext != null && xcontext.getWiki() != null) {
320    // Note: if you don't want any template to be used set the property named
321    // xwiki.render.velocity.macrolist to an empty string value.
322  48 String macroList = xcontext.getWiki().Param("xwiki.render.velocity.macrolist");
323  48 if (macroList == null) {
324  32 macroList = "/templates/macros.vm";
325    }
326  48 properties.put(RuntimeConstants.VM_LIBRARY, macroList);
327    }
328  48 velocityEngine = this.velocityFactory.createVelocityEngine(cacheKey, properties);
329   
330  48 if (template != null) {
331    // Local macros template
332    // We execute it ourself to support any kind of template, Velocity only support resource
333    // template by default
334  32 try {
335  32 final VelocityEngine finalVelocityEngine = velocityEngine;
336   
337  32 this.authorExecutor.call(() -> {
338  32 finalVelocityEngine.evaluate(new VelocityContext(), NullWriter.NULL_WRITER, "",
339    template.getContent().getContent());
340   
341  32 return null;
342    }, template.getContent().getAuthorReference());
343    } catch (Exception e) {
344  0 this.logger.error("Failed to evaluate macros templates [{}]", template.getPath(), e);
345    }
346    }
347    }
348    }
349    }
350   
351  161266 return velocityEngine;
352    }
353   
 
354  88417 toggle @Override
355    public boolean evaluate(Writer out, String templateName, Reader source) throws XWikiVelocityException
356    {
357    // Get up to date Velocity context
358  88414 VelocityContext velocityContext = getVelocityContext();
359   
360    // Execute Velocity context
361  88412 return getVelocityEngine().evaluate(velocityContext, out, templateName, source);
362    }
363    }