Custom Triggers and Actions

Designing custom triggers and actions requires experience coding with Java. This documentation assumes that you are comfortable writing and working with Java code.

Custom triggers and actions provide amazing flexibility and strength to your Flux application, allowing for seamless integration into every aspect of your application and environment.

A custom action is an action that you create, invoking your custom code and accepting any custom parameters you require, that can be used in the drag-and-drop interface of the Flux Designer. This allows you to expose API-level functionality to the end users who visually design your workflows; your users don’t need any knowledge of Java coding to use the custom triggers and action.

To see a complete, working example that demonstrates how to create and use a custom action, with parameters exposed to your end users, see <Flux Home>/examples/software_developers/custom_action.

For an example of a custom trigger, see <Flux Home>/examples/software_developers/custom_synchronous_trigger.

Custom Triggers

You can create a custom trigger by following these steps:

  1. Create an interface that is used to initialize your trigger. This interface must extend flux.Trigger and must contain getter and setter methods for each trigger property that you want to expose to end users.
  2. Create a custom variable class to persist the trigger’s data to the database (data including internal state data that must be saved between pollings, or the trigger properties that are shown to end users). The variable class must implement java.io.Serializable and must follow the rules for Persistence.
  3. Create an implementation class that extends fluximpl.TriggerImpl and implements your interface. The implementation class must follow the naming convention “Impl". For instance, if your interface is named "MyTrigger", the implementation must be named "MyTriggerImpl". In this implementation class, make sure the following methods are defined:
    1. Getter and setter methods defined in step 1. Make sure the getter and setter methods store and retrieve data changes by calling the get() and put() methods of the variable manager retrieved by calling getVariableManager().
    2. One constructor that calls super(FlowChartImpl, String), and another that calls super(FlowChartImpl, “<My Trigger Name>”), where “" is the default name you want this trigger to use (that is, the name that will appear when a user initially adds the trigger to a workflow).
    3. getHiddenVariables() — this method must return a java.util.Set that contains the names of any persistent variables you use in this trigger.
    4. verify() — this method should check if all of the required properties have been set on the trigger. If not, throw an EngineException from this method.
    5. execute() — the method that Flux calls to see if the trigger is ready to fire. Your code in this method should check to see whether your required event has occurred, and if not, throw a flux.dev.NotTriggeredException. You can also throw your own exception here, which will be handled using the error handling mechanism you’ve defined. Finally, you can return data, which will then become accessible through the flow context using the standard “RESULT” variable name for trigger and action results, and can be used in a conditional flow or retrieved by the next action in the workflow.
    6. getNextPollingDate() — this method must return a java.util.Date that specifies the next time the engine should poll your trigger (the next time the engine will call the trigger’s execute() method). For optimal performance, your trigger should wait as long as your requirements allow before polling again.

Custom Actions

You can create a custom trigger by following these steps:

  1. Create an interface that is used to initialize your action. This interface must extend flux.Action and must contain getter and setter methods for each action property that you want to expose to end users.
  2. Create a custom variable class to persist the action’s data to the database (data including internal state data that must be saved between executions, or the action properties that are shown to end users). The variable class must implement java.io.Serializable and must follow the rules for Persistence.
  3. Create an implementation class that extends fluximpl.ActionImpl and implements your interface. The implementation class must follow the naming convention “Impl". For instance, if your interface is named "MyAction", the implementation must be named "MyActionImpl". In this implementation class, make sure the following methods are defined:
    1. Getter and setter methods defined in step 1. Make sure the getter and setter methods store and retrieve data changes by calling the get() and put() methods of the variable manager retrieved by calling getVariableManager().
    2. One constructor that calls super(FlowChartImpl, String), and another that calls super(FlowChartImpl, “<My Action Name>”), where “" is the default name you want this action to use (that is, the name that will appear when a user initially adds the action to a workflow).
    3. getHiddenVariables() — this method must return a java.util.Set that contains the names of any persistent variables you use in this action.
    4. verify() — this method should check if all of the required properties have been set on the action. If not, throw an EngineException from this method.
    5. execute() — the method that Flux calls when the action runs in a workflow. You can also throw an own exception here, which will be handled using the error handling mechanism you’ve defined. You can also return data, which will then become accessible through the flow context using the standard “RESULT” variable name for trigger and action results, and can be used in a conditional flow or retrieved by the next action in the workflow.
    6. getNextPollingDate() — this method must return a java.util.Date that specifies the next time the engine should poll your trigger (the next time the engine will call the trigger’s execute() method). For optimal performance, your trigger should wait as long as your requirements allow before polling again.

