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

File ScriptClassLoaderHandlerListener.java

 

Coverage histogram

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

Code metrics

16
42
7
1
222
122
18
0.43
6
7
2.57

Classes

Class Line # Actions
ScriptClassLoaderHandlerListener 57 42 0% 18 8
0.876923187.7%
 

Contributing tests

This file is covered by 94 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.rendering.internal.macro.script;
21   
22    import java.util.LinkedList;
23    import java.util.List;
24   
25    import javax.inject.Inject;
26    import javax.inject.Named;
27    import javax.inject.Singleton;
28   
29    import org.apache.commons.lang3.StringUtils;
30    import org.xwiki.classloader.ExtendedURLClassLoader;
31    import org.xwiki.component.annotation.Component;
32    import org.xwiki.context.Execution;
33    import org.xwiki.observation.EventListener;
34    import org.xwiki.observation.event.CancelableEvent;
35    import org.xwiki.observation.event.Event;
36    import org.xwiki.rendering.macro.MacroExecutionException;
37    import org.xwiki.rendering.macro.script.ScriptMacroParameters;
38    import org.xwiki.script.event.ScriptEvaluatedEvent;
39    import org.xwiki.script.event.ScriptEvaluatingEvent;
40    import org.xwiki.security.authorization.ContextualAuthorizationManager;
41    import org.xwiki.security.authorization.Right;
42   
43    /**
44    * Replaces the context class loader by a custom one that takes into account the "jars" Script parameter that allows
45    * to add jars that will be visible to the executing script.
46    *
47    * Listens to script evaluation events ({@link org.xwiki.script.event.ScriptEvaluatingEvent} and
48    * {@link org.xwiki.script.event.ScriptEvaluatedEvent}) to set the context class loader and to restore the original
49    * one.
50    *
51    * @version $Id: 1e4119f371e84ea8d8b1f783f644036be9bfff3e $
52    * @since 2.5M1
53    */
54    @Component
55    @Named("scriptmacroclassloader")
56    @Singleton
 
