org.apache.bsf.engines.netrexx.NetRexxEngine.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.bsf.engines.netrexx.NetRexxEngine.java

Source

/*
 * Copyright 2004,2004 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.bsf.engines.netrexx;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Hashtable;
import java.util.Vector;

import org.apache.bsf.BSFDeclaredBean;
import org.apache.bsf.BSFException;
import org.apache.bsf.BSFManager;
import org.apache.bsf.util.BSFEngineImpl;
import org.apache.bsf.util.BSFFunctions;
import org.apache.bsf.util.EngineUtils;
import org.apache.bsf.util.MethodUtils;
import org.apache.bsf.util.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This is the interface to NetRexx from the
 * Bean Scripting Framework.
 * <p>
 * The NetRexx code must be written script-style, without a "class" or
 * "properties" section preceeding the executable code. The NetRexxEngine will
 * generate a prefix for this code:
 * <pre>
 * <code>
 * class $$CLASSNAME$$;
 * method BSFNetRexxEngineEntry(bsf=org.apache.bsf.BSFManager) public static;
 * </code>
 * </pre>
 * $$CLASSNAME$$ will be replaced by a generated classname of the form
 * BSFNetRexx*, and the bsf parameter can be used to retrieve application
 * objects registered with the Bean Scripting Framework.
 * <p>
 * If you use the placeholder string $$CLASSNAME$$ elsewhere
 * in your script -- including within text strings -- BSFNetRexxEngine will
 * replace it with the generated name of the class before the NetRexx code
 * is compiled.
 * <p>
 * If you need to use full NetRexx functionality, we recommend that your
 * NetRexx script define and invoke a "minor class", with or without the
 * "dependent" keyword as suits your needs. You'll have to use $$CLASSNAME$$
 * in naming the minor class, since the name of the main class is synthesized;
 * for example, to create the minor class "bar" you'd write
 * "class $$CLASSNAME$$.Bar".
 * <p>
 * <h2>Hazards:</h2>
 * <p>
 * Since NetRexx has to be _compiled_ to a Java classfile, invoking it involves
 * a fair amount of computation to load and execute the compiler. We are
 * currently making an attempt to manage that by caching the class
 * after it has been loaded, but the indexing is fairly primitive; we
 * hash against the script string to find the class for it.
 * <p>
 * Minor-class .class files are now being deleted after the major class loads.
 * This coould potentially cause problems.
 *
 * @author  Joe Kesselman
 * @author  Sanjiva Weerawarana
 */
public class NetRexxEngine extends BSFEngineImpl {
    BSFFunctions mgrfuncs;
    static Hashtable codeToClass = new Hashtable();
    static String serializeCompilation = "";
    static String placeholder = "$$CLASSNAME$$";
    String minorPrefix;

    private Log logger = LogFactory.getLog(this.getClass().getName());

    /**
     * Create a scratchfile, open it for writing, return its name.
     * Relies on the filesystem to provide us with uniqueness testing.
     * NOTE THAT uniqueFileOffset continues to count; we don't want to
     * risk reusing a classname we have previously loaded in this session
     * even if the classfile has been deleted.
     *
     * I've made the offset static, due to concerns about reuse/reentrancy
     * of the NetRexx engine.
     */
    private static int uniqueFileOffset = 0;

    private class GeneratedFile {
        File file = null;
        FileOutputStream fos = null;
        String className = null;

        GeneratedFile(File file, FileOutputStream fos, String className) {
            this.file = file;
            this.fos = fos;
            this.className = className;
        }
    }

    // rexxclass used to be an instance variable, on the theory that
    // each NetRexxEngine was an instance of a specific script.
    // BSF is currently reusing Engines, so caching the class
    // no longer makes sense.
    // Class rexxclass;

    /**
     * Constructor.
     */
    public NetRexxEngine() {
        /*
          The following line is intended to cause the constructor to
          throw a NoClassDefFoundError if the NetRexxC.zip dependency
          is not resolved.
              
          If this line was not here, the problem would not surface until
          the actual processing of a script. We want to know all is well
          at the time the engine is instantiated, not when we attempt to
          process a script.
          */

        new netrexx.lang.BadArgumentException();
    }

    /**
     * Return an object from an extension.
     * @param object object from which to call our static method
     * @param method The name of the method to call.
     * @param args an array of arguments to be
     * passed to the extension, which may be either
     * Vectors of Nodes, or Strings.
     */
    public Object call(Object object, String method, Object[] args) throws BSFException {
        throw new BSFException(BSFException.REASON_UNSUPPORTED_FEATURE, "NetRexx doesn't currently support call()",
                null);
    }

