From 358afd448673798ea22d99d616b776e8bea2bffa Mon Sep 17 00:00:00 2001 From: Kenji Nagahashi Date: Mon, 10 Jun 2013 14:17:27 -0700 Subject: [PATCH] revised scripting console xml response fix based on comments. removed scripting console actions from ScriptModel into ScriptingConsole class (resurrection of ScriptExec). added basic ScriptingConsoleTest. --- .../crawler/restlet/ScriptResource.java | 20 ++- .../crawler/restlet/ScriptingConsole.java | 129 ++++++++++++++++++ .../crawler/restlet/models/ScriptModel.java | 114 ++-------------- .../crawler/restlet/ScriptingConsoleTest.java | 64 +++++++++ 4 files changed, 215 insertions(+), 112 deletions(-) create mode 100644 engine/src/main/java/org/archive/crawler/restlet/ScriptingConsole.java create mode 100644 engine/src/test/java/org/archive/crawler/restlet/ScriptingConsoleTest.java diff --git a/engine/src/main/java/org/archive/crawler/restlet/ScriptResource.java b/engine/src/main/java/org/archive/crawler/restlet/ScriptResource.java index 5f3da976..dd0fa55c 100644 --- a/engine/src/main/java/org/archive/crawler/restlet/ScriptResource.java +++ b/engine/src/main/java/org/archive/crawler/restlet/ScriptResource.java @@ -92,6 +92,8 @@ public class ScriptResource extends JobRelatedResource { tmpltCfg.setClassForTemplateLoading(this.getClass(),""); tmpltCfg.setObjectWrapper(ObjectWrapper.BEANS_WRAPPER); setTemplateConfiguration(tmpltCfg); + + scriptingConsole = new ScriptingConsole(cj); } public void setTemplateConfiguration(Configuration tmpltCfg) { _templateConfiguration=tmpltCfg; @@ -100,7 +102,7 @@ public class ScriptResource extends JobRelatedResource { return _templateConfiguration; } - private ScriptModel scriptModel; + private ScriptingConsole scriptingConsole; @Override public void acceptRepresentation(Representation entity) throws ResourceException { @@ -113,10 +115,9 @@ public class ScriptResource extends JobRelatedResource { ScriptEngine eng = MANAGER.getEngineByName(chosenEngine); - scriptModel = makeDataModel(); - scriptModel.bind("scriptResource", this); - scriptModel.execute(eng, script); - scriptModel.unbind("scriptResource"); + scriptingConsole.bind("scriptResource", this); + scriptingConsole.execute(eng, script); + scriptingConsole.unbind("scriptResource"); //TODO: log script, results somewhere; job log INFO? @@ -124,14 +125,11 @@ public class ScriptResource extends JobRelatedResource { } public Representation represent(Variant variant) throws ResourceException { - if (scriptModel == null) { - scriptModel = makeDataModel(); - } Representation representation; if (variant.getMediaType() == MediaType.APPLICATION_XML) { representation = new WriterRepresentation(MediaType.APPLICATION_XML) { public void write(Writer writer) throws IOException { - XmlMarshaller.marshalDocument(writer,"script", scriptModel); + XmlMarshaller.marshalDocument(writer,"script", makeDataModel()); } }; } else { @@ -172,7 +170,7 @@ public class ScriptResource extends JobRelatedResource { } Reference baseRefRef = new Reference(baseRef); - ScriptModel model = new ScriptModel(cj, + ScriptModel model = new ScriptModel(scriptingConsole, new Reference(baseRefRef, "..").getTargetRef().toString(), getAvailableScriptEngines()); @@ -193,7 +191,7 @@ public class ScriptResource extends JobRelatedResource { viewModel.put("cssRef", getStylesheetRef()); viewModel.put("staticRef", getStaticRef("")); viewModel.put("baseResourceRef",getRequest().getRootRef().toString()+"/engine/static/"); - viewModel.put("model", scriptModel); + viewModel.put("model", makeDataModel()); viewModel.put("selectedEngine", chosenEngine); try { diff --git a/engine/src/main/java/org/archive/crawler/restlet/ScriptingConsole.java b/engine/src/main/java/org/archive/crawler/restlet/ScriptingConsole.java new file mode 100644 index 00000000..e18629b9 --- /dev/null +++ b/engine/src/main/java/org/archive/crawler/restlet/ScriptingConsole.java @@ -0,0 +1,129 @@ +/** + * + */ +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> 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 var = new LinkedHashMap(); + var.put("variable", name); + var.put("description", desc); + availableGlobalVariables.add(var); + } + private void setupAvailableGlobalVariables() { + availableGlobalVariables = new LinkedList>(); + 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> getAvailableGlobalVariables() { + return availableGlobalVariables; + } +} diff --git a/engine/src/main/java/org/archive/crawler/restlet/models/ScriptModel.java b/engine/src/main/java/org/archive/crawler/restlet/models/ScriptModel.java index e748ed76..0fa5a970 100644 --- a/engine/src/main/java/org/archive/crawler/restlet/models/ScriptModel.java +++ b/engine/src/main/java/org/archive/crawler/restlet/models/ScriptModel.java @@ -3,19 +3,13 @@ package org.archive.crawler.restlet.models; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Collection; -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 javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; -import org.archive.crawler.framework.BeanLookupBindings; -import org.archive.crawler.framework.CrawlJob; +import org.archive.crawler.restlet.ScriptingConsole; @XmlRootElement(name="script") @XmlType(propOrder={ @@ -24,134 +18,52 @@ import org.archive.crawler.framework.CrawlJob; "availableGlobalVariables" }) public class ScriptModel { - private CrawlJob cj; private String crawlJobUrl; private Collection> availableScriptEngines; - - //private ApplicationContext appCtx; - private ScriptEngine eng; - private String script; - private Bindings bindings; - - private StringWriter rawString; - private StringWriter htmlString; - private Throwable exception; - private int linesExecuted; - - private List> availableGlobalVariables; + private ScriptingConsole scriptingConsole; - public ScriptModel(CrawlJob cj, + public ScriptModel(ScriptingConsole cc, String crawlJobUrl, Collection> scriptEngines) { + scriptingConsole = cc; this.crawlJobUrl = crawlJobUrl; - this.cj = cj; this.availableScriptEngines = scriptEngines; - - List> vars = new LinkedList>(); - Map var; - - var = new LinkedHashMap(); - var.put("variable", "rawOut"); - var.put("description", "a PrintWriter for arbitrary text output to this page"); - vars.add(var); - - var = new LinkedHashMap(); - var.put("variable", "htmlOut"); - var.put("description", "a PrintWriter for HTML output to this page"); - vars.add(var); - - var = new LinkedHashMap(); - var.put("variable", "job"); - var.put("description", "the current CrawlJob instance"); - vars.add(var); - - var = new LinkedHashMap(); - var.put("variable", "appCtx"); - var.put("description", "current job ApplicationContext, if any"); - vars.add(var); - - var = new LinkedHashMap(); - var.put("variable", "scriptResource"); - var.put("description", "the ScriptResource implementing this page, which offers utility methods"); - vars.add(var); - - this.availableGlobalVariables = vars; - - this.bindings = new BeanLookupBindings(this.cj.getJobContext()); - this.script = ""; } - 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()); - 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 boolean isFailure() { - return exception != null; + return scriptingConsole.getException() != null; } public String getStackTrace() { + Throwable exception = scriptingConsole.getException(); if (exception == null) return ""; StringWriter s = new StringWriter(); exception.printStackTrace(new PrintWriter(s)); return s.toString(); } public Throwable getException() { - return exception; + return scriptingConsole.getException(); } public int getLinesExecuted() { - return linesExecuted; + return scriptingConsole.getLinesExecuted(); } public String getRawOutput() { - return rawString != null ? rawString.toString() : ""; + return scriptingConsole.getRawOutput(); } public String getHtmlOutput() { - return htmlString != null ? htmlString.toString() : ""; + return scriptingConsole.getHtmlOutput(); } public String getScript() { - return script; + return scriptingConsole.getScript(); } public String getCrawlJobShortName() { - return cj.getShortName(); + return scriptingConsole.getCrawlJob().getShortName(); } public Collection> getAvailableScriptEngines() { return availableScriptEngines; } public List> getAvailableGlobalVariables() { - return availableGlobalVariables; + return scriptingConsole.getAvailableGlobalVariables(); } public String getCrawlJobUrl() { return crawlJobUrl; diff --git a/engine/src/test/java/org/archive/crawler/restlet/ScriptingConsoleTest.java b/engine/src/test/java/org/archive/crawler/restlet/ScriptingConsoleTest.java new file mode 100644 index 00000000..34c096b3 --- /dev/null +++ b/engine/src/test/java/org/archive/crawler/restlet/ScriptingConsoleTest.java @@ -0,0 +1,64 @@ +package org.archive.crawler.restlet; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; + +import junit.framework.TestCase; + +import org.archive.crawler.framework.CrawlJob; +import org.archive.spring.PathSharingContext; + +public class ScriptingConsoleTest extends TestCase { + // barebone CrawlJob object. + public static class TestCrawlJob extends CrawlJob { + public TestCrawlJob() { + super(null); + this.ac = new PathSharingContext(new String[0]); + } + @Override + protected void scanJobLog() { + } + } + CrawlJob cj; + ScriptingConsole sc; + + protected void setUp() throws Exception { + super.setUp(); + cj = new TestCrawlJob(); + sc = new ScriptingConsole(cj); + } + + public void testInitialState() { + assertEquals("script is empty", "", sc.getScript()); + assertNull("exception is null", sc.getException()); + } + + public void testExecute() { + final String script = "rawOut.println 'elk'"; + final ScriptEngineManager manager = new ScriptEngineManager(); + final ScriptEngine eng = manager.getEngineByName("groovy"); + sc.execute(eng, script); + + assertNull("exception is null", sc.getException()); + assertEquals("has the same script", sc.getScript(), script); + assertEquals("linesExecuted", 1, sc.getLinesExecuted()); + assertEquals("rawOut", "elk\n", sc.getRawOutput()); + + } + + public void testExecuteError() { + final String script = "rawOut.println undef"; + final ScriptEngineManager manager = new ScriptEngineManager(); + final ScriptEngine eng = manager.getEngineByName("groovy"); + sc.execute(eng, script); + + assertNotNull("exception is non-null", sc.getException()); + assertEquals("rawOut", "", sc.getRawOutput()); + assertEquals("linesExecuted", 0, sc.getLinesExecuted()); + + // extra test - it is okay to fail this test is okay because + // ScriptingConsole is single-use now. + sc.execute(eng, "rawOut.println 1"); + assertNull("exception is cleared", sc.getException()); + } +}