User Community Service Desk Downloads
If you can't find the product or version you're looking for, visit support.ataccama.com/downloads

Workflow Listeners

Workflow listeners are a mechanism allowing to listen to the changes in the workflow component and instances processing and perform additional actions.

During the instance’s lifecycle, the instance itself as well as all of its tasks gets into various states. Each change (transition) between two states is signald by some event fired by the workflow engine.

Workflow listeners allow listening to those events and take an action in response to the event.

An action in this context means some additional job that does not affect further workflow processing. For example, it excludes actions such as stopping the running workflow instance and similar.

Listeners structure

The listeners infrastructure consists of two listeners.

The primary interface IEwfComponentListener represents the component-level listener. It informs about the workflow component start and stop events as well as workflow instances start and stop events.

The second level interface (IEwfExecution) represents the interface to the individual workflow instance itself. The instance start and stop events are signaled here too, but the stop, taskChange, and linkEvaluation events are also reported.

If you want to use the workflow instance listener, listen to the IEwfComponentListener.onInstanceStart on the component listener and append your own workflow-instance listener to the obtained workflow instance object (IEwfExecution) using the IEwfExecution.addPlannerListener() method.

Component events (IEwfComponentListener)

The interface reports the following events:

  • Workflow component started - The workflow server component was successfully loaded and started.

  • Workflow component stopped - The workflow server component is about to be stopped.

  • Workflow instance about to be started - The instance is about to be executed by the workflow engine.

  • Workflow instance finished - The instance has just been finished (IEwfExecution).

Workflow instance events (IEwfExecution)

Once the instance is set for processing, the following events are reported:

  • Processor started - The objects related to the instance processing were created and the processing has just started. This is the first event fired during the instance processing.

  • Processor finished - The processing of the instance has finished. This is the last event fired during the instance processing.

  • Task state changed - This event is fired for any change in any task. For possible taks states, see Workflow States and Evaluation.

  • Link evaluated - This event is fired after any link is evaluated.

  • Stop initiated - This event is fired once a stop request is detected. The stop can be fired either by the workflow engine due to task errors (and continueOnFailure=false) or by the user.

  • Stop performed - This event is fired once the user-stop is successfully finished.

Listener implementation

Any component listener class must implement primary interface IEwfComponentListener. Any method of this interface should not throw any exceptions because it would affect other listeners in the chain (it is a limitation of listener’s processing implementation).

Synchronization needs to be handled in the listener implementation. Several methods of the interface can (and will) be called concurrently from various threads, so if you share some resources (for example, a file to write or database connection to use), you must synchronize the methods accessing them in order to prevent collisions.

The following methods represent potentially the most dangerous ones as they might be quite frequently called from several threads:

  • IEwfComponentListener.onInstanceStart

  • IEwfComponentListener.onInstanceStop

  • IEwfTaskProcessorListener.linkEvaluated

  • IEwfTaskProcessorListener.taskStateChanged

In general, we advise implementing all listeners' methods as synchronized while keeping in mind that listeners are processed synchronously so performing some time-intensive operations in the listeners will slow down the workflow processing.

Example

The following example demonstrates a simple listener implementation:

  • It creates a shared synchronized file writer when the workflow component starts.

  • When the new workflow instance is started, it creates a TaskListener (which uses the shared file writer) and registers it to the instance.

  • The task listener then listens to the task changes and if the task fails with an error, it collects the error information and writes it out using the writer.

  • When the server component stops, the file writer is closed.

Click here to see the implementation code example
public class FileErrorCollectingListener implements IEwfComponentListener {
    // ---------------------------------------------------------------------------------------------------------
    // Simple getter/setter section - it can be used to pass parameters from the server-config listener
    // Configuration section, configuration example:
    //  <listeners className=ttt>
    //       <logFile>some-file.txt</logFile>
    //  </listeners>

    private String logFile;

    public String getLogFile() {
        return logFile;
    }

    public void setLogFile(String logFile) {
        this.logFile = logFile;
    }

    // ---------------------------------------------------------------------------------------------------------
    // Component listener implementation, it keeps a single instance of the synchronized file writer which is
    // used to write errors.
So writer is created on component start and closed on the component stop.
    private SynchronizedFileWriter fileWriter;

    public void onComponentStart(IEwfEnvironment environment) {
               // note: parameter 'environment' provides the access to the workflow framework
        try {
            if (logFile == null) {
                // error no log file configured
            } else {
                fileWriter = new SynchronizedFileWriter(environment.resolverResourceFile(logFile));
            }
        } catch (Throwable t) {
            // cannot create writer
        }
    }

    public void onComponentStop() {
        try {
            if (fileWriter != null) {
                fileWriter.close();
            }
        } catch (Throwable t) {
            // cannot close writer
        }
    }

    public synchronized void onInstanceStart(IEwfExecution instance) {
        try {
            if (fileWriter != null) {
                // If the writer was successfully created, register listener to the task execution and use the writer
                // to report detected errors
                instance.addPlannerListener(new MyTaskListener(fileWriter, instance.getWorkflow().getWorkflowId()));
            }
        } catch (Throwable t) {
            // Problem registering the task listener
        }
    }

