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}