1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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.lang3.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
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
98
99
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
117
118
119 public Terminal(final SimpleTranslator simpleTranslator, final boolean useSystemContext, final boolean requiresNotificationMessageUponCompletion, final MessagesManager messagesManager) {
120 this.simpleTranslator = simpleTranslator;
121 this.useSystemContext = useSystemContext;
122 this.messagesManager = messagesManager;
123 this.getState().greetings = simpleTranslator.translate("console.greetings", GroovySystem.getVersion());
124 this.requiresNotificationMessageUponCompletion = requiresNotificationMessageUponCompletion;
125
126 setPredefinedVariables();
127
128
129 createSuggestionsPerClass("ctx", new MgnlGroovyConsoleContext(MgnlContext.getInstance()));
130
131 this.getState().suggestions = gson.toJson(suggestions);
132
133 setSizeFull();
134
135
136 addFunction("executeCommand", new JavaScriptFunction() {
137 @Override
138 public void call(JsonArray arguments) {
139 getState().command = arguments.getString(0);
140 resetState();
141 try {
142 if (isUseSystemContext()) {
143 MgnlContext.doInSystemContext(new Op<Void, Exception>() {
144
145 @Override
146 public Void exec() throws Exception {
147 execute(getCommand());
148 return null;
149 }
150 });
151 } else {
152 execute(getCommand());
153 }
154 } catch (Exception e) {
155 getState().output = e.getMessage();
156 }
157 }
158 });
159
160 addFunction("saveStatus", new JavaScriptFunction() {
161 @Override
162 public void call(JsonArray arguments) {
163 resetState();
164 getState().view = arguments.get(0).toJson();
165 getState().history = arguments.get(1).toJson();
166 log.debug("saved status [view:{}, history{}]", getState().view, getState().history);
167 }
168 });
169 }
170
171
172
173
174 @Deprecated
175 public Terminal(final SimpleTranslator simpleTranslator, final boolean useSystemContext) {
176 this(simpleTranslator, useSystemContext, true, Components.getComponent(MessagesManager.class));
177 }
178
179 public boolean isUseSystemContext() {
180 return useSystemContext;
181 }
182
183 public String getCommand() {
184 return getState().command;
185 }
186
187 @Override
188 public TerminalState getState() {
189 return (TerminalState) super.getState();
190 }
191
192 public void execute(String command) throws Exception {
193 User currentUser = MgnlContext.getUser();
194 if (!isAuthorized(currentUser)) {
195 String msg = simpleTranslator.translate("console.user.unauthorized", currentUser.getName());
196 log.warn(msg);
197 getState().output = msg;
198 return;
199 }
200
201 log.debug("executing command [{}]", command);
202 StringWriter sw = new StringWriter();
203
204 if (handleDefaultCommand(command, sw)) {
205 getState().output = sw.toString();
206 } else {
207 getState().output = simpleTranslator.translate("groovy.script.consoleOutput.run.wait");
208 runAsync(command);
209 }
210 }
211
212 private void runAsync(final String command) throws CompilationFailedException, IOException, InterruptedException {
213 final MgnlGroovyConsole console = getOrCreateMgnlGroovyConsole();
214 console.runAsync(command, UI.getCurrent(), new ScriptCallback() {
215 @Override
216 public void onSuccess(String result) {
217 getState().output = result;
218 getState().inProgress = false;
219
220 Binding context = console.getContext();
221 updateCodeSuggestions(context);
222 updateBindingSuggestions(context);
223 }
224
225 @Override
226 public void onFailure(Throwable e) {
227 getState().output = e.getMessage();
228 getState().inProgress = false;
229 }
230
231 @Override
232 public void onProgress(Writer out) {
233 getState().inProgress = true;
234 }
235
236 @Override
237 public boolean requiresNotificationMessageUponCompletion() {
238 return requiresNotificationMessageUponCompletion;
239 }
240 });
241 }
242
243
244
245
246
247 private boolean handleDefaultCommand(final String command, final StringWriter out) throws Exception {
248 if (StringUtils.isEmpty(command)) {
249
250 return true;
251 }
252
253 String[] tokens = command.trim().split("\\s+");
254
255 if ("help".equals(tokens[0]) || "?".equals(tokens[0])) {
256 out.write(simpleTranslator.translate("console.help"));
257 return true;
258 } else if ("clear".equals(tokens[0])) {
259 return true;
260 } else if ("clean".equals(tokens[0])) {
261 MgnlContext.setAttribute(BINDING_SESSION_ATTRIBUTE, new SerializableBinding(), Context.SESSION_SCOPE);
262 setPredefinedVariables();
263 return true;
264 } else if ("run".equals(tokens[0])) {
265 out.write(simpleTranslator.translate("groovy.script.consoleOutput.run.wait"));
266 return runScript(tokens, out);
267 }
268 return false;
269 }
270
271 private boolean runScript(final String[] tokens, final StringWriter out) throws Exception {
272 if (tokens.length != 2) {
273 out.write(simpleTranslator.translate("console.commands.run.usage"));
274 return true;
275 }
276
277 String source = null;
278 String path = tokens[1];
279
280 Node scriptNode = SessionUtil.getNode("scripts", path);
281 if (scriptNode != null) {
282 source = PropertyUtil.getString(scriptNode, "text", "");
283 } else {
284
285 try {
286 byte[] bytes = Files.readAllBytes(Paths.get(path));
287 source = new String(bytes);
288 } catch (IOException e) {
289
290 }
291 }
292 if (source == null) {
293 out.write(simpleTranslator.translate("console.commands.run.notFound", path) + "\n");
294 out.write(simpleTranslator.translate("console.commands.run.usage"));
295 } else {
296 runAsync(source);
297 }
298 return true;
299 }
300
301 private void resetState() {
302 getState().output = "";
303 getState().view = "";
304 getState().history = "";
305 getState().inProgress = false;
306 }
307
308 protected boolean isAuthorized(User currentUser) {
309 final Collection<String> roles = currentUser.getRoles();
310 return roles.contains("superuser") || roles.contains("scripter");
311 }
312
313 private MgnlGroovyConsole getOrCreateMgnlGroovyConsole() {
314 Binding binding = MgnlContext.getAttribute(BINDING_SESSION_ATTRIBUTE, Context.SESSION_SCOPE);
315 MgnlGroovyConsole console = null;
316 if (binding != null) {
317 console = new MgnlGroovyConsole(binding, messagesManager, simpleTranslator);
318 } else {
319 Binding newBinding = new SerializableBinding();
320 MgnlContext.setAttribute(BINDING_SESSION_ATTRIBUTE, newBinding, Context.SESSION_SCOPE);
321 console = new MgnlGroovyConsole(newBinding, messagesManager, simpleTranslator);
322 }
323 return console;
324 }
325
326 private void setPredefinedVariables() {
327 this.getState().binding = gson.toJson(PREDEFINED_VARS);
328 }
329
330 private void updateBindingSuggestions(final Binding context) {
331 Set<String> predefined = new HashSet<String>();
332 predefined.addAll(PREDEFINED_VARS);
333 predefined.addAll(context.getVariables().keySet());
334 getState().binding = gson.toJson(predefined);
335 }
336
337 private void updateCodeSuggestions(final Binding context) {
338 Map<String, Object> variables = context.getVariables();
339 for (Entry<String, Object> entry : variables.entrySet()) {
340 String key = entry.getKey();
341 if (suggestions.containsKey(key)) {
342 continue;
343 }
344 createSuggestionsPerClass(key, entry.getValue());
345 }
346 getState().suggestions = gson.toJson(suggestions);
347 }
348
349 private void createSuggestionsPerClass(String key, Object value) {
350 Set<String> data = new HashSet<String>();
351 for (Method method : value.getClass().getMethods()) {
352 data.add(method.getName());
353 }
354
355 if (value instanceof GroovyObject) {
356 GroovyObject obj = (GroovyObject) value;
357 for (MetaMethod method : obj.getMetaClass().getMethods()) {
358 data.add(method.getName());
359 }
360 }
361 if (!data.isEmpty()) {
362 data.removeAll(METHODS_BLACK_LIST);
363 suggestions.put(key, data);
364 }
365 }
366 }