Save This Page
Home » bsf-src-2.4.0 » org.apache.bsf.engines.netrexx » [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.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   }

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