revised scripting console xml response fix based on comments.

removed scripting console actions from ScriptModel into ScriptingConsole
class (resurrection of ScriptExec).
added basic ScriptingConsoleTest.
This commit is contained in:
Kenji Nagahashi
2013-06-10 14:17:27 -07:00
parent 3a54600a79
commit 358afd4486
4 changed files with 215 additions and 112 deletions
@@ -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 {
@@ -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<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;
}
}
@@ -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<Map<String, String>> 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<Map<String, String>> availableGlobalVariables;
private ScriptingConsole scriptingConsole;
public ScriptModel(CrawlJob cj,
public ScriptModel(ScriptingConsole cc,
String crawlJobUrl,
Collection<Map<String, String>> scriptEngines) {
scriptingConsole = cc;
this.crawlJobUrl = crawlJobUrl;
this.cj = cj;
this.availableScriptEngines = scriptEngines;
List<Map<String,String>> vars = new LinkedList<Map<String,String>>();
Map<String,String> var;
var = new LinkedHashMap<String,String>();
var.put("variable", "rawOut");
var.put("description", "a PrintWriter for arbitrary text output to this page");
vars.add(var);
var = new LinkedHashMap<String,String>();
var.put("variable", "htmlOut");
var.put("description", "a PrintWriter for HTML output to this page");
vars.add(var);
var = new LinkedHashMap<String,String>();
var.put("variable", "job");
var.put("description", "the current CrawlJob instance");
vars.add(var);
var = new LinkedHashMap<String,String>();
var.put("variable", "appCtx");
var.put("description", "current job ApplicationContext, if any");
vars.add(var);
var = new LinkedHashMap<String,String>();
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<Map<String, String>> getAvailableScriptEngines() {
return availableScriptEngines;
}
public List<Map<String, String>> getAvailableGlobalVariables() {
return availableGlobalVariables;
return scriptingConsole.getAvailableGlobalVariables();
}
public String getCrawlJobUrl() {
return crawlJobUrl;
@@ -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());
}
}