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<>(); 056 } 057 058 public GroovyScripting(ClassLoader parent, CompilerConfiguration cfg) { 059 loader = new GroovyClassLoader(parent, cfg); 060 cache = new ConcurrentHashMap<>(); 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<>(); 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}