    /**
     * Invoke a static method.
     * @param rexxclass Class to invoke the method against
     * @param method The name of the method to call.
     * @param args an array of arguments to be
     * passed to the extension, which may be either
     * Vectors of Nodes, or Strings.
     */
    Object callStatic(Class rexxclass, String method, Object[] args) throws BSFException {
        //***** ISSUE: Currently supports only static methods
        Object retval = null;
        try {
            if (rexxclass != null) {
                //***** This should call the lookup used in BML, for typesafety
                Class[] argtypes = new Class[args.length];
                for (int i = 0; i < args.length; ++i)
                    argtypes[i] = args[i].getClass();

                Method m = MethodUtils.getMethod(rexxclass, method, argtypes);
                retval = m.invoke(null, args);
            } else {
                logger.error("NetRexxEngine: ERROR: rexxclass==null!");
            }
        } catch (Exception e) {
            e.printStackTrace();
            if (e instanceof InvocationTargetException) {
                Throwable t = ((InvocationTargetException) e).getTargetException();
                t.printStackTrace();
            }
            throw new BSFException(BSFException.REASON_IO_ERROR, e.getMessage(), e);
        }
        return retval;
    }

    public void declareBean(BSFDeclaredBean bean) throws BSFException {
    }

    /**
     * Override impl of execute. In NetRexx, methods which do not wish
     * to return a value should be invoked via exec, which will cause them
     * to be generated without the "returns" clause.
     * Those which wish to return a value should call eval instead.
     * which will add "returns java.lang.Object" to the header.
     *
     * Note: It would be nice to have the "real" return type avaialable, so
     * we could do something more type-safe than Object, and so we could
     * return primitive types without having to enclose them in their
     * object wrappers. BSF does not currently support that concept.
     */
    public Object eval(String source, int lineNo, int columnNo, Object script) throws BSFException {
        return execEvalShared(source, lineNo, columnNo, script, true);
    }

    /**
     * Override impl of execute. In NetRexx, methods which do not wish
     * to return a value should be invoked via exec, which will cause them
     * to be generated without the "returns" clause.
     * Those which wish to return a value should call eval instead.
     * which will add "returns java.lang.Object" to the header.
     */
    public void exec(String source, int lineNo, int columnNo, Object script) throws BSFException {
        execEvalShared(source, lineNo, columnNo, script, false);
    }

