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

File DefaultVelocityEngine.java

 

Coverage histogram

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

Code metrics

38
82
14
1
340
209
38
0.46
5.86
14
2.71

Classes

Class Line # Actions
DefaultVelocityEngine 64 82 0% 38 12
0.910447891%
 

Contributing tests

This file is covered by 74 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.velocity.internal;
21   
22    import java.io.Reader;
23    import java.io.StringReader;
24    import java.io.Writer;
25    import java.util.Enumeration;
26    import java.util.Map;
27    import java.util.Properties;
28    import java.util.concurrent.ConcurrentHashMap;
29   
30    import javax.inject.Inject;
31   
32    import org.apache.commons.lang3.StringUtils;
33    import org.apache.velocity.context.Context;
34    import org.apache.velocity.context.InternalContextAdapterImpl;
35    import org.apache.velocity.runtime.RuntimeConstants;
36    import org.apache.velocity.runtime.RuntimeServices;
37    import org.apache.velocity.runtime.directive.Scope;
38    import org.apache.velocity.runtime.directive.StopCommand;
39    import org.apache.velocity.runtime.parser.node.SimpleNode;
40    import org.slf4j.Logger;
41    import org.xwiki.component.annotation.Component;
42    import org.xwiki.component.annotation.InstantiationStrategy;
43    import org.xwiki.component.descriptor.ComponentInstantiationStrategy;
44    import org.xwiki.component.manager.ComponentManager;
45    import org.xwiki.velocity.VelocityConfiguration;
46    import org.xwiki.velocity.VelocityContextFactory;
47    import org.xwiki.velocity.VelocityEngine;
48    import org.xwiki.velocity.XWikiVelocityException;
49    import org.xwiki.velocity.internal.log.AbstractSLF4JLogChute;
50    import org.xwiki.velocity.introspection.TryCatchDirective;
51   
52    /**
53    * Default implementation of the Velocity service which initializes the Velocity system using configuration values
54    * defined in the component's configuration. Note that the {@link #initialize} method has to be executed before any
55    * other method can be called.
56    * <p>
57    * This class implements {@link org.apache.velocity.runtime.log.LogChute} (through {@link AbstractSLF4JLogChute}) to
58    * access to {@link RuntimeServices}.
59    *
60    * @version $Id: 07d9d2bfe193207ee921334dec94215e0cae616a $
61    */
62    @Component
63    @InstantiationStrategy(ComponentInstantiationStrategy.PER_LOOKUP)
 