57    public class ScriptClassLoaderHandlerListener implements EventListener
58    {
59    /** Key used to store the original class loader in the Execution Context. */
60    private static final String EXECUTION_CONTEXT_ORIG_CLASSLOADER_KEY = "originalClassLoader";
61   
62    /** Key used to store the class loader used by scripts in the Execution Context, see {@link #execution}. */
63    private static final String EXECUTION_CONTEXT_CLASSLOADER_KEY = "scriptClassLoader";
64   
65    /** Key under which the jar params used for the last macro execution are cached in the Execution Context. */
66    private static final String EXECUTION_CONTEXT_JARPARAMS_KEY = "scriptJarParams";
67   
68    /** Used to check if programming rights is allowed. */
69    @Inject
70    private ContextualAuthorizationManager authorizationManager;
71   
72    /**
73    * Used to set the classLoader to be used by scripts across invocations. We save it in the Execution Context to be
74    * sure it's the same classLoader used.
75    */
76    @Inject
77    private Execution execution;
78   
79    /**
80    * Used to create a custom class loader that knows how to support JARs attached to wiki page.
81    */
82    @Inject
83    private AttachmentClassLoaderFactory attachmentClassLoaderFactory;
84   
 
85  664 toggle @Override
86    public String getName()
87    {
88  664 return "scriptmacroclassloader";
89    }
90   
 
91  146 toggle @Override
92    public List<Event> getEvents()
93    {
94  146 List<Event> events = new LinkedList<Event>();
95  146 events.add(new ScriptEvaluatingEvent());
96  146 events.add(new ScriptEvaluatedEvent());
97  146 return events;
98    }
99   
 
100  17492 toggle @Override
101    public void onEvent(Event event, Object source, Object data)
102    {
103  17492 if (!(data instanceof ScriptMacroParameters)) {
104  0 return;
105    }
106  17491 if (event instanceof ScriptEvaluatingEvent) {
107    // Set the context class loader to the script CL to ensure that any script engine using the context
108    // classloader will work just fine.
109    // Note: We must absolutely ensure that we always use the same context CL during the whole execution
110    // request since JSR223 script engines (for example) that create internal class loaders need to
111    // continue using these class loaders (where classes defined in scripts have been loaded for example).
112  8745 ScriptMacroParameters parameters = (ScriptMacroParameters) data;
113  8745 ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
114  8746 this.execution.getContext().setProperty(EXECUTION_CONTEXT_ORIG_CLASSLOADER_KEY, originalClassLoader);
115  8746 try {
116  8746 ClassLoader newClassLoader = getClassLoader(parameters.getJars(), originalClassLoader);
117  8746 Thread.currentThread().setContextClassLoader(newClassLoader);
118    } catch (Exception exception) {
119    // abort execution
120  0 ((CancelableEvent) event).cancel(exception.getMessage());
121    }
122  8746 } else if (event instanceof ScriptEvaluatedEvent) {
123    // Restore original class loader.
124  8746 ClassLoader originalClassLoader =
125    (ClassLoader) this.execution.getContext().getProperty(EXECUTION_CONTEXT_ORIG_CLASSLOADER_KEY);
126  8746 Thread.currentThread().setContextClassLoader(originalClassLoader);
127    }
128    }
129   
130    /**
131    * @param jarsParameterValue the value of the macro parameters used to pass extra URLs that should be in the
132    * execution class loader
133    * @param parent the parent classloader for the classloader to create (if it doesn't already exist)
134    * @return the class loader to use for executing the script
135    * @throws MacroExecutionException in case of an error in building the class loader
136    */
 
137  8745 toggle private ClassLoader getClassLoader(String jarsParameterValue, ClassLoader parent) throws MacroExecutionException
138    {
139  8745 try {
140  8745 return findClassLoader(jarsParameterValue, parent);
141    } catch (MacroExecutionException mee) {
142  0 throw mee;
143    } catch (Exception e) {
144  0 throw new MacroExecutionException("Failed to add JAR URLs to the current class loader for ["
145    + jarsParameterValue + "]", e);
146    }
147    }
148   
149    /**
150    * @param jarsParameterValue the value of the macro parameters used to pass extra URLs that should be in the
151    * execution class loader
152    * @param parent the parent classloader for the classloader to create (if it doesn't already exist)
153    * @return the class loader to use for executing the script
154    * @throws Exception in case of an error in building the class loader
155    */
 
156  8745 toggle private ClassLoader findClassLoader(String jarsParameterValue, ClassLoader parent) throws Exception
157    {
158    // We cache the Class Loader for improved performances and we check if the saved class loader had the same
159    // jar parameters value as the current execution. If not, we compute a new class loader.
160  8745 ExtendedURLClassLoader cl =
161    (ExtendedURLClassLoader) this.execution.getContext().getProperty(EXECUTION_CONTEXT_CLASSLOADER_KEY);
162   
163  8746 if (cl == null) {
164  6296 if (StringUtils.isNotEmpty(jarsParameterValue)) {
165  1 cl = createOrExtendClassLoader(true, jarsParameterValue, parent);
166    } else {
167  6295 cl = this.attachmentClassLoaderFactory.createAttachmentClassLoader("", parent);
168    }
169    } else {
170  2450 String cachedJarsParameterValue =
171    (String) this.execution.getContext().getProperty(EXECUTION_CONTEXT_JARPARAMS_KEY);
172  2450 if (cachedJarsParameterValue != jarsParameterValue) {
173  3 cl = createOrExtendClassLoader(false, jarsParameterValue, cl);
174    }
175    }
176  8746 this.execution.getContext().setProperty(EXECUTION_CONTEXT_CLASSLOADER_KEY, cl);
177   
178  8746 return cl;
179    }
180   
181    /**
182    * @param createNewClassLoader if true create a new classloader and if false extend an existing one with the passed
183    * additional jars
184    * @param jarsParameterValue the value of the macro parameters used to pass extra URLs that should be in the
185    * execution class loader
186    * @param classLoader the parent classloader for the classloader to create or the classloader to extend, depending
187    * on the value of the createNewClassLoader parameter
188    * @return the new classloader or the extended one
189    * @throws Exception in case of an error in building or extending the class loader
190    */
 
191  4 toggle private ExtendedURLClassLoader createOrExtendClassLoader(boolean createNewClassLoader, String jarsParameterValue,
192    ClassLoader classLoader) throws Exception
193    {
194  4 ExtendedURLClassLoader cl;
195  4 if (canHaveJarsParameters()) {
196  4 if (createNewClassLoader) {
197  1 cl = this.attachmentClassLoaderFactory.createAttachmentClassLoader(jarsParameterValue, classLoader);
198    } else {
199  3 cl = (ExtendedURLClassLoader) classLoader;
200  3 this.attachmentClassLoaderFactory.extendAttachmentClassLoader(jarsParameterValue, cl);
201    }
202  4 this.execution.getContext().setProperty(EXECUTION_CONTEXT_JARPARAMS_KEY, jarsParameterValue);
203    } else {
204  0 throw new MacroExecutionException(
205    "You cannot pass additional jars since you don't have programming rights");
206    }
207  4 return cl;
208    }
209   
210    /**
211    * Note that this method allows extending classes to override it to allow jars parameters to be used without
212    * programming rights for example or to use some other conditions.
213    *
214    * @return true if the user can use the macro parameter used to pass additional JARs to the class loader used to
215    * evaluate a script
216    */
 
217  4 toggle private boolean canHaveJarsParameters()
218    {
219  4 return this.authorizationManager.hasAccess(Right.PROGRAM);
220    }
221    }
222