1 /*
2 * Copyright 2004,2004 The Apache Software Foundation.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.apache.bsf.engines.netrexx;
18
19 import java.io.File;
20 import java.io.FileOutputStream;
21 import java.io.FilenameFilter;
22 import java.io.PrintWriter;
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25 import java.util.Hashtable;
26 import java.util.Vector;
27
28 import org.apache.bsf.BSFDeclaredBean;
29 import org.apache.bsf.BSFException;
30 import org.apache.bsf.BSFManager;
31 import org.apache.bsf.util.BSFEngineImpl;
32 import org.apache.bsf.util.BSFFunctions;
33 import org.apache.bsf.util.EngineUtils;
34 import org.apache.bsf.util.MethodUtils;
35 import org.apache.bsf.util.StringUtils;
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38
39 /**
40 * This is the interface to NetRexx from the
41 * Bean Scripting Framework.
42 * <p>
43 * The NetRexx code must be written script-style, without a "class" or
44 * "properties" section preceeding the executable code. The NetRexxEngine will
45 * generate a prefix for this code:
46 * <pre>
47 * <code>
48 * class $$CLASSNAME$$;
49 * method BSFNetRexxEngineEntry(bsf=org.apache.bsf.BSFManager) public static;
50 * </code>
51 * </pre>
52 * $$CLASSNAME$$ will be replaced by a generated classname of the form
53 * BSFNetRexx*, and the bsf parameter can be used to retrieve application
54 * objects registered with the Bean Scripting Framework.
55 * <p>
56 * If you use the placeholder string $$CLASSNAME$$ elsewhere
57 * in your script -- including within text strings -- BSFNetRexxEngine will
58 * replace it with the generated name of the class before the NetRexx code
59 * is compiled.
60 * <p>
61 * If you need to use full NetRexx functionality, we recommend that your
62 * NetRexx script define and invoke a "minor class", with or without the
63 * "dependent" keyword as suits your needs. You'll have to use $$CLASSNAME$$
64 * in naming the minor class, since the name of the main class is synthesized;
65 * for example, to create the minor class "bar" you'd write
66 * "class $$CLASSNAME$$.Bar".
67 * <p>
68 * <h2>Hazards:</h2>
69 * <p>
70 * Since NetRexx has to be _compiled_ to a Java classfile, invoking it involves
71 * a fair amount of computation to load and execute the compiler. We are
72 * currently making an attempt to manage that by caching the class
73 * after it has been loaded, but the indexing is fairly primitive; we
74 * hash against the script string to find the class for it.
75 * <p>
76 * Minor-class .class files are now being deleted after the major class loads.
77 * This coould potentially cause problems.
78 *
79 * @author Joe Kesselman
80 * @author Sanjiva Weerawarana
81 */
82 public class NetRexxEngine extends BSFEngineImpl
83 {
84 BSFFunctions mgrfuncs;
85 static Hashtable codeToClass=new Hashtable();
86 static String serializeCompilation="";
87 static String placeholder="$$CLASSNAME$$";
88 String minorPrefix;
89
90 private Log logger = LogFactory.getLog(this.getClass().getName());
91
92 /**
93 * Create a scratchfile, open it for writing, return its name.
94 * Relies on the filesystem to provide us with uniqueness testing.
95 * NOTE THAT uniqueFileOffset continues to count; we don't want to
96 * risk reusing a classname we have previously loaded in this session
97 * even if the classfile has been deleted.
98 *
99 * I've made the offset static, due to concerns about reuse/reentrancy
100 * of the NetRexx engine.
101 */
102 private static int uniqueFileOffset=0;
103 private class GeneratedFile
104 {
105 File file=null;
106 FileOutputStream fos=null;
107 String className=null;
108 GeneratedFile(File file,FileOutputStream fos,String className)
109 {
110 this.file=file;
111 this.fos=fos;
112 this.className=className;
113 }
114 }
115
116 // rexxclass used to be an instance variable, on the theory that
117 // each NetRexxEngine was an instance of a specific script.
118 // BSF is currently reusing Engines, so caching the class
119 // no longer makes sense.
120 // Class rexxclass;
121
122 /**
123 * Constructor.
124 */
125 public NetRexxEngine ()
126 {
127 /*
128 The following line is intended to cause the constructor to
129 throw a NoClassDefFoundError if the NetRexxC.zip dependency
130 is not resolved.
131
132 If this line was not here, the problem would not surface until
133 the actual processing of a script. We want to know all is well
134 at the time the engine is instantiated, not when we attempt to
135 process a script.
136 */
137
138 new netrexx.lang.BadArgumentException();
139 }
140 /**
141 * Return an object from an extension.
142 * @param object object from which to call our static method
143 * @param method The name of the method to call.
144 * @param args an array of arguments to be
145 * passed to the extension, which may be either
146 * Vectors of Nodes, or Strings.
147 */
148 public Object call (Object object, String method, Object[] args)
149 throws BSFException
150 {
151 throw new BSFException(BSFException.REASON_UNSUPPORTED_FEATURE,
152 "NetRexx doesn't currently support call()",
153 null);
154 }
155 /**
156 * Invoke a static method.
157 * @param rexxclass Class to invoke the method against
158 * @param method The name of the method to call.
159 * @param args an array of arguments to be
160 * passed to the extension, which may be either
161 * Vectors of Nodes, or Strings.
162 */
163 Object callStatic(Class rexxclass, String method, Object[] args)
164 throws BSFException
165 {
166 //***** ISSUE: Currently supports only static methods
167 Object retval = null;
168 try
169 {
170 if (rexxclass != null)
171 {
172 //***** This should call the lookup used in BML, for typesafety
173 Class[] argtypes=new Class[args.length];
174 for(int i=0;i<args.length;++i)
175 argtypes[i]=args[i].getClass();
176
177 Method m=MethodUtils.getMethod(rexxclass, method, argtypes);
178 retval=m.invoke(null,args);
179 }
180 else
181 {
182 logger.error("NetRexxEngine: ERROR: rexxclass==null!");
183 }
184 }
185 catch(Exception e)
186 {
187 e.printStackTrace ();
188 if (e instanceof InvocationTargetException)
189 {
190 Throwable t = ((InvocationTargetException)e).getTargetException ();
191 t.printStackTrace ();
192 }
193 throw new BSFException (BSFException.REASON_IO_ERROR,
194 e.getMessage (),
195 e);
196 }
197 return retval;
198 }
199 public void declareBean (BSFDeclaredBean bean) throws BSFException {}
200 /**
201 * Override impl of execute. In NetRexx, methods which do not wish
202 * to return a value should be invoked via exec, which will cause them
203 * to be generated without the "returns" clause.
204 * Those which wish to return a value should call eval instead.
205 * which will add "returns java.lang.Object" to the header.
206 *
207 * Note: It would be nice to have the "real" return type avaialable, so
208 * we could do something more type-safe than Object, and so we could
209 * return primitive types without having to enclose them in their
210 * object wrappers. BSF does not currently support that concept.
211 */
212 public Object eval (String source, int lineNo, int columnNo,
213 Object script)
214 throws BSFException
215 {
216 return execEvalShared(source, lineNo, columnNo, script,true);
217 }
218 /**
219 * Override impl of execute. In NetRexx, methods which do not wish
220 * to return a value should be invoked via exec, which will cause them
221 * to be generated without the "returns" clause.
222 * Those which wish to return a value should call eval instead.
223 * which will add "returns java.lang.Object" to the header.
224 */
225 public void exec (String source, int lineNo, int columnNo,
226 Object script)
227 throws BSFException
228 {
229 execEvalShared(source, lineNo, columnNo, script,false);
230 }
231 /**
232 * This is shared code for the exec() and eval() operations. It will
233 * evaluate a string containing a NetRexx method body -- which may be
234 * as simple as a single return statement.
235 * It should store the "bsf" handle where the
236 * script can get to it, for callback purposes.
237 * <p>
238 * Note that NetRexx compilation imposes serious overhead -- 11 seconds for
239 * the first compile, about 3 thereafter -- but in exchange you get
240 * Java-like speeds once the classes have been created (minus the cache
241 * lookup cost).
242 * <p>
243 * Nobody knows whether javac is threadsafe.
244 * I'm going to serialize access to the compilers to protect it.
245 */
246 public Object execEvalShared (String source, int lineNo, int columnNo,
247 Object oscript,boolean returnsObject)
248 throws BSFException
249 {
250 Object retval=null;
251 String classname=null;
252 GeneratedFile gf=null;
253
254 // Moved into the exec process; see comment above.
255 Class rexxclass=null;
256
257 String basescript=oscript.toString();
258 String script=basescript; // May be altered by $$CLASSNAME$$ expansion
259
260 try {
261 // Do we already have a class exactly matching this code?
262 rexxclass=(Class)codeToClass.get(basescript);
263
264 if(rexxclass!=null)
265
266 {
267 logger.debug("NetRexxEngine: Found pre-compiled class" +
268 " for script '" + basescript + "'");
269 classname=rexxclass.getName();
270 }
271 else
272 {
273 gf=openUniqueFile(tempDir,"BSFNetRexx",".nrx");
274 if(gf==null)
275 throw new BSFException("couldn't create NetRexx scratchfile");
276
277 // Obtain classname
278 classname=gf.className;
279
280 // Decide whether to declare a return type
281 String returnsDecl="";
282 if(returnsObject)
283 returnsDecl="returns java.lang.Object";
284
285 // Write the kluge header to the file.
286 // ***** By doing so we give up the ability to use Property blocks.
287 gf.fos.write(("class "+classname+";\n")
288 .getBytes());
289 gf.fos.write(
290 ("method BSFNetRexxEngineEntry(bsf=org.apache.bsf.util.BSFFunctions) "+
291 " public static "+returnsDecl+";\n")
292 .getBytes());
293
294 // Edit the script to replace placeholder with the generated
295 // classname. Note that this occurs _after_ the cache was
296 // checked!
297 int startpoint,endpoint;
298 if((startpoint=script.indexOf(placeholder))>=0)
299 {
300 StringBuffer changed=new StringBuffer();
301 for(;
302 startpoint>=0;
303 startpoint=script.indexOf(placeholder,startpoint))
304 {
305 changed.setLength(0); // Reset for 2nd pass or later
306 if(startpoint>0)
307 changed.append(script.substring(0,startpoint));
308 changed.append(classname);
309 endpoint=startpoint+placeholder.length();
310 if(endpoint<script.length())
311 changed.append(script.substring(endpoint));
312 script=changed.toString();
313 }
314 }
315
316 BSFDeclaredBean tempBean;
317 String className;
318
319 for (int i = 0; i < declaredBeans.size (); i++)
320 {
321 tempBean = (BSFDeclaredBean) declaredBeans.elementAt (i);
322 className = StringUtils.getClassName (tempBean.type);
323
324 gf.fos.write ((tempBean.name + " =" + className + " bsf.lookupBean(\"" +
325 tempBean.name + "\");").getBytes());
326 }
327
328 if(returnsObject)
329 gf.fos.write("return ".getBytes());
330
331 // Copy the input to the file.
332 // Assumes all available -- probably mistake, but same as
333 // other engines.
334 gf.fos.write(script.getBytes());
335 gf.fos.close();
336
337 logger.debug("NetRexxEngine: wrote temp file " +
338 gf.file.getPath () + ", now compiling");
339
340 // Compile through Java to .class file
341 String command=gf.file.getPath(); //classname;
342 if (logger.isDebugEnabled()) {
343 command += " -verbose4";
344 } else {
345 command += " -noverbose";
346 command += " -noconsole";
347 }
348
349 netrexx.lang.Rexx cmdline= new netrexx.lang.Rexx(command);
350 int retValue;
351
352 // May not be threadsafe. Serialize access on static object:
353 synchronized(serializeCompilation)
354 {
355 // compile to a .java file
356 retValue =
357 COM.ibm.netrexx.process.NetRexxC.main(cmdline,
358 new PrintWriter(System.err));
359 }
360
361 // Check if there were errors while compiling the Rexx code.
362 if (retValue == 2)
363 {
364 throw new BSFException(BSFException.REASON_EXECUTION_ERROR,
365 "There were NetRexx errors.");
366 }
367
368 // Load class.
369 logger.debug("NetRexxEngine: loading class "+classname);
370 rexxclass=EngineUtils.loadClass (mgr, classname);
371
372 // Stash class for reuse
373 codeToClass.put(basescript,rexxclass);
374 }
375
376 Object[] args={mgrfuncs};
377 retval=callStatic(rexxclass, "BSFNetRexxEngineEntry",args);
378 }
379 catch (BSFException e)
380 {
381 // Just forward the exception on.
382 throw e;
383 }
384 catch(Exception e)
385 {
386 e.printStackTrace ();
387 if (e instanceof InvocationTargetException)
388 {
389 Throwable t = ((InvocationTargetException)e).getTargetException ();
390 t.printStackTrace ();
391 }
392 throw new BSFException (BSFException.REASON_IO_ERROR,
393 e.getMessage (), e);
394 }
395 finally
396 {
397 // Cleanup: delete the .nrx and .class files
398 // (if any) generated by NetRexx Trace requests.
399
400 if(gf!=null && gf.file!=null && gf.file.exists())
401 gf.file.delete(); // .nrx file
402
403 if(classname!=null)
404 {
405 // Generated src
406 File file=new File(tempDir+File.separatorChar+classname+".java");
407 if(file.exists())
408 file.delete();
409
410 // Generated class
411 file=new File(classname+".class");
412 if(file.exists())
413 file.delete();
414
415 // Can this be done without disrupting trace?
416 file=new File(tempDir+File.separatorChar+classname+".crossref");
417 if(file.exists())
418 file.delete();
419
420 // Search for and clean up minor classes, classname$xxx.class
421 file=new File(tempDir);
422 minorPrefix=classname+"$"; // Indirect arg to filter
423 String[] minor_classfiles=
424 file.list(
425 // ANONYMOUS CLASS for filter:
426 new FilenameFilter()
427 {
428 // Starts with classname$ and ends with .class
429 public boolean accept(File dir,String name)
430 {
431 return
432 (0==name.indexOf(minorPrefix))
433 &&
434 (name.lastIndexOf(".class")==name.length()-6)
435 ;
436 }
437 }
438 );
439 if(minor_classfiles!=null)
440 for(int i=minor_classfiles.length;i>0;)
441 {
442 file=new File(minor_classfiles[--i]);
443 file.delete();
444 }
445 }
446 }
447
448 return retval;
449 }
450 public void initialize(BSFManager mgr, String lang,Vector declaredBeans)
451 throws BSFException
452 {
453 super.initialize(mgr, lang, declaredBeans);
454 mgrfuncs = new BSFFunctions (mgr, this);
455 }
456 private GeneratedFile openUniqueFile(String directory,String prefix,String suffix)
457 {
458 File file=null,obj=null;
459 FileOutputStream fos=null;
460 int max=1000; // Don't try forever
461 GeneratedFile gf=null;
462 int i;
463 String className = null;
464 for(i=max,++uniqueFileOffset;
465 fos==null && i>0;
466 --i,++uniqueFileOffset)
467 {
468 // Probably a timing hazard here... ***************
469 try
470 {
471 className = prefix+uniqueFileOffset;
472 file=new File(directory+File.separatorChar+className+suffix);
473 obj=new File(directory+File.separatorChar+className+".class");
474 if(file!=null && !file.exists() & obj!=null & !obj.exists())
475 fos=new FileOutputStream(file);
476 }
477 catch(Exception e)
478 {
479 // File could not be opened for write, or Security Exception
480 // was thrown. If someone else created the file before we could
481 // open it, that's probably a threading conflict and we don't
482 // bother reporting it.
483 if(!file.exists())
484 {
485 logger.error("openUniqueFile: unexpected "+e);
486 }
487 }
488 }
489 if(fos==null)
490 logger.error("openUniqueFile: Failed "+max+"attempts.");
491 else
492 gf=new GeneratedFile(file,fos,className);
493 return gf;
494 }
495
496 public void undeclareBean (BSFDeclaredBean bean) throws BSFException {}
497 }