Creating a Rule

We will now proceed to create a basic new rule using the development environment that we set up in the precious section.

Right click your package and select New->Class..:

Complete the entries as shown and click Finish. You now have a new class that looks as follows:

We are going to use this skeleton to create our new rule. The rule we are going to create will determine if the value of a given variable is above or below a certain threshold.

We are going to need two input fields: the variable, and the threshold; and three chain points: Above, Equal and Below.

Defining class variables

The first thing we need is a placeholder for our input fields:

We will store the initialization parameters in these variables. Please note that you should refrain from using static variables within the rules. Static variables will be shared across ALL instances of the rule within the running rule sets. Class variables, on the other hand, remain linked to every single instance and stay valid for the duration of the running of the rules. This means that items such as counters can be created as instance variables (provided access to the variable is properly synchronized).

Coding the cleanup() method

Next we quickly remove the TODO tag from the cleanup() method. Our cleanup method in this instance does nothing:

If, however, we had allocated resources (for example a TCP/IP socket, a Thread or some other resource that isn't subject to garbage collection), then this is the place to clean it up. It is important to notice that the cleanup() method should never throw an exception. All possible exceptions should be caught and handled with the cleanup method itself.

Coding the initialize() method

The initialize method is called when the rule is loaded from within a rule set. It is also called when a rule set is validated.

The primary goal of the initialize method is to read and validate rule parameters. Here is the simplest form of reading a variable name:

In the above code, we read the variable name set in the rule set. We then make sure it isn't null (rule definition incorrectly configured), and that it isn't blank (we always need a variable to test for this rule to make sense). If either of error occurs, we send an error back to the X Engine with the setError method. The setError method takes two parameters: an error code identifier, and an array of strings to insert into the error message to help with diagnostics. The "BASE003" error code identifier is defined in the Base Rules errors.properties file. It is defined as follows:

The &1 and &2 replacement variables are predefined as the rule's label and the rule set's file name respectively. All other &x instances are replaced with the Strings from the errorInfo array passed to the setError method in the order they appear. So &3 will be errorInfo[0], &4 will be errorInfo[1] and so forth.

You can create your own error code identifiers and include them in your extension. We will do this next for the threshold value.

The threshold value is different to the variable name in that it can take a number of different forms. It can be a variable (from which we must read the content at run time) or it can be a constant. What we do know for sure, is that it can't be blank, null or a String. So, we will test accordingly:

The getValueType() method used here will tell you if any given parameter passed to a rule is either a variable (VAR_VARIABLE), number (VAR_NUMBER) or a quoted string (VAR_STRING). The resulting error message will be defined later as follows:

ABRL001=The threshold &3 set on rule &1 in rule set &2 is invalid. It must be a number or a variable

For our quick sample this is all that is required, but we will quickly cover a few other examples.

Allocating resources

If you are planning on allocating resources in this method, then it is important to only do so in a live setting. You can determine if the call is a live setting by using the following code snippet (taken for a rule that accesses a database table):

Working with data bases

If your rule were attempting to access a database table, part of the validation before the rule is allowed to run would be to check if that table exists. The following example shows the code required to perform this task using the X Engine database connectivity framework:

The above example shows the basic concept of obtaining a database connection based on a database name, checking the existence of a table in that database, and acting upon the returned information.

Notice that from within the rules you do not need to worry about schemas or database definitions, you are only dealing with databases and tables.

Of vital importance is how exceptions are handled and the integrity of the connection pool.

The example illustrates how an exception is caught and not only sent to the user via the console (setError), but also logged into the logging system of the user's choice (log). Significant exceptions should always be logged this way, whereas user resolvable problems shouldn't be logged.

You should also notice the return of the obtained connection to the connection pool. This step should never be missed.

We will now take the example one step further and include the creation of a table. The following example is from the History Recorder rule:

// Connect to the database and make sure our table exists. If it
			// doesn't, create it
			Statement stmt = null;
			CachedConnection connection = null;
			String BIGINT = getRuleSet().getRulesEngine().getDatabase(database).getBIGINT();
			String LONGVARCHAR = getRuleSet().getRulesEngine().getDatabase(database).getLONGVARCHAR();
			try {
				connection = getRuleSet().getCachedConnection(database);
				DatabaseMetaData dbmd = connection.getConnection().getMetaData();
	
				// Check if our table exists. If not, create it
				if (!DataBase.tableExists(dbmd, table)) {
					stmt = connection.getConnection().createStatement();
					stmt.executeUpdate("CREATE TABLE "
									+ table
									+ " (KEYFIELD VARCHAR(255) NOT NULL, TIMEFIELD " + BIGINT
									+" NOT NULL, VALUE " + LONGVARCHAR +")");
					stmt.executeUpdate("ALTER TABLE " + table
							+ " ADD PRIMARY KEY (KEYFIELD, TIMEFIELD)");
				}
			} catch (SQLException ex) {
				String[] errorInfo = { table, database };
				log(LogAdapter.LOGLEVEL_FATAL,renderMessage("BASE014", errorInfo),ex,this,null);
				setError("BASE014", errorInfo);
			} finally {
				try {
					if (stmt != null)
						stmt.close();
				} catch (SQLException ex) {
				}
				try {
					getRuleSet().returnCachedConnection(database, connection);
				} catch (SQLException ex) {
				}
			}

The important lessons from the above code are the mapping of BIGINT and LONGVARCHAR, as well as the handling of the statements used.

BIGINT is intended as a mapping for the Java "long" integer type. Many databases will handle long as a BIGINT, but some databases (for example Oracle) require it to be called NUMBER. So, to make the rule code as portable as possible between databases, the console allows you to configure the preferred data type for each database driver.

LONGVARCHAR is similar in that not all databases support the LONGVARCHAR data type. So once again, it can be replaced with the largest string possible (for example VARCHAR(32767) or some other maximum value for the database in use).

Finally, we need to point out that ad-hoc statements such as executeUpdate only have a place in the initialize method where the re-use factor is almost non-existent. For all runtime use, prepared statements should be used. We will cover this in the next section.

Coding the processRule() method

The processRule method is where all of the action happens. Reverting to our basic sample, what we need to do is obtain the value of the variable and the value of the threshold, and then compare them. We subsequently take a chain point action depending on the result.

The first step is to make the skeleton code a little more meaningful by naming the inherited arguments a little better:

The next step is to obtain the values. We will limit the rules to integers only, so we can use the following code for the variable value:

Notice the use of setWarning on the exception. In this case the message in errors.properties is defined as:

ABRL002=WARNING: The value "&4" contained in the variable &3 on rule &1 in rule set &2 is invalid. It must be a valid integer.

The difference between setError and setWarning in the processRule method is that setError results in the termination of the rule set flow, whereas setWarning allows the rules to continue running.

We will now obtain the threshold to compare against. The following code is used:

It is fundamentally the same as the variable value, except that we are not told whether the threshold parameter contains a variable or a constant. The getRuntimeValue method solves this problem for us by detecting the correct data type and returning the appropriate value for us as a simple String.

Now that we have all the relevant data, we can proceed to the logic of our rule. It looks as follows:

We essentially establish which chain point to follow and then return the value of the call to that chain point, to the calling rule. If you choose not to chain anywhere, you can simply return the vao variable instead. Alternatively, you can chain down all chain points of a rule using the chainAll method:

Our rule now compiles and is ready to be included in an extension.

Packaging the rule

Each extension contains not just the rule code, but also a manifest defining the rule parameters, a list of error messages, icon images and documentation. In this section we cover how to properly package a rule into an extension.

Note that each extension can contain more than one rule and, in most circumstances, do.

Package structure

The basic file structure of an extension looks as follows:

Start by creating an empty folder called "My Rules" with the classes, doc, images and lib folders inside it.

The doc folder is used for rules documentation and the images folder is used for icons for the rules editor.

The classes and lib folder respectively are no different to the corresponding web app folders and probably need little explanation. We will now package up our sample rule into a jar file and place it in the lib folder.

In Eclipse, right click the package rule.sample.tomorrow.com and select Export. You will be presented with the Export wizard. Select Java->JAR file as shown:

Click on Next >

Check that the export contains the package (and the package only), then key in your export destination and click on Finish.

You will get an export confirmation and you are done. We can now move on to defining the rules manifest.

Note: When selecting a name for your JAR file, take care to ensure that it is unlikely to already exist in another extension. We suggest using your company name (or some other unique identifier) in the name itself.

The rules manifest

The manifest is designed as a piece of XML. It describes each rule, it's properties and its chain points.

We will now create the rules manifest from within Eclipse. Right click the "src" folder and select New->Other and then expand the XML folder:

Select XML and click Next >

Name the XML file rules.xml and click Finish:

You will now have a basic XML file:

To create the root element for the rules catalogue, right click the ?=? xml element and select Add After->New Element:

Name the new element "rulesCatalogue" and click OK. Your document now looks like this:

We now need to add a child element for the rule. Right click the rulesCatalogue and select Add Child->New Element:

The element name must be "rule". Enter that and click on OK.

Rule element attributes

Each rule has a number of attributes that must be defined at the element level. You will need to enter them one by one, by selecting the "rule" element and then Add Attribute->New Attribute. The following shows the first attribute to add:

Proceed to add all of the following attributes:

These attributes provide a name that will be used for the rule in the rules editor (Threshold comparer), the class that contains the rule, an image to use for the rule in the editor (in this case we are re-using the "?" image used for the conditions rule), instructions for the rules editor about whether or not new chain points can be added to the rule by the user, a short note that explains the purpose of the rule (this note shows up in the rules editor and is also the default help instruction for a rule where no documentation has been created) and finally it defines the group where the rule can be found in the rules editor (this group name can be anything, but using an existing meaningful group is recommended).

Rule attributes

Rule attributes in this context refer to child elements named "attribute". These elements are in fact the individual parameters being passed to each rule on initialization. We are going to start by adding our variable name:

Right click the rule element and select Add Child->New Element..:

Name the new element "attribute" and click on OK.

Proceed to add new attributes to the attribute element. You are going to need to add the following:

Then add another child to the rule and complete it as follows:

This provides all of the input parameters we need.

Note: You may find it quicker to edit the XML directly. We are providing the IDE method here for clarity.

Rule chain points

Chain points must be defined as rule child elements as well. The following shows the required chain points:

The complete XML looks like this:

Save the rules.xml file, right click it and select Export.. Select the File System as the destination:

Save it to the My Rules folder:

Our rules manifest is now complete.

The errors.properties file

The next step is to create the file that contains our custom error messages. This file must be named "errors.properties". From within Eclipse, select the src folder, right click and select New..->Other..:

Select "Untitled Text File" and click on Finish:

You will have a new untitled text file. Simply key in the following:

Then click Ctrl+S. You will be asked for a location and a name. Select the src folder and the name "errors.properties":

Once again, export the file to the "My Rules" folder (like we did with the "rules.xml" file).

Testing your extension

We have now done enough work to actually test the extension. The first step is to zip it up so that it can be installed in the console. The way this is done is very important. If you include the wrong path in the zip structure, then the extension will fail to work. Generally, in most zip programs (WinZip, 7Zip) you simply right click the extension folder and click "Add to xxx.zip":

You should now return to the Composable Architecture Platform console and log in with administrative credentials. Then select Extensions in the Administration section:

You can now upload your new extension:

You will see your extension in the list, and you can select it to see the included rules:

You should now open the rules editor. You can either create a new rule set or simply edit an existing one. When you do, the new rules show up in the Conditions folder:

You can now drag it onto the canvas to see the properties:

From here you can proceed to create a basic example, deploy it and test that the rule works as intended. We will not cover that portion here as it is adequately covered in the main manual.

Optional extension features

Including icons

The images folder is used for icons that appear in the rules editor, in the rules tree, and in the top left corner of each rule. These icons must be 16x16 pixel transparent GIF images, and for each icon there needs to be a corresponding highlight icon. The highlight icon is the icon that appears when the user hovers their mouse over the icon. The base icon can have whatever name is suitable and likely to be unused. The highlight icon must be named the same as the icon but preceded by the text "hi_". For example, if there is an icon named "database.gif" then there must also be an icon named "hi_database.gif".

Adding documentation

Every new rule should have proper documentation. Although not a requirement to make a rule work, it will greatly help future users of the rule, and also completes the integration with the rules editor and the rules reference.

The documentation system relies on a template Microsoft Word document, saved as a "Filtered Web Page". The easiest way to get started is to copy an existing example document from the Base Rules extension. The included example is the Alias rule. The Alias source document can be found in the Base Rules doc folder with the name "software.tomorrow.rules.rules.Alias.docx". Copy the file into your own doc folder and rename it so that it matches your package and rule name. For our example, it means renaming it to "software.tomorrow.rules.sample.AboveBelowRule.docx".

Open the document in Microsoft Word:

You are seeing the basic structure of a Rules Reference help item. All rules should have the heading, Group, Extension and Since version completed.

They should also contain a basic snapshot of the rule, a short description, a snapshot of the properties, and then some more extensive help information.

So, our resulting document looks something like this:

Make sure you save the document in its ordinary format (Ctrl-S).

Converting to HTML

It is now time to save the documented rule as HTML so that it can be used by both the rules editor, and the rules reference builder. The outcome of this step is critical, so make sure you pay attention to every detail.

The first step is to select "Save As..":

You will see the save dialog. Select "Web Page, Filtered" as the target:

Click on Save. You now have properly formatted documentation. You will need to repackage your extension ZIP file and re-install it to have the rule included in the rules reference.

Handling data files (FileMonitorCallback)

If your rule requires data files (for example a CSV file or an HTML page), it must notify the X Engine about files it requires and also implement the FileMonitorCallback interface. The first notification should happen during the initialization phase of the rule using the following method call:

addMonitoredFile(pathFile);

You must provide a full path to the file. Subsequently, whenever the file is modified, the rule will receive a callback to allow it to update any internally cached information based on that file. The callback is performed by the following method:

public void updateFile(File file)

In addition, the rule must implement a method for deployment verification. This method is:

public String[] getDependentFiles()

It must return a list of the file names the rule depends on, to allow the deployment engine to determine which rules to deploy.

The following code snippet shows how this method can be implemented:

public String[] getDependentFiles() {
String file = (String)getProperties().get("DataFile");
int fileType = getValueType(file);
if (fileType==VAR_STRING) {
file = getValueString(file);
} else {
String[] errorInfo = { };
setError("BASE052", errorInfo);
}
String[] files = { file };
return(files);
}

Using the HttpBrowser helper class

As a large number of extensions deal with the need for accessing HTTP sites and services, the X Engine runtime (from version 8 and up) includes a helper class that greatly simplifies the task of dealing with that protocol.

The helper class automatically deals with self-signed certificates, proxy configuration and authentication schemes.

The helper class acts like a full featured browser, carrying forward elements such as cookies and providing simple interfaces for using the default HTTP protocol methods (GET, PUT, POST, PATCH and DELETE).

The following code snippet provides a simple sample on how to use this class:

// Prepare the URL for sending
HttpBrowser browser = new HttpBrowser(getRuleSet().getRulesEngine());
try {
    HttpResponseAsString rsp = browser.doHttpGet("http://www.kapow.co.uk/scripts/sendsms.php?username="
            +user+"&password="+password+"&mobile="+phone+"&sms="+message,"UTF-8");
    browser.close();


    // Check if the reply contains an OK message
    if (rsp.getResponse().indexOf("OK")>-1) {
        cp = (ChainPoint)getRuleChainPoints().get("OK");
    } else {
        vao.setAttribute("ERROR",rsp.getResponse());
        writeConsole("WARNING: Kapow SMS fail >> "+phone+" >> "+rsp.getResponse());
    }	

The following shows the key methods available for the HttpBrowser class:

Adding an installer

At times there may be tasks that you wish to perform when a certain extension is installed. For example, you may wish to add new credentials to the credential vault. To do this, you need to create a class within your extension named "RulesInstaller". Which package name you use is not important as long as the name is correct. You can then proceed to access console objects as part of the extension installation and un-install. A typical RulesInstaller class looks as follows:

package software.tomorrow.rules.rules;

import com.javacoop.Coop;

import software.tomorrow.coop.CredentialsVault;

public class RulesInstaller {

	public static void install() {
		// Create the credential vault entry
		try {
			CredentialsVault cv = (CredentialsVault)Coop.findByUniqueKey(CredentialsVault.class
					,new String[]{"key"},new String[]{"KapowSMS"});
			if (cv==null) {
				Coop.create(new CredentialsVault("KapowSMS","","Credentials for Kapow SMS"
						, new String[]{"UserID","Password"}, new String[]{"",""}
						, new boolean[]{false,true}));
			}
		} catch(Exception ex) {
			ex.printStackTrace();
		}
	}
	
	public static void uninstall() {
		// Remove the credential vault entry
		try {
			CredentialsVault cv = (CredentialsVault)Coop.findByUniqueKey(CredentialsVault.class
					,new String[]{"key"},new String[]{"KapowSMS"});
			if (cv!=null) {
				Coop.delete(cv);
			}
		} catch(Exception ex) {
			ex.printStackTrace();
		}
	}
	
}

Extension by example: Kapow SMS

In this example, we will provide a new extension that allows the sending of an SMS text message using the Web-based gateway from Kapow.

The full source is provided later in this manual. In the following example we only list the parts that are relevant to this discussion as we go through what is happening in the code.

Declaring variables

As the Kapow SMS service requires a user ID and password, we are going to obtain those from the credential vault. So, we need placeholders for them:

private String user;
private String password;

The next step is to declare variables to match the input properties supplied to the rule at initialization time. In this case, the input properties are as follows:

So, we declare:

private String messageAttr;
private int messageType;
private String phoneAttr;
private int phoneType;

Note the additional integer with the suffix “Type” declared for each property. We need this type, as an input property can either be a fixed string in quotes, or a variable name. If it is a fixed string, it can be resolved at initialization. If it is a variable, it can only be resolved at execution time.

The Kapow SMS service is accessed over HTTP using a simple GET method. The easiest way to do that is to use the HttpBrowser class as previously discussed.

Obtaining proxy information

If however you need to use your own HTTP client, you may need to traverse a corporate proxy server to get to the internet. To obtain the proxy information:

private boolean useProxy = false;
private String proxyHost;
private int proxyPort = 0;
private String proxyUser;
private String proxyPass;
private String proxyDomain;
// Read the proxy credentials
proxyHost = getConfigurationSetting("proxyHost");
if (proxyHost == null || proxyHost.trim().equals("")) {
useProxy = false;
} else {
String proxyPortStr =
getConfigurationSetting("proxyPort");
if (proxyPortStr!=null && !"".equals(proxyPortStr)) {
try {
proxyPort = Integer.parseInt(proxyPortStr);
} catch (Exception ex) {
// Ignore
}
// Negative and zero numbers not allowed
if (proxyPort < 0) {
String[] errorInfo = {};
setError("BASE056", errorInfo);
}
// A zero proxy port means the proxy isn't used
if (proxyPort==0) {
useProxy = false;
}
}
proxyUser = getConfigurationSetting("proxyUser");
proxyPass = getConfigurationSetting("proxyPass");
proxyDomain = getConfigurationSetting("proxyDomain");
}

The initialize() method

All of the private variables will be set up in the initialize method. This method is called whenever the rule set is first loaded. At this point we have access to all of the properties set in the rules editor, credentials, and configuration items. We must store and validate them. The following code snippet shows this process for the Kapow user ID:

public void initialize() {
// Read the user ID and password from the credentials // vault
user = getCredentialsValue("KapowSMS","UserID");
password = getCredentialsValue("KapowSMS","Password");
// Check that the credentials are available
if (user==null || password==null) {
String[] errorInfo = { };
setError("KPOW001", errorInfo);
}

This is a very simple way of obtaining the values provided via the configuration. Next, we are going to read the input properties. We only show the message property here:

// Read the message
messageAttr =
(String)getProperties().get("MessageAttribute");
if (messageAttr==null || messageAttr.trim().equals("")) {
String[] errorInfo = { };
setError("KPOW003", errorInfo);
}
// Determine the type of value used. It can be a variable // or a String in quotes
messageType = getValueType(messageAttr);
if (messageType==VAR_STRING) {
messageAttr = getValueString(messageAttr);
}

There are a number of elements to highlight. The error checking simply ensures that whatever is provided is not a blank value. If it is, however, a call to the setError() method follows, providing a message ID to display to the user. Error messages are read from a properties file that must be packaged up with our extension code. In this case, the file contains the following values:

; Error codes for Kapow SMS
KPOW001=The rule &1 in ruleset &2 does not have the KapowSMS credentials correctly set in the credential vault.
KPOW003=The rule &1 in ruleset &2 is missing a message variable
KPOW004=The rule &1 in ruleset &2 is missing a phone number variable

Notice the replacement text &1 and &2 in the error messages. These are automatically replaced with the rule name and rule set name when the error is displayed. Any following replacement text (&3, &4 and so on) will be replaced with elements from the errorInfo array passed to the method.

Next we use the getValueType()method to determine the type of property passed to us. The possibilities are VAR_NUMBER, VAR_STRING or VAR_VARIABLE. In this case, if we are dealing with a fixed string, we set the contents of the user variable to that string (without any surrounding quotes) using the getValueString()method. If it is not a string (a variable), then we need to determine the value at execution time.

The code basically repeats for each property until all input is validated and set into the rule.

Next the code obtains and validates the proxy configuration from the configuration. This proxy configuration will only be used to connect to Kapow via a proxy if required.

We now have all of the information we need.

The cleanup() method

The next step is to provide any code required to clean up once the rule is unloaded from the X Engine. This could involve terminating an open connection, closing a file, etc. In this case there is nothing to do, so the code looks like this:

public void cleanup() {
// Nothing here
}

The processRule() method

The rule is now prepared for execution time and this method takes care of that. The most relevant parts are in the first section of the method:

public VariableAttributeObject processRule(
VariableAttributeObject vao, ChainPoint source)
throws Throwable {
// Default to the error chain point
ChainPoint cp = (ChainPoint)getRuleChainPoints().get("Failed");

Notice the method parameters. The first VariableAttributeObject contains the current data being passed to the rule. The second is the originating chain point. The most important of those is the data object named “vao”.

In the next line, we load the default chain point from the rules configuration. This rule has two chain points as shown here (OK and Failed).

In this code example we default to failed and only override it if we have a successful message transmission.

The next step is to make sure the message we are going to send to Kapow is in fact the right one. If the message was provided as a constant, we simply load it from the internal variable created during initialization. If it is a variable, we load it now.

// Late determination of the value type. If it is a variable (not a // constant), we must retrieve it now.
String messageInp = messageAttr;
if (messageType==VAR_VARIABLE) {
messageInp = vao.getAttribute(messageAttr);
}

We proceed to do this for all properties.

An alternative way to do the same thing is to use the getRuntimeValue() method as shown in the previous example. This requires less work but is also slightly slower at execution time.

Before sending anything, we check the run type of the current execution. As the code can be either run in production, or on a test server, it is important that any rule that can make updates, or send real alerts, has the ability to disable the actual sending. The flags in the rule configuration determine if the alert should be sent, so we must honor this behavior in the rule:

// Prepare to send the SMS. It can be a real SMS or just a simulation // on the test server
if ((getRunType() & Rule.RUNTYPE_SEND_ALERTS)==0) {
// This is a simulation - just let it through
writeConsole("SMS: "+messageInp+" >> "+phone);
cp = (ChainPoint)getRuleChainPoints().get("OK");
} else {

The flags are bit maps, hence the check in the above for the RUNTYPE_SEND_ALERTS flag (see the JavaDoc for the full list of flags). If it is determined that we should not send the alert, we output a message to the Composable Architecture Platform console instead and override the chain point to “OK”.

The code now goes ahead and uses the HttpBrowser object to send the message to Kapow.

// Send SMS via Vodafone Kapow
// Escape the message, so that special characters get encoded
String message = Encoding.escape(messageInp);
// Strip any special characters from the phone number
StringBuffer no = new StringBuffer();
for (int loop=0; loop<phone.length(); loop++) {
char nc = phone.charAt(loop);
if (nc=='+' || nc=='0' || nc=='1' || nc=='2' || nc=='3'
|| nc=='4' || nc=='5' || nc=='6' || nc=='7' || nc=='8'
|| nc=='9') {
no.append(nc);
}
}
phone = no.toString();
// Prepare the URL for sending
HttpBrowser browser = new HttpBrowser(getRuleSet().getRulesEngine());
try {
HttpResponseAsString rsp = browser.doHttpGet("http://www.kapow.co.uk/scripts/sendsms.php?username="+user+"&password="+password+"&mobile="+phone+"&sms="+message,"UTF-8");
browser.close();
// Check if the reply contains an OK message
if (rsp.getResponse().indexOf("OK")>-1) {
cp = (ChainPoint)getRuleChainPoints().get("OK");
} else {
vao.setAttribute("ERROR",rsp.getResponse());
writeConsole("WARNING: Kapow SMS fail >> "+phone
+" >> "+rsp.getResponse());
}
In addition, focusing on the last few lines of the method:
} catch(Exception ex) {
// In case of an exception, ignore and set the ERROR attribute // to the exception
vao.setAttribute("ERROR",ex.toString());
writeConsole("WARNING: Kapow SMS fail >> "+phone
+" >> "+ex.toString());
// Also log it
String[] errorInfo = { "\""+ex.getLocalizedMessage()+"\"" };
log(LogAdapter.LOGLEVEL_WARNING,renderMessage("BASE070",
errorInfo),ex,this,null);
} finally {
browser.close();
}

Notice the blanket catch of exceptions and the setting of an error attribute if something unusual occurs. It is paramount the X Engine is resilient to exception and internal failures. The use of the Composable Architecture Platform logging framework is also visible from this code.

Finally, chain out to the chosen chain point and return the result of that chain to the parent rule.

// Chain through to the correct chain point
if (cp!=null) vao = cp.chain(vao,this);
return vao;

Packaging your extension

Once we have completed our extension, we must define a manifest to go with it, so that the rules editor can visualize it and package it up, so the X Engine can load the code.

Defining the manifest

The manifest is designed as a piece of XML. It describes the rule, its properties, and its chain points. The following is the sample XML used for the Kapow SMS rule:

<?xml version="1.0" encoding="UTF-8"?>
<rulesCatalogue>
<rule name="Send Kapow SMS"
ruleClass="software.tomorrow.rules.rules.KapowSMS"
addChainPoints="false"
note="This rule sends an SMS to a nominated phone number"
group="Alert">
<attribute name="MessageAttribute"
label="Message Variable" type="text" quote=”true”/>
<attribute name="PhoneAttribute"
label="Phone Number Variable" type="text" quote=”true”/>
<chainpoint name="OK" label="OK"/>
<chainpoint name="Failed" label="Failed"/>
</rule>
</rulesCatalogue>

It starts by defining the rule name (as it shows up in the rules editor), the class, and whether chain points can be dynamically added (this property must match the code and should always be false for custom rules).

A note is added to describe the behavior of the rule. This note shows up in the Extension details in the rules console and also in the documentation pane in the rules editor itself.

A group is also set. This is the group where the rule can be found in the rules list tree in the rules editor.

Next, the various properties that are being set on the rule are defined (the “attribute” tags). Each defines a name (how the rule knows it), a label (how the user sees it) and a data type that can be “text” or “list”. An example of a list definition is shown below:

<attribute name="Data" label="Data Type" type="list">
<listvalue name="String" label="String"/>
<listvalue name="IgnoreCase"
label="String (Ignore Case)"/>
<listvalue name="Number" label="Number"/>

Note that if you need to have your rules editor in multiple languages, you must supply a manifest for each language required.

Finally, the chain points are defined. Once again it contains an internal value (name) and a label (what the user sees).

You are now ready to package up your new extension.

Packaging into a zip file

Create a file structure that looks as follows:

Place your manifest in the “rules.xml” file, your error message in the “error.properties” file, and your new class file in the “classes” folder. If your class needs to use any JAR files not already in other extensions, add them to the lib folder. Then zip the entire structure into a single zip file.

This new zip file is now ready to be loaded into the rules editor using the Composable Architecture Platform console. Please refer to the “Extensions” chapter in the product reference for instructions on how to accomplish this.

Last updated

General Website Terms & Conditions and Privacy Policy

Terms of UsePrivacy Policy

Contact Us

Get Help

© Copyright TomorrowX 2024 Composable Architecture Platform and Connected Agile are trademarks and servicemarks of TomorrowX .