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