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);
096        map.put("CurrentDate", new DateWrapper());
097        map.put("Context", ctx);
098        if (ctx.get(Constants.VAR_WORKFLOW) != null) {
099            map.put(Constants.VAR_WORKFLOW, ctx.get(Constants.VAR_WORKFLOW));
100        }
101        if (ctx.get(Constants.VAR_WORKFLOW_NODE) != null) {
102            map.put(Constants.VAR_WORKFLOW_NODE, ctx.get(Constants.VAR_WORKFLOW_NODE));
103        }
104        map.put("This", input);
105        map.put("Session", ctx.getCoreSession());
106        PrincipalWrapper principalWrapper = new PrincipalWrapper((NuxeoPrincipal) ctx.getPrincipal());
107        map.put("CurrentUser", principalWrapper);
108        // Alias
109        map.put("currentUser", principalWrapper);
110        map.put("Env", Framework.getProperties());
111
112        // Helpers injection
113        ContextService contextService = Framework.getService(ContextService.class);
114        map.putAll(contextService.getHelperFunctions());
115
116        if (input instanceof DocumentModel) {
117            DocumentWrapper documentWrapper = new DocumentWrapper(ctx.getCoreSession(), (DocumentModel) input);
118            map.put("Document", documentWrapper);
119            // Alias
120            map.put("currentDocument", documentWrapper);
121        }
122        if (input instanceof DocumentModelList) {
123            List<DocumentWrapper> docs = new ArrayList<>();
124            for (DocumentModel doc : (DocumentModelList) input) {
125                docs.add(new DocumentWrapper(ctx.getCoreSession(), doc));
126            }
127            map.put("Documents", docs);
128            if (docs.size() >= 1) {
129                map.put("Document", docs.get(0));
130            }
131        }
132        return map;
133    }
134
135    public interface Script {
136        // protected long lastModified;
137        Object eval(OperationContext ctx);
138    }
139
140    public static class MvelScript implements Script {
141        final Serializable c;
142
143        public static MvelScript compile(String script) {
144            return new MvelScript(MVEL.compileExpression(script));
145        }
146
147        public MvelScript(Serializable c) {
148            this.c = c;
149        }
150
151        @Override
152        public Object eval(OperationContext ctx) {
153            return MVEL.executeExpression(c, Scripting.initBindings(ctx));
154        }
155    }
156
157    public static class GroovyScript implements Script {
158        final groovy.lang.Script c;
159
160        public GroovyScript(String c) {
161            this.c = gscripting.getScript(c, new Binding());
162        }
163
164        @Override
165        public Object eval(OperationContext ctx) {
166            Binding binding = new Binding();
167            for (Map.Entry<String, Object> entry : initBindings(ctx).entrySet()) {
168                binding.setVariable(entry.getKey(), entry.getValue());
169            }
170            ByteArrayOutputStream baos = new ByteArrayOutputStream();
171            binding.setVariable("out", new PrintStream(baos));
172            c.setBinding(binding);
173            c.run();
174            return baos;
175        }
176    }
177
178}