001/*
002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     bstefanescu
016 *
017 * $Id$
018 */
019
020package org.nuxeo.ecm.webengine.scripting;
021
022import groovy.lang.Binding;
023import groovy.lang.GroovyClassLoader;
024import groovy.lang.GroovyCodeSource;
025import groovy.lang.GroovyRuntimeException;
026import groovy.lang.Script;
027
028import java.io.File;
029import java.io.IOException;
030import java.net.URL;
031import java.util.Map;
032import java.util.concurrent.ConcurrentHashMap;
033
034import org.codehaus.groovy.control.CompilerConfiguration;
035import org.codehaus.groovy.runtime.InvokerHelper;
036
037/**
038 * For Groovy we are not using the javax.script API because we need more control over debug mode and script class
039 * loader. Groovy scritps will be processed by this class
040 *
041 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
042 */
043public class GroovyScripting {
044
045    protected final GroovyClassLoader loader;
046
047    // compiled script class cache
048    protected Map<File, Entry> cache;
049
050    public GroovyScripting(ClassLoader parent) {
051        CompilerConfiguration cfg = new CompilerConfiguration();
052        loader = new GroovyClassLoader(parent, cfg);
053        cache = new ConcurrentHashMap<File, Entry>();
054    }
055
056    public GroovyScripting(ClassLoader parent, CompilerConfiguration cfg) {
057        loader = new GroovyClassLoader(parent, cfg);
058        cache = new ConcurrentHashMap<File, Entry>();
059    }
060
061    public void addClasspath(String cp) {
062        loader.addClasspath(cp);
063    }
064
065    public void addClasspathUrl(URL cp) {
066        loader.addURL(cp);
067    }
068
069    public void clearCache() {
070        cache = new ConcurrentHashMap<File, Entry>();
071        loader.clearCache();
072    }
073
074    public Class<?> loadClass(String className) throws ClassNotFoundException {
075        return loader.loadClass(className);
076    }
077
078    public GroovyClassLoader getGroovyClassLoader() {
079        return loader;
080    }
081
082    // TODO add debug mode : return new GroovyShell(new Binding(args)).evaluate(script.getFile()); ?
083    public Object eval(File file, Map<String, Object> context) throws GroovyRuntimeException {
084        return eval(file, context == null ? new Binding() : new Binding(context));
085    }
086
087    public Object eval(File file, Binding context) throws GroovyRuntimeException {
088        // convenience out global variable (for compatibility with scripts on webengine 1.0 beta)
089        context.setVariable("out", System.out);
090        return getScript(file, context).run();
091    }
092
093    public Class<?> compile(File file) throws GroovyRuntimeException {
094        GroovyCodeSource codeSource;
095        try {
096            codeSource = new GroovyCodeSource(file);
097        } catch (IOException e) {
098            throw new GroovyRuntimeException(e);
099        }
100        // do not use cache - we are maintaining our proper cache - based on lastModified
101        return loader.parseClass(codeSource, false);
102    }
103
104    public Script getScript(File file, Binding context) throws GroovyRuntimeException {
105        Class<?> klass = null;
106        long lastModified = file.lastModified();
107        Entry entry = cache.get(file);
108        if (entry != null && entry.lastModified == lastModified) { // in cache - use it
109            klass = entry.klass;
110        } else { // not in cache or invalid
111            klass = compile(file); // compile
112            cache.put(file, new Entry(klass, lastModified)); // cache it
113        }
114        return InvokerHelper.createScript(klass, context);
115    }
116
117    static class Entry {
118        final long lastModified;
119
120        final Class<?> klass;
121
122        Entry(Class<?> klass, long lastModified) {
123            this.klass = klass;
124            this.lastModified = lastModified;
125        }
126    }
127
128}