View Javadoc

1   /*
2    * $Id: GroovyScriptEngine.java,v 1.6 2005/04/24 05:55:00 spullara 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      * @param roots This an array of URLs where Groovy scripts will be stored. They should
145      * be layed out using their package structure like Java classes 
146 	 */
147 	public GroovyScriptEngine(URL[] roots) {
148 		this.roots = roots;
149 		this.rc = this;
150 	}
151 
152 	public GroovyScriptEngine(String[] args) throws IOException {
153 		roots = new URL[args.length];
154 		for (int i = 0; i < roots.length; i++) {
155 			roots[i] = new File(args[i]).toURL();
156 		}
157 		this.rc = this;
158 	}
159 
160 	public GroovyScriptEngine(String arg) throws IOException {
161 		roots = new URL[1];
162 		roots[0] = new File(arg).toURL();
163 		this.rc = this;
164 	}
165 
166 	public GroovyScriptEngine(ResourceConnector rc) {
167 		this.rc = rc;
168 	}
169 
170 	public String run(String script, String argument) throws ResourceException, ScriptException {
171 		Binding binding = new Binding();
172 		binding.setVariable("arg", argument);
173 		Object result = run(script, binding);
174 		return result == null ? "" : result.toString();
175 	}
176 
177 	public Object run(String script, Binding binding) throws ResourceException, ScriptException {
178 
179 		ScriptCacheEntry entry;
180 
181 		script = script.intern();
182 		synchronized (script) {
183 
184 			URLConnection groovyScriptConn = rc.getResourceConnection(script);
185 
186 			// URL last modified
187 			long lastModified = groovyScriptConn.getLastModified();
188 			// Check the cache for the script
189 			entry = (ScriptCacheEntry) scriptCache.get(script);
190 			// If the entry isn't null check all the dependencies
191 			boolean dependencyOutOfDate = false;
192 			if (entry != null) {
193 				for (Iterator i = entry.dependencies.keySet().iterator(); i.hasNext();) {
194 					URLConnection urlc = null;
195 					URL url = (URL) i.next();
196 					try {
197 						urlc = url.openConnection();
198 						urlc.setDoInput(false);
199 						urlc.setDoOutput(false);
200 						long dependentLastModified = urlc.getLastModified();
201 						if (dependentLastModified > ((Long) entry.dependencies.get(url)).longValue()) {
202 							dependencyOutOfDate = true;
203 							break;
204 						}
205 					} catch (IOException ioe) {
206 						dependencyOutOfDate = true;
207 						break;
208 					}
209 				}
210 			}
211 
212 			if (entry == null || entry.lastModified < lastModified || dependencyOutOfDate) {
213 				// Make a new entry
214 				entry = new ScriptCacheEntry();
215 
216 				// Closure variable
217 				final ScriptCacheEntry finalEntry = entry;
218 
219 				// Compile the script into an object
220 				GroovyClassLoader groovyLoader = 
221 					(GroovyClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
222 						public Object run() {
223 							return new GroovyClassLoader(getClass().getClassLoader()) {
224 								protected Class findClass(String className) throws ClassNotFoundException {
225 									String filename = className.replace('.', File.separatorChar) + ".groovy";
226 									URLConnection dependentScriptConn = null;
227 									try {
228 										dependentScriptConn = rc.getResourceConnection(filename);
229 										finalEntry.dependencies.put(
230 											dependentScriptConn.getURL(),
231 											new Long(dependentScriptConn.getLastModified()));
232 									} catch (ResourceException e1) {
233 										throw new ClassNotFoundException("Could not read " + className + ": " + e1);
234 									}
235 									try {
236 										return parseClass(dependentScriptConn.getInputStream(), filename);
237 									} catch (CompilationFailedException e2) {
238 										throw new ClassNotFoundException("Syntax error in " + className + ": " + e2);
239 									} catch (IOException e2) {
240 										throw new ClassNotFoundException("Problem reading " + className + ": " + e2);
241 									}
242 								}
243 							};
244 						}
245 					});
246 
247 				try {
248 					entry.scriptClass = groovyLoader.parseClass(groovyScriptConn.getInputStream(), script);
249 				} catch (Exception e) {
250 					throw new ScriptException("Could not parse script: " + script, e);
251 				}
252 				entry.lastModified = lastModified;
253 				scriptCache.put(script, entry);
254 			}
255 		}
256 		Script scriptObject = InvokerHelper.createScript(entry.scriptClass, binding);
257 		return scriptObject.run();
258 	}
259 }