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 static java.nio.charset.StandardCharsets.UTF_8;
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.IOUtils;
036import org.mvel2.MVEL;
037import org.nuxeo.ecm.automation.OperationContext;
038import org.nuxeo.ecm.automation.OperationException;
039import org.nuxeo.ecm.automation.context.ContextService;
040import org.nuxeo.ecm.automation.core.Constants;
041import org.nuxeo.ecm.core.api.DocumentModel;
042import org.nuxeo.ecm.core.api.DocumentModelList;
043import org.nuxeo.runtime.api.Framework;
044
045import groovy.lang.Binding;
046
047/**
048 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
049 */
050public class Scripting {
051
052    protected static final Map<String, Script> cache = new ConcurrentHashMap<>();
053
054    protected static final GroovyScripting gscripting = new GroovyScripting();
055
056    public static Expression newExpression(String expr) {
057        return new MvelExpression(expr);
058    }
059
060    public static Expression newTemplate(String expr) {
061        return new MvelTemplate(expr);
062    }
063
064    public static void run(OperationContext ctx, URL script) throws OperationException, IOException {
065        String key = script.toExternalForm();
066        Script cs = cache.get(key);
067        if (cs != null) {
068            cs.eval(ctx);
069            return;
070        }
071        String path = script.getPath();
072        int p = path.lastIndexOf('.');
073        if (p == -1) {
074            throw new OperationException("Script files must have an extension: " + script);
075        }
076        String ext = path.substring(p + 1).toLowerCase();
077        try (InputStream in = script.openStream()) {
078            if ("mvel".equals(ext)) {
079                Serializable c = MVEL.compileExpression(IOUtils.toString(in, UTF_8));
080                cs = new MvelScript(c);
081            } else if ("groovy".equals(ext)) {
082                cs = new GroovyScript(IOUtils.toString(in, UTF_8));
083            } else {
084                throw new OperationException(
085                        "Unsupported script file: " + script + ". Only MVEL and Groovy scripts are supported");
086            }
087            cache.put(key, cs);
088            cs.eval(ctx);
089        }
090    }
091
092    public static Map<String, Object> initBindings(OperationContext ctx) {
093        Object input = ctx.getInput(); // get last output
094        Map<String, Object> map = new HashMap<>(ctx.getVars());
095        map.put("CurrentDate", new DateWrapper());
096        map.put("Context", ctx);
097        if (ctx.get(Constants.VAR_WORKFLOW) != null) {
098            map.put(Constants.VAR_WORKFLOW, ctx.get(Constants.VAR_WORKFLOW));
099        }
100        if (ctx.get(Constants.VAR_WORKFLOW_NODE) != null) {
101            map.put(Constants.VAR_WORKFLOW_NODE, ctx.get(Constants.VAR_WORKFLOW_NODE));
102        }
103        map.put("This", input);
104        map.put("Session", ctx.getCoreSession());
105        PrincipalWrapper principalWrapper = new PrincipalWrapper(ctx.getPrincipal());
106        map.put("CurrentUser", principalWrapper);
107        // Alias
108        map.put("currentUser", principalWrapper);
109        map.put("Env", Framework.getProperties());
110
111        // Helpers injection
112        ContextService contextService = Framework.getService(ContextService.class);
113        map.putAll(contextService.getHelperFunctions());
114
115        if (input instanceof DocumentModel) {
116            DocumentWrapper documentWrapper = new DocumentWrapper(ctx.getCoreSession(), (DocumentModel) input);
117            map.put("Document", documentWrapper);
118            // Alias
119            map.put("currentDocument", documentWrapper);
120        }
121        if (input instanceof DocumentModelList) {
122            List<DocumentWrapper> docs = new ArrayList<>();
123            for (DocumentModel doc : (DocumentModelList) input) {
124                docs.add(new DocumentWrapper(ctx.getCoreSession(), doc));
125            }
126            map.put("Documents", docs);
127            if (!docs.isEmpty()) {
128                map.put("Document", docs.get(0));
129            }
130        }
131        return map;
132    }
133
134    public interface Script {
135        Object eval(OperationContext ctx);
136    }
137
138    public static class MvelScript implements Script {
139        final Serializable c;
140
141        public static MvelScript compile(String script) {
142            return new MvelScript(MVEL.compileExpression(script));
143        }
144
145        public MvelScript(Serializable c) {
146            this.c = c;
147        }
148
149        @Override
150        public Object eval(OperationContext ctx) {
151            return MVEL.executeExpression(c, Scripting.initBindings(ctx));
152        }
153    }
154
155    public static class GroovyScript implements Script {
156        final groovy.lang.Script c;
157
158        public GroovyScript(String c) {
159            this.c = gscripting.getScript(c, new Binding());
160        }
161
162        @Override
163        public Object eval(OperationContext ctx) {
164            Binding binding = new Binding();
165            for (Map.Entry<String, Object> entry : initBindings(ctx).entrySet()) {
166                binding.setVariable(entry.getKey(), entry.getValue());
167            }
168            ByteArrayOutputStream baos = new ByteArrayOutputStream();
169            binding.setVariable("out", new PrintStream(baos));
170            c.setBinding(binding);
171            c.run();
172            return baos;
173        }
174    }
175
176}