1
2
3
4
5
6
7
8
9
10
11 package org.astrogrid.jes.jobscheduler.impl.groovy;
12
13 import org.astrogrid.applications.beans.v1.parameters.ParameterValue;
14 import org.astrogrid.community.User;
15 import org.astrogrid.community.beans.v1.Credentials;
16 import org.astrogrid.scripting.Toolbox;
17 import org.astrogrid.store.Ivorn;
18 import org.astrogrid.workflow.beans.v1.For;
19 import org.astrogrid.workflow.beans.v1.If;
20 import org.astrogrid.workflow.beans.v1.Input;
21 import org.astrogrid.workflow.beans.v1.Output;
22 import org.astrogrid.workflow.beans.v1.Parfor;
23 import org.astrogrid.workflow.beans.v1.Set;
24 import org.astrogrid.workflow.beans.v1.Tool;
25 import org.astrogrid.workflow.beans.v1.While;
26
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 import org.codehaus.groovy.GroovyException;
30 import org.codehaus.groovy.control.CompilationFailedException;
31 import org.codehaus.groovy.control.CompilerConfiguration;
32 import org.codehaus.groovy.control.messages.WarningMessage;
33
34 import EDU.oswego.cs.dl.util.concurrent.Callable;
35 import EDU.oswego.cs.dl.util.concurrent.TimedCallable;
36 import groovy.lang.Binding;
37 import groovy.lang.GroovyShell;
38 import groovy.lang.Script;
39
40 import java.io.IOException;
41 import java.io.PrintStream;
42 import java.lang.ref.SoftReference;
43 import java.net.URISyntaxException;
44 import java.util.Map;
45
46 import javax.imageio.IIOException;
47
48 /*** class that encapuslates the execution and evaluation of grouvy code.
49 * <p>
50 * Uses a <a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/TimedCallable.html">Timed Callable</a>
51 * to abort execution after a certain time.
52 * <p>
53 * Added in codebases - to differentiate between user and system script.
54 * @todo find out how to add in security managers to this too.
55 * @author Noel Winstanley nw@jb.man.ac.uk 29-Jul-2004
56 *
57 */
58 public class JesShell {
59 /***
60 * Commons Logger for this class
61 */
62 private static final Log logger = LogFactory.getLog(JesShell.class);
63
64
65 /*** Construct a new JesShell
66 *
67 */
68 public JesShell() {
69 super();
70 }
71
72 private static final long EVAL_LIMIT = 60 * 1000;
73 private static final long EXEC_LIMIT = 10 * 60 * 1000;
74
75 /*** for efficiencies sake, only ever create a single, static interpreter.
76 * this will be ok - as we parse-run all code we pass through it.
77 * plus, we know it is being called in a single thread (the scheduler) only.
78 * however, don't know for sure that this isn't memory leaking. so will be more cautious, and create a new one for each new shell
79 * (i.e. for each workflow processed). but this seems really slow. will implement a kind of pool instead - reuse each object 500 times, then get shot of it.
80 * @modified - made this an soft reference too - so can get reclaimed if things get tight. Done the same with the cached scripts - incase they hold references back to the shell that created them
81 */
82 private static SoftReference shell = new SoftReference(new GroovyShell());
83 private static int useCount = 0;
84 private static final int USE_LIMIT = 500;
85 private static final CompilerConfiguration config = new CompilerConfiguration();
86 static {
87 config.setVerbose(true);
88 config.setWarningLevel(WarningMessage.PARANOIA);
89 }
90 protected static GroovyShell getGroovyShell() {
91
92 GroovyShell s = (GroovyShell)shell.get();
93 if (useCount++ > USE_LIMIT || s == null) {
94 logger.info("Recreating groovy shell");
95 useCount = 0;
96 shell.clear();
97 s = new GroovyShell(config);
98 shell = new SoftReference(s);
99 }
100 return s;
101 }
102
103
104 private static SoftReference toolbox = new SoftReference(new Toolbox());
105 protected static Toolbox getToolbox() {
106 Toolbox t = (Toolbox)toolbox.get();
107 if (t == null) {
108 logger.info("Recreating toolbox");
109 t = new Toolbox();
110 toolbox = new SoftReference(t);
111 }
112 return t;
113 }
114
115 protected JesInterface jes;
116
117
118
119 public String evaluateIndex(Script indexScript,ActivityStatusStore statusStore) {
120 Binding triggerBinding = createTriggerBinding(statusStore);
121 indexScript.setBinding(triggerBinding);
122 return (String)indexScript.run();
123 }
124 /***
125 * compile a rule script into the rule codebase.
126 * @modified - specifies a code source while compiling.
127 *
128 * commented out for now - needs further tuning and investigation
129 * */
130 public Script compileRuleScript (String script) throws CompilationFailedException, IOException {
131
132
133
134
135 return getGroovyShell().parse(script);
136 }
137
138 /***compile a user script into the user codebase.
139 * @modified - specifies a code source while compiling.
140 * commented out for now - needs further tuning and investigation
141 * */
142 public Script compileUserScript (String script) throws CompilationFailedException, IOException {
143
144
145
146
147 return getGroovyShell().parse(script);
148 }
149
150
151
152
153
154
155
156
157
158
159
160 /*** execute the body (concequence) of a rule */
161 public void executeBody(Rule r,ActivityStatusStore statusStore, Map rules) throws CompilationFailedException, IOException {
162 logger.info("firing " + r.getName());
163 Binding bodyBinding = createBodyBinding(statusStore,rules);
164 logger.debug(r);
165 Script sc = r.getCompiledBody();
166 if (sc == null) {
167 sc = compileRuleScript( r.getBody());
168 r.setCompiledBody(sc);
169 }
170 sc.setBinding(bodyBinding);
171 sc.run();
172 }
173 /*** execute a script activity
174 * @throws InterruptedException
175 * @throws IOException
176 * @throws InterruptedException
177 * @throws GroovyException*/
178 public void executeScript(String script,String id, ActivityStatusStore statusStore,Map rules, PrintStream errStream, PrintStream outStream) throws GroovyException, IOException, InterruptedException {
179 logger.info("Running script for id " + id);
180 logger.debug(script);
181 Binding scriptBinding = createScriptBinding(statusStore,rules);
182 Vars vars= statusStore.getEnv(id);
183 vars.addToBinding(scriptBinding);
184 Script sc = compileUserScript(script);
185 sc.setBinding(scriptBinding);
186 PrintStream originalErr = System.err;
187 PrintStream originalOut = System.out;
188 try {
189 System.setErr(errStream);
190 System.setOut(outStream);
191 runWithTimeLimit(sc,EXEC_LIMIT);
192 } finally {
193 System.setErr(originalErr);
194 System.setOut(originalOut);
195 }
196 logger.debug("Completed Script execution");
197 vars.readFromBinding(scriptBinding);
198 }
199
200 /*** used to evaluate user-supplied expressions - if tests, etc
201 * @param expr
202 * @param id
203 * @param statusStore
204 * @param rules
205 * @return
206 * @throws IOException
207 * @throws InterruptedException
208 * @throws GroovyException*/
209 public Object evaluateUserExpr(String expr,String id,ActivityStatusStore statusStore, Map rules) throws GroovyException, IOException, InterruptedException {
210 logger.debug("exaluating " + expr);
211 Binding scriptBinding = createScriptBinding(statusStore,rules);
212 Vars vars = statusStore.getEnv(id);
213 vars.addToBinding(scriptBinding);
214
215
216
217 StringBuffer gExpr = (new StringBuffer("x = <<<GSTRING\n"))
218 .append(expr)
219 .append("\nGSTRING\n ")
220 .append("(x instanceof GString && x.getValueCount() == 1 && x.getStrings().find{it.size() > 0} == null) ? x.getValue(0) : x");
221
222 Script sc = compileUserScript(gExpr.toString());
223 sc.setBinding(scriptBinding);
224 Object result =runWithTimeLimit(sc,EVAL_LIMIT);
225
226 if (logger.isDebugEnabled()) {
227 logger.debug("result: '" + result + "' type: " + result.getClass().getName());
228 }
229 return result;
230
231 }
232 /*** wrap a script in a timed callable - will throws an interrupted if script overruns the time
233 *
234 * @param sc script to execute
235 * @param timeLimit limit in milliseconds to execute the script for,
236 * @return the result
237 * @throws GroovyException if therer's a problem compiling or executing the script
238 * @throws IOException
239 * @throws InterruptedException if the script execution times out.
240 */
241 protected Object runWithTimeLimit(final Script sc,long timeLimit) throws GroovyException, IOException, InterruptedException {
242 Callable c = new Callable() {
243
244 public Object call() throws Exception {
245 return sc.run();
246 }
247 };
248 TimedCallable tc = new TimedCallable(c,timeLimit);
249 try {
250 return tc.call();
251 } catch (InterruptedException e) {
252 throw e;
253 } catch (GroovyException e) {
254 throw e;
255 } catch (IIOException e) {
256 throw e;
257 } catch (Exception e) {
258 GroovyException x =new GroovyException(e.getMessage());
259 x.initCause(e);
260 throw x;
261 }
262 }
263
264 /*** executes a 'set' activity
265 * @throws IOException
266 * @throws InterruptedException
267 * @throws GroovyException*/
268 public void executeSet(Set set,String state,ActivityStatusStore statusStore, Map rules) throws GroovyException, IOException, InterruptedException {
269 Vars vars = statusStore.getEnv(state);
270 if (set.getValue() != null) {
271 Object result = evaluateUserExpr(set.getValue(),state,statusStore,rules);
272 vars.set(set.getVar(),result);
273 } else {
274
275 vars.set(set.getVar(),null);
276 }
277 }
278
279
280
281 /*** evaluates the test of an 'if' activity
282 * @throws IOException
283 * @throws InterruptedException
284 * @throws GroovyException*/
285 public Object evaluateIfCondition(If ifObj,ActivityStatusStore statusStore,Map rules) throws GroovyException, IOException, InterruptedException {
286 return evaluateUserExpr(ifObj.getTest(),ifObj.getId(),statusStore,rules);
287
288 }
289 /*** evaluates the test of a while activity
290 * @throws IOException
291 * @throws InterruptedException
292 * @throws GroovyException*/
293 public Object evaluateWhileCondition(While whileObj,ActivityStatusStore statusStore,Map rules) throws GroovyException, IOException, InterruptedException {
294 return evaluateUserExpr(whileObj.getTest(), whileObj.getId(),statusStore,rules);
295 }
296 /*** evaluates the items of a for activity
297 * @throws IOException
298 * @throws InterruptedException
299 * @throws GroovyException*/
300 public Object evaluateForItems(For forObj,ActivityStatusStore statusStore,Map rules) throws GroovyException, IOException, InterruptedException {
301 return evaluateUserExpr(forObj.getItems(),forObj.getId(),statusStore,rules);
302
303 }
304 /*** evaluate the items of a parfor activity
305 * @throws IOException
306 * @throws InterruptedException
307 * @throws GroovyException*/
308 public Object evaluateParforItems(Parfor forObj,ActivityStatusStore statusStore,Map rules) throws GroovyException, IOException, InterruptedException {
309 return evaluateUserExpr(forObj.getItems(),forObj.getId(),statusStore,rules);
310
311 }
312 /*** evaluate the parameter values of a tool */
313 public Tool evaluateTool(Tool original,String id,ActivityStatusStore statusStore, Map rules) throws CompilationFailedException, IOException {
314 Tool copy = new Tool();
315 copy.setInterface(original.getInterface());
316 copy.setName(original.getName());
317 Input input = new Input();
318 copy.setInput(input);
319
320 Vars vars = statusStore.getEnv(id);
321 Binding scriptBinding = createScriptBinding(statusStore,rules);
322 vars.addToBinding(scriptBinding);
323 if (original.getInput() != null) {
324 for (int i = 0; i < original.getInput().getParameterCount(); i++) {
325 ParameterValue originalP = original.getInput().getParameter(i);
326 ParameterValue copyP = processParameter(originalP, scriptBinding);
327 input.addParameter(copyP);
328 }
329 }
330
331 if (original.getOutput() != null) {
332
333 Output output = new Output();
334 copy.setOutput(output);
335 for (int i =0; i < original.getOutput().getParameterCount(); i++) {
336 ParameterValue originalP = original.getOutput().getParameter(i);
337 ParameterValue copyP = processParameter(originalP,scriptBinding);
338 output.addParameter(copyP);
339 }
340 }
341 return copy;
342 }
343
344 /***
345 * @param original
346 * @param scriptBinding
347 * @param i
348 * @return
349 * @throws CompilationFailedException
350 * @throws IOException
351 */
352 private ParameterValue processParameter(ParameterValue originalP, Binding scriptBinding) throws CompilationFailedException, IOException {
353 ParameterValue copyP = new ParameterValue();
354 copyP.setName(originalP.getName());
355 copyP.setIndirect(originalP.getIndirect());
356 copyP.setEncoding(originalP.getEncoding());
357
358 StringBuffer expr = (new StringBuffer("<<<GSTRING\n")).append(originalP.getValue()).append("\nGSTRING\n");
359 Script sc = compileUserScript(expr.toString());
360 sc.setBinding(scriptBinding);
361 Object result = sc.run();
362 copyP.setValue( result.toString());
363 return copyP;
364 }
365
366
367 private Binding createTriggerBinding(ActivityStatusStore statusStore) {
368 Binding b = new Binding();
369 b.setVariable("states",statusStore);
370 for (int i = 0; i < Status.allStatus.size() ; i++ ) {
371 Status stat = (Status)Status.allStatus.get(i);
372 b.setVariable(stat.getName(),stat);
373 }
374 return b;
375 }
376
377 private Binding createBodyBinding(ActivityStatusStore statusStore,Map rules){
378 Binding b = createTriggerBinding(statusStore);
379 b.setVariable("rules",rules);
380 b.setVariable("jes",jes);
381 b.setVariable("shell",this);
382 return b;
383 }
384
385 /*** create environment for user scripts to run in - don't provide access to system objects.
386 * @param state
387 * @param statusStore
388 * @return
389 */
390 private Binding createScriptBinding(ActivityStatusStore statusStore, Map rules) {
391 Binding b = new Binding();
392 b.setVariable("jes",jes);
393 for (int i = 0; i < Status.allStatus.size() ; i++ ) {
394 Status stat = (Status)Status.allStatus.get(i);
395 b.setVariable(stat.getName(),stat);
396 }
397
398 JesShell.createBasicScriptBinding(b, getToolbox(), jes.getWorkflow().getCredentials());
399
400 b.setVariable("__states",statusStore);
401 b.setVariable("__rules",rules);
402 b.setVariable("__shell",this);
403
404
405 return b;
406 }
407
408 /***
409 * @param jesInterface
410 */
411 public void setJesInterface(JesInterface jesInterface) {
412 this.jes = jesInterface;
413 }
414 /***
415 * @param b
416 * @param toolbox2 @todo
417 * @param credentials @todo
418 */
419 public static void createBasicScriptBinding(Binding b, Toolbox toolbox2, Credentials credentials) {
420 b.setVariable("astrogrid",toolbox2);
421
422 b.setVariable("account",credentials.getAccount());
423 String name = credentials.getAccount().getName();
424 String community = credentials.getAccount().getCommunity();
425 User u = new User(name,community,credentials.getGroup().getName(),credentials.getSecurityToken());
426 b.setVariable("user",u);
427
428 try {
429 b.setVariable("userIvorn",new Ivorn("ivo://" + community + "/" + name));
430 } catch (URISyntaxException e) {
431 logger.error("URISyntaxException when creating userIvorn.",e);
432 }
433 b.setVariable("homeIvorn",new Ivorn(community,name,name + "/"));
434
435 }
436
437
438
439
440 }
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530