001/*
002 * Copyright (c) 2006-2015 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     bstefanescu, jcarsique
011 */
012package org.nuxeo.ecm.automation.core.scripting;
013
014import groovy.lang.Binding;
015
016import java.io.ByteArrayOutputStream;
017import java.io.IOException;
018import java.io.InputStream;
019import java.io.PrintStream;
020import java.io.Serializable;
021import java.net.URL;
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026import java.util.concurrent.ConcurrentHashMap;
027
028import org.apache.commons.io.Charsets;
029import org.apache.commons.io.IOUtils;
030import org.mvel2.MVEL;
031
032import org.nuxeo.ecm.automation.OperationContext;
033import org.nuxeo.ecm.automation.OperationException;
034import org.nuxeo.ecm.automation.context.ContextService;
035import org.nuxeo.ecm.automation.core.Constants;
036import org.nuxeo.ecm.core.api.DocumentModel;
037import org.nuxeo.ecm.core.api.DocumentModelList;
038import org.nuxeo.ecm.core.api.NuxeoPrincipal;
039import org.nuxeo.runtime.api.Framework;
040
041/**
042 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
043 */
044public class Scripting {
045
046    protected static final Map<String, Script> cache = new ConcurrentHashMap<>();
047
048    protected static final GroovyScripting gscripting = new GroovyScripting();
049
050    public static Expression newExpression(String expr) {
051        return new MvelExpression(expr);
052    }
053
054    public static Expression newTemplate(String expr) {
055        return new MvelTemplate(expr);
056    }
057
058    public static void run(OperationContext ctx, URL script) throws OperationException, IOException {
059        String key = script.toExternalForm();
060        Script cs = cache.get(key);
061        if (cs != null) {
062            cs.eval(ctx);
063            return;
064        }
065        String path = script.getPath();
066        int p = path.lastIndexOf('.');
067        if (p == -1) {
068            throw new OperationException("Script files must have an extension: " + script);
069        }
070        String ext = path.substring(p + 1).toLowerCase();
071        try (InputStream in = script.openStream()) {
072            if ("mvel".equals(ext)) {
073                Serializable c = MVEL.compileExpression(IOUtils.toString(in, Charsets.UTF_8));
074                cs = new MvelScript(c);
075            } else if ("groovy".equals(ext)) {
076                cs = new GroovyScript(IOUtils.toString(in, Charsets.UTF_8));
077            } else {
078                throw new OperationException("Unsupported script file: " + script
079                        + ". Only MVEL and Groovy scripts are supported");
080            }
081            cache.put(key, cs);
082            cs.eval(ctx);
083        }
084    }
085
086    public static Map<String, Object> initBindings(OperationContext ctx) {
087        Object input = ctx.getInput(); // get last output
088        Map<String, Object> map = new HashMap<>(ctx);
089        map.put("CurrentDate", new DateWrapper());
090        map.put("Context", ctx);
091        if (ctx.get(Constants.VAR_WORKFLOW) != null) {
092            map.put(Constants.VAR_WORKFLOW, ctx.get(Constants.VAR_WORKFLOW));
093        }
094        if (ctx.get(Constants.VAR_WORKFLOW_NODE) != null) {
095            map.put(Constants.VAR_WORKFLOW_NODE, ctx.get(Constants.VAR_WORKFLOW_NODE));
096        }
097        map.put("This", input);
098        map.put("Session", ctx.getCoreSession());
099        PrincipalWrapper principalWrapper = new PrincipalWrapper((NuxeoPrincipal) ctx.getPrincipal());
100        map.put("CurrentUser", principalWrapper);
101        // Alias
102        map.put("currentUser", principalWrapper);
103        map.put("Env", Framework.getProperties());
104
105        // Helpers injection
106        ContextService contextService = Framework.getService(ContextService.class);
107        map.putAll(contextService.getHelperFunctions());
108
109        if (input instanceof DocumentModel) {
110            DocumentWrapper documentWrapper = new DocumentWrapper(ctx.getCoreSession(), (DocumentModel) input);
111            map.put("Document", documentWrapper);
112            // Alias
113            map.put("currentDocument", documentWrapper);
114        }
115        if (input instanceof DocumentModelList) {
116            List<DocumentWrapper> docs = new ArrayList<>();
117            for (DocumentModel doc : (DocumentModelList) input) {
118                docs.add(new DocumentWrapper(ctx.getCoreSession(), doc));
119            }
120            map.put("Documents", docs);
121            if (docs.size() >= 1) {
122                map.put("Document", docs.get(0));
123            }
124        }
125        return map;
126    }
127
128    public interface Script {
129        // protected long lastModified;
130        Object eval(OperationContext ctx);
131    }
132
133    public static class MvelScript implements Script {
134        final Serializable c;
135
136        public static MvelScript compile(String script) {
137            return new MvelScript(MVEL.compileExpression(script));
138        }
139
140        public MvelScript(Serializable c) {
141            this.c = c;
142        }
143
144        @Override
145        public Object eval(OperationContext ctx) {
146            return MVEL.executeExpression(c, Scripting.initBindings(ctx));
147        }
148    }
149
150    public static class GroovyScript implements Script {
151        final groovy.lang.Script c;
152
153        public GroovyScript(String c) {
154            this.c = gscripting.getScript(c, new Binding());
155        }
156
157        @Override
158        public Object eval(OperationContext ctx) {
159            Binding binding = new Binding();
160            for (Map.Entry<String, Object> entry : initBindings(ctx).entrySet()) {
161                binding.setVariable(entry.getKey(), entry.getValue());
162            }
163            ByteArrayOutputStream baos = new ByteArrayOutputStream();
164            binding.setVariable("out", new PrintStream(baos));
165            c.setBinding(binding);
166            c.run();
167            return baos;
168        }
169    }
170
171}