blob: 5c0824400267093c2fcc5a5717cf7d42b1409d35 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.BSF_Log;
import org.apache.bsf.BSF_LogFactory;
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;
/**
* 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
* @author Rony G. Flatscher (added BSF_Log[Factory] to allow BSF to run without org.apache.commons.logging present)
*/
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());
private BSF_Log logger = null;
/**
* 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 ()
{
// handle logger
logger = BSF_LogFactory.getLog(this.getClass().getName());
/*
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 {}
}