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}