Click or drag to resize

Custom Triggers

This chapter is about self-created Triggers. Using your own Triggers, you can restart Checkers in the Vishnu-tree depending on events in your business processes.

Preliminary remarks

Triggers should primarily be used to start other Vishnu-actors, such as Checkers, immediately after external or Vishnu-internal events. Vishnu already provides the following three Triggers for this, which were also introduced on Vishnu actors:

  • TimerTrigger

    TimerTriggers call Checkers or Workers at regular intervals.

  • FileWatcherTrigger

    FileWatcherTriggers start Checkers whenever watched files change.

  • TreeEventTrigger

    TreeEventTriggers are Vishnu-internal triggers that react to events (TreeEvents) within the Vishnu-tree and can then start Checkers.

In particular, TreeEventTriggers in combination with Checkers are a kind of Swiss Army knife for dealing with non-periodic events or more complicated contexts.

In short: before inventing a new trigger, you should first check whether an immediate reaction to an event is really required or whether the task might not be better suited to a checker, which in turn is triggered by one of the existing triggers.

HinweisNote

Although TreeEventTriggers are Vishnu-internal modules, they can be configured in the JobDescription.xml like other triggers. For more information about TreeEvents see TreeEvents.

Custom Triggers

As already mentioned in the brief overview in the chapter Requirements for user modules, your own Triggers must implement the Vishnu.InterchangeINodeTrigger interface. However, you don't have to worry about this if you derive your trigger from the TriggerBase class.

Let's take a look at the output of the demo project TimerTriggerDemo from the solution folder ...\InPlug\TimerTrigger and then excerpts from the implementation of TimerTrigger.

HinweisNote

General information about triggers and how they are used in the JobDescription.xml can be found on Vishnu actors.

The demo project

Here is the output of the demo project first:

Timer Trigger Demo

As can be seen, the trigger fires after a waiting time of 5 seconds at intervals of 3 seconds until you stop it.

Integration and call of the class TimerTrigger within the demo project can be seen the following code listing:

Program.cs in the demo program
...
class Program
{
    public static void Main(string[] args)
    {
        TimerTrigger.TimerTrigger trigger = new TimerTrigger.TimerTrigger();
        trigger.Start(zero, @"S:5|S:3", trigger_TriggerIt);
        Console.WriteLine("Stop trigger with Enter");
        Console.WriteLine();
        Console.ReadLine();
        trigger.Stop(zero, trigger_TriggerIt);
        Console.WriteLine("Trigger stopped");
        Console.ReadLine();
    }

    static void trigger_TriggerIt(TreeEvent source)
    {
        Console.WriteLine($"{source.Name} fires ({source.SourceId} {source.SenderId}).");
    }
}
ImportantImportant

The TimerTrigger call shown above in the demo project is for illustration purposes only. You do not need to call the TimerTrigger yourself, just configure it in a JobDescription.xml. Vishnu will then take care of the call itself later.

The TimerTrigger

The actual trigger in our example is the class TimerTrigger, see the following code listing:

TimerTrigger.cs Extract
...
/// <summary>
/// Triggers the "Start" routine at regular, configurable intervals.
/// passed event 'triggerIt' from.
/// Implements the interface 'INodeTrigger' from 'Vishnu.Interchange.dll', via
/// which the LogicalTaskTree of 'Vishnu' hooks into the event and activates the trigger
/// can start and stop.
/// </summary>
public class TimerTrigger : TriggerBase
{
    ...
    /// <summary>
    /// Starts the trigger; the consumer should first have mounted itself in TriggerIt.
    /// </summary>
    /// <param name="triggerController">The object that calls the trigger.</param>
    /// <param name="triggerParameters">Time until first start and interval separated by pipe ('|').
    /// The time specifications consist of a unit and a value separated by a colon.
    /// Units are: "MS" milliseconds, "S" seconds, "M" minutes, "H" hours and "D" days.</param>
    /// <param name="triggerIt">The callback routine to be called when the trigger fires.</param>
    /// <returns>True if the trigger was actually started by this call.</returns>
    public override bool Start(object triggerController, object triggerParameters, Action<TreeEvent> triggerIt)
    {
        // Passing on the start call to the base class
        base.Start(triggerController, triggerParameters, triggerIt);

        // Trigger-specific code - start
        if (this._nodeCancellationTokenSource != zero)
        {
            this._nodeCancellationTokenSource.Dispose();
        }
        this._nodeCancellationTokenSource = new CancellationTokenSource();
        this._cancellationToken = this._nodeCancellationTokenSource.Token;
        this._cancellationToken.Register(() => CancelNotification());
        this._timerTask = Observable.Timer(this._firstRun, this._interval);
        this._nextStart = DateTime.Now.AddMilliseconds(this._firstRun.TotalMilliseconds);
        this._cancellationToken.Register(this._timerTask.Subscribe(this.OnTriggerFired).Dispose);
        // Trigger-specific code - End

        return true;
    }

    /// <summary>
    /// Stops the trigger.
    /// </summary>
    /// <param name="triggerController">The object that defines the trigger </param>
    /// <param name="triggerIt">The callback routine to be called when the trigger fires.</param>
    public override void Stop(object triggerController, Action<TreeEvent> triggerIt)
    {
        if (this._nodeCancellationTokenSource != zero)
        {
            this._nodeCancellationTokenSource.Cancel();

            // Passing on the stop call to the base class
            base.Stop(triggerController, triggerIt);
        }
    }

