/*
* Sun Public License
*
* The contents of this file are subject to the Sun Public License Version
* 1.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is available at http://www.sun.com/
*
* The Original Code is the SLAMD Distributed Load Generation Engine.
* The Initial Developer of the Original Code is Alan Field.
* Portions created by Neil A. Wilson are Copyright (C) 2004-2010.
* Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): Neil A. Wilson and Alan Field
*/
package com.slamd.scripting;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import org.apache.bsf.BSFManager;
import com.slamd.job.JobClass;
import com.slamd.job.UnableToRunException;
import com.slamd.parameter.FileURLParameter;
import com.slamd.parameter.IntegerParameter;
import com.slamd.parameter.InvalidValueException;
import com.slamd.parameter.LabelParameter;
import com.slamd.parameter.MultiLineTextParameter;
import com.slamd.parameter.Parameter;
import com.slamd.parameter.ParameterList;
import com.slamd.parameter.StringParameter;
import com.slamd.stat.RealTimeStatReporter;
import com.slamd.stat.StatTracker;
/**
* This class implements a SLAMD job that executes a Script file to generate
* load. It implements the standard SLAMD job methods and adds three new methods
* to allow the job to be referenced from the Script file.
*
* @author Alan Field
*/
public class BSFJobClass
extends JobClass
{
/**
* The name of the job parameter that defines the script file to use.
*/
public static final String SCRIPT_LANGUAGE_PARAMETER_NAME = "script_language";
/**
* The name of the job parameter that defines the script file to use.
*/
public static final String SCRIPT_EXTENSION_PARAMETER_NAME =
"script_extension";
/**
* The name of the job parameter that defines the script file to use.
*/
public static final String ENGINE_CLASS_PARAMETER_NAME = "engine_class";
/**
* The name of the job parameter that defines the script file to use.
*/
public static final String FILE_PATH_PARAMETER_NAME = "file_path";
/**
* The name of the job parameter that allows the user to pass arguments to
* the script.
*/
public static final String SCRIPT_ARGUMENTS_PARAMETER_NAME =
"script_arguments";
/**
* The name of the job parameter that defines the number of clients running
* this job.
*/
public static final String NUMBER_OF_CLIENTS_PARAMETER_NAME = "num_clients";
// The map containing the set of script arguments provided when the
// job was scheduled.
private static HashMap<String, String> arguments = null;
// The file parameter assigned to this job
private static FileURLParameter bsfFile = null;
// Parameters needed for an external BSFEngine class
private static String bsfLanguage = null;
private static String bsfLanguageExtension = null;
private static String bsfEngine = null;
// The contents of bsfFile
private static StringBuilder script = null;
// The BSF Manager object
private BSFManager bsfManager = null;
// A Vector of StatTracker objects used by the script file
private ArrayList<StatTracker> statTrackers = null;
/**
* Creates a new instance of this job thread. This constructor does not
* need to do anything other than invoke the constructor for the
* superclass.
*/
public BSFJobClass()
{
super();
}
/**
* {@inheritDoc}
*/
@Override()
public void destroyThread()
{
if (this.bsfManager != null)
{
this.bsfManager.unregisterBean("jobClass");
this.bsfManager.terminate();
this.bsfManager = null;
}
}
/**
* {@inheritDoc}
*/
@Override()
public String getJobName()
{
return "Bean Scripting Framework";
}
/**
* {@inheritDoc}
*/
@Override()
public String getShortDescription()
{
return "Execute a job written in a script supported by the Bean " +
"Scripting Framework";
}
/**
* {@inheritDoc}
*/
@Override()
public String getJobCategoryName()
{
return "Scripting";
}
/**
*/
@Override()
public ParameterList getParameterStubs()
{
LabelParameter scriptDescription = new LabelParameter(
"To use an external script engine, supply the next three parameters." +
" If the engine is included in the bsf.jar file, leave these " +
"arguments blank.");
StringParameter scriptLanguageParameter = new StringParameter(
SCRIPT_LANGUAGE_PARAMETER_NAME, "BSF Language Name",
"The name of the language to register with the BSFManager.", false,
null);
StringParameter scriptExtensionParameter = new StringParameter(
SCRIPT_EXTENSION_PARAMETER_NAME, "Scripting File Extension",
"The file name extension for this scripting language.", false, null);
StringParameter scriptEngineParameter = new StringParameter(
ENGINE_CLASS_PARAMETER_NAME, "Script Engine Class Name",
"The fully qualified class name for the BSFEngine class implemented " +
"for this scripting language.",
false, null);
FileURLParameter scriptFileParameter =
new FileURLParameter(FILE_PATH_PARAMETER_NAME, "Script File URL",
"The URL to the file containing the script to " +
"execute. This URL may reference the file "
+ "location using http, https, ftp, or file.",
null, true);
MultiLineTextParameter scriptArgumentsParameter =
new MultiLineTextParameter(SCRIPT_ARGUMENTS_PARAMETER_NAME,
"Script Arguments",
"A set of arguments that may be provided "
+ "to the script that can customize the " +
"way that it behaves. The arguments "
+
"should be provided one per line in the " +
"format \"name=value\".", new String[0],
false);
scriptArgumentsParameter.setVisibleColumns(80);
scriptArgumentsParameter.setVisibleRows(10);
Parameter[] parameterArray = new Parameter[]
{
scriptDescription,
scriptLanguageParameter,
scriptExtensionParameter,
scriptEngineParameter,
scriptFileParameter,
scriptArgumentsParameter
};
return new ParameterList(parameterArray);
}
/**
* {@inheritDoc}
*/
@Override()
public StatTracker[] getStatTrackerStubs(String clientID, String threadID,
int collectionInterval)
{
return new StatTracker[0];
}
/**
* {@inheritDoc}
*/
@Override()
public StatTracker[] getStatTrackers()
{
// If the script has added stat trackers, then get the stat trackers
// associated with it. Otherwise, just return an empty set.
if (statTrackers.isEmpty())
{
return new StatTracker[0];
}
return statTrackers.toArray(new StatTracker[0]);
}
/**
* {@inheritDoc}
*/
@Override()
public void validateJobInfo(int numClients, int threadsPerClient,
int threadStartupDelay, Date startTime,
Date stopTime, int duration,
int collectionInterval, ParameterList parameters)
throws InvalidValueException
{
// This is sneaky. We're going to pass the number of clients to the job as
// a job parameter. Otherwise, the job would have no idea how many clients
// there are, and therefore would have no idea how to break up the work for
// each client.
IntegerParameter clientsParameter =
new IntegerParameter(NUMBER_OF_CLIENTS_PARAMETER_NAME, numClients);
parameters.addParameter(clientsParameter);
//Determine if all of the arguments to register a script engine have been
// supplied
bsfLanguage = parameters.getStringParameter(SCRIPT_LANGUAGE_PARAMETER_NAME)
.getStringValue();
bsfLanguageExtension = parameters
.getStringParameter(SCRIPT_EXTENSION_PARAMETER_NAME).getStringValue();
bsfEngine = parameters.getStringParameter(ENGINE_CLASS_PARAMETER_NAME)
.getStringValue();
if ((!(bsfLanguage.length() + bsfLanguageExtension.length() +
bsfEngine.length() == 0)) &&
!((bsfLanguage.length() > 0) &&
(bsfLanguageExtension.length() > 0) &&
(bsfEngine.length() > 0)))
{
throw new InvalidValueException(
"To use an external script engine, you must supply a value for " +
"all three of these parameters, otherwise leave them all blank: " +
"'BSF Language Name', 'Scripting File Extension', and 'Script " +
"Engine Class Name'");
}
}
/**
* {@inheritDoc}
*/
@Override()
public void initializeClient(String clientID, ParameterList parameters)
throws UnableToRunException
{
// Retrieve the Script file to ensure that it is available and can
// be parsed properly. If not, then throw an exception to let the end
// user know what the problem is.
bsfFile = parameters.getFileURLParameter(FILE_PATH_PARAMETER_NAME);
try
{
String[] scriptLines = bsfFile.getFileLines();
script = new StringBuilder();
for (int i = 0; i < scriptLines.length; i++)
{
script.append(scriptLines[i])
.append(System.getProperty("line.separator"));
}
}
catch (Exception e)
{
throw new UnableToRunException("Unable to retrieve the Script file: " +
bsfFile.getValueString() + '\n' +
stackTraceToString(e));
}
MultiLineTextParameter mtp =
parameters.getMultiLineTextParameter(SCRIPT_ARGUMENTS_PARAMETER_NAME);
arguments = new HashMap<String, String>();
// Get the total number of clients.
IntegerParameter clientsParameter =
parameters.getIntegerParameter(NUMBER_OF_CLIENTS_PARAMETER_NAME);
if (clientsParameter != null)
{
arguments.put(NUMBER_OF_CLIENTS_PARAMETER_NAME,
clientsParameter.getValueString());
}
// All three parameters are needed to register a new scripting engine,
// because the JobClass uses getLangFromFilename() to determine the language
// from the file extension.
if ((bsfLanguage != null) && (bsfLanguageExtension != null) &&
(bsfEngine != null))
{
BSFManager.registerScriptingEngine(bsfLanguage, bsfEngine, new String[]{
bsfLanguage,
bsfLanguageExtension });
}
if (mtp != null)
{
String[] lines = mtp.getNonBlankLines();
for (int i = 0; i < lines.length; i++)
{
int equalPos = lines[i].indexOf('=');
if (equalPos > 0)
{
String name = lines[i].substring(0, equalPos);
String value = lines[i].substring(equalPos + 1);
arguments.put(name, value);
}
}
}
}
/**
* {@inheritDoc}
*/
@Override()
public void initializeThread(String clientID, String threadID,
int collectionInterval,
ParameterList parameters)
throws UnableToRunException
{
this.statTrackers = new ArrayList<StatTracker>();
this.bsfManager = new BSFManager();
//Add the jobClass to the namespace of the script
this.bsfManager.registerBean("jobClass", this);
//Add the Hashtable named "arguments" to the namespace of the script
this.bsfManager.registerBean("arguments", arguments);
}
/**
* {@inheritDoc}
*/
@Override()
public void runJob()
{
// Execute the instructions contained in the script.
try
{
// Execute the script using the file name extension to determine the
// language
this.bsfManager.exec(
BSFManager.getLangFromFilename(bsfFile.getValueString()),
new File(bsfFile.getValueString()).getName(), 1, 1,
script.toString());
}
catch (Exception e)
{
this.logMessage(
"Exception occurred while running script : " + e.toString());
e.printStackTrace();
indicateStoppedDueToError();
}
finally
{
if (this.bsfManager != null)
{
this.bsfManager.unregisterBean("jobClass");
this.bsfManager.terminate();
this.bsfManager = null;
}
}
}
/**
* Initializes a StatTracker created in the Script file. Adds the
* StatTracker to the Vector of StatTrackers that will be returned by the
* job in the getStatTrackers() function. Starts the tracker and enables
* real time statistics if necessary.
*
* @param theTracker The StatTracker class.
* @param displayName The StatTracker's display name.
*
* @return The initialized StatTracker.
*/
public StatTracker initializeTracker(StatTracker theTracker,
String displayName)
{
statTrackers.add(theTracker);
theTracker.setDisplayName(displayName);
theTracker.setCollectionInterval(this.getCollectionInterval());
theTracker.setClientID(this.getClientID());
theTracker.setThreadID(this.getThreadID());
theTracker.startTracker();
RealTimeStatReporter statReporter = this.getStatReporter();
if ((this.enableRealTimeStats()) && (statReporter != null))
{
theTracker.enableRealTimeStats(statReporter, this.getJobID());
}
return theTracker;
}
/**
* Retrieves the value from a specified job argument
*
* @param argName The name of the argument to retrieve.
* @param defaultValue The default value if one is not in the Hashtable.
*
* @return The argument's value as a string.
*/
public String getArgument(String argName, String defaultValue)
{
String value = defaultValue;
if (arguments.containsKey(argName))
{
value = arguments.get(argName);
}
else
{
if (defaultValue != null)
{
arguments.put(argName, defaultValue);
}
}
if (this.getThreadNumber() == 0)
{
this.logMessage(GregorianCalendar.getInstance().getTime().toString() +
'\t' + this.getThreadID() + "\t:\t Argument '" + argName +
"' has the value '" + value + '\'');
}
return value;
}
/**
* Retrieves the value from a specified job argument
*
* @param argName The name of the argument to retrieve.
*
* @return The argument's value as a string or null.
*/
public String getArgument(String argName)
{
return getArgument(argName, null);
}
}