Using Scripts in Workflows

Information about using scripting with Policy Manager workflows.

Table of Contents

Introduction

Scripting can be used to provide customer-specific processing for workflow <condition> and <function> elements of service and contract workflow. Scripts can be embedded in the workflow XML definition or they can be referenced from the workflow to a file system location. Workflow scripting can currently be in BeanShell, Jython, and JavaScript.

The following Workflow function can be used to invoke a script to perform any special processing that is required. This same structure is used to invoke a script in a <condition> workflow element. In this case, the script must return a Boolean value.

invoking a script with runScript

Supported Arguments

<arg name="id">script-id</arg>
An optional, unique identifier assigned to the script. This identifier is used as the key to the compiled script when stored in memory for reuse. Useful for scripts that occur within workflow <step> definitions as opposed to those that appear in <common-functions>.
<arg name="import">
   script-object-key ...
</arg>
An optional list of script objects that are to be imported into this script definition. These script objects must use the same scripting language as the workflow script.
<arg name="useLegacy"> true | false </arg>
An optional flag that enables the older style script variables to be set in addition to the new ones discussed below. This is included for compatibility with existing scripts.

Built-In Script Variables

The native script variables below are assigned before the script begins execution. If the useLegacy workflow function argument is set to true, the legacy script variables will also be set. If you're writing new scripts, it's best to use these variables rather than the legacy variables, which will be deprecated at some point in the future.

workflowContext
The WorkflowContext object that contains important information about the current workflow execution that is calling the script.
systemLog
A logger that logs to the container's log file. This functions exactly the same as for process and policy scripts
alerter
The AlertReporter that allows workflow scripts to generate Policy Manager alerts. This functions exactly the same as for process and policy scripts
msgFactory
Provides the ability to create Message objects that can be used to communicate from workflow to external services and APIs. This functions exactly the same as for process and policy scripts
msgrService
A MessengerService that provides a messaging bus for exchanging messages with services and APIs. This functions exactly the same as for process and policy scripts

Built-In Script Variables: Legacy

The script variables below will set if the useLegacy workflow function argument is set to true.

The following native script variables related to the workflow context are assigned before the script begins execution.

callerSubject
The Subject for the current caller of the workflow action. This contains the actual Java Subject object while the ${caller} workflow variable will only contain the user name of the current caller.
transientVars
A set of name/value pairs that can be referenced as workflow variables. This is a Map<String,Object> Java object. The information contained in this variable only exists for the current action execution (and any triggered automatic actions.)
propertySet
A collection of named data elements that can be accessed as workflow variables. The information contained in this variable is persisted throughout the lifetime of the workflow instance. This is an Open Symphony PropertySet Java object.
serviceKey
The unique key of the service. (Defined for service workflows only.)
contractKey
The unique key of the contract. (Defined for contract workflows only.)
others
Any additional <arg> elements present in the scripted function or condition definition will be assigned to script variables. For instance, if the script definition contains the element below, a script variable named myPhone will be defined and assigned the script value of 555-1212:
<arg name="myPhone">555-1212</arg>

Workflow Context Methods

The new WorkflowContext is designed to expose the workflow execution context in much the same manner as the processContext and messageContext to the process and policy scripts. The information available to workflow scripts through the legacy variables is made available in a more structured form through the new WorkflowContext. This interface includes the methods listed below.

For more information, refer to the generated Scripting API Documentation.

getObjectKey
Returns the key of the object being processed by the workflow. For Policy Manager objects, this will be either the key of the Service, Contract, or Policy. For Community Manager, this will be the FDN value of the object processing the workflow (for example, API Version).
getObjectType
Returns a lower-case string with the type of the object being processed by the workflow for example, service, contract).
getScriptArg
Retrieve the contents of one of the <arg> elements in the runScript function call.
getFunctionArg
The runScript function is often included within a <common-function> workflow element. This method retrieves the value of <arg> elements in the workflow function that called the common function.
getVariable
Retrieves the value of a workflow variable. The variable name is coded as ${name} or as just the variable name. The standard workflow variable resolution process is used to retrieve the variable's value.
setVariable
Sets the value of a workflow variable that can be accessed by other places in the current action(s) being performed. The variable name is coded as ${name} or as just the variable name. Only string values can be assigned to workflow variables.
getCallerSubject
Retrieves the script Subject of the user performing the current workflow action.
getWorkflowDefinitionKey
Returns the key of the Workflow Definition being executed. For Policy Manager, this is the internally configured key assigned to the Workflow XML definition loaded from the Policy Manager Console. For Community Manager, this is the key value assigned by the administrator when the Workflow XML document was loaded from the CM Portal. This value is needed when making calls to the Policy Manager Workflow Service.

Workflow Script Management

This section includes:

Script Management Overview

Workflow scripts are maintained in a private static Map. Since most systems will not have more than a handful of workflow scripts, there is no need to use any complex caching mechanism. The entries in this Map will contain the following:

  • Information about the source of the script (a file or inline content)
  • The com.soa.container.script.repository.DynamicScript object

The key to this Map will be the key of the script itself concatenated with the key of the workflow definition. This way, scripts will be managed on a per-definition basis rather than a per-instance level. The key of the script is one of the following:

  • The value if the id <arg> element
  • The value of the file <arg> element if the script is coming from a file
  • The hash code of the script source string for in-line scripts