64    public class DefaultVelocityEngine extends AbstractSLF4JLogChute implements VelocityEngine
65    {
66    /**
67    * The name of the context variable used for the template-level scope.
68    */
69    private static final String TEMPLATE_SCOPE_NAME = "template";
70   
71    /**
72    * Used to set it as a Velocity Application Attribute so that Velocity extensions done by XWiki can use it to lookup
73    * other components.
74    */
75    @Inject
76    private ComponentManager componentManager;
77   
78    /**
79    * Velocity configuration to get the list of configured Velocity properties.
80    */
81    @Inject
82    private VelocityConfiguration velocityConfiguration;
83   
84    /**
85    * Used to create a new context whenever one isn't already provided to the
86    * {@link #evaluate(Context, Writer, String, Reader)} method.
87    */
88    @Inject
89    private VelocityContextFactory velocityContextFactory;
90   
91    /**
92    * The logger to use for logging.
93    */
94    @Inject
95    private Logger logger;
96   
97    /**
98    * The Velocity engine we're wrapping.
99    */
100    private org.apache.velocity.app.VelocityEngine engine;
101   
102    /**
103    * See the comment in {@link #init(org.apache.velocity.runtime.RuntimeServices)}.
104    */
105    private RuntimeServices rsvc;
106   
107    /** Counter for the number of active rendering processes using each namespace. */
108    private final Map<String, Integer> namespaceUsageCount = new ConcurrentHashMap<String, Integer>();
109   
 
110  110 toggle @Override
111    public void initialize(Properties overridingProperties) throws XWikiVelocityException
112    {
113  110 org.apache.velocity.app.VelocityEngine velocityEngine = new org.apache.velocity.app.VelocityEngine();
114   
115    // Add the Component Manager to allow Velocity extensions to lookup components.
116  110 velocityEngine.setApplicationAttribute(ComponentManager.class.getName(), this.componentManager);
117   
118    // Set up properties
119  110 initializeProperties(velocityEngine, this.velocityConfiguration.getProperties(), overridingProperties);
120   
121    // Set up directives
122  110 velocityEngine.loadDirective(TryCatchDirective.class.getName());
123   
124  110 try {
125  110 velocityEngine.init();
126    } catch (Exception e) {
127  0 throw new XWikiVelocityException("Cannot start the Velocity engine", e);
128    }
129   
130  110 this.engine = velocityEngine;
131    }
132   
133    /**
134    * @param velocityEngine the Velocity engine against which to initialize Velocity properties
135    * @param configurationProperties the Velocity properties coming from XWiki's configuration
136    * @param overridingProperties the Velocity properties that override the properties coming from XWiki's
137    * configuration
138    */
 
139  110 toggle private void initializeProperties(org.apache.velocity.app.VelocityEngine velocityEngine,
140    Properties configurationProperties, Properties overridingProperties)
141    {
142    // Avoid "unable to find resource 'VM_global_library.vm' in any resource loader." if no
143    // Velocimacro library is defined. This value is overriden below.
144  110 velocityEngine.setProperty("velocimacro.library", "");
145   
146  110 velocityEngine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, this);
147   
148    // Configure Velocity by passing the properties defined in this component's configuration
149  110 if (configurationProperties != null) {
150  1042 for (Enumeration<?> e = configurationProperties.propertyNames(); e.hasMoreElements();) {
151  932 String key = e.nextElement().toString();
152    // Only set a property if it's not overridden by one of the passed properties
153  932 if (!overridingProperties.containsKey(key)) {
154  916 String value = configurationProperties.getProperty(key);
155  916 velocityEngine.setProperty(key, value);
156  916 this.logger.debug("Setting property [{}] = [{}]", key, value);
157    }
158    }
159    }
160   
161    // Override the component's static properties with the ones passed in parameter
162  110 if (overridingProperties != null) {
163  308 for (Enumeration<?> e = overridingProperties.propertyNames(); e.hasMoreElements();) {
164  198 String key = e.nextElement().toString();
165  198 String value = overridingProperties.getProperty(key);
166  198 velocityEngine.setProperty(key, value);
167  198 this.logger.debug("Overriding property [{}] = [{}]", key, value);
168    }
169    }
170    }
171   
172    /**
173    * Restore the previous {@code $template} variable, if any, in the velocity context.
174    *
175    * @param ica the current velocity context
176    * @param currentTemplateScope the current Scope, from which to take the replaced variable
177    */
 
178  100973 toggle private void restoreTemplateScope(InternalContextAdapterImpl ica, Scope currentTemplateScope)
179    {
180  100964 if (currentTemplateScope.getParent() != null) {
181  90433 ica.put(TEMPLATE_SCOPE_NAME, currentTemplateScope.getParent());
182  10533 } else if (currentTemplateScope.getReplaced() != null) {
183  174 ica.put(TEMPLATE_SCOPE_NAME, currentTemplateScope.getReplaced());
184    } else {
185  10359 ica.remove(TEMPLATE_SCOPE_NAME);
186    }
187    }
188   
 
189  167739 toggle private String toThreadSafeNamespace(String namespace)
190    {
191  167739 return StringUtils.isNotEmpty(namespace) ? Thread.currentThread().getId() + ":" + namespace : namespace;
192    }
193   
 
194  11298 toggle @Override
195    public boolean evaluate(Context context, Writer out, String templateName, String source)
196    throws XWikiVelocityException
197    {
198  11298 return evaluate(context, out, templateName, new StringReader(source));
199    }
200   
 
201  100968 toggle @Override
202    public boolean evaluate(Context context, Writer out, String templateName, Reader source)
203    throws XWikiVelocityException
204    {
205    // Ensure that initialization has been called
206  100967 if (this.engine == null) {
207  0 throw new XWikiVelocityException("This Velocity Engine has not yet been initialized. "
208    + " You must call its initialize() method before you can use it.");
209    }
210   
211    // Velocity macros handling is all but thread safe. We try to make sure that the same namespace is not going to
212    // be manipulated by several threads at the same time
213  100969 String namespace = toThreadSafeNamespace(templateName);
214   
215    // We override the default implementation here. See #init(RuntimeServices)
216    // for explanations.
217  100961 try {
218  100966 if (StringUtils.isNotEmpty(namespace)) {
219  100929 startedUsingMacroNamespaceInternal(namespace);
220    }
221   
222  100975 return evaluateInternal(context, out, namespace, source);
223    } catch (StopCommand s) {
224    // Someone explicitly stopped the script with something like #stop. No reason to make a scene.
225  4 return true;
226    } catch (Exception e) {
227  1 throw new XWikiVelocityException("Failed to evaluate content with id [" + templateName + "]", e);
228    } finally {
229  100962 if (StringUtils.isNotEmpty(namespace)) {
230  100931 stoppedUsingMacroNamespaceInternal(namespace);
231    }
232    }
233    }
234   
 
