| /* |
| * 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.java; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.FilenameFilter; |
| import java.lang.reflect.Method; |
| import java.util.Hashtable; |
| import java.util.Vector; |
| |
| import org.apache.bsf.BSFException; |
| import org.apache.bsf.BSFManager; |
| import org.apache.bsf.util.BSFEngineImpl; |
| import org.apache.bsf.util.CodeBuffer; |
| import org.apache.bsf.util.EngineUtils; |
| import org.apache.bsf.util.JavaUtils; |
| import org.apache.bsf.util.MethodUtils; |
| import org.apache.bsf.util.ObjInfo; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| /** |
| * This is the interface to Java from the |
| * Bean Scripting Framework. |
| * <p> |
| * The Java code must be written script-style -- that is, just the body of |
| * the function, without class or method headers or footers. |
| * The JavaEngine will generate those via a "boilerplate" wrapper: |
| * <pre> |
| * <code> |
| * import java.lang.*; |
| * import java.util.*; |
| * public class $$CLASSNAME$$ { |
| * static public Object BSFJavaEngineEntry(org.apache.bsf.BSFManager bsf) { |
| * // Your code will be placed here |
| * } |
| * } |
| * </code> |
| * </pre> |
| * $$CLASSNAME$$ will be replaced by a generated classname of the form |
| * BSFJava*, 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 -- BSFJavaEngine will |
| * replace it with the generated name of the class before the Java code |
| * is compiled. |
| * <p> |
| * <h2>Hazards:</h2> |
| * <p> |
| * NOTE that it is your responsibility to convert the code into an acceptable |
| * Java string. If you're invoking the JavaEngine directly (as in the |
| * JSPLikeInJava example) that means \"quoting\" characters that would |
| * otherwise cause trouble. |
| * <p> |
| * ALSO NOTE that it is your responsibility to return an object, or null in |
| * lieu thereof! |
| * <p> |
| * Since the code 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. It has |
| * been suggested that the Bean Scripting Framework may want to support |
| * preload-and-name-script and execute-preloaded-script-by-name options to |
| * provide better control over when and how much overhead occurs. |
| * <p> |
| * @author Joe Kesselman |
| */ |
| public class JavaEngine extends BSFEngineImpl { |
| Class javaclass = null; |
| 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. |
| */ |
| private int uniqueFileOffset = -1; |
| |
| 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; |
| } |
| } |
| |
| /** |
| * Constructor. |
| */ |
| public JavaEngine () { |
| // Do compilation-possible check here?????????????? |
| } |
| |
| public Object call (Object object, String method, Object[] args) |
| throws BSFException |
| { |
| throw new BSFException (BSFException.REASON_UNSUPPORTED_FEATURE, |
| "call() is not currently supported by JavaEngine"); |
| } |
| |
| public void compileScript (String source, int lineNo, int columnNo, |
| Object script, CodeBuffer cb) throws BSFException { |
| ObjInfo oldRet = cb.getFinalServiceMethodStatement (); |
| |
| if (oldRet != null && oldRet.isExecutable ()) { |
| cb.addServiceMethodStatement (oldRet.objName + ";"); |
| } |
| |
| cb.addServiceMethodStatement (script.toString ()); |
| cb.setFinalServiceMethodStatement (null); |
| } |
| |
| /** |
| * This is used by an application to evaluate a string containing |
| * some expression. It should store the "bsf" handle where the |
| * script can get to it, for callback purposes. |
| * <p> |
| * Note that Java compilation imposes serious overhead, |
| * but in exchange you get full Java performance |
| * 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 protect it. |
| * <p> |
| * There is no published API for invoking javac as a class. There's a trick |
| * that seems to work for Java 1.1.x, but it stopped working in Java 1.2. |
| * We will attempt to use it, then if necessary fall back on invoking |
| * javac via the command line. |
| */ |
| public Object eval (String source, int lineNo, int columnNo, |
| Object oscript) throws BSFException |
| { |
| Object retval = null; |
| String classname = null; |
| GeneratedFile gf = 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? |
| javaclass = (Class)codeToClass.get(basescript); |
| |
| if(javaclass != null) { |
| classname=javaclass.getName(); |
| } else { |
| gf = openUniqueFile(tempDir, "BSFJava",".java"); |
| if( gf == null) { |
| throw new BSFException("couldn't create JavaEngine scratchfile"); |
| } |
| // Obtain classname |
| classname = gf.className; |
| |
| // Write the kluge header to the file. |
| gf.fos.write(("import java.lang.*;"+ |
| "import java.util.*;"+ |
| "public class "+classname+" {\n" + |
| " static public Object BSFJavaEngineEntry(org.apache.bsf.BSFManager bsf) {\n") |
| .getBytes()); |
| |
| // Edit the script to replace placeholder with the generated |
| // classname. Note that this occurs _after_ the cache was checked! |
| int startpoint = script.indexOf(placeholder); |
| int endpoint; |
| if(startpoint >= 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(); |
| } |
| } |
| |
| // MJD - debug |
| // BSFDeclaredBean tempBean; |
| // String className; |
| // |
| // for (int i = 0; i < declaredBeans.size (); i++) { |
| // tempBean = (BSFDeclaredBean) declaredBeans.elementAt (i); |
| // className = StringUtils.getClassName (tempBean.bean.getClass ()); |
| // |
| // gf.fos.write ((className + " " + |
| // tempBean.name + " = (" + className + |
| // ")bsf.lookupBean(\"" + |
| // tempBean.name + "\");").getBytes ()); |
| // } |
| // MJD - debug |
| |
| // Copy the input to the file. |
| // Assumes all available -- probably mistake, but same as other engines. |
| gf.fos.write(script.getBytes()); |
| // Close the method and class |
| gf.fos.write(("\n }\n}\n").getBytes()); |
| gf.fos.close(); |
| |
| // Compile through Java to .class file |
| // May not be threadsafe. Serialize access on static object: |
| synchronized(serializeCompilation) { |
| JavaUtils.JDKcompile(gf.file.getPath(), classPath); |
| } |
| |
| // Load class. |
| javaclass = EngineUtils.loadClass(mgr, classname); |
| |
| // Stash class for reuse |
| codeToClass.put(basescript, javaclass); |
| } |
| |
| Object[] callArgs = {mgr}; |
| retval = internalCall(this,"BSFJavaEngineEntry",callArgs); |
| } |
| |
| |
| catch(Exception e) { |
| e.printStackTrace (); |
| throw new BSFException (BSFException.REASON_IO_ERROR, e.getMessage ()); |
| } finally { |
| // Cleanup: delete the .java and .class files |
| |
| // if(gf!=null && gf.file!=null && gf.file.exists()) |
| // gf.file.delete(); // .java file |
| |
| |
| if(classname!=null) { |
| // Generated class |
| File file = new File(tempDir+File.separatorChar+classname+".class"); |
| // if(file.exists()) |
| // file.delete(); |
| |
| // Search for and clean up minor classes, classname$xxx.class |
| file = new File(tempDir); // ***** Is this required? |
| minorPrefix = classname+"$"; // Indirect arg to filter |
| String[] minorClassfiles = file.list(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); |
| } |
| }); |
| for(int i = 0; i < minorClassfiles.length; ++i) { |
| file = new File(minorClassfiles[i]); |
| // file.delete(); |
| } |
| } |
| } |
| return retval; |
| } |
| |
| public void initialize (BSFManager mgr, String lang, |
| Vector declaredBeans) throws BSFException { |
| super.initialize (mgr, lang, declaredBeans); |
| } |
| /** |
| * Return an object from an extension. |
| * @param object Object on which to make the internal_call (ignored). |
| * @param method The name of the method to internal_call. |
| * @param args an array of arguments to be |
| * passed to the extension, which may be either |
| * Vectors of Nodes, or Strings. |
| */ |
| Object internalCall (Object object, String method, Object[] args) |
| throws BSFException |
| { |
| //***** ISSUE: Only static methods are currently supported |
| Object retval = null; |
| try { |
| if(javaclass != 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(javaclass, method, argtypes); |
| retval = m.invoke(null, args); |
| } |
| } |
| catch(Exception e) { |
| throw new BSFException (BSFException.REASON_IO_ERROR, e.getMessage ()); |
| } |
| return retval; |
| } |
| |
| private GeneratedFile openUniqueFile(String directory,String prefix,String suffix) { |
| File file = 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); |
| if(file != null && !file.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; |
| } |
| } |