/**
* Copyright (C) 2010-2017 Structr GmbH
*
* This file is part of Structr <http://structr.org>.
*
* Structr is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Structr is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Structr. If not, see <http://www.gnu.org/licenses/>.
*/
package org.structr.console;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ScriptableObject;
import org.structr.common.SecurityContext;
import org.structr.common.error.FrameworkException;
import org.structr.console.rest.RestCommand;
import org.structr.console.shell.AdminConsoleCommand;
import org.structr.console.tabcompletion.AdminTabCompletionProvider;
import org.structr.console.tabcompletion.CypherTabCompletionProvider;
import org.structr.console.tabcompletion.JavaScriptTabCompletionProvider;
import org.structr.console.tabcompletion.RestTabCompletionProvider;
import org.structr.console.tabcompletion.StructrScriptTabCompletionProvider;
import org.structr.console.tabcompletion.TabCompletionProvider;
import org.structr.console.tabcompletion.TabCompletionResult;
import org.structr.core.GraphObject;
import org.structr.core.app.App;
import org.structr.core.app.StructrApp;
import org.structr.core.entity.Principal;
import org.structr.core.function.Functions;
import org.structr.core.graph.Tx;
import org.structr.core.script.StructrScriptable;
import org.structr.schema.action.ActionContext;
import org.structr.util.Writable;
/**
*
*/
public class Console {
public enum ConsoleMode {
Cypher, JavaScript, StructrScript, AdminShell, REST
};
private final Map<ConsoleMode, TabCompletionProvider> tabCompletionProviders = new HashMap<>();
private ConsoleMode mode = ConsoleMode.JavaScript;
private StructrScriptable scriptable = null;
private ActionContext actionContext = null;
private ScriptableObject scope = null;
private String username = null;
private String password = null;
public Console(final SecurityContext securityContext, final Map<String, Object> parameters) {
this(securityContext, ConsoleMode.JavaScript, parameters);
}
public Console(final SecurityContext securityContext, final ConsoleMode consoleMode, final Map<String, Object> parameters) {
this.actionContext = new ActionContext(securityContext, parameters);
this.mode = consoleMode;
tabCompletionProviders.put(ConsoleMode.Cypher, new CypherTabCompletionProvider());
tabCompletionProviders.put(ConsoleMode.JavaScript, new JavaScriptTabCompletionProvider());
tabCompletionProviders.put(ConsoleMode.StructrScript, new StructrScriptTabCompletionProvider());
tabCompletionProviders.put(ConsoleMode.AdminShell, new AdminTabCompletionProvider());
tabCompletionProviders.put(ConsoleMode.REST, new RestTabCompletionProvider());
}
public String runForTest(final String line) throws FrameworkException {
final PrintWritable writable = new PrintWritable();
// run
try { run(line, writable); } catch (IOException ioex) {}
return writable.getBuffer();
}
public void run(final String line, final Writable output) throws FrameworkException, IOException {
if (line.startsWith("Console.getMode()")) {
output.println("Mode is '" + getMode() + "'.");
} else if (line.startsWith("Console.setMode('" + ConsoleMode.JavaScript.name() + "')") || line.startsWith("Console.setMode(\"" + ConsoleMode.JavaScript.name() + "\")")) {
mode = ConsoleMode.JavaScript;
output.println("Mode set to '" + ConsoleMode.JavaScript.name() + "'.");
} else if (line.startsWith("Console.setMode('" + ConsoleMode.Cypher.name() + "')") || line.startsWith("Console.setMode(\"" + ConsoleMode.Cypher.name() + "\")")) {
mode = ConsoleMode.Cypher;
output.println("Mode set to '" + ConsoleMode.Cypher.name() + "'.");
} else if (line.startsWith("Console.setMode('" + ConsoleMode.StructrScript.name() + "')") || line.startsWith("Console.setMode(\"" + ConsoleMode.StructrScript.name() + "\")")) {
mode = ConsoleMode.StructrScript;
output.println("Mode set to '" + ConsoleMode.StructrScript.name() + "'.");
} else if (line.startsWith("Console.setMode('" + ConsoleMode.AdminShell.name() + "')") || line.startsWith("Console.setMode(\"" + ConsoleMode.AdminShell.name() + "\")")) {
mode = ConsoleMode.AdminShell;
output.println("Mode set to '" + ConsoleMode.AdminShell.name() + "'. Type 'help' to get a list of commands.");
} else if (line.startsWith("Console.setMode('" + ConsoleMode.REST.name() + "')") || line.startsWith("Console.setMode(\"" + ConsoleMode.REST.name() + "\")")) {
mode = ConsoleMode.REST;
output.println("Mode set to '" + ConsoleMode.REST.name() + "'. Type 'help' to get a list of commands.");
} else {
switch (mode) {
case Cypher:
runCypher(line, output);
break;
case JavaScript:
runJavascript(line, output);
break;
case StructrScript:
runStructrScript(line, output);
break;
case AdminShell:
runAdminShell(line, output);
break;
case REST:
RestCommand.run(this, line, output);
break;
}
}
}
public List<TabCompletionResult> getTabCompletion(final String line) {
final TabCompletionProvider provider = tabCompletionProviders.get(mode);
if (provider != null) {
return provider.getTabCompletion(actionContext.getSecurityContext(), line);
}
return Collections.emptyList();
}
public SecurityContext getSecurityContext() {
return actionContext.getSecurityContext();
}
public String getMode() {
return mode.name();
}
public String getPrompt() {
final Principal principal = actionContext.getSecurityContext().getUser(false);
final StringBuilder buf = new StringBuilder();
switch (mode) {
case Cypher:
case JavaScript:
case StructrScript:
case AdminShell:
if (principal != null) {
buf.append(principal.getName());
}
break;
case REST:
if (username != null) {
buf.append(username);
} else {
buf.append("anonymous");
}
break;
}
buf.append("@");
buf.append("Structr");
return buf.toString();
}
public void setUsername(final String username) {
this.username = username;
}
public void setPassword(final String password) {
this.password = password;
}
public String getUsername() {
return this.username;
}
public String getPassword() {
return this.password;
}
public Map<String, Object> getVariables() {
return actionContext.getAllVariables();
}
public void store(final String key, final Object value) {
actionContext.store(key, value);
}
public Object retrieve(final String key) {
return actionContext.retrieve(key);
}
// ----- private methods -----
private void runCypher(final String line, final Writable writable) throws FrameworkException, IOException {
final App app = StructrApp.getInstance(actionContext.getSecurityContext());
try (final Tx tx = app.tx()) {
final long t0 = System.currentTimeMillis();
final List<GraphObject> result = app.cypher(line, Collections.emptyMap());
final long t1 = System.currentTimeMillis();
final int size = result.size();
writable.print("Query returned ", size, " objects in ", (t1-t0), " ms.");
writable.println();
writable.println();
if (size <= 10) {
writable.print(Functions.get("to_json").apply(actionContext, null, new Object[] { result } ));
} else {
writable.print("Too many results (> 10), please use LIMIT to reduce the result count of your Cypher query.");
}
writable.println();
tx.success();
}
}
private void runStructrScript(final String line, final Writable writable) throws FrameworkException, IOException {
try (final Tx tx = StructrApp.getInstance(actionContext.getSecurityContext()).tx()) {
final Object result = Functions.evaluate(actionContext, null, line);
if (result != null) {
writable.println(result.toString());
}
tx.success();
}
}
private void runJavascript(final String line, final Writable writable) throws FrameworkException {
final Context scriptingContext = Context.enter();
init(scriptingContext);
try (final Tx tx = StructrApp.getInstance(actionContext.getSecurityContext()).tx()) {
Object extractedValue = scriptingContext.evaluateString(scope, line, "interactive script, line ", 1, null);
if (scriptable.hasException()) {
throw scriptable.getException();
}
// prioritize written output over result returned from method
final String output = actionContext.getOutput();
if (output != null && !output.isEmpty()) {
extractedValue = output;
}
if (extractedValue != null) {
writable.println(extractedValue.toString());
}
tx.success();
} catch (final FrameworkException fex) {
// just throw the FrameworkException so we dont lose the information contained
throw fex;
} catch (final Throwable t) {
throw new FrameworkException(422, t.getMessage());
} finally {
Context.exit();
}
}
private void runAdminShell(final String line, final Writable writable) throws FrameworkException, IOException {
final List<String> parts = splitAndClean(line);
if (!parts.isEmpty()) {
final AdminConsoleCommand cmd = AdminConsoleCommand.getCommand(parts.get(0));
if (cmd != null) {
if (cmd.requiresEnclosingTransaction()) {
try (final Tx tx = StructrApp.getInstance(actionContext.getSecurityContext()).tx()) {
cmd.run(actionContext.getSecurityContext(), parts, writable);
tx.success();
}
} else {
cmd.run(actionContext.getSecurityContext(), parts, writable);
}
} else {
writable.println("Unknown command '" + line + "'.");
}
} else {
writable.println("Syntax error.");
}
}
private void init(final Context scriptingContext) {
// Set version to JavaScript1.2 so that we get object-literal style
// printing instead of "[object Object]"
scriptingContext.setLanguageVersion(Context.VERSION_1_2);
// Initialize the standard objects (Object, Function, etc.)
// This must be done before scripts can be executed.
if (this.scope == null) {
this.scope = scriptingContext.initStandardObjects();
}
// set optimization level to interpreter mode to avoid
// class loading / PermGen space bug in Rhino
//scriptingContext.setOptimizationLevel(-1);
if (this.scriptable == null) {
this.scriptable = new StructrScriptable(actionContext, null, scriptingContext);
this.scriptable.setParentScope(scope);
// register Structr scriptable
scope.put("Structr", scope, scriptable);
}
// clear output buffer
actionContext.clear();
}
private List<String> splitAndClean(final String src) {
final List<String> parts = new ArrayList<>();
for (final String part : src.split("[ ]+")) {
final String trimmed = part.trim();
if (StringUtils.isNotBlank(trimmed)) {
parts.add(trimmed);
}
}
return parts;
}
// ----- nested classes -----
private static class PrintWritable implements Writable {
final StringBuilder buf = new StringBuilder();
@Override
public void print(final Object... text) throws IOException {
for (final Object o : text) {
buf.append(o);
}
}
@Override
public void println(final Object... text) throws IOException {
for (final Object o : text) {
buf.append(o);
}
println();
}
@Override
public void println() throws IOException {
buf.append("\r\n");
}
@Override
public void flush() throws IOException {
}
public String getBuffer() {
return buf.toString();
}
}
}