001/*
002 * (C) Copyright 2006-2008 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
018 *
019 * $Id$
020 */
021
022package org.nuxeo.ecm.webengine.scripting;
023
024import java.io.File;
025import java.io.FileReader;
026import java.io.IOException;
027import java.io.Reader;
028import java.util.Map;
029import java.util.concurrent.ConcurrentHashMap;
030import java.util.concurrent.ConcurrentMap;
031
032import javax.script.Compilable;
033import javax.script.CompiledScript;
034import javax.script.ScriptContext;
035import javax.script.ScriptEngine;
036import javax.script.ScriptEngineManager;
037import javax.script.ScriptException;
038import javax.script.SimpleBindings;
039import javax.script.SimpleScriptContext;
040
041import org.apache.commons.logging.Log;
042import org.apache.commons.logging.LogFactory;
043import org.nuxeo.ecm.webengine.loader.WebLoader;
044
045import groovy.lang.GroovyRuntimeException;
046
047/**
048 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
049 */
050public class Scripting {
051
052    private static final Log log = LogFactory.getLog(Scripting.class);
053
054    private final ConcurrentMap<File, Entry> cache = new ConcurrentHashMap<File, Entry>();
055
056    // this will be lazy initialized
057    private ScriptEngineManager scriptMgr;
058
059    private final WebLoader loader;
060
061    public Scripting(WebLoader loader) {
062        this.loader = loader;
063    }
064
065    public static CompiledScript compileScript(ScriptEngine engine, File file) throws ScriptException {
066        if (engine instanceof Compilable) {
067            Compilable comp = (Compilable) engine;
068            try {
069                Reader reader = new FileReader(file);
070                try {
071                    return comp.compile(reader);
072                } finally {
073                    reader.close();
074                }
075            } catch (IOException e) {
076                throw new ScriptException(e);
077            }
078        } else {
079            return null;
080        }
081    }
082
083    /**
084     * Lazy init scripting manager to avoid loading script engines when no scripting is used.
085     * <p>
086     * Javax Scripting is not used by default in WebWengine, we are using directly the Groovy engine. This also fixes an
087     * annoying pb on Mac in java5 due to AppleScripting which is failing to register.
088     *
089     * @return the scriptMgr
090     */
091    public ScriptEngineManager getEngineManager() {
092        if (scriptMgr == null) {
093            scriptMgr = new ScriptEngineManager();
094        }
095        return scriptMgr;
096    }
097
098    public boolean isScript(String ext) {
099        return getEngineManager().getEngineByExtension(ext) != null;
100    }
101
102    public Object runScript(ScriptFile script) throws ScriptException {
103        return runScript(script, null);
104    }
105
106    public Object runScript(ScriptFile script, Map<String, Object> args) throws ScriptException {
107        if (log.isDebugEnabled()) {
108            log.debug("## Running Script: " + script.getFile());
109        }
110        if ("groovy".equals(script.getExtension())) {
111            try {
112                return loader.getGroovyScripting().eval(script.file, args);
113            } catch (GroovyRuntimeException e) {
114                throw new ScriptException(e);
115            }
116        } else {
117            return _runScript(script, args);
118        }
119    }
120
121    // TODO: add an output stream to use as arg?
122    protected Object _runScript(ScriptFile script, Map<String, Object> args) throws ScriptException {
123        SimpleBindings bindings = new SimpleBindings();
124        if (args != null) {
125            bindings.putAll(args);
126        }
127        String ext = script.getExtension();
128        // check for a script engine
129        ScriptEngine engine = getEngineManager().getEngineByExtension(ext);
130        if (engine != null) {
131            ScriptContext ctx = new SimpleScriptContext();
132            ctx.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
133            CompiledScript comp = getCompiledScript(engine, script.getFile()); // use cache for compiled scripts
134            if (comp != null) {
135                return comp.eval(ctx);
136            } // compilation not supported - eval it on the fly
137            try {
138                Reader reader = new FileReader(script.getFile());
139                try { // TODO use __result__ to pass return value for engine that doesn't returns like jython
140                    Object result = engine.eval(reader, ctx);
141                    if (result == null) {
142                        result = bindings.get("__result__");
143                    }
144                    return result;
145                } finally {
146                    reader.close();
147                }
148            } catch (IOException e) {
149                throw new ScriptException(e);
150            }
151        }
152        return null;
153    }
154
155    public CompiledScript getCompiledScript(ScriptEngine engine, File file) throws ScriptException {
156        Entry entry = cache.get(file);
157        long tm = file.lastModified();
158        if (entry != null) {
159            if (entry.lastModified < tm) { // recompile
160                entry.script = compileScript(engine, file);
161                entry.lastModified = tm;
162            }
163            return entry.script;
164        }
165        CompiledScript script = compileScript(engine, file);
166        if (script != null) {
167            cache.putIfAbsent(file, new Entry(script, tm));
168            return script;
169        }
170        return null;
171    }
172
173    class Entry {
174        public CompiledScript script;
175
176        public long lastModified;
177
178        Entry(CompiledScript script, long lastModified) {
179            this.lastModified = lastModified;
180            this.script = script;
181        }
182    }
183
184}