I’m working on universal CRON runner, which runs scripts saved somewhere in repository (more on this next time). Running webscripts isn’t so easy, as I thought, there is no scriptService.runScriptWithThisDescription(path) method, so I did some research and got some results, which I’d like to share.

It’s possible to run java backed actions from js-webscripts. Everything you need is just a bean extending ActionExecuterAbstractBase with definition parent=“action-executer“ and it’s possible to access that bean from webscript like actions.create(„that_bean_id“). But not in reverse, at least not so simple.

First think I found was post on forum and advice to look at unit tests of webscripts, but everything I found were invocations through http. So I went deeper and figured out that Alfresco uses Rhino JavaScript implementation for scripts. At Rhino pages are good examples, so I had first working script soon. At following codelist I firstly load content of node (script body) from repository and then invocate script itself.

NodeRef nodeRef = new NodeRef("workspace://SpacesStore/4a96aaaa-bb80-eeee-aaaa-800a43fcddb8");
ContentReader contentReader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
InputStreamReader isr = new InputStreamReader(contentReader.getContentInputStream());
BufferedReader reader = new BufferedReader(isr);

Context cx = Context.enter();
try {
    Scriptable scope = cx.initStandardObjects();
    Object result = cx.evaluateReader(scope, reader, "scriptInvocation", 1, null);
} finally {
    Context.exit();
}

But I wanted something more – some model in and out and something simplier. So I found ScriptService. Now all those long lines can be ommited, because there is executeScript method!

Map<String, Object> model = new HashMap<String, Object>();
model.put("foo", "bar");

Object result = scriptService.executeScript(scriptRef, model);

Template rendering (I’m using FreeMarker’s ftls) is done the same way, but through TemplateService and processTemplate(nodeRef, model) method, which accepts the same model (altered by script running).

The most beautiful thing is ability of script (and also template) to call java methods, which classes are „injected“ through model, let’s see an example.

Map<String, Object> model = new HashMap<String, Object>();

model.put("something", new Something());
model.put("bar", "foo");

Object result = scriptService.executeScript(scriptRef, model);
String templateResult = templateService.processTemplate("workspace://SpacesStore/noderef", model);

. . .

public class Something {
    public String text = "aaa";

    public String getText() {
        return "bbb";
    }
}

Now it’s possible to use script / template:

(** script **)
bar = something.text;

(** template **)
${something.text} ?=? ${bar}

What is output? Is in templateResult „aaa ?=? aaa“? Nope! Script accesses class attributes directly, on the other side template accesses methods. So output is „bbb ?=? aaa“!!!

One more important thing: this JavaScript implementation is just a stub, so every alfresco service needs to be „injected“ through model, so for example it’s impossible to run stuff like this without modifications:

var connector = remote.connect("http");
var result = connector.call("http://www.google.com/");

Remote object has to be injected (this script is from Share/Surf), more on this in this blogpost. You have to add ScriptRemote to model and create spring-webscripts-config-custom.xml with endpoints defined, see that post. Also another services needs to be injected – to get access to repository, search service… good starting point is Alfresco Wiki.

Edit: Native Java API Access described in wiki is wrong, IT IS POSSIBLE to access maps in JavaScript like this:

var value = javaMap.key;