The code below demonstrates a simple “Hello World!” custom action.

public interface CustomAction extends flux.Action {
//  Normally, getter() and setter() methods would go here, but
//  this action has no properties to get or set.
}
import flux.EngineException;
        import flux.FlowContext;
        import flux.file.FileActionException;
        import fluximpl.ActionImpl;
        import fluximpl.FlowChartImpl;
        import java.util.Set;

public class CustomActionImpl extends ActionImpl implements CustomAction {

    //  This constructor creates the action with a default name.
    public CustomActionImpl() {
        super(new FlowChartImpl(), "Custom Action");
    }

    //  This constructor is required too.
    public CustomActionImpl(FlowChartImpl fc, String name) {
        super(fc, name);
    }

    //  No persistent variables, so nothing to return.
    public Set getHiddenVariableNames() {
        return null;
    }

    //  This action has no data that needs verified.
    public void verify() throws EngineException, FileActionException {
    }

    //  Code that will be executed when this action runs.
    public Object execute(FlowContext flowContext) throws Exception {
        System.out.println("Hello World!");

//  Always return for the the result. Represented as the
//  flow context variable RESULT in the workflow.
        return new Boolean(true);
    }

}

Adapter Factories

When you create custom triggers and actions, you must make them available for inclusion in your workflows by creating adapter factories. An adapter factory is a factory that makes new instances of your custom triggers and actions.

To create an adapter factory, first create a class that implements flux.dev.AdapterFactory. Your custom factory must implement the init(FlowChartImpl) method of AdapterFactory, as in the following code:

import flux.dev.AdapterFactory;
import fluximpl.FlowChartImpl;

public class MyCustomFactory implements AdapterFactory {

    private FlowChartImpl workflow;

    public void init(FlowChartImpl workflow) {
        this.workflow = workflow;
    }
}

Make sure that the variable workflow is saved in a field. This variable is used to attach your custom triggers or actions to a specific workflow when they are created.

You will also need to create additional methods that make new instances of your custom triggers and actions. These methods must take a String argument that specifies the name of the trigger or action instance, and call the appropriate constructor on your custom class. For example:

public MyCustomAction makeCustomAction (String name) {
return new MyCustomActionImpl(flowChart, name);
}

By passing in a reference to the workflow, you bind the custom trigger or action to that workflow.

Once you have created your adapter factory, you will need to make it available to the engine. to do so, create a file called factories.properties. In this file, give your adapter factory a short name that links it to your custom factory class, like so:

MyFactoryShortName=examples.custom_action.FileFactory

You can give your factory any short name you like, as long as it does not conflict with any of the built-in factory names. These are:

  • file
  • gui
  • jee
  • jmx
  • messaging
  • transform
  • web_services
  • xml

You can also view all of the built-in adapter factories by calling the flux.EngineHelper.getActionCatalog() method. This method returns a CatalogNode object representing the root node of the tree of actions. Any node in this tree that has at least one child represents an action factory.

Transient Variables

Any variable you return from your trigger or action’s execute() method is assumed to be persistent and will be saved to the database. If you need to return a non-persistent variable instead, call flux.FlowContext.returnTransient() immediately before you return your variable from the execute() method.

By calling the returnTransient() method just before returning your variable, you ensure that the variable will not be stored in the database. Instead, it will be removed from the workflow at the next available transaction break.

Creating your BeanInfo

A JavaBean BeanInfo class can be created for custom actions by following the standard JavaBean conventions and extending ActionImplBeanInfo.