    /**
     * This is shared code for the exec() and eval() operations. It will
     * evaluate a string containing a NetRexx method body -- which may be
     * as simple as a single return statement.
     * It should store the "bsf" handle where the
     * script can get to it, for callback purposes.
     * <p>
     * Note that NetRexx compilation imposes serious overhead -- 11 seconds for
     * the first compile, about 3 thereafter -- but in exchange you get
     * Java-like speeds once the classes have been created (minus the cache
     * lookup cost).
     * <p>
     * Nobody knows whether javac is threadsafe.
     * I'm going to serialize access to the compilers to protect it.
     */
    public Object execEvalShared(String source, int lineNo, int columnNo, Object oscript, boolean returnsObject)
            throws BSFException {
        Object retval = null;
        String classname = null;
        GeneratedFile gf = null;

        // Moved into the exec process; see comment above.
        Class rexxclass = null;

        String basescript = oscript.toString();
        String script = basescript; // May be altered by $$CLASSNAME$$ expansion

        try {
            // Do we already have a class exactly matching this code?
            rexxclass = (Class) codeToClass.get(basescript);

            if (rexxclass != null)

            {
                logger.debug("NetRexxEngine: Found pre-compiled class" + " for script '" + basescript + "'");
                classname = rexxclass.getName();
            } else {
                gf = openUniqueFile(tempDir, "BSFNetRexx", ".nrx");
                if (gf == null)
                    throw new BSFException("couldn't create NetRexx scratchfile");

                // Obtain classname
                classname = gf.className;

                // Decide whether to declare a return type
                String returnsDecl = "";
                if (returnsObject)
                    returnsDecl = "returns java.lang.Object";

                // Write the kluge header to the file.
                // ***** By doing so we give up the ability to use Property blocks.
                gf.fos.write(("class " + classname + ";\n").getBytes());
                gf.fos.write(("method BSFNetRexxEngineEntry(bsf=org.apache.bsf.util.BSFFunctions) "
                        + " public static " + returnsDecl + ";\n").getBytes());

                // Edit the script to replace placeholder with the generated
                // classname. Note that this occurs _after_ the cache was
                // checked!
                int startpoint, endpoint;
                if ((startpoint = script.indexOf(placeholder)) >= 0) {
                    StringBuffer changed = new StringBuffer();
                    for (; startpoint >= 0; startpoint = script.indexOf(placeholder, startpoint)) {
                        changed.setLength(0); // Reset for 2nd pass or later
                        if (startpoint > 0)
                            changed.append(script.substring(0, startpoint));
                        changed.append(classname);
                        endpoint = startpoint + placeholder.length();
                        if (endpoint < script.length())
                            changed.append(script.substring(endpoint));
                        script = changed.toString();
                    }
                }

                BSFDeclaredBean tempBean;
                String className;

                for (int i = 0; i < declaredBeans.size(); i++) {
                    tempBean = (BSFDeclaredBean) declaredBeans.elementAt(i);
                    className = StringUtils.getClassName(tempBean.type);

                    gf.fos.write(
                            (tempBean.name + " =" + className + "   bsf.lookupBean(\"" + tempBean.name + "\");")
                                    .getBytes());
                }

                if (returnsObject)
                    gf.fos.write("return ".getBytes());

                // Copy the input to the file.
                // Assumes all available -- probably mistake, but same as
                // other engines.
                gf.fos.write(script.getBytes());
                gf.fos.close();

                logger.debug("NetRexxEngine: wrote temp file " + gf.file.getPath() + ", now compiling");

                // Compile through Java to .class file
                String command = gf.file.getPath(); //classname;
                if (logger.isDebugEnabled()) {
                    command += " -verbose4";
                } else {
                    command += " -noverbose";
                    command += " -noconsole";
                }

                netrexx.lang.Rexx cmdline = new netrexx.lang.Rexx(command);
                int retValue;

                // May not be threadsafe. Serialize access on static object:
                synchronized (serializeCompilation) {
                    // compile to a .java file
                    retValue = COM.ibm.netrexx.process.NetRexxC.main(cmdline, new PrintWriter(System.err));
                }

                // Check if there were errors while compiling the Rexx code.
                if (retValue == 2) {
                    throw new BSFException(BSFException.REASON_EXECUTION_ERROR, "There were NetRexx errors.");
                }

                // Load class.
                logger.debug("NetRexxEngine: loading class " + classname);
                rexxclass = EngineUtils.loadClass(mgr, classname);

                // Stash class for reuse
                codeToClass.put(basescript, rexxclass);
            }

            Object[] args = { mgrfuncs };
            retval = callStatic(rexxclass, "BSFNetRexxEngineEntry", args);
        } catch (BSFException e) {
            // Just forward the exception on.
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            if (e instanceof InvocationTargetException) {
                Throwable t = ((InvocationTargetException) e).getTargetException();
                t.printStackTrace();
            }
            throw new BSFException(BSFException.REASON_IO_ERROR, e.getMessage(), e);
        } finally {
            // Cleanup: delete the .nrx and .class files
            // (if any) generated by NetRexx Trace requests.

            if (gf != null && gf.file != null && gf.file.exists())
                gf.file.delete(); // .nrx file

            if (classname != null) {
                // Generated src
                File file = new File(tempDir + File.separatorChar + classname + ".java");
                if (file.exists())
                    file.delete();

                // Generated class
                file = new File(classname + ".class");
                if (file.exists())
                    file.delete();

                // Can this be done without disrupting trace?
                file = new File(tempDir + File.separatorChar + classname + ".crossref");
                if (file.exists())
                    file.delete();

                // Search for and clean up minor classes, classname$xxx.class
                file = new File(tempDir);
                minorPrefix = classname + "$"; // Indirect arg to filter
                String[] minor_classfiles = file.list(
                        // ANONYMOUS CLASS for filter:
                        new FilenameFilter() {
                            // Starts with classname$ and ends with .class
                            public boolean accept(File dir, String name) {
                                return (0 == name.indexOf(minorPrefix))
                                        && (name.lastIndexOf(".class") == name.length() - 6);
                            }
                        });
                if (minor_classfiles != null)
                    for (int i = minor_classfiles.length; i > 0;) {
                        file = new File(minor_classfiles[--i]);
                        file.delete();
                    }
            }
        }

        return retval;
    }

    public void initialize(BSFManager mgr, String lang, Vector declaredBeans) throws BSFException {
        super.initialize(mgr, lang, declaredBeans);
        mgrfuncs = new BSFFunctions(mgr, this);
    }

    private GeneratedFile openUniqueFile(String directory, String prefix, String suffix) {
        File file = null, obj = null;
        FileOutputStream fos = null;
        int max = 1000; // Don't try forever
        GeneratedFile gf = null;
        int i;
        String className = null;
        for (i = max, ++uniqueFileOffset; fos == null && i > 0; --i, ++uniqueFileOffset) {
            // Probably a timing hazard here... ***************
            try {
                className = prefix + uniqueFileOffset;
                file = new File(directory + File.separatorChar + className + suffix);
                obj = new File(directory + File.separatorChar + className + ".class");
                if (file != null && !file.exists() & obj != null & !obj.exists())
                    fos = new FileOutputStream(file);
            } catch (Exception e) {
                // File could not be opened for write, or Security Exception
                // was thrown. If someone else created the file before we could
                // open it, that's probably a threading conflict and we don't
                // bother reporting it.
                if (!file.exists()) {
                    logger.error("openUniqueFile: unexpected " + e);
                }
            }
        }
        if (fos == null)
            logger.error("openUniqueFile: Failed " + max + "attempts.");
        else
            gf = new GeneratedFile(file, fos, className);
        return gf;
    }

    public void undeclareBean(BSFDeclaredBean bean) throws BSFException {
    }
}