Clover icon

magnolia-module-groovy 2.4.7

  1. Project Clover database Thu Dec 1 2016 10:48:40 CET
  2. Package info.magnolia.module.groovy.console

File MgnlGroovyConsole.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart8.png
20% of files have more coverage

Code metrics

18
68
13
3
306
171
24
0.35
5.23
4.33
1.85

Classes

Class Line # Actions
MgnlGroovyConsole 76 56 0% 20 12
0.851851985.2%
MgnlGroovyConsole.ScriptCallback 214 0 - 0 0
-1.0 -
MgnlGroovyConsole.ScriptDonePollListener 272 12 0% 4 11
0.388888938.9%
 

Contributing tests

This file is covered by 8 tests. .

Source view

1    /**
2    * This file Copyright (c) 2003-2016 Magnolia International
3    * Ltd. (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10    * This file is distributed in the hope that it will be
11    * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12    * implied warranty of MERCHANTABILITY or FITNESS FOR A
13    * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14    * Redistribution, except as permitted by whichever of the GPL
15    * or MNA you select, is prohibited.
16    *
17    * 1. For the GPL license (GPL), you can redistribute and/or
18    * modify this file under the terms of the GNU General
19    * Public License, Version 3, as published by the Free Software
20    * Foundation. You should have received a copy of the GNU
21    * General Public License, Version 3 along with this program;
22    * if not, write to the Free Software Foundation, Inc., 51
23    * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24    *
25    * 2. For the Magnolia Network Agreement (MNA), this file
26    * and the accompanying materials are made available under the
27    * terms of the MNA which accompanies this distribution, and
28    * is available at http://www.magnolia-cms.com/mna.html
29    *
30    * Any modifications to this file must keep this entire header
31    * intact.
32    *
33    */
34    package info.magnolia.module.groovy.console;
35   
36    import info.magnolia.context.Context;
37    import info.magnolia.context.MgnlContext;
38    import info.magnolia.i18nsystem.SimpleTranslator;
39    import info.magnolia.module.groovy.support.classes.MgnlGroovyClassLoader;
40    import info.magnolia.objectfactory.Components;
41    import info.magnolia.ui.api.message.Message;
42    import info.magnolia.ui.api.message.MessageType;
43    import info.magnolia.ui.framework.message.MessagesManager;
44   
45    import java.io.ByteArrayInputStream;
46    import java.io.IOException;
47    import java.io.InputStream;
48    import java.io.InputStreamReader;
49    import java.io.StringWriter;
50    import java.io.Writer;
51    import java.util.Objects;
52    import java.util.concurrent.Callable;
53    import java.util.concurrent.ExecutionException;
54    import java.util.concurrent.ExecutorService;
55    import java.util.concurrent.Executors;
56    import java.util.concurrent.Future;
57   
58    import org.apache.commons.lang.CharEncoding;
59    import org.codehaus.groovy.control.CompilationFailedException;
60    import org.codehaus.groovy.runtime.InvokerHelper;
61    import org.slf4j.Logger;
62    import org.slf4j.LoggerFactory;
63   
64    import com.vaadin.event.UIEvents.PollEvent;
65    import com.vaadin.event.UIEvents.PollListener;
66    import com.vaadin.ui.UI;
67   
68    import groovy.lang.Binding;
69    import groovy.lang.GroovyCodeSource;
70    import groovy.lang.GroovyShell;
71    import groovy.lang.Script;
72   
73    /**
74    * Groovy console using {@link MgnlGroovyClassLoader} to parse Groovy source.
75    */
 
