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.terminal

File Terminal.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart2.png
44% of files have more coverage

Code metrics

30
116
20
2
366
263
38
0.33
5.8
10
1.9
3.5% of code in this file is excluded from these metrics.

Classes

Class Line # Actions
Terminal 90 116 3.5% 38 139
0.162650616.3%
Terminal.SerializableBinding 101 0 - 0 0
-1.0 -
 

Contributing tests

This file is covered by 2 tests. .

Source view

1    /**
2    * This file Copyright (c) 2013-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.terminal;
35   
36    import info.magnolia.cms.security.User;
37    import info.magnolia.context.Context;
38    import info.magnolia.context.MgnlContext;
39    import info.magnolia.context.MgnlContext.Op;
40    import info.magnolia.i18nsystem.SimpleTranslator;
41    import info.magnolia.jcr.util.PropertyUtil;
42    import info.magnolia.jcr.util.SessionUtil;
43    import info.magnolia.module.groovy.console.MgnlGroovyConsole;
44    import info.magnolia.module.groovy.console.MgnlGroovyConsole.ScriptCallback;
45    import info.magnolia.module.groovy.console.MgnlGroovyConsoleContext;
46    import info.magnolia.objectfactory.Components;
47    import info.magnolia.ui.framework.message.MessagesManager;
48   
49    import java.io.IOException;
50    import java.io.Serializable;
51    import java.io.StringWriter;
52    import java.io.Writer;
53    import java.lang.reflect.Method;
54    import java.nio.file.Files;
55    import java.nio.file.Paths;
56    import java.util.Arrays;
57    import java.util.Collection;
58    import java.util.HashMap;
59    import java.util.HashSet;
60    import java.util.List;
61    import java.util.Map;
62    import java.util.Map.Entry;
63    import java.util.Set;
64   
65    import javax.jcr.Node;
66   
67    import org.apache.commons.lang.StringUtils;
68    import org.codehaus.groovy.control.CompilationFailedException;
69    import org.slf4j.Logger;
70    import org.slf4j.LoggerFactory;
71   
72    import com.google.gson.Gson;
73    import com.vaadin.annotations.JavaScript;
74    import com.vaadin.annotations.StyleSheet;
75    import com.vaadin.ui.AbstractJavaScriptComponent;
76    import com.vaadin.ui.JavaScriptFunction;
77    import com.vaadin.ui.UI;
78   
79    import elemental.json.JsonArray;
80    import groovy.lang.Binding;
81    import groovy.lang.GroovyObject;
82    import groovy.lang.GroovySystem;
83    import groovy.lang.MetaMethod;
84   
85    /**
86    * Wraps JQuery Terminal Emulator plugin (http://terminal.jcubic.pl) as Vaadin component. Loading jQuery from Google apis is needed when using the terminal component in the {@link info.magnolia.module.groovy.rescue.MgnlGroovyRescueApp} where jQuery can't be provided by Magnolia 5 framework.
87    */
88    @JavaScript({ "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js", "jquery.terminal-0.7.12.custom.min.js", "terminal_connector.js" })
89    @StyleSheet("jquery.terminal.custom.css")
 
