Distributed BeanShell Question
Michael Pitoniak <michael_pitoniak-okLH5SSHHyRWk0Htik3J/[email protected]> Thu, 30 Jun 2005 06:16:24 -0400
| Newsgroups | gmane.comp.java.beanshell.devel,gmane.comp.java.beanshell.user |
|---|---|
| Message-ID | <OF2FE7635E.763A2F2D-ON85257030.0037672D-85257030.00387FC8@symantec.com> |
team,
i have developed a test automation tool that essentially allows one
BeanShell interpreter to call another remote instance and request that it
run a script. i presently subclass the interpreter and use Object
Serialization to send the request. on the other side i unpack the request,
spawn the interpreter, buffer the response, and send it back. works
nicely. i recently extended it to allow it to execute BeanShell commands
on the other side, rather than scripts (after all commands are really just
scripts anyway). i developed a hokey mechanism for doing this:
1) send the command name over along with a hashmap of the named arguments
2) set this variable names equal to the values in the remote interpreter
3) dynamically build the method call signature and execute it
i.e.
set x = 1;
set y=2;
eval("foo(x, y););
it works, but there must be a more eleant way to accomplish this. can
anyone chime in if something more intellegent comes to mind? i am an
engineer in pursuit of excellence who is sometimes poorly equipt :)
best regards all,
mp
here is my class. it has some unrelated stuff in it, so please ignore
that:
/*
* Copyright (C) 2002 by Michael Pitoniak ([email protected]).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to:
*
* The Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330
* Boston, MA 02111-1307
* USA
*/
package harness.parsers.beanShell;
import harness.common.CommonConstants;
import harness.jComponents.jPanels.interfaces.JPanelConstants;
import harness.jComponents.jPanels.interfaces.TestPanelInterface;
import harness.logging.LogManager;
import harness.parsers.beanShell.interfaces.BeanShellParserConstants;
import harness.parsers.interfaces.ParserInterface;
import harness.parsers.interfaces.RemoteParserInterface;
import harness.parsers.interfaces.ScriptManagerInterface;
import harness.utils.classServices.ClassServices;
import harness.utils.exceptionServices.ExceptionServices;
import harness.utils.threadServices.BooleanLock;
import java.awt.Component;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**************************************************
*
* TEST FOR THIS IN:
* ExecRemoteScriptRequest.execte()
* ScriptJPanel.runScript()
*
* *************************************************/
public class BeanShellCMDParser extends java.lang.Thread implements
ParserInterface, ScriptManagerInterface, BeanShellParserConstants,
JPanelConstants, CommonConstants{
protected RemoteParserInterface m_RemoteParserInterface =
null;
protected BooleanLock m_BooleanLock = null;
protected String m_BeanShellCMD = null;
protected HashMap m_ArgsHashMap = null;
protected String m_PrefsFilePath = null;
protected long m_TimeOutMS;
private BeanShellInterptererThread2 m_BeanShellInterptererThread2 =
null;
private HashMap m_ResponseHashMap = null;
protected String m_InetAddrName = null;
private boolean m_ParserStopped = false;
protected boolean m_ParserTimeOut = false;
protected LogManager m_LogManager = null;
//TODO is this constructor needed anymore
//it looks like we use it just to initialize all common code to all
constructors
//note it is called by other constructors in this class
//following statement is not true...verify
/** Default constructor needed by SEFBeanShellScriptParser */
public BeanShellCMDParser() throws Exception{
m_ResponseHashMap = new HashMap();
m_LogManager = LogManager.Instance();
}
/** Creates a new instance of BeanShellParser */
public BeanShellCMDParser(RemoteParserInterface remoteParserInterface,
String bshCMD, HashMap args, String prefsFilePath, long timeOutMS) throws
Exception{
this();
m_RemoteParserInterface = remoteParserInterface;
m_BeanShellCMD = bshCMD;
m_ArgsHashMap = args;
m_PrefsFilePath = prefsFilePath;
m_TimeOutMS = timeOutMS;
}
public void run(){
scriptStateMachine();
}
protected void scriptStateMachine(){
int state = INITIALIZE;
try{
while(state != SCRIPT_COMPLETE) {
switch(state) {
case INITIALIZE:
initialize();
state = EVAL_CMD;
break;
case EVAL_CMD:
evalCMD(m_BeanShellCMD);
state = SCRIPT_COMPLETE;
break;
default:
writeError(ClassServices.getCurrentMethodName() +
"Illegal State Machine State");
state = SCRIPT_COMPLETE;
}
}
}catch(Error error){
/*If any ThreadDeath Errors slip past we get them here before
EventQue
An instance of ThreadDeath is thrown in the victim thread when
the stop
method with zero arguments in class Thread is called. An
application
should catch instances of this class only if it must clean up
after being
terminated asynchronously. If ThreadDeath is caught by a
method, it is
important that it be rethrown so that the thread actually
dies.
The top-level error handler does not print out a message if
ThreadDeath
is never caught. The class ThreadDeath is specifically a
subclass of
Error rather than Exception, even though it is a "normal
occurrence",
because many applications catch all occurrences of Exception
and then
discard the exception.*/
writeError(ClassServices.getCurrentMethodName() + error);
writeError(error.getStackTrace());
}catch(Exception e){
writeError(ClassServices.getCurrentMethodName(), e);
}finally{
//ScriptManager tracks timeout if we never get here
if(m_BooleanLock != null){
m_BooleanLock.setValue(true);
}
//set System.out and System,.err back before
//exiting because Redicted Streams are persistent
cleanUp();
}
}
//If this fails we throw an Exception and stop state machine
protected void initialize() throws Exception{
//reset output buffers on each script execution
try{
m_BooleanLock = new BooleanLock();
m_InetAddrName = InetAddress.getLocalHost().getHostName();
if(m_InetAddrName == null){
m_InetAddrName = "REMOTE";
}
//Set name for ThreadViewer Local/Remote Identification
if(m_RemoteParserInterface instanceof TestPanelInterface){
this.setName(ClassServices.getClassNameNoPackage(this.getClass().getName()));
}else{
this.setName("REMOTE_" +
ClassServices.getClassNameNoPackage(this.getClass().getName()));
}
}catch(Exception e){
writeError(ClassServices.getCurrentMethodName(), e);
throw e;
}
}
/* Note: Additional local namespace variables are initialized by
* BeanShellInterptererThread.initializeBshInterpreterNameSpace()
* Any additional variables that need to be loaded by a script
* can be achieved by just sourcing a file with varables
* defined in it. That file should follow java syntax.
*
* If this fails it throws an Exception and stops State Machine
*/
public void loadScriptArgs(Object args) throws Exception{
m_BeanShellInterptererThread2.loadScriptArgs(args);
}
/*
* NOTE: There are multiple instances of bsh classes in the jar file
mounts. Test Harness
* BeanShell support will not work unless the latest beanshell jar is
found first.
* The order in which directories and archives appear in the
Filesystems tabbed pane
* can affect the running and debugging of sources in the IDE. For
example, if you have
* Java files with identical class and package names in different
mounted filesystems,
* the file in the first mounted filesystem is always loaded during
execution and debugging.
* This happens even if you have select the second class before
calling the command.
* To change the order of mounted filesystems:
* In the Explorer, right-click the root Filesystems ( ) node and
choose Customize.
* Right-click the Filesystems Settings node. Choose Change Order from
the contextual menu.
* In the Change Order dialog box, select the filesystem you want to
move.
* Click the Move Up button or the Move Down button to change the
position
* of the filesystem relative to the other filesystems.
*/
//TODO this must be public because intrinsics call it....fix that
public void evalCMD(String bshCmd){
try{
//build the bsh cmd to evaluate
boolean bFirst = true;
StringBuffer bshCmdBuffer = new StringBuffer();
bshCmdBuffer.append(bshCmd + "(");
if(m_ArgsHashMap != null){
Set mappings = m_ArgsHashMap.entrySet();
for (Iterator i = mappings.iterator(); i.hasNext();) {
Map.Entry me = (Map.Entry)i.next();
Object key = me.getKey();
if(!bFirst){
bshCmdBuffer.append(", ");
}
bshCmdBuffer.append((String)key);
bFirst = false;
}
}
bshCmdBuffer.append(");");
writeInfo(ClassServices.getCurrentMethodName() + "Evaluating:
" + bshCmdBuffer.toString());
//TODO design change, BeanShellInterpreter should really take
ParserInterface
m_BeanShellInterptererThread2 = new
BeanShellInterptererThread2(this, m_BooleanLock, bshCmdBuffer.toString(),
m_ArgsHashMap, m_PrefsFilePath);
//the BeanShellInterpreter will source the script relative the
//the "BeanShell" CWD which was set in this.initialize()
//note: this call is synchronous, and returns when script is
complete
m_BeanShellInterptererThread2.start();
m_BooleanLock.waitUntilTrue(m_TimeOutMS);
if(m_BooleanLock.isFalse()){
m_ParserTimeOut = true;
m_RemoteParserInterface.writeError(ClassServices.getCurrentMethodName() +
m_InetAddrName + "->" + "Script TimeOut.......Exceeded: " + m_TimeOutMS +
"Ms");
}else{
m_RemoteParserInterface.writeInfo(ClassServices.getCurrentMethodName() +
"BooleanLock Released......Script Complete");
}
writeInfo(ClassServices.getCurrentMethodName() + "Done
Evaluating: " + bshCmd);
}catch(Exception e){
writeError(ClassServices.getCurrentMethodName(), e);
}catch(Error error){
writeError(ClassServices.getCurrentMethodName() +
ExceptionServices.getStackTrace(error));
throw error;
}finally{
if(m_RemoteParserInterface instanceof TestPanelInterface){
((TestPanelInterface)m_RemoteParserInterface).setRunButtonEnabled(true);
m_LogManager.closeScriptLogFiles();
//insure ElapsedTimerThread in BeanShellScriptParser dies
m_RemoteParserInterface = null;
}
}
}
public Object getEvalReturnObj(){
return m_BeanShellInterptererThread2.getEvalReturnObj();
}
//callesd by ScriptJPanel.stopScript() to perform a user abort.
public void stopParser(){
try{
//TODO find appropriate way to stop interpreter
//interrupt didn't work here, and stop is depricated
/* Thread.stop()
* The thread represented by this thread is forced to stop
whatever
* it is doing abnormally and to throw a newly created
ThreadDeath
* object as an exception.
* It is permitted to stop a thread that has not yet been
started.
* If the thread is eventually started, it immediately
terminates.
*/
setParserStopped();
if(m_BeanShellInterptererThread2.isAlive()){
writeInfo(ClassServices.getCurrentMethodName() + "STOPPING
INTERPRETER");
m_BeanShellInterptererThread2.stop();
}
}catch(Exception e){
}catch(Error err){
}
}
/*
* By setting Interpreter's parser and interpreter variables to null
* when the Parser completes, or times out, we insure that any script
* spawned threads that try to access the parser's methods after the
* script completes, or is stopped; will throw an exception and exit.
* This prevents unwanted Script spawned Threads executing after the
* RemoteClient session exits.
*
* Called at end of ScriptManager.scriptStateMachine()
*
* */
public void disableParser(){
try{
m_BeanShellInterptererThread2.setInterpreterVar("parser",
null);
m_BeanShellInterptererThread2.setInterpreterVar("interpreter",
null);
}catch(Exception e){
e.printStackTrace();
}
}
/* This method is Overridden in derived subclasses to perform any
parser
* specific cleanup required by that class. This is very usefull as
"any"
* syntax error in a script will cause the beanshell interpreter to
exit
* without executing any catch or finally in the script. This method
ends
* up being the only way to implement finally() for the interpreter.
*/
protected void cleanUp(){
//override for specific cleanup needs
}
/* This method is used to initialize BeanShellInterpreter local
varaibles "
* before" scriptexecution.
**/
public void setVariableValue(String var, Object val){
try{
m_BeanShellInterptererThread2.setInterpreterVar(var, val);
}catch(Exception e){
writeError(ClassServices.getCurrentMethodName(), e);
}
}
//TODO can we consolidate setVariableValue and this??
public void addResponseVar(String var, Object value){
m_ResponseHashMap.put(var, value);
}
//TODO deprecate?
public void addResponseHashMap(HashMap hashMap){
m_ResponseHashMap = hashMap;
}
/*Scripts pass varaibles back to RemoteClient Matser via
addResponseVar()
* ExecRemoteScriptRequest constructs an ExecRemoteScriptResponse, and
calls
* getResponseHashMap() to get the HashMap to pass back
*/
public HashMap getResponseHashMap(){
return m_ResponseHashMap;
}
//getParserStopped() is used by scripts to determine in parser has
been halted
public boolean getParserStopped() {
return m_ParserStopped;
}
public void setParserStopped() {
m_ParserStopped = true;
}
/*
*
* RemoteParserInterface Methods
*
* */
public boolean getScriptSuccess(){
return m_RemoteParserInterface.getScriptSuccess();
}
public void setScriptSuccess(boolean bVal){
m_RemoteParserInterface.setScriptSuccess(bVal);
}
public long incrFailCnt(){
return m_RemoteParserInterface.incrFailCnt();
}
public long incrSuccessCnt(){
return m_RemoteParserInterface.incrSuccessCnt();
}
public long incrScriptIterationCnt(){
return m_RemoteParserInterface.incrScriptIterationCnt();
}
public void writeOutput(Object obj){
m_RemoteParserInterface.writeOutput(obj);
}
public void writeOutput(String[] strArray){
m_RemoteParserInterface.writeOutput(strArray);
}
public void writeOutput(int[] intArray) {
m_RemoteParserInterface.writeOutput(intArray);
}
public void writeInfo(Object obj){
m_RemoteParserInterface.writeInfo(obj);
}
public void writeError(String methodName, Exception e){
m_RemoteParserInterface.writeError(methodName, e);
}
public void writeError(Object obj){
m_RemoteParserInterface.writeError(obj);
}
public void writeError(String[] strArray){
m_RemoteParserInterface.writeError(strArray);
}
public void writeError(int[] intArray){
m_RemoteParserInterface.writeError(intArray);
}
public void writeError(StackTraceElement[] stackTraceElementArray){
StringBuffer sb = new StringBuffer();
for(int i=0; i< stackTraceElementArray.length; i++){
sb.append(stackTraceElementArray[i] + NEWLINE_CHAR);
}
m_RemoteParserInterface.writeError(sb.toString());
}
public void writeWarning(Object obj) {
m_RemoteParserInterface.writeWarning(obj);
}
public void writeDebug(Object obj){
m_RemoteParserInterface.writeDebug(obj);
}
public void writeDebug(String[] strArray){
m_RemoteParserInterface.writeDebug(strArray);
}
public void writeLogger(Object obj){
m_RemoteParserInterface.writeLogger(obj);
}
public void writeLogger(String[] strArray){
m_RemoteParserInterface.writeLogger(strArray);
}
public void writeReporting(Object obj){
m_RemoteParserInterface.writeReporting(obj);
}
public void writeStackTrace(Exception e) {
m_RemoteParserInterface.writeStackTrace(e);
}
public void closeLogFiles(){
//TODO what about console logs
m_LogManager.closeScriptLogFiles();
}
/*
*
* ParserInterface Methods
*
* */
public void addJTabbedPaneComponent(String tabTitle, Component
component) {
try{
if(m_RemoteParserInterface instanceof ParserInterface){
((ParserInterface)m_RemoteParserInterface).addJTabbedPaneComponent(tabTitle,
component);
}else{
m_RemoteParserInterface.writeError(ClassServices.getCurrentMethodName() +
"addJTabbedPaneComponent() not supported on RemoteExecution");
}
}catch(Exception e){
writeError(ClassServices.getCurrentMethodName(), e);
}
}
public void enableTab(int index, boolean bval){
try{
if(m_RemoteParserInterface instanceof ParserInterface){
((ParserInterface)m_RemoteParserInterface).enableTab(index, bval);
}else{
m_RemoteParserInterface.writeError(ClassServices.getCurrentMethodName() +
"enableTab() not supported on RemoteExecution");
}
}catch(Exception e){
writeError(ClassServices.getCurrentMethodName(), e);
}
}
public void removeTab(int index){
try{
if(m_RemoteParserInterface instanceof ParserInterface){
((ParserInterface)m_RemoteParserInterface).removeTab(index);
}else{
m_RemoteParserInterface.writeError(ClassServices.getCurrentMethodName() +
"removeTab() not supported on RemoteExecution");
}
}catch(Exception e){
writeError(ClassServices.getCurrentMethodName(), e);
}
}
public void removeTab(String tabTitle){
try{
if(m_RemoteParserInterface instanceof ParserInterface){
((ParserInterface)m_RemoteParserInterface).removeTab(tabTitle);
}else{
m_RemoteParserInterface.writeError(ClassServices.getCurrentMethodName() +
"removeTab() not supported on RemoteExecution");
}
}catch(Exception e){
writeError(ClassServices.getCurrentMethodName(), e);
}
}
public void setActiveTab(String tabTitle){
try{
if(m_RemoteParserInterface instanceof ParserInterface){
((ParserInterface)m_RemoteParserInterface).setActiveTab(tabTitle);
}else{
m_RemoteParserInterface.writeError(ClassServices.getCurrentMethodName() +
"setActiveTab() not supported on RemoteExecution");
}
}catch(Exception e){
writeError(ClassServices.getCurrentMethodName(), e);
}
}
//used in ExecRemoteScriptRequest/ExecRemoteScriptResponse to track
Remote Script time-out
//TODO we presently dont use this info in executeRemoteScript()
intrinsic
//since we propogate timeout value, the RemoteClient connection will
expire before
//the remote script times out....analyze design here
public boolean getParserTimedOut(){
return m_ParserTimeOut;
}
/*this method does not need to be synchronized because it is
called before BeanShellScriptParser is started, and therefor
is only called by this Thread*/
public void zeroCounters() {
//TODO finish
}
public static void main(String args[]) {
int i=0;
try{
while(true){
BeanShellCMDParser beanShellCMDParser = new
BeanShellCMDParser();
System.out.println(i++);
}
}catch(Exception e){}
}
}