Using the RedwoodScript Definition Type
RedwoodScript is available with the Scripting module; please check the License section for more information.
RedwoodScript is based on the Java language. It allows you to create process definitions with RedwoodScript in its source. The RedwoodScript definition type is used to create processes that interact primarily with the server interfaces themselves. For instance, you can write a process that submits other processes or retrieves and combines meta data or output from other processes.
note
You must assign at least one process server to run RedwoodScript process definitions in order to use the definition type.
Variable and Parameters
Variables that you require to hold intermediary values are defined in the same way as you would normally define variables in Java, using the standard syntax String myVar
or String myVar = "initial value"
. Array parameters are available for RedwoodScript and are accessed like regular Java arrays. Arrays must have unique elements.
Process definition parameters are available directly in RedwoodScript as Java variables, as shown in the examples below.
note
When a process reaches status Error, the Out values of parameters are not set. You can use a Post Running action to set them if required.
Completion Strategy
A completion strategy defines how a process is to have reached a final state. By default, RedwoodScript process definitions behave like other definition types, when processing of the source has completed or is interrupted (by an error or Redwood Server shutdown), the process definition reaches a final state. The following completion strategies are available:
Default
- sets the process definition to a final state once its thread has completed.External
- the status of the process definition will be determined externally; a caller will set it for Redwood Server.ExternalWaitForChildJobs
- the status of the process definition will be determined externally; a caller will set it for Redwood Server, however, the process will wait for its children.Resilient
- the process survives central Redwood Server shutdowns. Once the central Redwood Server is restarted, the process is restarted; a process must be restartable to be able to use this completion strategy reliably.
RedwoodScript Versions
By default, RedwoodScript is based on Java 11; you set the /configuration/javatoolkit/SourceVersion
and /configuration/javatoolkit/TargetVersion
registry entries to your desired version and can use all the features of the version you specify.
Examples
The following code shows how to access the process's input parameter names and values:
{
jcsOutLog.debug("Parameters for process " + jcsJob.getJobId());
for (JobParameter parameter : jcsJob.getJobParameters())
{
jcsOutLog.debug(parameter.getJobDefinitionParameter().getName() + "=" + parameter.getInValue());
}
}
Within a chain you can not only access the parameters of the current process, but also the parameters of the chain, if needed. The following code shows how to do this, by instantiating an object that is the grandparent of the current chain process. The parent of the chain process is the step within the chain, and the grandparent is the chain process for the chain itself. If you need to access further ancestors the method would be to call getParentJob()
recursively. The following code can also be used to execute code depending on the depth of the chain process in the chain, or to detect if the process is part of a chain or not.
{
// Does this process have a step - which would imply that we are part of a chain
if (jcsJob.getJobChainStep() != null)
{
JobChain chain = jcsJob.getJobChainStep().getJobChain();
if (chain != null)
{
Job jcjob = jcsJob.getParentJob().getParentJob();
// We are in a chain, our parent's parent is the chain job
// Since our parent is a step, the step's parent is the chain.
jcsOutLog.debug("Part of a chain " + chain.getJobDefinition().getName());
jcsOutLog.debug("Chain parameters");
for (JobParameter parameter : jcjob.getJobParameters())
{
jcsOutLog.debug(parameter.getJobDefinitionParameter().getName() + "=" + parameter.getInValue());
}
}
}
else
{
jcsOutLog.debug("Not in a chain");
}
}
Resilient Completion Strategy
The following code submits three processes in 30 second intervals, the process is persistent, which means that it survives a restart of the central Redwood Server. The code stores the state in the ResilientTable table, which means that it keeps track of the last phase it was in and only submits the remaining processes, if any.
This code is for illustration purposes, only. You will need to create a table definition and table, named ResilientTable
, with a Value column for this example to work in your environment.
package customer;
import com.redwood.scheduler.api.model.*;
import com.redwood.scheduler.api.model.enumeration.*;
import java.math.BigDecimal;
public class ResilientExample extends ResilientExampleStub
{
private static final int PHASE1 = 1;
private static final int PHASE2 = 2;
private static final int PHASE3 = 3;
private static final BigDecimal SLEEP_SECONDS = new BigDecimal("30000");
private static final String TABLE_PARTITION = "GLOBAL";
private static final String TABLE_NAME = "ResilientTable";
private static final String COLUMN_NAME = "Value";
public void execute() throws Exception
{
final CompletionStrategyType strategy = jcsJob.getCompletionStrategy();
if(strategy == null)
{
jcsOut.println("First run");
Partition p = jcsSession.getPartitionByName(TABLE_PARTITION);
Table t = jcsSession.getTableByName(p, TABLE_NAME);
if (t == null)
{
throw new RuntimeException("Table " + TABLE_PARTITION + "." + TABLE_NAME + " does not exist or you cannot access it.");
}
// Starting here make the process resilient immediately (persists on jcsSession)
jcsJobContext.becomeResilient(jcsSession);
// Start at the beginning of our process
process(PHASE1);
}
else if(strategy == CompletionStrategyType.Resilient)
{
jcsOut.println("Additional run, resilient run");
// This means new run, the process ran before but
// didn't finish - system went down
// and now we get a chance to finish it.
// Determine where we should start now.
final int startPhase = determineStartPhase();
process(startPhase);
}
}
private void process(final int startPhase) throws Exception
{
switch(startPhase)
{
case PHASE1:
processPhase1();
case PHASE2:
processPhase2();
case PHASE3:
processPhase3();
break;
}
}
private void processPhase1() throws Exception
{
jcsOut.println("Phase 1");
saveProgress(PHASE1);
// This will assure that child processes of jcsJob, get killed along
// if it is killed. In our example that would be the child System_Sleep
jcsJobContext.killJobWithParent(jcsJob);
// As an example submit a child job sleep (30sec sleep)
final JobDefinition jd = jcsSession.getJobDefinitionByName("System_Sleep");
final Job job = jd.prepare();
job.getJobParameterByName("MilliSeconds").setInValueNumber(SLEEP_SECONDS);
// The persist after progress, so the above succeeds together with storing
// the progress (so in one transaction)
jcsSession.persist();
// Wait for all children of jcsJob
jcsJobContext.waitForAllChildren(jcsSession);
}
private void processPhase2() throws Exception
{
jcsOut.println("Phase 2");
saveProgress(PHASE2);
// Do things
final JobDefinition jd = jcsSession.getJobDefinitionByName("System_Sleep");
final Job job = jd.prepare();
job.getJobParameterByName("MilliSeconds").setInValueNumber(SLEEP_SECONDS);
jcsSession.persist();
jcsJobContext.waitForAllChildren(jcsSession);
}
private void processPhase3() throws Exception
{
jcsOut.println("Phase 3");
saveProgress(PHASE3);
final JobDefinition jd = jcsSession.getJobDefinitionByName("System_Sleep");
final Job job = jd.prepare();
job.getJobParameterByName("MilliSeconds").setInValueNumber(SLEEP_SECONDS);
jcsSession.persist();
jcsJobContext.waitForAllChildren(jcsSession);
}
private TableValue getTableValue() throws Exception
{
Partition p = jcsSession.getPartitionByName(TABLE_PARTITION);
Table t = jcsSession.getTableByName(p, TABLE_NAME);
TableValue tv = t.getTableValueBySearchKeySearchColumnName(jcsJob.getJobId().toString(), COLUMN_NAME);
if (tv != null)
{
return tv;
}
else
{
tv = t.createTableValue();
tv.setColumnName(COLUMN_NAME);
tv.setColumnValue("1");
tv.setKey(jcsJob.getJobId().toString());
jcsSession.persist();
return tv;
}
}
private void saveProgress(int phase) throws Exception
// throws SchedulerAPIPersistenceException;
{
// Store phase (possibly more data),
// in a jobfile or in a parameter or in a table.
// Something that survives, so on a new run of this
// process you can figure out how far this process
// successfully got, then start on the next phase.
TableValue tv = getTableValue();
tv.setColumnValue(Integer.toString(phase));
jcsSession.persist();
}
private int determineStartPhase() throws Exception
// throws SchedulerAPIPersistenceException;
{
// Lookup the stored progress, in the example
// this would be a phase number.
//return PHASE2; // Just an example
TableValue tv = getTableValue();
int phase = Integer.parseInt(tv.getColumnValue());
if (phase > 0)
{
return phase;
}
else
{
return 0;
}
}
}
More examples are available in the Using RedwoodScript in Processes section.