    public void onInstanceFinish(IEwfExecution instance) {
        // empty
    }

    // ---------------------------------------------------------------------------------------------------------
    /* Shared synchronized writer used by the task listeners to write data to the file */
    private static class SynchronizedFileWriter {
        private OutputStreamWriter osw;
        public SynchronizedFileWriter(File f) throws Exception {
             osw = new OutputStreamWriter(new FileOutputStream(f), StringUtil.UTF8);
        }

        public void close() throws IOException {
            if (osw != null) {
                osw.close();
            }
        }
        /**
         * Synchronize write action - it may come from multiple tasks of multiple wf instances.
         * @param message
         */
        public synchronized void write(String message) {
            try {
                osw.write(message);
                osw.flush();
            } catch (IOException e) {
                // problem writing data to the file
            }
        }
    }

    // ---------------------------------------------------------------------------------------------------------
    /* Task listener instance, uses the synchronized file writer to report problems */
    private static class MyTaskListener implements IEwfTaskPlannerListener {
        private SynchronizedFileWriter writer;
        private String workflowId;
        private String instanceId;

        private MyTaskListener(SynchronizedFileWriter writer, String workflowId) {
            this.writer = writer;
            this.workflowId = workflowId;
        }

        // Task state changes must be synchronized, can be called from multiple tasks at the same time
        public synchronized void taskStateChanged(String id, EwfTaskStatus newState, Throwable t) {
            try {
                if (EwfTaskStatus.FINISHED_FAILURE == newState) {
                    StringBuilder sb = new StringBuilder();
                    sb.append("[ERROR] [" + DateUtil.formatUsDate(new Date()) + "]\n");
                    sb.append("WF NAME: ").append(workflowId).append("\n");
                    sb.append("WF INSTANCE ID: ").append(instanceId).append("\n");
                    sb.append("WF TASK ID: ").append(id).append("\n");
                    sb.append("STACK: ").append(ExceptionUtil.collectStackTrace(t)).append("\n");
                    writer.write(sb.toString());
                }
            } catch (Throwable ex) {
                // This should not happen as writer.write() handles exceptions on its own and
                // working with the StringBuilder should be safe
            }
        }

        public void processorStarted(IEwfExecutionContext execContext) {
            this.instanceId = execContext.getInstanceId();
        }

        public void processorFinished(EwfExecutionStatus state) {
            // empty
        }

        public void stopInitiated() {
            // empty
        }

        public void stopPerformed(EwfExecutionStatus state) {
            // empty
        }

        public void linkEvaluated(String sourceId, String targetId,    EwfLinkEvaluation evaluation) {
            // empty
        }
    }
}

Create a listeners project

This guide assumes that you use Eclipse IDE, that is, ONE Desktop.

To create you own listeners:

  1. Create a new Java project and enter a name (for example, adt.listeners).

  2. Right-click the project.

    1. Select Properties > Java Build Path.

    2. Select the Libraries tab.

    3. Select Add External JARs and add the following JAR files from your product installation ([your product]/runtime/lib):

      • cif-commons.jar

      • adt-workflow.jar

      • adt-workflow-server.jar

    4. Select OK.

  3. Go to the newly created project and create some new package (right-click the src element, select New > Package, and provide a name, for example, com.ataccama.adt.listener).

  4. Select a newly created package, right-click it, and choose New > Class. Enter a name (for example, MyListener).

  5. Implement the class as described in the implementation example (see Example).

  6. Ensure you have no errors in the source code and right-click the project (adt.listeners). Select Export > Java > JAR file.

  7. Copy the created JAR file to the product lib folder (<product>/runtime/lib).

  8. Update the workflows section in the server.xml configuration file and define a new listener. As the class name, use com.ataccama.adt.listener.MyListener if you have followed the names suggested in this guide, or the correct fullpath name if you have chosen your own names.

  9. Restart the online server to reflect the changes (that is, reread the configuration files and reload JAR files).

Listener registration

Listeners are registered via a dedicated configuration section inside the workflow component configuration in the server configuration file.

Configuration example
<components class="com.ataccama.adt.web.WorkflowServerComponent">
      ...
    <componentListeners>
          <listeners class="com.ataccama.adt.test.listener.FileErrorCollectingListener">
              <logFile>c:/tmp/file1.log</logFile>
          </listeners>
          <listeners class="com.ataccama.adt.test.listener.FileErrorCollectingListener">
              <logFile>c:/tmp/anotherFile.log</logFile>
          </listeners>
          ...
    </componentListeners>
</components>

From the configuration point of view, the listener implementations are considered beans, so you can set them up with the data read from the configuration element. In the example, the listener FileErrorCollectingListener has one parameter logFile which contains the path to the file which will be used for storing collected information.

Integration with the server

Listener implementation must be loaded by the server during the startup, so you must copy the JAR file containing your listener implementation to the runtime/lib directory of your server (ONE Runtime Server installation).

Was this page useful?