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.IOException;
026import java.net.URL;
027import java.util.Map;
028import java.util.concurrent.ConcurrentHashMap;
029
030import org.codehaus.groovy.control.CompilerConfiguration;
031import org.codehaus.groovy.runtime.InvokerHelper;
032
033import groovy.lang.Binding;
034import groovy.lang.GroovyClassLoader;
035import groovy.lang.GroovyCodeSource;
036import groovy.lang.GroovyRuntimeException;
037import groovy.lang.Script;
038
039/**
040 * For Groovy we are not using the javax.script API because we need more control over debug mode and script class
041 * loader. Groovy scritps will be processed by this class
042 *
043 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
044 */
045public class GroovyScripting {
046
047    protected final GroovyClassLoader loader;
048
049    // compiled script class cache
050    protected Map<File, Entry> cache;
051
052    public GroovyScripting(ClassLoader parent) {
053        CompilerConfiguration cfg = new CompilerConfiguration();
054        loader = new GroovyClassLoader(parent, cfg);
055        cache = new ConcurrentHashMap<File, Entry>();
056    }
057
058    public GroovyScripting(ClassLoader parent, CompilerConfiguration cfg) {
059        loader = new GroovyClassLoader(parent, cfg);
060        cache = new ConcurrentHashMap<File, Entry>();
061    }
062
063    public void addClasspath(String cp) {
064        loader.addClasspath(cp);
065    }
066
067    public void addClasspathUrl(URL cp) {
068        loader.addURL(cp);
069    }
070
071    public void clearCache() {
072        cache = new ConcurrentHashMap<File, Entry>();
073        loader.clearCache();
074    }
075
076    public Class<?> loadClass(String className) throws ClassNotFoundException {
077        return loader.loadClass(className);
078    }
079
080    public GroovyClassLoader getGroovyClassLoader() {
081        return loader;
082    }
083
084    // TODO add debug mode : return new GroovyShell(new Binding(args)).evaluate(script.getFile()); ?
085    public Object eval(File file, Map<String, Object> context) throws GroovyRuntimeException {
086        return eval(file, context == null ? new Binding() : new Binding(context));
087    }
088
089    public Object eval(File file, Binding context) throws GroovyRuntimeException {
090        // convenience out global variable (for compatibility with scripts on webengine 1.0 beta)
091        context.setVariable("out", System.out);
092        return getScript(file, context).run();
093    }
094
095    public Class<?> compile(File file) throws GroovyRuntimeException {
096        GroovyCodeSource codeSource;
097        try {
098            codeSource = new GroovyCodeSource(file);
099        } catch (IOException e) {
100            throw new GroovyRuntimeException(e);
101        }
102        // do not use cache - we are maintaining our proper cache - based on lastModified
103        return loader.parseClass(codeSource, false);
104    }
105
106    public Script getScript(File file, Binding context) throws GroovyRuntimeException {
107        Class<?> klass = null;
108        long lastModified = file.lastModified();
109        Entry entry = cache.get(file);
110        if (entry != null && entry.lastModified == lastModified) { // in cache - use it
111            klass = entry.klass;
112        } else { // not in cache or invalid
113            klass = compile(file); // compile
114            cache.put(file, new Entry(klass, lastModified)); // cache it
115        }
116        return InvokerHelper.createScript(klass, context);
117    }
118
119    static class Entry {
120        final long lastModified;
121
122        final Class<?> klass;
123
124        Entry(Class<?> klass, long lastModified) {
125            this.klass = klass;
126            this.lastModified = lastModified;
127        }
128    }
129
130}