235  100950 toggle private boolean evaluateInternal(Context context, Writer out, String namespace, Reader source) throws Exception
236    {
237    // The trick is done here: We use the signature that allows
238    // passing a boolean and we pass false, thus preventing Velocity
239    // from cleaning the namespace of its velocimacros even though the
240    // config property velocimacro.permissions.allow.inline.local.scope
241    // is set to true.
242  100955 SimpleNode nodeTree = this.rsvc.parse(source, namespace, false);
243   
244  100979 if (nodeTree != null) {
245  100979 InternalContextAdapterImpl ica =
246  100978 new InternalContextAdapterImpl(context != null ? context : this.velocityContextFactory.createContext());
247  100974 ica.pushCurrentTemplateName(namespace);
248  100966 boolean provideTemplateScope = this.rsvc.getBoolean("template.provide.scope.control", true);
249  100979 Object templateScopeMarker = new Object();
250  100978 Scope templateScope = null;
251  100978 if (provideTemplateScope) {
252  100979 Object previous = ica.get(TEMPLATE_SCOPE_NAME);
253  100977 templateScope = new Scope(templateScopeMarker, previous);
254  100976 templateScope.put("templateName", namespace);
255  100974 ica.put(TEMPLATE_SCOPE_NAME, templateScope);
256    }
257  100968 try {
258  100968 nodeTree.init(ica, this.rsvc);
259  100978 nodeTree.render(ica, out);
260    } catch (StopCommand stop) {
261    // Check if we're supposed to stop here or not:
262    // - stop if the template is breaking explicitly on the provided $template
263    // - or stop if this is the topmost evaluation
264  920 if (!stop.isFor(templateScopeMarker) && ica.getTemplateNameStack().length > 1) {
265  4 throw stop;
266    }
267    } finally {
268  100956 ica.popCurrentTemplateName();
269  100959 if (provideTemplateScope) {
270  100966 restoreTemplateScope(ica, templateScope);
271    }
272    }
273  100959 return true;
274    }
275   
276  0 return false;
277    }
278   
 
279  0 toggle @Override
280    public void clearMacroNamespace(String templateName)
281    {
282  0 this.rsvc.dumpVMNamespace(toThreadSafeNamespace(templateName));
283    }
284   
 
285  33386 toggle @Override
286    public void startedUsingMacroNamespace(String namespace)
287    {
288  33387 startedUsingMacroNamespaceInternal(toThreadSafeNamespace(namespace));
289    }
290   
 
291  134313 toggle private void startedUsingMacroNamespaceInternal(String namespace)
292    {
293  134294 Integer count = this.namespaceUsageCount.get(namespace);
294  134314 if (count == null) {
295  30866 count = Integer.valueOf(0);
296    }
297  134310 count = count + 1;
298  134317 this.namespaceUsageCount.put(namespace, count);
299    }
300   
 
301  33389 toggle @Override
302    public void stoppedUsingMacroNamespace(String namespace)
303    {
304  33389 stoppedUsingMacroNamespaceInternal(toThreadSafeNamespace(namespace));
305    }
306   
 
307  134325 toggle private void stoppedUsingMacroNamespaceInternal(String namespace)
308    {
309  134319 Integer count = this.namespaceUsageCount.get(namespace);
310  134321 if (count == null) {
311    // This shouldn't happen
312  2 this.logger.warn("Wrong usage count for namespace [{}]", namespace);
313  2 return;
314    }
315  134323 count = count - 1;
316  134316 if (count <= 0) {
317  30877 this.namespaceUsageCount.remove(namespace);
318  30880 this.rsvc.dumpVMNamespace(namespace);
319    } else {
320  103439 this.namespaceUsageCount.put(namespace, count);
321    }
322    }
323   
 
324  110 toggle @Override
325    public void init(RuntimeServices runtimeServices)
326    {
327    // We save the RuntimeServices instance in order to be able to override the
328    // VelocityEngine.evaluate() method. We need to do this so that it's possible
329    // to make macros included with #includeMacros() work even though we're using
330    // the Velocity setting:
331    // velocimacro.permissions.allow.inline.local.scope = true
332  110 this.rsvc = runtimeServices;
333    }
334   
 
335  270821 toggle @Override
336    public Logger getLogger()
337    {
338  270820 return this.logger;
339    }
340    }