A list of the scripts associated with each workflow definition is also maintained so these can be purged if the workflow definition is updated or deleted.

Loading New Scripts

The following logic will be used to create a new instance of the object to save in the script Map. This code is taken from the Operation Policy Script. The resulting script variable is saved in the script Map.

import com.soa.container.configuration.scripting.model.Script;
import com.soa.container.configuration.scripting.model.ScriptType;
import com.soa.container.script.repository.DynamicScript;
import com.soa.container.script.repository.ScriptLoader;
. . .
private ScriptLoader scriptLoader;  // loaded by Spring wiring
. . .
Script scriptDef = new Script(new ScriptType());
scriptDef.getValue().setSource(scriptSource);  // script source string
scriptDef.getValue().setLanguage(lang);  // script language
if (importArgs != null) {
    for (String import : importArgs) {  // imported script object keys
        scriptDef.getValue().getImport().add(import);
    }
}
DynamicScript script = this.scriptLoader.loadDynamically(scriptDef);

Purging Scripts

When a workflow definition is changed or deleted, scripts associated with the workflow definition must be removed from the system. The WorkflowScripting class (home of the runScript workflow function) must implement the WorkflowEventListener interface for this purpose. Associated Spring wiring is needed to publish the new WorkflowEventListener instance as an OSGi service.

Workflow Script Execution

All scripts must be executed with the Thread Context Class Loader (TCCL) set to the class loader of the Script Engine Manager. The following code snippet shows this:

import com.soa.script.frameworks.ScriptEngineManager;
. . .
private ScriptEngineManager scriptEngineManager;  // loaded by Spring wiring
. . .
ClassLoader contextClassLoader = null;
. . .
try {
    contextClassLoader = Thread.currentThread().getContextClassLoader();
    ClassLoader appClassLoader = scriptEngineManager.getClass().getClassLoader();
    Thread.currentThread().setContextClassLoader(appClassLoader);
    . . .
} finally {
    if (contextClassLoader != null) {
        try {
            Thread.currentThread().setContextClassLoader(contextClassLoader);
        } catch (Throwable t) {}
    }
}

Script execution is begun by retrieving the DynamicScript object from the scripts Map. This is the result of loading the workflow script as discussed in the previous section.

The DynamicScript object can contain an instance of the DynamicCompiledScript class if a pre-compiled version of the script is available. If this exists, the script can be directly executed. Otherwise, the script will need to be compiled and executed by the ScriptEngine.

The code outline to do all of this is shown below.

import javax.script.ScriptEngine;
import com.soa.container.script.repository.DynamicCompiledScript;
import com.soa.script.frameworks.ScriptEngineManager;
. . .
private ScriptEngineManager scriptEngineManager;  // loaded by Spring wiring
. . .
DynamicScript script;  // retrieved from scripts Map
DynamicCompiledScript compiled = script.getCompiled();
Object scriptResult;
. . .
if (compiled != null) {
    // Set script variables into compiled.getEngine()
    compiled.eval();
    scriptResult = compiled.getEngine().get("success");
    compiled.close();
} else {
    ScriptEngine engine = scriptEngineManager.getEngineByLanguage(script.getLanguage());
    if (null == engine) {
        throw new GException(. . .);
    }
    // Set script variables into engine
    engine.eval(script.getSource());
    scriptResult = engine.get("success");
}

Processing Script Results

Scripts called from workflow <function> elements do not have any return values but scripts called from workflow <condition> elements must return a boolean value. As is shown in the above code sample, the script must assign this value to a global variable named success.

Setting Up Script Variables

In addition to setting the existing legacy script variables, the following code sample shows how to set up the workflow script variables.

import com.soa.alert.reporter.AlertReporter;
import com.soa.alert.reporter.script.ScriptAlertReporter;
import com.soa.message.script.InternalMessageFactory;
import com.soa.message.script.ScriptedLoggerImpl;
import com.soa.message.script.ScriptedMessageFactoryImpl;
import com.soa.messenger.wsf.WSFMessengerService;
import com.soa.messenger.wsf.script.MessengerServiceImpl;
import com.soa.workflow.engine.adaptor.WorkflowExtension;
. . .
// these are loaded by Spring wiring
private AlertReporter alertReporter;
private InternalMessageFactory messageFactory;
private WorkflowExtension workflowEngine; // must extend AbstractWorkflow
. . .
// from execution code
ScriptEngine engine;
// from workflow function method
Subject caller; 
WorkflowDocument document 
Map args
. . .
WorkflowContextImpl context = new WorkflowContextImpl(caller, document, args);
context.setWorkflowEngine(workflowEngine);
engine.put("workflowContext", context);
engine.put("systemLog", new ScriptedLoggerImpl(log));
ScriptAlertReporter reporter = new ScriptAlertReporter();
reporter.setReporter(alertReporter);
engine.put("alerter", reporter);
engine.put("msgFactory", new ScriptedMessageFactoryImpl(messageFactory));
WSFMessengerService wsfMessenger = WSFMessengerService.getInstance();
MessengerServiceImpl msgrService = new MessengerServiceImpl(wsfMessenger, null);
msgrService.setMessageFactory(messageFactory);