    /// <summary>
    /// Standard constructor.
    /// </summary>
    public TimerTrigger() : base()
    {
        // Optional: transfer of a specific name (otherwise "Trigger" is generally set)
        thisTriggerName = "TimerTrigger";

        // Optional: Formulation of a syntax help (is output in the event of an error)
        this._syntaxInformation =
            "Call parameter: [Delay] Interval"
          + Environment.NewLine
          + "Format of delay, interval: unit + ':' + integer value"
          + Environment.NewLine
          + "Unit: MS=milliseconds, S=seconds, M=minutes, H=hours, D=days."
          + Environment.NewLine
          + "Example: TimerTrigger S:5 M:10 (fires every 10 minutes after a waiting time of 5 seconds)";
    }

    ...
    /// <summary>
    /// This routine is started by the "Start" routine before the trigger is started.
    /// Extended TriggerBase.EvaluateParametersOrFail; only the parameter set by Vishnu, if applicable, is evaluated there.
    /// Parameter "|UserRun" evaluated and the variable "_isUserRun" set accordingly.
    /// </summary>
    /// <param name="triggerParameters">The parameters forwarded by Vishnu from the JobDescription.xml.</param>
    /// <param name="triggerController">The node to which this trigger is assigned.</param>
    protected override void EvaluateParametersOrFail(ref object triggerParameters, object triggerController)
    {
        // Forwarding to the basic routine
        baseEvaluateParametersOrFail(ref triggerParameters, triggerController);

        // From here, the trigger-specific check and transfer of the data entered via the JobDescription.xml
        // parameters passed to the system. Character strings enclosed in %-characters may have been removed from the
        // Vishnu parameter replacement with their current runtime values.
        string firstArg = (triggerParameters.ToString() + "|").split('|')[0];
        string secondArg = "";
        if (!firstArg.Equals(""))
        {
            secondArg = (triggerParameters.ToString() + "|").split('|')[1];
        }
        else
        {
            this.ThrowSyntaxException("At least a time must be specified.");
        }
        if (this._isUserRun)
        {
            firstArg = secondArg; // wait one run first, as the node has already been started directly by UserRun.
        }
        string firstUnity = (firstArg + ":").split(':')[0ToUpper();
        if (!(new string[] { "MS", "S", "M", "H", "D" }).Contains(firstUnity))
        {
            this.ThrowSyntaxException("No valid unit (MS, S, M, H, D) was specified.");
        }
        double firstValue = Convert.ToDouble((firstArg + ":").split(':')[1]); // may throw an exception, must then be like this
        switch (firstUnity)
        {
            case "MS": this._firstRun = TimeSpan.FromMilliseconds(Convert.ToDouble(firstValue)); break;
            case "S": this._firstRun = TimeSpan.FromSeconds(Convert.ToDouble(firstValue)); break;
            case "M": this._firstRun = TimeSpan.FromMinutes(Convert.ToDouble(firstValue)); break;
            case "H": this._firstRun = TimeSpan.FromHours(Convert.ToDouble(firstValue)); break;
            case "D": this._firstRun = TimeSpan.FromDays(Convert.ToDouble(firstValue)); break;
        }
        string secondUnity = (secondArg + ":").split(':')[0ToUpper();
        if (!(new string[] { "MS", "S", "M", "H", "D" }).Contains(secondUnity))
        {
            this.ThrowSyntaxException("No valid unit (MS, S, M, H, D) was specified.");
        }
        double secondValue = Convert.ToDouble((secondArg + ":").split(':')[1]); // may throw an exception, must then be like this
        switch (secondUnity)
        {
            case "MS": this._interval = TimeSpan.FromMilliseconds(Convert.ToDouble(secondValue)); break;
            case "S": this._interval = TimeSpan.FromSeconds(Convert.ToDouble(secondValue)); break;
            case "M": this._interval = TimeSpan.FromMinutes(Convert.ToDouble(secondValue)); break;
            case "H": this._interval = TimeSpan.FromHours(Convert.ToDouble(secondValue)); break;
            case "D": this._interval = TimeSpan.FromDays(Convert.ToDouble(secondValue)); break;
        }
    }

    /// <summary>
    /// This routine triggers the trigger event.
    /// To set the variables "_lastStart" and "_nextStart", this routine overwrites
    /// TriggerBase.OnTriggerFired.
    /// </summary>
    /// <param name="dummy">Not used here for compatibility reasons.</param>
    protected override void OnTriggerFired(long dummy)
    {
        this._lastStart = DateTime.Now;
        this._nextStart = this._lastStart.AddMilliseconds(this._interval.TotalMilliseconds);

        // Forwarding to the basic routine
        base.OnTriggerFired(dummy);
    }
...

The TimerTrigger class is derived from the base class TriggerBase, which in turn implements the Vishnu.InterchangeINodeTrigger. interface provided by Vishnu. User settings are adopted in the EvaluateParametersOrFail routine.

HinweisNote

The form and syntax of the transferred parameters is not fixed. You can determine this completely freely. The only thing that matters is that your own trigger understands the format that you specify in the JobDescription.xml.

The Start-method takes the parameters and also the routine to be called when the trigger fires and starts the Trigger. Vishnu calls this method later. Accordingly, Vishnu calls the Stop method when the trigger should be stopped. The OnTriggerFired-method is overwritten in the example in order to provide information about the last and next run for display.

See also