/*
* (C) Copyright 2006-2015 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* bstefanescu, jcarsique
*/
package org.nuxeo.ecm.automation.core.scripting;
import groovy.lang.Binding;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.IOUtils;
import org.mvel2.MVEL;
import org.nuxeo.ecm.automation.OperationContext;
import org.nuxeo.ecm.automation.OperationException;
import org.nuxeo.ecm.automation.context.ContextService;
import org.nuxeo.ecm.automation.core.Constants;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentModelList;
import org.nuxeo.ecm.core.api.NuxeoPrincipal;
import org.nuxeo.runtime.api.Framework;
/**
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
public class Scripting {
protected static final Map<String, Script> cache = new ConcurrentHashMap<>();
protected static final GroovyScripting gscripting = new GroovyScripting();
public static Expression newExpression(String expr) {
return new MvelExpression(expr);
}
public static Expression newTemplate(String expr) {
return new MvelTemplate(expr);
}
public static void run(OperationContext ctx, URL script) throws OperationException, IOException {
String key = script.toExternalForm();
Script cs = cache.get(key);
if (cs != null) {
cs.eval(ctx);
return;
}
String path = script.getPath();
int p = path.lastIndexOf('.');
if (p == -1) {
throw new OperationException("Script files must have an extension: " + script);
}
String ext = path.substring(p + 1).toLowerCase();
try (InputStream in = script.openStream()) {
if ("mvel".equals(ext)) {
Serializable c = MVEL.compileExpression(IOUtils.toString(in, Charsets.UTF_8));
cs = new MvelScript(c);
} else if ("groovy".equals(ext)) {
cs = new GroovyScript(IOUtils.toString(in, Charsets.UTF_8));
} else {
throw new OperationException("Unsupported script file: " + script
+ ". Only MVEL and Groovy scripts are supported");
}
cache.put(key, cs);
cs.eval(ctx);
}
}
public static Map<String, Object> initBindings(OperationContext ctx) {
Object input = ctx.getInput(); // get last output
Map<String, Object> map = new HashMap<>(ctx.getVars());
map.put("CurrentDate", new DateWrapper());
map.put("Context", ctx);
map.put(Constants.VAR_RUNTIME_CHAIN, ctx);
if (ctx.get(Constants.VAR_WORKFLOW) != null) {
map.put(Constants.VAR_WORKFLOW, ctx.get(Constants.VAR_WORKFLOW));
}
if (ctx.get(Constants.VAR_WORKFLOW_NODE) != null) {
map.put(Constants.VAR_WORKFLOW_NODE, ctx.get(Constants.VAR_WORKFLOW_NODE));
}
map.put("This", input);
map.put("Session", ctx.getCoreSession());
PrincipalWrapper principalWrapper = new PrincipalWrapper((NuxeoPrincipal) ctx.getPrincipal());
map.put("CurrentUser", principalWrapper);
// Alias
map.put("currentUser", principalWrapper);
map.put("Env", Framework.getProperties());
// Helpers injection
ContextService contextService = Framework.getService(ContextService.class);
map.putAll(contextService.getHelperFunctions());
if (input instanceof DocumentModel) {
DocumentWrapper documentWrapper = new DocumentWrapper(ctx.getCoreSession(), (DocumentModel) input);
map.put("Document", documentWrapper);
// Alias
map.put("currentDocument", documentWrapper);
}
if (input instanceof DocumentModelList) {
List<DocumentWrapper> docs = new ArrayList<>();
for (DocumentModel doc : (DocumentModelList) input) {
docs.add(new DocumentWrapper(ctx.getCoreSession(), doc));
}
map.put("Documents", docs);
if (docs.size() >= 1) {
map.put("Document", docs.get(0));
}
}
return map;
}
public interface Script {
// protected long lastModified;
Object eval(OperationContext ctx);
}
public static class MvelScript implements Script {
final Serializable c;
public static MvelScript compile(String script) {
return new MvelScript(MVEL.compileExpression(script));
}
public MvelScript(Serializable c) {
this.c = c;
}
@Override
public Object eval(OperationContext ctx) {
return MVEL.executeExpression(c, Scripting.initBindings(ctx));
}
}
public static class GroovyScript implements Script {
final groovy.lang.Script c;
public GroovyScript(String c) {
this.c = gscripting.getScript(c, new Binding());
}
@Override
public Object eval(OperationContext ctx) {
Binding binding = new Binding();
for (Map.Entry<String, Object> entry : initBindings(ctx).entrySet()) {
binding.setVariable(entry.getKey(), entry.getValue());
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
binding.setVariable("out", new PrintStream(baos));
c.setBinding(binding);
c.run();
return baos;
}
}
}