In the configurePropertyDescriptors() method of your BeanInfo class, call super.configurePropertyDescriptors() to retrieve the base property descriptors. By calling the addPropertyDescriptor() method in your configurePropertyDescriptors() method as shown below You can also specify which properties should be shown in the editor, the display names Flux should use, and the getter and setter methods used to read and write the properties.

The Designer uses these property descriptors to display and edit your custom action’s properties.

import fluximpl.ActionImplBeanInfo;
import fluximpl.bean.PropertyDescriptor;

import java.awt.*;
import java.beans.BeanDescriptor;

public class FileActionImplBeanInfo extends ActionImplBeanInfo {

   /**
    * This BeanInfo needs a BeanDescriptor.
    */
   public FileActionImplBeanInfo() {
      super(new BeanDescriptor(FileActionImpl.class), "Custom Action", ActionImplBeanInfo.FILE);
   } // constructor

   /**
    *  Specify which properties should be shown in the editor, the display names Flux should, and the getter and setter
    *  methods used to read and write the properties.
    */
   @Override
   protected void configurePropertyDescriptors() throws Exception {
      super.configurePropertyDescriptors();

      addPropertyDescriptor(new PropertyDescriptor("File Name", "fileName", FileActionImpl.class, "getFileName", "setFileName")).setRequired(true);
      addPropertyDescriptor(new PropertyDescriptor("Content", "content", FileActionImpl.class, "getContent", "setContent")).setRequired(true);
   }
}

Using Custom Triggers and Actions

To use your custom triggers and actions in Java code, you must include the following on your engine’s class path:

  • All of your custom classes, including interfaces, implementation classes, variable classes, and adapter factories.
  • The factories.properties files for your custom adapter factories.

You can access your custom adapter factory in code by calling FlowChart.makeFactory(), like so:

FlowChart workflow = EngineHelper.makeFlowChart();
MyCustomFactory myCustomFactory = workflow.makeFactory("MyFactoryShortName");

This method returns an instance of your adapter factory, which you can then use to make your custom triggers and actions.

Any property that contains an attribute named “transient” with its value set to Boolean.TRUE is not saved to XML.

In the Flux Operations Console

  1. Once your custom trigger or action is created, just package it into a JAR file, make sure the factories.properties file (noted in the previous example) is available at the root level of the JAR, then place the JAR onto the class path of your Flux engine and Operations Console (which is located under webapp/flux/WEB-INF) and restart both the engine and console.
  2. Next, you need to add your action or trigger to the XSL style sheet. Navigate to webapp/flux/styles and look for ffcToMx.xsl style sheet. You can open this file with a text editor of your choice. Navigate down until you reach <xsl:template match="//action"> . Under <xsl:choose> you will add your new action. Example of adding a custom action called “Custom Action”:
    <xsl:when test="@type ='Custom Action'">
        <xsl:text>CustomAction</xsl:text>
    </xsl:when>
    
  3. To add a custom action/trigger image that will be rendered in the Flux Designer, you can add your png image to webapp/flux/stencils/clipart. For best practice, name your file image as your custom action/trigger. For example, CustomAction.png
  4. Navigate to webapp/flux/javascript/mx and open the Sidebar.js file by using a text editor. You will need to locate the Sidebar.prototype.init = function() function and find the list of categories where your custom action or trigger will be under. Note - this should match the category that was chosen in your Java BeanInfo class. You will find that each category has two arrays underneath. You will need to add the name of your image without the extension under the first array, and add the name that you want displayed in the sidebar in the second array. The order that the action or trigger is inserted into the array corresponds with the order the designer side bar renders the action. Make sure the action location matches in both arrays. Below is an example of a custom action called “Custom Action” that will be located as the first action under File Actions group. Notice that the only changes made were adding the ‘CustomAction’(name of the image without .png) under the first array and ‘File Action’ under the second array (which displays the name that the end user sees).
    this.addImagePalette('fileActions', mxResources.get('fileActions'), dir + '/clipart/', '.png',
         ['CustomAction','FileReadAction','FileCopyAction','FileMoveAction','FileDeleteAction','FileCreateAction','FileExistTrigger','FileNotExistTrigger','FileModifiedTrigger','FileRenameAction','FileDecryptPgpAction','FileEncryptPgpAction','FileUnzipAction','FileZipAction','SshFileUploadAction','SshFileDownloadAction','FtpCommandAction'],
         ['File Action','File Read Action','File Copy','File Move','File Delete','File Create','File Exist','File Not Exist','File Modified','File Rename','File Decrypt PGP','File Encrypt PGP','File Unzip','File Zip','SSH File Upload','SSH File Download','FTP Commands']);
    
  5. Next, we need to edit the EditorUi.js file that is located under webapp/flux/javascript/mx. Navigate to the EditorUi = function(editor, container) constructor and to the json.type = action; statement. Below this, you will add an if statement that will declare your actions or triggers properties and their default values. Example below of a custom action with two properties.
     if (action == "Custom Action") { // name of your action or trigger
         (json["File Name"]) = ''; // the property display name from your Java BeanInfo class 
         (json["Content"]) = ''; the next property display name from your Java BeanInfo class 
     }
    
  6. One more file to go! Navigate and open Actions.js that is located under webapp/flux/javascript/mx.

