-
Notifications
You must be signed in to change notification settings - Fork 2
Actions
In workbench type applications we often want to be able to fire off some long running piece of processing as a background task and return later to see the results, including any progress or error messages a long the way. We also often need to be able configure such processing and create new configurations without writing new java code and restarting the application.
The Actions subsystem provides support for this.
Key features are:
- A uniform interface by which actions can report on progress and status and for that status to be later retrieved.
- The ability to configure actions dynamically, allowing us to change action configuration parameters or create new actions as configured called to existing actions.
- Ability to specify sequences of actions, in particular to invoke error reporting actions when an action fails (e.g. sending an email notification)
- Primitive actions can be implemented as java code or shell script but actions can then wrap and chain calls to these primitives.
- Assumes that there is a single non-distributed controller initiating the actions (although the work itself could be carried out by separate worker machines)
This is not a full blown workflow engine.
The interface for actions is:
JsonObject run(JsonObject parameters, ProgressMonitorReporter monitor)
This passes in general and run specific configuration as a JSON object, runs the action recording progress in the given monitor and can return information on the run as another JSON object.
In addition actions can have triggers, a timeout value (millisecond resolution) and an associated action to run in the event of an error.
This is a appbase component that manages the set of known actions through which actions can be invoked.
Actions are normally defined using JSON configuration files and the ActionManager can be configured to dynamically monitor a directory for such configuration files. So a typically app.conf entry for an ActionManager is:
aman = com.epimorphics.appbase.tasks.ActionManager
aman.directory = /opt/dms/conf/actions
aman.logFile = /var/opt/dms/log/actions.log
aman.traceDir = /var/opt/dms/traces
aman.scanInterval = 4000
#aman.productionMode = true
actionManager.scriptDir = src/test/actionApp/scripts
It is also possible to programmatically register actions with the ActionManager.
The primary interface for running actions is:
ActionExecution runAction(Action action, JsonObject parameters)
The given runtime parameters will augment and override any action configuration parameters passed to the Action's run interface. It is possible to specify the action by name and have the ActionManager locate it. It is also possible to pass in an external progress monitor for the action to report to.
The ActionManager is able to retrieve all currently executing actions and recently completed actions. It can be (TBD) configured to keep records of historic actions as files.
The start and finish of each action (along with the associated parameters) can be logged to a configured log file (in addition to being logged in the main webapp log).
Mostly actions are run by some user interface affordance which eventually calls runAction.
However, we also need the option to trigger an action through some external event (e.g. a POST to some web service endpoint, a file appearing in some monitored directory, a timer). This is supported through the following ActionManager call:
fireEvent(String event, JsonObject parameters)
The event comprises a name and an optional set of parameters (as a JSON object).
Actions optionally support an ActionTrigger which matches events and determines if the action should be fired. The simplest default trigger is a regex match to the event name though the ActionTrigger interface allows for more sophisticated matching in the future.
Actions are configured by default using JSON syntax files. [This may be a mistake, the pain with JSON is that it doesn't allow comments, to be reviewed.]
However, additional configuration options can be added by registering new FactoryLets with the singleton ActionFactory.
The simplest action is a direct call to some predefined java class that implements the Action interface. This is specified using:
{
"@name" : "dummy",
"@javaclass" : "com.epimorphics.appbase.task.DummyAction"
}
The @name is the name by which the action can be referred to in other actions and when invoking an action by name. An action without a name only useful when it is nested as part of some larger action.
Additional operation keys than can be configured on any action are:
| Key | Meaning |
|---|---|
@type |
The type of the action, simple (default), sequence or parallel
|
@description |
Option text documenting the action |
@timeout |
Timeout limit in milliseconds, if the action exceeds this it will be aborted and deemed to have failed |
@trigger |
A regular expression, if this matches the name of an incoming event (see above) the action will be fired |
@onError |
An action to run if this action fails, can be a name of an action or an embedded JSON action specification |
@onSuccess |
An action to run if this action succeeds, can be a name of an action or an embedded JSON action specification |
All other keys in the JSON object (not starting with @) are treated as configuration values to be passed on to the action at run time. Run time parameters will be added to (and take precedence over) these static configuration values.
For example the test action PrintAction reports a message passed to it. So the action:
{
"@name" : "TestMessage",
"@javaclass" : "com.epimorphics.appbase.task.PrintAction",
"message" : "Default message"
}
will report Default message.
A more complex example might be:
{
"@name" : "testErrorTimeout",
"@javaclass" : "com.epimorphics.appbase.task.DummyAction",
"count" : 50,
"message" : "ping",
"@timeout" : 40,
"@onError" : {
"@javaclass" : "com.epimorphics.appbase.task.PrintAction",
"message" : "Timeout detected"
}
}
This calls the DummyAction test class passing in the message ping and count 50 (this test class with report the given message every 10ms up to the given count). If the action takes longer than 40ms (which this example will) then the PrintAction will be called with message Timeout detected.
An action can be created from to some existing action with additional or modified configuration parameters. For example:
{
"@name" : "TestMessage2",
"@base" : "TestMessage",
"message" : "New default message"
}
A wrapped action calls the base action directly, it does not fire off a separate asynchronous thread.
A variant on wrapped actions allows a set of base actions to be called in sequence or in parallel:
{
"@name" : "sequenceTest",
"@type" : "sequence",
"@actions" : [
{"@javaclass" : "com.epimorphics.appbase.task.PrintAction", "message" : "sequence 1"},
{"@javaclass" : "com.epimorphics.appbase.task.PrintAction", "message" : "sequence 2"},
{"@javaclass" : "com.epimorphics.appbase.task.PrintAction", "message" : "sequence 3"}
]
}
and
{
"@name" : "parTest",
"@type" : "parallel",
"@actions" : [
{"@javaclass" : "com.epimorphics.appbase.task.PrintAction", "message" : "par 1"},
{"@javaclass" : "com.epimorphics.appbase.task.PrintAction", "message" : "par 2"},
{"@javaclass" : "com.epimorphics.appbase.task.PrintAction", "message" : "par 3"}
]
}
Actions can also specify shell scripts to invoke:
{
"@name" : "helloScriptArgs",
"@type" : "script",
"@script" : "hello.sh",
"arg1" : "arg one",
"@args" : ["arg1", "arg2"],
"@env" : { "env-arg1" : "value" }
},
The @script parameter specifies the name of the script file relative to the scriptDir directory specified in the ActionManager configuration.
The @args parameter specifies how the parameters should be passed to the script. If the value is an array of parameter names then the value of the parameters will be passed as script command line arguments. If the value is json then a JSON encoding of the parameters will be passed as a single command line argument. If the value is jsonRef then a JSON encoding of the parameters will be saved to a temporary file and the name of that file passed as the single command line argument.
It is also possible to pass some (currently fixed) environment settings using the @env object.
Each configuration file monitored and loaded by the ActionManager can contain a single Action specification object or an array of such objects.