/** * */ package org.archive.crawler.restlet; import java.io.PrintWriter; import java.io.StringWriter; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.script.Bindings; import javax.script.ScriptEngine; import javax.script.ScriptException; import org.archive.crawler.framework.BeanLookupBindings; import org.archive.crawler.framework.CrawlJob; /** * ScriptingConsole implements view-independent logic of scripting console. * * Currently it is short-lived; it is created by ScriptResource for each request and * destroyed after rendering the view. * * @contributor kenji * */ public class ScriptingConsole { private final CrawlJob cj; private ScriptEngine eng; private String script; private Bindings bindings; private StringWriter rawString; private StringWriter htmlString; private Throwable exception; private int linesExecuted; private List<Map<String, String>> availableGlobalVariables; public ScriptingConsole(CrawlJob job) { this.cj = job; this.bindings = new BeanLookupBindings(this.cj.getJobContext()); this.script = ""; setupAvailableGlobalVariables(); } protected void addGlobalVariable(String name, String desc) { Map<String, String> var = new LinkedHashMap<String, String>(); var.put("variable", name); var.put("description", desc); availableGlobalVariables.add(var); } private void setupAvailableGlobalVariables() { availableGlobalVariables = new LinkedList<Map<String,String>>(); addGlobalVariable("rawOut", "a PrintWriter for arbitrary text output to this page"); addGlobalVariable("htmlOut", "a PrintWriter for HTML output to this page"); addGlobalVariable("job", "the current CrawlJob instance"); addGlobalVariable("appCtx", "current job ApplicationContext, if any"); // TODO: a bit awkward to have this here, because ScriptingConsole has no ref to // ScriptResource. better to have ScriptResource call #addGlobalVariable(String, String)? addGlobalVariable("scriptResource", "the ScriptResource implementing this page, which offers utility methods"); } public void bind(String name, Object obj) { bindings.put(name, obj); } public Object unbind(String name) { return bindings.remove(name); } public void execute(ScriptEngine eng, String script) { // TODO: update through setter rather than passing as method arguments? this.eng = eng; this.script = script; bind("job", cj); rawString = new StringWriter(); htmlString = new StringWriter(); PrintWriter rawOut = new PrintWriter(rawString); PrintWriter htmlOut = new PrintWriter(htmlString); bind("rawOut", rawOut); bind("htmlOut", htmlOut); bind("appCtx", cj.getJobContext()); exception = null; try { this.eng.eval(this.script, bindings); // TODO: should count with RE rather than creating String[]? linesExecuted = script.split("\r?\n").length; } catch (ScriptException ex) { Throwable cause = ex.getCause(); exception = cause != null ? cause : ex; } catch (RuntimeException ex) { exception = ex; } finally { rawOut.flush(); htmlOut.flush(); // TODO: are these really necessary? unbind("rawOut"); unbind("htmlOut"); unbind("appCtx"); unbind("job"); } } public CrawlJob getCrawlJob( ) { return cj; } public Throwable getException() { return exception; } public int getLinesExecuted() { return linesExecuted; } public String getRawOutput() { return rawString != null ? rawString.toString() : ""; } public String getHtmlOutput() { return htmlString != null ? htmlString.toString() : ""; } public String getScript() { return script; } public List<Map<String, String>> getAvailableGlobalVariables() { return availableGlobalVariables; } }