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 }