scripting console:

fix bug: exception from script execution is not displayed.
remove extra space around exception and rawOutput.
involves a bit more than bug fix. introduced ScriptExecution class for
encapsulating script, execution, and result.
This commit is contained in:
Kenji Nagahashi
2013-05-10 17:54:40 -07:00
parent e531de0ed3
commit 73f58dc56a
3 changed files with 121 additions and 73 deletions
@@ -19,7 +19,6 @@
package org.archive.crawler.restlet;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -38,14 +37,10 @@ import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.archive.crawler.framework.BeanLookupBindings;
import org.archive.crawler.restlet.models.CrawlJobModel;
import org.archive.crawler.restlet.models.EngineModel;
import org.archive.crawler.restlet.models.ScriptModel;
import org.archive.crawler.restlet.models.ViewModel;
import org.archive.util.TextUtils;
import org.restlet.Context;
import org.restlet.data.CharacterSet;
import org.restlet.data.Form;
@@ -89,11 +84,13 @@ public class ScriptResource extends JobRelatedResource {
}
});
}
protected String script = "";
protected Exception ex = null;
protected int linesExecuted = 0;
protected String rawOutput = "";
protected String htmlOutput = "";
//protected String script = "";
//protected Exception ex = null;
//protected int linesExecuted = 0;
//protected String rawOutput = "";
//protected String htmlOutput = "";
ScriptExecution scriptExec = null;
protected String chosenEngine = FACTORIES.isEmpty() ? "" : FACTORIES.getFirst().getNames().get(0);
private Configuration _templateConfiguration;
@@ -115,48 +112,107 @@ public class ScriptResource extends JobRelatedResource {
return _templateConfiguration;
}
/**
* JavaBean that packages script, its execution and the result.
*
* TODO: the script could create an object that persists and retains a
* reference to the Bindings. by encapsulating this way, I've made it
* a bit more difficult? we could {@code eng} and {@code script} arguments
* to execute() method.
*/
public static class ScriptExecution {
private ScriptEngine eng;
private ApplicationContext appCtx;
private Bindings bindings;
private String script;
private StringWriter rawString;
private StringWriter htmlString;
private Throwable exception;
private int linesExecuted;
public ScriptExecution(ScriptEngine eng, ApplicationContext appCtx, String script) {
this.eng = eng;
this.appCtx = appCtx;
this.bindings = new BeanLookupBindings(appCtx);
this.script = script;
if (StringUtils.isBlank(this.script)) {
this.script = "";
}
this.rawString = new StringWriter();
this.htmlString = new StringWriter();
}
public void bind(String name, Object obj) {
bindings.put(name, obj);
}
public Object unbind(String name) {
return bindings.remove(name);
}
public void execute() {
PrintWriter rawOut = new PrintWriter(rawString);
PrintWriter htmlOut = new PrintWriter(htmlString);
bind("rawOut", rawOut);
bind("htmlOut", htmlOut);
bind("appCtx", appCtx);
try {
eng.eval(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");
}
}
public boolean isFailure() {
return exception != null;
}
public String getStackTrace() {
if (exception == null) return "";
StringWriter s = new StringWriter();
exception.printStackTrace(new PrintWriter(s));
return s.toString();
}
public Throwable getException() {
return exception;
}
public int getLinesExecuted() {
return linesExecuted;
}
public String getRawOutput() {
return rawString.toString();
}
public String getHtmlOutput() {
return htmlString.toString();
}
}
@Override
public void acceptRepresentation(Representation entity) throws ResourceException {
Form form = getRequest().getEntityAsForm();
chosenEngine = form.getFirstValue("engine");
script = form.getFirstValue("script");
String script = form.getFirstValue("script");
if(StringUtils.isBlank(script)) {
script="";
}
ScriptEngine eng = MANAGER.getEngineByName(chosenEngine);
StringWriter rawString = new StringWriter();
PrintWriter rawOut = new PrintWriter(rawString);
ApplicationContext appCtx = cj.getJobContext();
Bindings bindings = new BeanLookupBindings(appCtx);
bindings.put("rawOut", rawOut);
StringWriter htmlString = new StringWriter();
PrintWriter htmlOut = new PrintWriter(htmlString);
bindings.put("htmlOut", htmlOut);
bindings.put("job", cj);
bindings.put("appCtx", appCtx);
bindings.put("scriptResource", this);
try {
eng.eval(script, bindings);
linesExecuted = script.split("\r?\n").length;
} catch (ScriptException e) {
ex = e;
} catch (RuntimeException e) {
ex = e;
} finally {
rawOut.flush();
rawOutput = rawString.toString();
htmlOut.flush();
htmlOutput = htmlString.toString();
// the script could create an object that persists and retains a reference to the Bindings
bindings.put("rawOut", null);
bindings.put("htmlOut", null);
bindings.put("job", null);
bindings.put("appCtx", null);
bindings.put("scriptResource", null);
}
scriptExec = new ScriptExecution(eng, cj.getJobContext(), script);
scriptExec.bind("job", cj);
scriptExec.bind("scriptResource", this);
scriptExec.execute();
scriptExec.unbind("job");
scriptExec.unbind("scriptResource");
//TODO: log script, results somewhere; job log INFO?
getResponse().setEntity(represent());
@@ -211,10 +267,7 @@ public class ScriptResource extends JobRelatedResource {
ScriptModel model = new ScriptModel(cj.getShortName(),
new Reference(baseRefRef, "..").getTargetRef().toString(),
getAvailableScriptEngines(),
linesExecuted,
ex,
rawOutput,
htmlOutput);
scriptExec);
return model;
}
@@ -235,7 +288,6 @@ public class ScriptResource extends JobRelatedResource {
viewModel.put("baseResourceRef",getRequest().getRootRef().toString()+"/engine/static/");
viewModel.put("model",makeDataModel());
viewModel.put("selectedEngine", chosenEngine);
viewModel.put("script", script);
try {
Template template = tmpltCfg.getTemplate("Script.ftl");
@@ -6,29 +6,23 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.archive.crawler.restlet.ScriptResource.ScriptExecution;
@SuppressWarnings("serial")
public class ScriptModel extends LinkedHashMap<String, Object> {
public ScriptModel(String crawlJobShortName, String crawlJobUrl, Collection<Map<String,String>> scriptEngines, int linesExecuted, Exception exception, String rawOutput, String htmlOutput){
public ScriptModel(String crawlJobShortName, String crawlJobUrl,
Collection<Map<String, String>> scriptEngines,
ScriptExecution scriptExec) {// int linesExecuted, Exception
// exception, String rawOutput,
// String htmlOutput){
super();
this.put("crawlJobUrl",crawlJobUrl);
this.put("crawlJobShortName", crawlJobShortName);
this.put("availableScriptEngines", scriptEngines);
if (linesExecuted > 0) {
this.put("linesExecuted", linesExecuted);
}
if (exception != null) {
this.put("exception", exception);
}
if (StringUtils.isNotBlank(rawOutput)) {
this.put("rawOutput", rawOutput);
}
if (StringUtils.isNotBlank(htmlOutput)) {
this.put("htmlOutput", htmlOutput);
}
if (scriptExec != null)
this.put("scriptExec", scriptExec);
List<Map<String,String>> vars = new LinkedList<Map<String,String>>();
Map<String,String> var;
@@ -16,35 +16,37 @@
<body>
<h1>Execute script for job <i><a href='/engine/job/${model.crawlJobShortName}'>${model.crawlJobShortName}</a></i></h1>
<#if model.linesExecuted??>
<span class='success'>${model.linesExecuted} lines executed<span>
<#if model.scriptExec??>
<#if (model.scriptExec.linesExecuted > 0)>
<span class='success'>${model.scriptExec.linesExecuted} lines executed<span>
</#if>
<#if model.ex??>
<pre style='color:red; height:150px; overflow:auto'>
${model.ex.printStackTrace()}
<#if model.scriptExec.failure>
<pre style='color:red; height:150px; overflow:auto'>${model.scriptExec.stackTrace}
</pre>
</#if>
<#if model.htmlOutput??>
<#assign htmlOutput=model.scriptExec.htmlOutput>
<#if (htmlOutput?length > 0)>
<fieldset><legend>htmlOut</legend>
${model.htmlOutput}
${htmlOutput}
</fieldset>
</#if>
<#if model.rawOutput??>
<#assign rawOutput=model.scriptExec.rawOutput>
<#if (rawOutput?length > 0)>
<fieldset><legend>rawOutput</legend>
<pre>
${model.rawOutput?html}
<pre style="margin:0;">${rawOutput}
</pre>
</fieldset>
</#if>
</#if>
<form method="POST">
<input type="submit" value="execute">
<select name="engine" id="selectEngine">
<#list model.availableScriptEngines as scriptEngine>
<option<#if selectedEngine=scriptEngine.engine> selected='selected'</#if> value='${scriptEngine.engine}'>${scriptEngine.language}</option>
<option<#if selectedEngine=scriptEngine.engine> selected="selected"</#if> value="${scriptEngine.engine}">${scriptEngine.language}</option>
</#list>
</select>
<textarea rows='20' style='width:100%' name='script' id='editor'>${script}</textarea>
<textarea rows='20' style='width:100%' name='script' id='editor'>${(model.scriptExec.script)!""}</textarea>
<input type='submit' value='execute'></input>
</form>
The script will be executed in an engine preloaded