RedwoodScript

RedwoodScript is available with the Scripting module; please check the License section 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 11. It allows you to script the repository using an extensive API, see the API documentation.

Tip: It is important to follow guidelines and conventions when writing code! This makes it easier for you and others to understand your code and will save you a lot of time when you will have to change something, even in your own code, 6 months from now.

Note: The Redwood_Script system privilege is available to restrict access to RedwoodScript, it is disabled by default.

You can use RedwoodScript (expressions) in:

Most contexts have predefined objects, see the Scripting Contexts section for more information.

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 of java.util)
  • java.math.* (including subpackages)
  • java.text.* (including subpackages)

Disabled Java Packages

Packages starting with sun and com.sun are no longer allowed to be imported by default, this can be changed by setting the /configuration/javatoolkit/importManager/allowSun registry entry to true.

Redwood recommends 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 Redwood Server from RedwoodScript. You can disable the methods setting the /configuration/javatoolkit/importManager/disableRuntimeExec registry entry to true.

Data Protection

The customer has to deal with any personal data that is brought in and consumed as part of the Redwood solution.

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 boiler plate code that is normally required to write RedwoodScript code that interacts with Redwood Server. The boiler plate 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 mode imports the following:

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:

Copy
{
  jcsOut.println("Hello, World!");
}

The following illustrates the "Hello, World" example when it is fully expanded into RedwoodScript:

Copy
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.

Please look at each of the links in the Scheduler API and Language Reference at the bottom of the page carefully, 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 the Querying the database section below 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 object called the jcsSession which is an instance of SchedulerSession in the API documentation:

Copy
[...]
JobDefinition jDefinition = jcsSession.getJobDefinitionByName("System_Info");
[...]

Once you have an object, you use methods to get information from it, or to manipulate it.

Copy
[...]
jcsOut.println(jDefinition.getName());
[...]

Submitting a job

Copy
{
  //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();
}

More documentation is available in the API documentation and on the Internet, RedwoodScript being a Java-like language, there are a lot of Java resources online.

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 PrintWriters.

You use the jcsOutLog and jcsErrLog objects for generating logging to stdout and stderr, respectively. These are available in some RedwoodScript contexts and are Loggers.

The following verbosity levels are available:

  • fatal
  • error
  • warn
  • info
  • debug

By default, the output is flushed automatically; this is needed for the debugger view in the Process Definition editor and for the tail functionality for inspecting output while the process is still running. You can disable this by issuing the following:

jcsOut/jcsErr

Copy
PrintWriter jcsErrLogNoFlush = new PrintWriter(jcsJob.getJobFileByName("stdout.log").getFileName());
or
PrintWriter jcsOutLogNoFlush = new PrintWriter(jcsJob.getJobFileByName("stdout.log").getFileName());

jcsOutLog/jcsErrLog

Copy
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. The data model can be found under the Scheduler API and Language Reference section on the index page.

Warning: Querying the database tables directly using other means is not supported. The table names, column names, and data types can and will change without prior notice.

The data model can be queried with the following function which is defined in the SchedulerSession object:

Copy
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 ( . ) as shown below:

Copy
jcsSession.executeQuery("select Job.JobId,Job.Description from Job", null, destination);

Or

Copy
jcsSession.executeQuery("select j.JobId,j.Description from Job j", null, destination);
Copy
{
  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 (for output on screen), CSV (comma separated values, for spreadsheet applications) and XML. For this you need to define a Reporter and ReportDestination; both require their own classes. The code below illustrates how to create a Reporter, a ReportDestination; and how to use a query for the report.

Copy
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:

Copy
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, like 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 is for illustration purposes, when 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:

Copy
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)

Copy
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 Process 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, that 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 only ever 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 )

Copy
{
  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, the code will report an invalid month in this case.

Copy
{
  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 with i for Olson time zone name.

The following code illustrates comparing two dates:

Copy
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);
}

Redwood Script