Save This Page
Home » bsf-src-2.4.0 » org.apache.bsf.engines.java » [javadoc | source]
    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.java;
   18   
   19   import java.io.File;
   20   import java.io.FileOutputStream;
   21   import java.io.FilenameFilter;
   22   import java.lang.reflect.Method;
   23   import java.util.Hashtable;
   24   import java.util.Vector;
   25   
   26   import org.apache.bsf.BSFException;
   27   import org.apache.bsf.BSFManager;
   28   import org.apache.bsf.util.BSFEngineImpl;
   29   import org.apache.bsf.util.CodeBuffer;
   30   import org.apache.bsf.util.EngineUtils;
   31   import org.apache.bsf.util.JavaUtils;
   32   import org.apache.bsf.util.MethodUtils;
   33   import org.apache.bsf.util.ObjInfo;
   34   import org.apache.commons.logging.Log;
   35   import org.apache.commons.logging.LogFactory;
   36   
   37   /**
   38    * This is the interface to Java from the
   39    * Bean Scripting Framework.
   40    * <p>
   41    * The Java code must be written script-style -- that is, just the body of
   42    * the function, without class or method headers or footers.
   43    * The JavaEngine will generate those via a "boilerplate" wrapper:
   44    * <pre>
   45    * <code>
   46    * import java.lang.*;
   47    * import java.util.*;
   48    * public class $$CLASSNAME$$ {
   49    *   static public Object BSFJavaEngineEntry(org.apache.bsf.BSFManager bsf) {
   50    *     // Your code will be placed here
   51    *   }
   52    * }
   53    * </code>
   54    * </pre>
   55    * $$CLASSNAME$$ will be replaced by a generated classname of the form
   56    * BSFJava*, and the bsf parameter can be used to retrieve application
   57    * objects registered with the Bean Scripting Framework.
   58    * <p>
   59    * If you use the placeholder string $$CLASSNAME$$ elsewhere
   60    * in your script -- including within text strings -- BSFJavaEngine will
   61    * replace it with the generated name of the class before the Java code
   62    * is compiled.
   63    * <p>
   64    * <h2>Hazards:</h2>
   65    * <p>
   66    * NOTE that it is your responsibility to convert the code into an acceptable
   67    * Java string. If you're invoking the JavaEngine directly (as in the
   68    * JSPLikeInJava example) that means \"quoting\" characters that would
   69    * otherwise cause trouble.
   70    * <p>
   71    * ALSO NOTE that it is your responsibility to return an object, or null in
   72    * lieu thereof!
   73    * <p>
   74    * Since the code has to be compiled to a Java classfile, invoking it involves
   75    * a fair amount of computation to load and execute the compiler. We are
   76    * currently making an attempt to manage that by caching the class
   77    * after it has been loaded, but the indexing is fairly primitive. It has
   78    * been suggested that the Bean Scripting Framework may want to support
   79    * preload-and-name-script and execute-preloaded-script-by-name options to
   80    * provide better control over when and how much overhead occurs.
   81    * <p>
   82    * @author Joe Kesselman
   83    */
   84   public class JavaEngine extends BSFEngineImpl {
   85       Class javaclass = null;
   86       static Hashtable codeToClass = new Hashtable();
   87       static String serializeCompilation = "";
   88       static String placeholder = "$$CLASSNAME$$";
   89       String minorPrefix;
   90           
   91       private Log logger = LogFactory.getLog(this.getClass().getName());
   92       
   93       /**
   94        * Create a scratchfile, open it for writing, return its name.
   95        * Relies on the filesystem to provide us with uniqueness testing.
   96        * NOTE THAT uniqueFileOffset continues to count; we don't want to
   97        * risk reusing a classname we have previously loaded in this session
   98        * even if the classfile has been deleted.
   99        */
  100       private int uniqueFileOffset = -1;
  101       
  102       private class GeneratedFile {
  103           File file = null;
  104           FileOutputStream fos = null;
  105           String className = null;
  106           GeneratedFile(File file, FileOutputStream fos, String className) {
  107               this.file = file;
  108               this.fos = fos;
  109               this.className = className;
  110           }
  111       }
  112       
  113       /**
  114        * Constructor.
  115        */
  116       public JavaEngine () {
  117           // Do compilation-possible check here??????????????
  118       }
  119       
  120       public Object call (Object object, String method, Object[] args) 
  121       throws BSFException
  122       {
  123           throw new BSFException (BSFException.REASON_UNSUPPORTED_FEATURE,
  124           "call() is not currently supported by JavaEngine");
  125       }
  126       
  127       public void compileScript (String source, int lineNo, int columnNo,
  128               Object script, CodeBuffer cb) throws BSFException {
  129           ObjInfo oldRet = cb.getFinalServiceMethodStatement ();
  130           
  131           if (oldRet != null && oldRet.isExecutable ()) {
  132               cb.addServiceMethodStatement (oldRet.objName + ";");
  133           }
  134           
  135           cb.addServiceMethodStatement (script.toString ());
  136           cb.setFinalServiceMethodStatement (null);
  137       }
  138       
  139       /**
  140        * This is used by an application to evaluate a string containing
  141        * some expression. It should store the "bsf" handle where the
  142        * script can get to it, for callback purposes.
  143        * <p>
  144        * Note that Java compilation imposes serious overhead,
  145        * but in exchange you get full Java performance
  146        * once the classes have been created (minus the cache lookup cost).
  147        * <p>
  148        * Nobody knows whether javac is threadsafe.
  149        * I'm going to serialize access to protect it.
  150        * <p>
  151        * There is no published API for invoking javac as a class. There's a trick
  152        * that seems to work for Java 1.1.x, but it stopped working in Java 1.2.
  153        * We will attempt to use it, then if necessary fall back on invoking
  154        * javac via the command line.
  155        */
  156       public Object eval (String source, int lineNo, int columnNo, 
  157               Object oscript) throws BSFException
  158               {
  159           Object retval = null;
  160           String classname = null;
  161           GeneratedFile gf = null;
  162           
  163           String basescript = oscript.toString();
  164           String script = basescript;	// May be altered by $$CLASSNAME$$ expansion
  165           
  166           try {
  167               // Do we already have a class exactly matching this code?
  168               javaclass = (Class)codeToClass.get(basescript);
  169               
  170               if(javaclass != null) {
  171                   classname=javaclass.getName();
  172               } else {
  173                   gf = openUniqueFile(tempDir, "BSFJava",".java");
  174                   if( gf == null) {
  175                       throw new BSFException("couldn't create JavaEngine scratchfile");
  176                   }
  177                   // Obtain classname
  178                   classname = gf.className;
  179                   
  180                   // Write the kluge header to the file.
  181                   gf.fos.write(("import java.lang.*;"+
  182                           "import java.util.*;"+
  183                           "public class "+classname+" {\n" +
  184                   "  static public Object BSFJavaEngineEntry(org.apache.bsf.BSFManager bsf) {\n")
  185                   .getBytes());
  186                   
  187                   // Edit the script to replace placeholder with the generated
  188                   // classname. Note that this occurs _after_ the cache was checked!
  189                   int startpoint = script.indexOf(placeholder);
  190                   int endpoint;
  191                   if(startpoint >= 0) {
  192                       StringBuffer changed = new StringBuffer();
  193                       for(; startpoint >=0; startpoint = script.indexOf(placeholder,startpoint)) {
  194                           changed.setLength(0);	// Reset for 2nd pass or later
  195                           if(startpoint > 0) {
  196                               changed.append(script.substring(0,startpoint));
  197                           }
  198                           changed.append(classname);
  199                           endpoint = startpoint+placeholder.length();
  200                           if(endpoint < script.length()) {
  201                               changed.append(script.substring(endpoint));
  202                           }
  203                           script = changed.toString();
  204                       }
  205                   }
  206                   
  207                   // MJD - debug
  208   //              BSFDeclaredBean tempBean;
  209   //              String          className;
  210   //              
  211   //              for (int i = 0; i < declaredBeans.size (); i++) {
  212   //              tempBean  = (BSFDeclaredBean) declaredBeans.elementAt (i);
  213   //              className = StringUtils.getClassName (tempBean.bean.getClass ());
  214   //              
  215   //              gf.fos.write ((className + " " +
  216   //              tempBean.name + " = (" + className +
  217   //              ")bsf.lookupBean(\"" +
  218   //              tempBean.name + "\");").getBytes ());
  219   //              }
  220                   // MJD - debug
  221                   
  222                   // Copy the input to the file.
  223                   // Assumes all available -- probably mistake, but same as other engines.
  224                   gf.fos.write(script.getBytes());
  225                   // Close the method and class
  226                   gf.fos.write(("\n  }\n}\n").getBytes());
  227                   gf.fos.close();
  228                   
  229                   // Compile through Java to .class file
  230                   // May not be threadsafe. Serialize access on static object:
  231                   synchronized(serializeCompilation) {
  232                       JavaUtils.JDKcompile(gf.file.getPath(), classPath);
  233                   }
  234                   
  235                   // Load class.
  236                   javaclass = EngineUtils.loadClass(mgr, classname);
  237                   
  238                   // Stash class for reuse
  239                   codeToClass.put(basescript, javaclass);
  240               }
  241               
  242               Object[] callArgs = {mgr};      
  243               retval = internalCall(this,"BSFJavaEngineEntry",callArgs);
  244           }
  245           
  246           
  247           catch(Exception e) {
  248               e.printStackTrace ();
  249               throw new BSFException (BSFException.REASON_IO_ERROR, e.getMessage ());
  250           } finally {
  251               // Cleanup: delete the .java and .class files
  252               
  253   //          if(gf!=null && gf.file!=null && gf.file.exists())
  254   //          gf.file.delete();  // .java file
  255               
  256               
  257               if(classname!=null) {
  258                   // Generated class
  259                   File file = new File(tempDir+File.separatorChar+classname+".class");
  260   //              if(file.exists())
  261   //              file.delete();
  262                   
  263                   // Search for and clean up minor classes, classname$xxx.class
  264                   file = new File(tempDir);  // ***** Is this required?
  265                   minorPrefix = classname+"$"; // Indirect arg to filter
  266                   String[] minorClassfiles = file.list(new FilenameFilter()
  267                               {
  268                           // Starts with classname$ and ends with .class
  269                           public boolean accept(File dir,String name) {
  270                               return
  271                               (0 == name.indexOf(minorPrefix))
  272                               &&
  273                               (name.lastIndexOf(".class") == name.length()-6);
  274                           }
  275                               });
  276                   for(int i = 0; i < minorClassfiles.length; ++i) {
  277                       file = new File(minorClassfiles[i]);
  278   //                  file.delete();
  279                   }
  280               }
  281           }
  282           return retval;
  283       }
  284       
  285       public void initialize (BSFManager mgr, String lang,
  286               Vector declaredBeans) throws BSFException {
  287           super.initialize (mgr, lang, declaredBeans);
  288       }
  289       /**
  290        * Return an object from an extension.
  291        * @param object Object on which to make the internal_call (ignored).
  292        * @param method The name of the method to internal_call.
  293        * @param args an array of arguments to be
  294        * passed to the extension, which may be either
  295        * Vectors of Nodes, or Strings.
  296        */
  297       Object internalCall (Object object, String method, Object[] args) 
  298       throws BSFException
  299       {
  300           //***** ISSUE: Only static methods are currently supported
  301           Object retval = null;
  302           try {
  303               if(javaclass != null) {
  304                   //***** This should call the lookup used in BML, for typesafety
  305                   Class[] argtypes = new Class[args.length];
  306                   for(int i=0; i<args.length; ++i) {
  307                       argtypes[i]=args[i].getClass();
  308                   }
  309                   Method m = MethodUtils.getMethod(javaclass, method, argtypes);
  310                   retval = m.invoke(null, args);
  311               }
  312           }
  313           catch(Exception e) {
  314               throw new BSFException (BSFException.REASON_IO_ERROR, e.getMessage ());
  315           }
  316           return retval;
  317       }
  318       
  319       private GeneratedFile openUniqueFile(String directory,String prefix,String suffix) {
  320           File file = null;
  321           FileOutputStream fos = null;
  322           int max = 1000;		// Don't try forever
  323           GeneratedFile gf = null;
  324           int i;
  325           String className = null;
  326           for(i=max,++uniqueFileOffset; fos==null && i>0;--i,++uniqueFileOffset) {
  327               // Probably a timing hazard here... ***************
  328               try {
  329                   className = prefix+uniqueFileOffset;
  330                   file = new File(directory+File.separatorChar+className+suffix);
  331                   if(file != null && !file.exists()) {
  332                       fos = new FileOutputStream(file);
  333                   }
  334               }
  335               catch(Exception e) {
  336                   // File could not be opened for write, or Security Exception
  337                   // was thrown. If someone else created the file before we could
  338                   // open it, that's probably a threading conflict and we don't
  339                   // bother reporting it.
  340                   if(!file.exists()) {
  341                       logger.error("openUniqueFile: unexpected ", e);
  342                   }
  343               }
  344           }
  345           if(fos==null) {
  346               logger.error("openUniqueFile: Failed "+max+"attempts.");
  347           } else {
  348               gf = new GeneratedFile(file,fos,className);
  349           }
  350           return gf;
  351       }
  352   }

Save This Page
Home » bsf-src-2.4.0 » org.apache.bsf.engines.java » [javadoc | source]