90    public class Terminal extends AbstractJavaScriptComponent {
91   
92    public static final String BINDING_SESSION_ATTRIBUTE = "info.magnolia.module.groovy.console.binding.session.attribute";
93   
94    private final Gson gson = new Gson();
95   
96    /**
97    * SerializableBinding. Need this otherwise adding Binding to the session fails with
98    * java.lang.IllegalArgumentException: setAttribute: Non-serializable attribute
99    * at org.apache.catalina.session.StandardSession.setAttribute(..)
100    */
 
101    public static final class SerializableBinding extends Binding implements Serializable {
102    public static final long serialVersionUID = 42L;
103    }
104   
105    private static final Logger log = LoggerFactory.getLogger(Terminal.class);
106    private static final List<String> METHODS_BLACK_LIST = Arrays.asList(new String[] { "clone", "equals", "finalize", "hashCode", "getClass", "notify", "notifyAll", "toString", "wait" });
107    private static final List<String> PREDEFINED_VARS = Arrays.asList(new String[] { "clean", "clear", "ctx", "err", "help", "out", "run" });
108   
109    private SimpleTranslator simpleTranslator;
110    private boolean useSystemContext;
111    private Map<String, Set<String>> suggestions = new HashMap<String, Set<String>>();
112    private boolean requiresNotificationMessageUponCompletion;
113    private MessagesManager messagesManager;
114   
115    /**
116    * @param useSystemContext if <code>true</code> will run {@link #execute(String)} method in system context (needed especially by {@link info.magnolia.module.groovy.rescue.MgnlGroovyRescueApp}).
117    * <p>
118    */
 
119  2 toggle public Terminal(final SimpleTranslator simpleTranslator, final boolean useSystemContext, final boolean requiresNotificationMessageUponCompletion, final MessagesManager messagesManager) {
120  2 this.simpleTranslator = simpleTranslator;
121  2 this.useSystemContext = useSystemContext;
122  2 this.messagesManager = messagesManager;
123  2 this.getState().greetings = simpleTranslator.translate("console.greetings", GroovySystem.getVersion());
124  2 this.requiresNotificationMessageUponCompletion = requiresNotificationMessageUponCompletion;
125   
126  2 setPredefinedVariables();
127    // ctx object is available when first starting the terminal, therefore we set its suggestions now
128    // so that they're available without first submitting a command.
129  2 createSuggestionsPerClass("ctx", new MgnlGroovyConsoleContext(MgnlContext.getInstance()));
130   
131  2 this.getState().suggestions = gson.toJson(suggestions);
132   
133  2 setSizeFull();
134   
135   
136  2 addFunction("executeCommand", new JavaScriptFunction() {
 
137  0 toggle @Override
138    public void call(JsonArray arguments) {
139  0 getState().command = arguments.getString(0);
140  0 resetState();
141  0 try {
142  0 if (isUseSystemContext()) {
143  0 MgnlContext.doInSystemContext(new Op<Void, Exception>() {
144   
 
145  0 toggle @Override
146    public Void exec() throws Exception {
147  0 execute(getCommand());
148  0 return null;
149    }
150    });
151    } else {
152  0 execute(getCommand());
153    }
154    } catch (Exception e) {
155  0 getState().output = e.getMessage();
156    }
157    }
158    });
159   
160  2 addFunction("saveStatus", new JavaScriptFunction() {
 
161  0 toggle @Override
162    public void call(JsonArray arguments) {
163  0 resetState();
164  0 getState().view = arguments.get(0).toJson();
165  0 getState().history = arguments.get(1).toJson();
166  0 log.debug("saved status [view:{}, history{}]", getState().view, getState().history);
167    }
168    });
169    }
170   
171    /**
172    * @deprecated since 2.4.6, please use {@link Terminal#Terminal(SimpleTranslator, boolean, boolean, MessagesManager)}
173    */
 
174  0 toggle @Deprecated
175    public Terminal(final SimpleTranslator simpleTranslator, final boolean useSystemContext) {
176  0 this(simpleTranslator, useSystemContext, true, Components.getComponent(MessagesManager.class));
177    }
178   
 
179    toggle public boolean isUseSystemContext() {
180    return useSystemContext;
181    }
182   
 
183    toggle public String getCommand() {
184    return getState().command;
185    }
186   
 
187    toggle @Override
188    public TerminalState getState() {
189    return (TerminalState) super.getState();
190    }
191   
 
192  0 toggle public void execute(String command) throws Exception {
193  0 User currentUser = MgnlContext.getUser();
194  0 if (!isAuthorized(currentUser)) {
195  0 String msg = simpleTranslator.translate("console.user.unauthorized", currentUser.getName());
196  0 log.warn(msg);
197  0 getState().output = msg;
198  0 return;
199    }
200   
201  0 log.debug("executing command [{}]", command);
202  0 StringWriter sw = new StringWriter();
203   
204  0 if (handleDefaultCommand(command, sw)) {
205  0 getState().output = sw.toString();
206    } else {
207  0 getState().output = simpleTranslator.translate("groovy.script.consoleOutput.run.wait");
208  0 runAsync(command);
209    }
210    }
211   
 
212  0 toggle private void runAsync(final String command) throws CompilationFailedException, IOException, InterruptedException {
213  0 final MgnlGroovyConsole console = getOrCreateMgnlGroovyConsole();
214  0 console.runAsync(command, UI.getCurrent(), new ScriptCallback() {
 
215  0 toggle @Override
216    public void onSuccess(String result) {
217  0 getState().output = result;
218  0 getState().inProgress = false;
219    // update suggestions
220  0 Binding context = console.getContext();
221  0 updateCodeSuggestions(context);
222  0 updateBindingSuggestions(context);
223    }
224   
 
225  0 toggle @Override
226    public void onFailure(Throwable e) {
227  0 getState().output = e.getMessage();
228  0 getState().inProgress = false;
229    }
230   
 
231  0 toggle @Override
232    public void onProgress(Writer out) {
233  0 getState().inProgress = true;
234    }
235   
 
236  0 toggle @Override
237    public boolean requiresNotificationMessageUponCompletion() {
238  0 return requiresNotificationMessageUponCompletion;
239    }
240    });
241    }
242   
243    /**
244    * @return <code>true</code> if the command could be processed, <code>false</code> otherwise.
245    * @throws Exception
246    */
 
247  0 toggle private boolean handleDefaultCommand(final String command, final StringWriter out) throws Exception {
248  0 if (StringUtils.isEmpty(command)) {
249    // just ignore it
250  0 return true;
251    }
252   
253  0 String[] tokens = command.trim().split("\\s+");
254   
255  0 if ("help".equals(tokens[0]) || "?".equals(tokens[0])) {
256  0 out.write(simpleTranslator.translate("console.help"));
257  0 return true;
258  0 } else if ("clear".equals(tokens[0])) {
259  0 return true;
260  0 } else if ("clean".equals(tokens[0])) {
261  0 MgnlContext.setAttribute(BINDING_SESSION_ATTRIBUTE, new SerializableBinding(), Context.SESSION_SCOPE);
262  0 setPredefinedVariables();
263  0 return true;
264  0 } else if ("run".equals(tokens[0])) {
265  0 out.write(simpleTranslator.translate("groovy.script.consoleOutput.run.wait"));
266  0 return runScript(tokens, out);
267    }
268  0 return false;
269    }
270   
 
271  0 toggle private boolean runScript(final String[] tokens, final StringWriter out) throws Exception {
272  0 if (tokens.length != 2) {
273  0 out.write(simpleTranslator.translate("console.commands.run.usage"));
274  0 return true;
275    }
276   
277  0 String source = null;
278  0 String path = tokens[1];
279    // first try to locate the script in the scripts workspace
280  0 Node scriptNode = SessionUtil.getNode("scripts", path);
281  0 if (scriptNode != null) {
282  0 source = PropertyUtil.getString(scriptNode, "text", "");
283    } else {
284    // try with the file system
285  0 try {
286  0 byte[] bytes = Files.readAllBytes(Paths.get(path));
287  0 source = new String(bytes);
288    } catch (IOException e) {
289    // ignore here
290    }
291    }
292  0 if (source == null) {
293  0 out.write(simpleTranslator.translate("console.commands.run.notFound", path) + "\n");
294  0 out.write(simpleTranslator.translate("console.commands.run.usage"));
295    } else {
296  0 runAsync(source);
297    }
298  0 return true;
299    }
300   
 
301  0 toggle private void resetState() {
302  0 getState().output = "";
303  0 getState().view = "";
304  0 getState().history = "";
305  0 getState().inProgress = false;
306    }
307   
 
308  1 toggle protected boolean isAuthorized(User currentUser) {
309  1 final Collection<String> roles = currentUser.getRoles();
310  1 return roles.contains("superuser") || roles.contains("scripter");
311    }
312   
 
313  0 toggle private MgnlGroovyConsole getOrCreateMgnlGroovyConsole() {
314  0 Binding binding = MgnlContext.getAttribute(BINDING_SESSION_ATTRIBUTE, Context.SESSION_SCOPE);
315  0 MgnlGroovyConsole console = null;
316  0 if (binding != null) {
317  0 console = new MgnlGroovyConsole(binding, messagesManager, simpleTranslator);
318    } else {
319  0 Binding newBinding = new SerializableBinding();
320  0 MgnlContext.setAttribute(BINDING_SESSION_ATTRIBUTE, newBinding, Context.SESSION_SCOPE);
321  0 console = new MgnlGroovyConsole(newBinding, messagesManager, simpleTranslator);
322    }
323  0 return console;
324    }
325   
 
326  2 toggle private void setPredefinedVariables() {
327  2 this.getState().binding = gson.toJson(PREDEFINED_VARS);
328    }
329   
 
330  0 toggle private void updateBindingSuggestions(final Binding context) {
331  0 Set<String> predefined = new HashSet<String>();
332  0 predefined.addAll(PREDEFINED_VARS);
333  0 predefined.addAll(context.getVariables().keySet());
334  0 getState().binding = gson.toJson(predefined);
335    }
336   
 
337  0 toggle private void updateCodeSuggestions(final Binding context) {
338  0 Map<String, Object> variables = context.getVariables();
339  0 for (Entry<String, Object> entry : variables.entrySet()) {
340  0 String key = entry.getKey();
341  0 if (suggestions.containsKey(key)) {
342  0 continue;
343    }
344  0 createSuggestionsPerClass(key, entry.getValue());
345    }
346  0 getState().suggestions = gson.toJson(suggestions);
347    }
348   
 
349  2 toggle private void createSuggestionsPerClass(String key, Object value) {
350  2 Set<String> data = new HashSet<String>();
351  2 for (Method method : value.getClass().getMethods()) {
352  86 data.add(method.getName());
353    }
354   
355  2 if (value instanceof GroovyObject) {
356  0 GroovyObject obj = (GroovyObject) value;
357  0 for (MetaMethod method : obj.getMetaClass().getMethods()) {
358  0 data.add(method.getName());
359    }
360    }
361  2 if (!data.isEmpty()) {
362  2 data.removeAll(METHODS_BLACK_LIST);
363  2 suggestions.put(key, data);
364    }
365    }
366    }