76    public class MgnlGroovyConsole extends GroovyShell {
77   
78    private static final Logger log = LoggerFactory.getLogger(MgnlGroovyConsole.class);
79   
80    private static final MgnlGroovyClassLoader GROOVY_CLASS_LOADER = new MgnlGroovyClassLoader();
81   
82    private static final int POLL_INTERVAL_MS = 1000;
83   
84    private MessagesManager messagesManager;
85   
86    private SimpleTranslator i18n;
87   
88    /**
89    * @deprecated since 2.4.6, please use {@link MgnlGroovyConsole#MgnlGroovyConsole(Binding, MessagesManager, SimpleTranslator))}
90    */
 
91  5 toggle public MgnlGroovyConsole(Binding binding) {
92  5 this(binding, Components.getComponent(MessagesManager.class), Components.getComponent(SimpleTranslator.class));
93    }
94   
 
95  8 toggle public MgnlGroovyConsole(Binding binding, MessagesManager messagesManager, SimpleTranslator simpleTranslator) {
96  8 super(binding);
97  8 this.messagesManager = messagesManager;
98  8 this.i18n = simpleTranslator;
99    }
100   
101    /**
102    * Creates and run a {@link Script} synchronously.
103    * {@link MgnlContext} is wrapped into a {@link MgnlGroovyConsoleContext} for the script running time
104    * and then set back to its original value.
105    *
106    * @deprecated since 2.4.6, please use {@link #runAsync(String, UI, ScriptCallback)}} instead.
107    */
 
108  3 toggle @Deprecated
109    public Object evaluate(InputStream in, String fileName, Writer out) throws CompilationFailedException {
110  3 Context originalCtx = MgnlContext.getInstance();
111  3 MgnlGroovyConsoleContext groovyCtx = new MgnlGroovyConsoleContext(originalCtx);
112  3 MgnlContext.setInstance(groovyCtx);
113   
114  3 Script script = null;
115  3 try {
116  3 script = createScript(in, out);
117  2 script.setProperty("ctx", MgnlContext.getInstance());
118  2 return script.run();
119    } catch (IOException e) {
120  0 throw new RuntimeException(e);
121    } finally {
122  3 if (script != null) {
123  2 InvokerHelper.removeClass(script.getClass());
124    }
125  3 MgnlContext.setInstance(originalCtx);
126    }
127    }
128   
129    /**
130    * Runs a {@link Script} asynchronously in a separate thread from the UI one.
131    * This prevents AdminCentral from being frozen until the script is done, in case of long running scripts.
132    * <p>
133    * {@link MgnlContext} is wrapped into a {@link MgnlGroovyConsoleContext} within the thread running the script.
134    *
135    * @throws IOException
136    * @throws InterruptedException
137    */
 
138  3 toggle public void runAsync(String source, UI ui, ScriptCallback callback) throws IOException, InterruptedException {
139  3 Writer out = new StringWriter();
140  3 final Script script = createScript(source, out);
141   
142  3 final Context originalCtx = MgnlContext.getInstance();
143  3 final MgnlGroovyConsoleContext groovyCtx = new MgnlGroovyConsoleContext(originalCtx);
144   
145  3 ExecutorService executorService = Executors.newSingleThreadExecutor();
146   
147  3 final Future<Object> task = executorService.submit(new Callable<Object>() {
 
148  3 toggle @Override
149    public Object call() throws Exception {
150    // this context is local to the thread running the script
151    // which is separate from the UI one, so no need to reset it.
152  3 MgnlContext.setInstance(groovyCtx);
153  3 script.setProperty("ctx", groovyCtx);
154  3 return script.run();
155    }
156    });
157   
158    // Allow a quick task to finish w/o starting polling. If done, just return.
159    // Otherwise users would likely get the impression of a sluggish console which needs one or two seconds
160    // just to return e.g. ctx.getJCRSession('website') or 1+1
161  3 Thread.sleep(200);
162  3 if (task.isDone()) {
163  2 handleScriptDone(callback, task, ui, out, messagesManager, i18n, false);
164  2 return;
165    }
166   
167    // if some other component already set a poll interval but it's less than ours, we go for the higher value
168  1 if (ui.getPollInterval() < POLL_INTERVAL_MS) {
169  1 ui.setPollInterval(POLL_INTERVAL_MS);
170    }
171  1 ui.addPollListener(new ScriptDonePollListener(task, ui, out, callback, messagesManager, i18n));
172    }
173   
 
174  3 toggle private Script createScript(String code, Writer out) throws CompilationFailedException, IOException {
175  3 return createScript(new ByteArrayInputStream(code.getBytes()), out);
176    }
177   
 
178  6 toggle private Script createScript(InputStream in, Writer out) throws CompilationFailedException, IOException {
179  6 Script script = parse(new InputStreamReader(in, CharEncoding.UTF_8), generateScriptName());
180  5 script.setProperty("out", out);
181  5 script.setProperty("err", out);
182  5 return script;
183    }
184   
185    /**
186    * Parses the given script and returns it ready to be run. When running in a secure environment
187    * (-Djava.security.manager) codeSource.getCodeSource() determines what policy grants should be given to the script.
188    *
189    * @param codeSource
190    * @return ready to run script
191    */
 
192  6 toggle @Override
193    public Script parse(final GroovyCodeSource codeSource) throws CompilationFailedException {
194  6 return InvokerHelper.createScript(parseClass(codeSource), getContext());
195    }
196   
 
197  6 toggle @Override
198    public String generateScriptName() {
199    // needed to expose it publicly (it's protected in parent class)
200  6 return super.generateScriptName();
201    }
202   
203    /**
204    * Parses the groovy code contained in codeSource and returns a java class.
205    */
 
206  6 toggle private Class parseClass(final GroovyCodeSource codeSource) throws CompilationFailedException {
207    // Don't cache scripts
208  6 return GROOVY_CLASS_LOADER.parseClass(codeSource, false);
209    }
210   
211    /**
212    * Callback to handle script progress and result asynchronously.
213    */
 
214    public interface ScriptCallback {
215    /**
216    * Called when the script is completed successfully.
217    *
218    * @param result the script outcome, including everything which has been printed to stdout during execution.
219    */
220    void onSuccess(String result);
221   
222    /**
223    * Called when the script is completed with an error.
224    *
225    * @param e the error occurred during script execution
226    */
227    void onFailure(Throwable e);
228   
229    /**
230    * Called when the script is running at each poll interval.
231    *
232    * @param out the script output thus far
233    */
234    void onProgress(Writer out);
235   
236    /**
237    * Called when the script is completed, either successfully or with an error.
238    */
239    boolean requiresNotificationMessageUponCompletion();
240    }
241   
 
242  2 toggle private static void handleScriptDone(ScriptCallback callback, final Future<Object> scriptTask, UI ui, Writer out, MessagesManager messagesManager, SimpleTranslator i18n, boolean sendNotificationMessage) {
243  2 try {
244  2 String resultAsString = scriptTask.get() == null ? "" : Objects.toString(scriptTask.get());
245  2 String completeOutput;
246  2 if (out.toString().isEmpty()) {
247  2 completeOutput = resultAsString;
248    } else {
249  0 completeOutput = out.toString().concat("\n").concat(resultAsString);
250    }
251   
252  2 log.debug("Script run successfully with result {}", completeOutput);
253  2 callback.onSuccess(completeOutput);
254  2 if (sendNotificationMessage) {
255  0 messagesManager.sendLocalMessage(new Message(MessageType.INFO, i18n.translate("groovy.script.console.done.success"), completeOutput));
256    }
257   
258    } catch (InterruptedException | ExecutionException e) {
259  0 log.error("An error occurred while running a Groovy script", e);
260  0 callback.onFailure(e);
261   
262  0 if (sendNotificationMessage) {
263  0 messagesManager.sendLocalMessage(new Message(MessageType.WARNING, i18n.translate("groovy.script.console.done.error"), e.getMessage()));
264    }
265    } finally {
266  2 log.debug("Stop polling");
267  2 ui.setPollInterval(-1);
268    }
269    }
270   
271    @SuppressWarnings("serial")
 
272    private static final class ScriptDonePollListener implements PollListener {
273    private Future<Object> scriptTask;
274    private UI ui;
275    private ScriptCallback callback;
276    private Writer out;
277    private MessagesManager messagesManager;
278    private SimpleTranslator i18n;
279   
 
280  1 toggle public ScriptDonePollListener(Future<Object> scriptTask, UI ui, Writer out, ScriptCallback callback, MessagesManager messagesManager, SimpleTranslator i18n) {
281  1 this.scriptTask = scriptTask;
282  1 this.ui = ui;
283  1 this.callback = callback;
284  1 this.out = out;
285  1 this.messagesManager = messagesManager;
286  1 this.i18n = i18n;
287    }
288   
 
289  0 toggle @Override
290    public void poll(PollEvent event) {
291  0 if (scriptTask.isDone()) {
292  0 handleScriptDone(callback, scriptTask, ui, out, messagesManager, i18n, callback.requiresNotificationMessageUponCompletion());
293    // we're done, we can remove ourselves from UI's PollListener(s)
294  0 ui.removePollListener(this);
295    } else {
296    // make sure we keep on polling until done.
297    // it may happen that a different component using polling ends before us and stops before we're done.
298    // if some other component already set a poll interval but it's less than ours, we go for the higher value
299  0 if (ui.getPollInterval() < POLL_INTERVAL_MS) {
300  0 ui.setPollInterval(POLL_INTERVAL_MS);
301    }
302  0 callback.onProgress(out);
303    }
304    }
305    }
306    }