Using the RedwoodScript Definition Type

You can use RedwoodScript to, for example, create a Job Definition that runs other Job Definitions, or retrieves and combines metadata or output from other Jobs.

Note: You must assign at least one Job Server to run RedwoodScript Job Definitions in order to use the Definition Type.

Note: RedwoodScript is available with the Scripting module. For more information, see the License topic.

Variables and Parameters

You can define variables with the syntax String myVar or String myVar = "initial value".

Job Definition Parameters are available directly in RedwoodScript as Java variables, as shown in the examples below.

Note: Array Parameters are available for RedwoodScript. They are accessed like regular Java arrays. They must have unique elements.

Note: If a Job 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 Job is to have reached a final state. By default, RedwoodScript Job Definitions behave like other Definition Types: when processing of the Source has completed or been interrupted (by an error or RunMyJobs server shutdown), the Job reaches a final state. The following completion strategies are available:

  • Default: Sets the Job to a final state once its thread has completed.
  • External: The status of the Job will be determined externally. A caller will set it for RunMyJobs.
  • ExternalWaitForChildJobs: The status of the Job will be determined externally. A caller will set it for RunMyJobs, but the Job will wait for its children.
  • Resilient: The Job survives a RunMyJobs shutdown. Once the RunMyJobs server is restarted, the Job is restarted. A Job must be restartable to be able to use this completion strategy reliably.

RedwoodScript Versions

By default, RedwoodScript is based on Java. You can 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 a Job's In Parameter names and values.

Copy
{
  jcsOutLog.debug("Parameters for process " + jcsJob.getJobId());
  for (JobParameter  parameter : jcsJob.getJobParameters())
  {
    jcsOutLog.debug(parameter.getJobDefinitionParameter().getName() + "=" + parameter.getInValue());
  }
}

Within a Workflow, you can access not only the Parameters of the current Job, but also the Parameters of the Workflow, if needed. The following code shows how to do this, by instantiating an object that is the grandparent of the current Job Call. The parent of the Job Call is the Step within the Workflow, and the grandparent is the Job Call for the Workflow itself. If you need to access further ancestors, you can call getParentJob() recursively. The following code can also be used to execute code depending on the depth of the Job Call in the Workflow, or to detect if the Job is part of a Workflow or not.

Copy
{
  // 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("Workflow 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 runs three Jobs at 30 second intervals. The Job is persistent, which means that it survives a restart of the RunMyJobs 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 Jobs, if any.

This code is for illustration purposes only. For this example to work in your environment, you will need to create a table definition and table named ResilientTable, with a Value column.

Copy
package customer;

import java.math.BigDecimal;

import com.redwood.scheduler.api.exception.*;
import com.redwood.scheduler.api.model.*;
import com.redwood.scheduler.api.model.enumeration.*;

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)
    {
      /*
       * When making the job resilient from within the script, you need to call this
       * as early as possible. The better solution is to set the completion strategy
       * on the definition to be Resilient, that way you do not have to worry about
       * the the job being terminated before it can set the completion strategy.
       */
      jcsJobContext.becomeResilient();
    }
    jcsOut.println("Starting job run");

    /*
     * We need to determine where in the process to run. If this is the first run,
     * then we start from the beginning, however if the job has been restarted, the
     * job started running before, but didn't finish, most likely because of a
     * system restart, and now we get a chance to finish it.
     */
    final int startPhase = determineStartPhase();
    process(startPhase);
  }

  private void process(final int startPhase)
    throws Exception
  {
    switch (startPhase)
    {
    case PHASE1:
      processPhase1();
      // FALLTHROUGH - in normal processing we run phase 2 when phase 1 finishes
    case PHASE2:
      processPhase2();
      // FALLTHROUGH - in normal processing we run phase 3 when phase 2 finishes
    case PHASE3:
      processPhase3();
    }
  }

  private void processPhase1()
    throws Exception
  {
    jcsOut.println("Phase 1");
    saveProgress(PHASE1);
    /*
     * This will assure that child processes of this job will get killed if this job
     * 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, in this example, we simply start a child sleep job
    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);
    // Do things, in this example, we simply start a child sleep job
    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 SchedulerAPIPersistenceException
  {
    /*
     * Store phase (possibly more data), in a jobfile, or in a parameter, or in a
     * table, or something that survives, so on a new run of this process can figure
     * out how far this process has completed, then start on the next phase.
     *
     * For this example, we are storing the phase in table.
     */
    TableValue tv = getTableValue();
    tv.setColumnValue(Integer.toString(phase));
    jcsSession.persist();
  }

  private int determineStartPhase()
  {
    // Lookup the stored progress, in this example, this is a phase number.
    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 topic.