001/*
002 * (C) Copyright 2006-2011 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 */
019package org.nuxeo.ecm.automation.core.scripting;
020
021import groovy.lang.Binding;
022import groovy.lang.GroovyClassLoader;
023import groovy.lang.GroovyCodeSource;
024import groovy.lang.Script;
025
026import java.io.File;
027import java.io.IOException;
028import java.net.URL;
029import java.util.Map;
030import java.util.concurrent.ConcurrentHashMap;
031
032import org.codehaus.groovy.control.CompilerConfiguration;
033import org.codehaus.groovy.runtime.InvokerHelper;
034
035/**
036 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
037 */
038public class GroovyScripting {
039
040    protected final GroovyClassLoader loader;
041
042    // compiled script class cache
043    protected Map<File, Entry> cache;
044
045    public static ClassLoader getParentLoader() {
046        ClassLoader cl = Thread.currentThread().getContextClassLoader();
047        return cl == null ? GroovyScripting.class.getClassLoader() : cl;
048    }
049
050    public GroovyScripting() {
051        this(getParentLoader(), new CompilerConfiguration());
052    }
053
054    public GroovyScripting(boolean debug) {
055        this(getParentLoader(), debug);
056    }
057
058    public GroovyScripting(ClassLoader parent, boolean debug) {
059        CompilerConfiguration cfg = new CompilerConfiguration();
060        cfg.setDebug(debug);
061        if (debug) {
062            cfg.setRecompileGroovySource(true);
063        }
064        loader = new GroovyClassLoader(parent, cfg);
065        cache = new ConcurrentHashMap<File, Entry>();
066    }
067
068    public GroovyScripting(ClassLoader parent, CompilerConfiguration cfg) {
069        loader = new GroovyClassLoader(parent, cfg);
070        cache = new ConcurrentHashMap<File, Entry>();
071    }
072
073    public void addClasspath(String cp) {
074        loader.addClasspath(cp);
075    }
076
077    public void addClasspathUrl(URL cp) {
078        loader.addURL(cp);
079    }
080
081    public void clearCache() {
082        cache = new ConcurrentHashMap<File, Entry>();
083        loader.clearCache();
084    }
085
086    public Class<?> loadClass(String className) throws ClassNotFoundException {
087        return loader.loadClass(className);
088    }
089
090    public GroovyClassLoader getGroovyClassLoader() {
091        return loader;
092    }
093
094    // TODO add debug mode : return new GroovyShell(new
095    // Binding(args)).evaluate(script.getFile()); ?
096    public Object eval(File file, Map<String, Object> context) throws IOException {
097        return eval(file, context == null ? new Binding() : new Binding(context));
098    }
099
100    public Object eval(File file, Binding context) throws IOException {
101        // convenience out global variable (for compatibility with scripts on
102        // webengine 1.0 beta)
103        context.setVariable("out", System.out);
104        return getScript(file, context).run();
105    }
106
107    public Class<?> compile(File file) throws IOException {
108        GroovyCodeSource codeSource = new GroovyCodeSource(file);
109        // do not use cache - we are maintaining our proper cache - based on
110        // lastModified
111        return loader.parseClass(codeSource, false);
112    }
113
114    public Script getScript(File file, Binding context) throws IOException {
115        Class<?> klass = null;
116        long lastModified = file.lastModified();
117        Entry entry = cache.get(file);
118        if (entry != null && entry.lastModified == lastModified) { // in cache -
119                                                                   // use it
120            klass = entry.klass;
121        } else { // not in cache or invalid
122            klass = compile(file); // compile
123            cache.put(file, new Entry(klass, lastModified)); // cache it
124        }
125        return InvokerHelper.createScript(klass, context);
126    }
127
128    public Script getScript(String content, Binding context) {
129        Class<?> klass = loader.parseClass(content); // compile
130        return InvokerHelper.createScript(klass, context);
131    }
132
133    static class Entry {
134        final long lastModified;
135
136        final Class<?> klass;
137
138        Entry(Class<?> klass, long lastModified) {
139            this.klass = klass;
140            this.lastModified = lastModified;
141        }
142    }
143
144}