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.
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).
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.
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.
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):
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:
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.
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.
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.
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 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.
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 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.
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 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).
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.
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".
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).
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.
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:
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:
In addition, the rule must implement a method for deployment verification. This method is:
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:
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:
The following shows the key methods available for the HttpBrowser class:
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:
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.
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:
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:
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.
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:
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:
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:
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:
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 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: