RedwoodScript
RedwoodScript is available with the Scripting module (See the License topic for more information). It is unrelated to Redwood Expression Language (REL) and can be used in different scripting contexts. You can create your own procedures and functions in RedwoodScript and use them from within REL using libraries and REL entry points.
RedwoodScript is a Java-like language based on Java. It allows you to script the repository using an extensive API, see the API documentation.
Note: The Redwood_Script system privilege is available to restrict access to RedwoodScript. It is disabled by default.
You can use RedwoodScript (expressions) in:
- Libraries
- Actions
- Job Definition on-change
- Job Definition Pre Running
- Job Definition post-running
- Import Rule Set Actions
- Active Monitoring Actions
- Triggers
- Before Definition Change
- Before Process On Change
- Before Process On Change Maintenance
- Before Process Pre Running
- Before Process Post Running
- On Process File Content Access
- Job Definitions using the RedwoodScript Definition Type.
- The Shell found in the user interface under Scripting.
Most contexts have predefined objects. For more information, see Scripting Contexts.
The following functions and methods are available in RedwoodScript:
- A subset of Java classes.
- Redwood API.
For security reasons, not all Java classes are available to RedwoodScript code. You can import the following classes like so: import java.util.*
java.lang.Byte
java.lang.Char
java.lang.Short
java.lang.Integer
java.lang.Long
java.lang.Number
java.lang.Math
java.lang.StrictMath
java.lang.Float
java.lang.Double
java.lang.String
java.lang.Boolean
java.util.*
(but not subpackages ofjava.util
)java.math.*
(including subpackages)java.text.*
(including subpackages)
Disabled Java Packages
Packages starting with sun
and com.sun
cannot be imported by default. This can be changed by setting the /configuration/javatoolkit/importManager/allowSun
registry entry to true
.
Redwood recommends that you disable the Runtime.getRuntime().exec()
methods using a registry entry. Note that these methods allow you to execute OS commands on the server hosting RunMyJobs from RedwoodScript. You can disable the methods setting the /configuration/javatoolkit/importManager/disableRuntimeExec
registry entry to true
.
Syntax
RedwoodScript provides three syntax options:
- Short
- Short with imports
- Full
The first two are abbreviated syntax that allow you to dispense with all or most of the boilerplate code that is normally required to write RedwoodScript code that interacts with RunMyJobs. The boilerplate code includes all the declarations you would usually have to make yourself.
The Short mode automatically generates everything, including import statements for classes that you are most likely to use in your task. This mode is activated if the first symbol is {
.
The Short with imports mode allows you to specify the imports. In this case there are no automatic imports, and you will have to specify all the imports you need. This mode is activated if the first symbol is import
.
The Full mode lets you write the entire Java class. This mode is activated if the first symbol is package
. In full mode you will have to explicitly extend the stub that is provided for you. This will be done automatically in all other modes.
Note: RedwoodScript code in the library source field must always be in Full mode.
The following illustrates the use of the short syntax for a Hello World! program:
{
jcsOut.println("Hello, World!");
}
The following illustrates the "Hello, World" example when it is fully expanded into RedwoodScript:
package com.redwood.scheduler.custom;
public class name
extends nameStub
{
public void execute()
throws Exception
{
jcsOut.println("Hello world");
}
//... lots of code ...
}
As you can see there is quite a considerable amount of code generated for you. The only difference between the short and short with imports is that in short mode, all the imports are specified for you, and in short with imports mode you must specify all required imports yourself.
Redwood API
In most contexts, frequently used Redwood API classes are imported by default. The imported classes and objects are visible in the Stub code.
You can find information about objects and their types in the API documentation.
The main index consists of two sections:
- Packages: The top table, in the form of JavaDoc.
- The Scheduler API and Language Reference: Documentation about context variables and programming using RedwoodScript.
Tip: Look carefully at each of the links in the Scheduler API and Language Reference at the bottom of the page. They contain the answers to many frequently asked questions.
The Data model shows you the available tables in the database for use with RedwoodScript. See Querying the database for examples of how to query the database with RedwoodScript.
Object Model
Repository Objects have types. For example:
- Job
- JobDefinition
- Queue
- TimeWindow
The type of the Object determines the methods you can call.
You call methods on an instance of an Object. The available methods are documented in the API documentation.
You can get repository Objects from a predefined RedwoodScript object called the jcsSession, which is an instance of SchedulerSession in the API documentation:
[...]
JobDefinition jDefinition = jcsSession.getJobDefinitionByName("System_Info");
[...]
Once you have an object, you use methods to get information from it, or to manipulate it.
[...]
jcsOut.println(jDefinition.getName());
[...]
To run a Job:
{
//Get the job definition
JobDefinition jDefinition = jcsSession.getJobDefinitionByName("System_Sleep");
//Create the Job
Job process = jDefinition.prepare();
//Get the Queue
Queue queue = jcsSession.getQueueByName("System");
//Attach queue to job
process.setQueue(queue);
//Print out the jobid of the job
jcsOut.println(process.getJobId());
//Submit the job
jcsSession.persist();
}
Producing Output and Logging
You use the jcsOut
and jcsErr
objects for generating output to stdout
and stderr
, respectively. These are available in all RedwoodScript contexts and are PrintWriter
s.
You use the jcsOutLog
and jcsErrLog
objects for generating logging to stdout
and stderr
, respectively. These are available in some RedwoodScript contexts and are Logger
s.
The following verbosity levels are available:
fatal
error
warn
info
debug
By default, the output is flushed automatically. This is necessary for the debugger view in the Job Definition editor and for the tail functionality for inspecting output while a Job is still running. You can disable this by issuing the following:
jcsOut
/jcsErr
PrintWriter jcsErrLogNoFlush = new PrintWriter(jcsJob.getJobFileByName("stdout.log").getFileName());
or
PrintWriter jcsOutLogNoFlush = new PrintWriter(jcsJob.getJobFileByName("stdout.log").getFileName());
jcsOutLog
/jcsErrLog
jcsJobContext.get{Error|Output}Logger.setAutoFlush(false);
Querying the Database
You cannot query the tables in the database directly, only an upper-layer table-implementation. The available tables in Redwood Server are listed in the API documentation.
Warning: Querying database tables directly using other means is not supported. The table names, column names, and data types can and will change without notice.
The data model can be queried with the following function, which is defined in the SchedulerSession
object:
executeQuery(query string, [[bind variables], <callback>)
query string
: A simple string containing a supported SQL'92 query (only a subset of the SQL'92 standard is supported)bind variables
: Used in the query to increase performance. Syntax: use?
in the query as a placeholder. These should be specified in the order they appear in your query.- Callback (optional): Contains the resultset from the query. Multiple classes implement / interfaces extend the APIResultSetCallback interface:
- LongCallBack: Used to retrieve the first column of the result and stores it in Long.
- ReportDestination: Interface that extends APIResultSetCallback - used for reporting
Standard SQL can be used in queries. However, when referring to a column, you need to prepend the table name, which name is the type of the object you want to query for, followed by a period:
jcsSession.executeQuery("select Job.JobId,Job.Description from Job", null, destination);
Or
jcsSession.executeQuery("select j.JobId,j.Description from Job j", null, destination);
{
LongCallBack callback = new LongCallBack(1);
try
{
jcsSession.executeQuery("select count(*) from Job where Job.Status = 'E'", null, callback);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
Long errCount = (Long) callback.getResult().get(0);
if (errCount != null)
{
jcsOut.println(errCount + " job(s) in status Error.");
}
}
The output can be generated in HTML, CSV, and XML formats. For this you need to define a Reporter
and ReportDestination
, and both require their own classes. The code below illustrates how to create a Reporter
and a ReportDestination
, and how to use a query for the report.
import com.redwood.scheduler.api.model.report.Reporter;
import com.redwood.scheduler.api.model.report.ReportDestination;
{
String query = "select Job.JobId,Job.Description from Job where Job.JobId <= 244";
Reporter reporter = jcsSession.createReporter(jcsOut);
ReportDestination destination = reporter.getCSVReportDestination();
jcsSession.executeQuery(query, null, destination);
}
The same example as above using bind variables:
import com.redwood.scheduler.api.model.report.Reporter;
import com.redwood.scheduler.api.model.report.ReportDestination;
{
String query = "select Job.JobId,Job.Description from Job where Job.JobId <= ?";
Reporter reporter = jcsSession.createReporter(jcsOut);
ReportDestination destination = reporter.getCSVReportDestination();
jcsSession.executeQuery(query, new Object[] { new Long(244L) }, destination);
}
Bind variables can increase the performance and scalability of queries, especially simple queries like the one above. Some supported databases, such as Oracle, parse every query once and skip some queries they have already parsed. So if the Job.JobId
above changes frequently, the database will have to parse each and every query for each Job.JobId
. If we use a bind variable, this step can be skipped.
The following example assumes that you do not know the status code and want to use it in a query (note that looking up the code is easier). You have to import the JobStatus
class. The fully qualified class name can be found in the API documentation:
import com.redwood.scheduler.api.model.enumeration.JobStatus;
import com.redwood.scheduler.api.model.report.Reporter;
import com.redwood.scheduler.api.model.report.ReportDestination;
{
String query = "select Job.JobId,Job.Description from Job where Job.Status = ?";
Reporter reporter = jcsSession.createReporter(jcsOut);
ReportDestination destination = reporter.getCSVReportDestination();
jcsSession.executeQuery(query, new Object[] { JobStatus.ScheduledCode }, destination);
}
Using executeQuery
to store results in a Map
(example with APIResultSetCallback
implementation):
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import com.redwood.scheduler.api.model.APIResultSetCallback;
import com.redwood.scheduler.api.model.ObjectGetter;
{
//Get the job ID and Job Server ID for each job that has status Killed, Canceled, Error, or Unknown
String query = "select j.JobId, j.ProcessServer from Job j where j.Status in ('K', 'A', 'E', 'U')";
final Map map = new HashMap<>();
jcsSession.executeQuery(query, null, new APIResultSetCallback() {
public void start()
{
//This might be called multiple times in a row when database timeouts occur
map.clear();
}
public boolean callback(ResultSet rs, ObjectGetter objectGetter)
throws SQLException
{
//ResultSet here stores only one row
Long jobId = new Long(rs.getLong(1));
Long psId = new Long(rs.getLong(2));
map.put(jobId, psId);
//Cool, we made it, return true
return true;
}
public void finish()
{
// Do nothing
}
});
jcsOut.println(map.values().size());
}
Dates
Dates are lenient. This means that 32/12/2021
is treated as 01/01/2022
. You can set dates to be treated strictly by setting the lenient flag to false.
Example: November has 30 days.
In this first example, the lenient flag is set to true
, which is the default. The resulting date is: 2022/12/01 12:10:10,010 Europe/Paris
(the time zone in this example is Europe/Paris).
{
DateTimeZone dtzone = new DateTimeZone(2022,10,31,12,10,10,10);
jcsOut.println(dtzone.toString());
}
In the following example, we set the lenient flag to false
. In this case, the code will report an invalid month.
{
DateTimeZone dtzone = new DateTimeZone(2022,10,31,12,10,10,10);
dtzone.setLenient(false);
jcsOut.println(dtzone.toString());
}
Comparing Timestamps
Dates or timestamps in the repository are stored as a combination of an offset since 'the start of time' and a time zone. The start of time (also known as the epoch ) is the number of milliseconds since midnight, Jan 1st 1970 UTC.
To compare dates in queries, a SQL function named TODATE
can be used:
TODATE(input, timezone)
: Use default format (yyyy-MM-dd HH:mm:ss)TODATE(input, timezone, format)
: Use Simple Date Format withi
for Olson time zone name.
The following code illustrates comparing two dates.
import com.redwood.scheduler.api.model.report.Reporter;
import com.redwood.scheduler.api.model.report.ReportDestination;
{
Reporter reporter = jcsSession.createReporter(jcsOut);
ReportDestination destination = reporter.getCSVReportDestination();
jcsSession.executeQuery("select JobId from Job where CreationTime > TODATE('2023-07-27', 'CET', 'yyyy-MM-dd')", null, destination);
}