You will need to add your custom action or trigger as a new entry to the properties object. Navigate to properties: { inside this.addAction('editData...', function () function. Here you can add your action as a key, the value to that key will be an object containing associated fields. Example of adding an action called “Custom Action” as an entry:

"SSH Command Action": {
    "type": "object",
    "title": "SSH Command Action"
},
"SSH File Upload Action": {
    "type": "object",
    "title": "SSH File Upload Action"
},
"SSH File Download Action": {
    "type": "object",
    "title": "SSH File Download Action"
},
"Custom Action": {
    "type": "object",
    "title": "Custom Action"
},

The same will need to be done to every property that is used by your action or trigger and that is defined in your Java BeanInfo class. Example of an action with two properties called “File Name” and “Content”.

"Bucket Name": {
 "title": "Bucket Name",
 "propertyOrder": 2,
 "type": "string",
 "minLength": 1,
 "description": "Bucket Name."
},
"File Name": {
 "title": "File Name",
 "propertyOrder": 2,
 "type": "string",
 "minLength": 1,
 "description": "File Name"
},
"Content": {
 "title": "Content",
 "propertyOrder": 2,
 "type": "string",
 "minLength": 1,
 "description": "Content"
},

Next, Search for var targetActionProperties = [""];, you will see if statements for all the different actions that are implemented in flux. You will need to add the properties keys that were defined in the properties object for each of your custom actions or triggers.

Example of adding the property keys if the action is “Custom Action”. These are the same properties display names that were defined in your Java BeanInfo class and in the properties object.

   else if (targetAction == 'Mail Action') {
      targetActionProperties = ["Predefined Mail Hosts", "Content Type", "Body Properties", "FROM Address", "TO Addresses", "Subject", "Body Header", "Body", "Body Footer", "CC Addresses", "BCC Addresses", "Attachments", "Extra Headers", "Mail Server", "Port", "Username", "SSL", "Password"];
   } else if (targetAction == 'Mail Trigger') {
      targetActionProperties = ["Server", "Polling Delay", "Port", "Username", "Password", "Protocol", "Delete Processed Message", "IMAP Folder", "IMAP Folder Copy Destination", "SSL"];
   } else if (targetAction == 'Custom Action') {
      targetActionProperties = ["File Name", "Content"];
   }

You will need to do the same for sourceActionProperties.

Example of adding the property keys if the action is “Custom Action”. These are the same properties display names that were defined in your Java BeanInfo class and in the properties object.

else if (sourceAction == 'Mail Action') {
    sourceActionProperties = [""];
} else if (sourceAction == 'Mail Trigger') {
    sourceActionProperties = ["from_addresses", "reply_to_addresses", "body", "message", "to_addresses", "subject", "content_type", "attachments", "sent_date", "size", "status_flags", "cc_addresses"];
} else if (sourceAction == 'Custom Action') {
    sourceActionProperties = ["File Name", "Content"];
}

If you restarted the engine and Operations Console from step 1, you just need to clear your browser cache and do a hard reload. Your new action will appear in the Designer, ready for use!

Custom Action Tutorial

Here’s a quick video tutorial for you to watch!