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