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