View Javadoc

    1   /*
    2    * $Id: GroovyScriptEngine.java,v 1.4 2004/04/19 07:29:43 cpoirier Exp $version Jan 9, 2004 12:19:58 PM $user Exp $
    3    *
    4    * Copyright 2003 (C) Sam Pullara. All Rights Reserved.
    5    *
    6    * Redistribution and use of this software and associated documentation
    7    * ("Software"), with or without modification, are permitted provided that the
    8    * following conditions are met: 1. Redistributions of source code must retain
    9    * copyright statements and notices. Redistributions must also contain a copy
    10   * of this document. 2. Redistributions in binary form must reproduce the above
    11   * copyright notice, this list of conditions and the following disclaimer in
    12   * the documentation and/or other materials provided with the distribution. 3.
    13   * The name "groovy" must not be used to endorse or promote products derived
    14   * from this Software without prior written permission of The Codehaus. For
    15   * written permission, please contact info@codehaus.org. 4. Products derived
    16   * from this Software may not be called "groovy" nor may "groovy" appear in
    17   * their names without prior written permission of The Codehaus. "groovy" is a
    18   * registered trademark of The Codehaus. 5. Due credit should be given to The
    19   * Codehaus - http://groovy.codehaus.org/
    20   *
    21   * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
    22   * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    23   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    24   * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
    25   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    26   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    27   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    28   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    29   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
    30   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
    31   * DAMAGE.
    32   *
    33   */
    34  package groovy.util;
    35
    36  import groovy.lang.Binding;
    37  import groovy.lang.GroovyClassLoader;
    38  import groovy.lang.Script;
    39
    40  import java.io.BufferedReader;
    41  import java.io.File;
    42  import java.io.IOException;
    43  import java.io.InputStreamReader;
    44  import java.net.MalformedURLException;
    45  import java.net.URL;
    46  import java.net.URLConnection;
    47  import java.security.AccessController;
    48  import java.security.PrivilegedAction;
    49  import java.util.Collections;
    50  import java.util.HashMap;
    51  import java.util.Iterator;
    52  import java.util.Map;
    53
    54  import org.codehaus.groovy.control.CompilationFailedException;
    55  import org.codehaus.groovy.runtime.InvokerHelper;
    56
    57  /***
    58   * @author sam
    59   *
    60   * To change the template for this generated type comment go to Window -
    61   * Preferences - Java - Code Generation - Code and Comments
    62   */
    63  public class GroovyScriptEngine implements ResourceConnector {
    64
    65  	/***
    66  	 * Simple testing harness for the GSE. Enter script roots as arguments and
    67  	 * then input script names to run them.
    68  	 *
    69  	 * @param args
    70  	 * @throws Exception
    71  	 */
    72  	public static void main(String[] args) throws Exception {
    73  		URL[] roots = new URL[args.length];
    74  		for (int i = 0; i < roots.length; i++) {
    75  			roots[i] = new File(args[i]).toURL();
    76  		}
    77  		GroovyScriptEngine gse = new GroovyScriptEngine(roots);
    78  		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    79  		String line;
    80  		while (true) {
    81  			System.out.print("groovy> ");
    82  			if ((line = br.readLine()) == null || line.equals("quit"))
    83  				break;
    84  			try {
    85  				System.out.println(gse.run(line, new Binding()));
    86  			} catch (Exception e) {
    87  				e.printStackTrace();
    88  			}
    89  		}
    90  	}
    91
    92  	private URL[] roots;
    93  	private Map scriptCache = Collections.synchronizedMap(new HashMap());
    94  	private ResourceConnector rc;
    95
    96  	private static class ScriptCacheEntry {
    97  		private Class scriptClass;
    98  		private long lastModified;
    99  		private Map dependencies = new HashMap();
    100 	}
    101
    102 	public URLConnection getResourceConnection(String resourceName) throws ResourceException {
    103 		// Get the URLConnection
    104 		URLConnection groovyScriptConn = null;
    105
    106 		ResourceException se = null;
    107 		for (int i = 0; i < roots.length; i++) {
    108 			URL scriptURL = null;
    109 			try {
    110 				scriptURL = new URL(roots[i], resourceName);
    111 				groovyScriptConn = scriptURL.openConnection();
    112 			} catch (MalformedURLException e) {
    113 				String message = "Malformed URL: " + roots[i] + ", " + resourceName;
    114 				if (se == null) {
    115 					se = new ResourceException(message);
    116 				} else {
    117 					se = new ResourceException(message, se);
    118 				}
    119 			} catch (IOException e1) {
    120 				String message = "Cannot open URL: " + scriptURL;
    121 				if (se == null) {
    122 					se = new ResourceException(message);
    123 				} else {
    124 					se = new ResourceException(message, se);
    125 				}
    126 			}
    127
    128 		}
    129
    130 		// If we didn't find anything, report on all the exceptions that
    131 		// occurred.
    132 		if (groovyScriptConn == null) {
    133 			throw se;
    134 		}
    135
    136 		return groovyScriptConn;
    137 	}
    138
    139 	/***
    140 	 * The groovy script engine will run groovy scripts and reload them and
    141 	 * their dependencies when they are modified. This is useful for embedding
    142 	 * groovy in other containers like games and application servers.
    143 	 */
    144 	public GroovyScriptEngine(URL[] roots) {
    145 		this.roots = roots;
    146 		this.rc = this;
    147 	}
    148
    149 	public GroovyScriptEngine(String[] args) throws IOException {
    150 		URL[] roots = new URL[args.length];
    151 		for (int i = 0; i < roots.length; i++) {
    152 			roots[i] = new File(args[i]).toURL();
    153 		}
    154 		this.rc = this;
    155 	}
    156
    157 	public GroovyScriptEngine(String arg) throws IOException {
    158 		this.roots = new URL[1];
    159 		roots[0] = new File(arg).toURL();
    160 		this.rc = this;
    161 	}
    162
    163 	public GroovyScriptEngine(ResourceConnector rc) {
    164 		this.rc = rc;
    165 	}
    166
    167 	public String run(String script, String argument) throws ResourceException, ScriptException {
    168 		Binding binding = new Binding();
    169 		binding.setVariable("arg", argument);
    170 		Object result = run(script, binding);
    171 		return result == null ? "" : result.toString();
    172 	}
    173
    174 	public Object run(String script, Binding binding) throws ResourceException, ScriptException {
    175
    176 		ScriptCacheEntry entry;
    177
    178 		script = script.intern();
    179 		synchronized (script) {
    180
    181 			URLConnection groovyScriptConn = rc.getResourceConnection(script);
    182
    183 			// URL last modified
    184 			long lastModified = groovyScriptConn.getLastModified();
    185 			// Check the cache for the script
    186 			entry = (ScriptCacheEntry) scriptCache.get(script);
    187 			// If the entry isn't null check all the dependencies
    188 			boolean dependencyOutOfDate = false;
    189 			if (entry != null) {
    190 				for (Iterator i = entry.dependencies.keySet().iterator(); i.hasNext();) {
    191 					URLConnection urlc = null;
    192 					URL url = (URL) i.next();
    193 					try {
    194 						urlc = url.openConnection();
    195 						urlc.setDoInput(false);
    196 						urlc.setDoOutput(false);
    197 						long dependentLastModified = urlc.getLastModified();
    198 						if (dependentLastModified > ((Long) entry.dependencies.get(url)).longValue()) {
    199 							dependencyOutOfDate = true;
    200 							break;
    201 						}
    202 					} catch (IOException ioe) {
    203 						dependencyOutOfDate = true;
    204 						break;
    205 					}
    206 				}
    207 			}
    208
    209 			if (entry == null || entry.lastModified < lastModified || dependencyOutOfDate) {
    210 				// Make a new entry
    211 				entry = new ScriptCacheEntry();
    212
    213 				// Closure variable
    214 				final ScriptCacheEntry finalEntry = entry;
    215
    216 				// Compile the script into an object
    217 				GroovyClassLoader groovyLoader =
    218 					(GroovyClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
    219 						public Object run() {
    220 							return new GroovyClassLoader(getClass().getClassLoader()) {
    221 								protected Class findClass(String className) throws ClassNotFoundException {
    222 									String filename = className.replace('.', File.separatorChar) + ".groovy";
    223 									URLConnection dependentScriptConn = null;
    224 									try {
    225 										dependentScriptConn = rc.getResourceConnection(filename);
    226 										finalEntry.dependencies.put(
    227 											dependentScriptConn.getURL(),
    228 											new Long(dependentScriptConn.getLastModified()));
    229 									} catch (ResourceException e1) {
    230 										throw new ClassNotFoundException("Could not read " + className + ": " + e1);
    231 									}
    232 									try {
    233 										return parseClass(dependentScriptConn.getInputStream(), filename);
    234 									} catch (CompilationFailedException e2) {
    235 										throw new ClassNotFoundException("Syntax error in " + className + ": " + e2);
    236 									} catch (IOException e2) {
    237 										throw new ClassNotFoundException("Problem reading " + className + ": " + e2);
    238 									}
    239 								}
    240 							};
    241 						}
    242 					});
    243
    244 				try {
    245 					entry.scriptClass = groovyLoader.parseClass(groovyScriptConn.getInputStream(), script);
    246 				} catch (Exception e) {
    247 					throw new ScriptException("Could not parse script: " + script, e);
    248 				}
    249 				entry.lastModified = lastModified;
    250 				scriptCache.put(script, entry);
    251 			}
    252 		}
    253 		Script scriptObject = InvokerHelper.createScript(entry.scriptClass, binding);
    254 		return scriptObject.run();
    255 	}
    256 }