up previous next
Next: More of the ABLE Up: AI Previous: Getting started with a

Subsections

A more complicated example

In the words of Emeril, let's ``kick up our example a few notches'' now by adding some real world functionality. I'm going to add the following capability to my earlier example:

  1. I'll create and use an EmailMessage class.
  2. I'll simulate the process of reading ``rejection keywords'' from a user's configuration file.
  3. I'll simulate the process of reading a ``whitelist'' of allowed senders from a user's configuration file.
  4. I'll pass the EmailMessage, rejection keywords, and whitelist objects into the rules engine for processing.
  5. I'll show how to access Java classes and methods from the ABLE rules script.
  6. I'll add other rules to the rules engine to make it smarter.

An EmailMessage class

The first thing I'm going to do is create a simple EmailMessage class. The contents of this class are shown in the code below:

package email2;

public class EmailMessage
{
    private String from;
    private String to;
    private String subject;
    private String contents;
    private boolean attachment;

    public EmailMessage()
    {
    }

    public void setFrom(String from) {
        this.from = from;
    }

    public void setTo(String to) {
        this.to = to;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public void setContents(String contents) {
        this.contents = contents;
    }

    public void setAttachment(boolean attachment) {
        this.attachment = attachment;
    }

    public String getFrom() {
        return from;
    }

    public String getTo() {
        return to;
    }

    public String getSubject() {
        return subject;
    }

    public String getContents() {
        return contents;
    }

    public boolean hasAttachment() {
        return attachment;
    }
}

This is a very basic Java class with no real functionality other than getters and setters. That being said, it's a nice simple class for the points I'm trying to convey.

EmailRuleSetApp2

The next thing to look at is the revised ``driver'' application for this example. Before looking at it, here is a quick list of changes to it:

  1. I moved it to a new package named email2.
  2. I'm accessing a new rules file.
  3. I'm creating a fake email message object.
  4. I create a list of keywords that should be tested against the Subject field to see if a message is spam.
  5. I create a simple whitelist of email addresses that should be allowed.
  6. I pass these new objects to the rules engine through the data object.

With that quick introduction, here is the source code for the new application named EmailRuleSetApp2:

package email2;

import java.util.Iterator;

import com.ibm.able.Able;
import com.ibm.able.AbleException;
import com.ibm.able.rules.AbleRuleSet;
import java.util.List;
import java.util.ArrayList;

public class EmailRuleSetApp2
{

  public static void main(String[] args)
  {
    try
    {

      // retrieve the rule set
      String rules = "C:/Projects/BorlandConference2004/AI/ABLE/JB_ABLE_Test/src/email2/email.arl";
      AbleRuleSet ruleSet = new AbleRuleSet();
      ruleSet.parseFromARL(rules);
      ruleSet.init();

      // create our (fake) data
      EmailMessage message = new EmailMessage();
      message.setFrom("Your Mother");
      message.setTo("al@missiondata.com");
      message.setSubject("");
      message.setContents("blah blah blah");
      message.setAttachment(true);

      // create a fake rejection list. note that this is really read from a
      // config file the user can control.
      List subjectKeywordsToReject = new ArrayList();
      subjectKeywordsToReject.add("viagra");
      subjectKeywordsToReject.add("vicodin");

      // create a fake whitelist, i.e., the users I know and will always allow mail from
      List whitelist = new ArrayList();
      whitelist.add("wife@devdaily.com");
      whitelist.add("mom@devdaily.com");
      whitelist.add("borland.com");

      Object[] data = new Object[]{
                      message,
                      subjectKeywordsToReject,
                      whitelist};
      Object[] input = null;
      Object[] output = null;

      Able.startTraceLogging(Able.TRC_LOW, Able.MSG_NONE, null);

      Able.TraceLog.text(Able.TRC_LOW, "Starting Run ----------");
      output = (Object[]) ruleSet.process(input = data);
      System.err.println("Output is: " + output[0]);

    }
    catch (AbleException ae)
    {
      if (ae.getExceptions() == null)
      {
        System.out.println(ae.getLocalizedMessage());
      } else
      {
        Iterator it = ae.getExceptions().iterator();
        while (it.hasNext())
        {
          Exception e = (Exception) it.next();
          System.out.println(e.getLocalizedMessage());
        }
      }
    } catch (Exception exp)
    {
      System.out.println(exp.getLocalizedMessage());
      exp.printStackTrace();
    }
    System.exit(0);
  }

}

Rules engine changes

The next thing to review is the changes to the rule set that I used in the simple example. Here is a quick list of the changes to the rules engine:

  1. I'm creating several new objects in the variables section of the code.
  2. I'm receiving several new object references from the calling program through the inputs method.
  3. I've changed the processing type to Forward Chaining with the line ``process() using Forward``.
  4. I've also changed the program to stop process rules once the first rule is fired using the 
    ``setControlParameter(ARL.ControlStrategy, ARL.FIRE_ONE_RULE)`` call.
  5. Because I'm using the Forward Chaining engine I can put a priority on each rule and make sure they fire in the order I want them to.
  6. I'm putting labels on the rules to make them easy to find.
  7. I've created several new rules, as follows:
    1. If the sender is unknown and the Subject field is empty the message is rejected.
    2. If the sender is unknown and there is an attachment the message is rejected.
    3. If the Subject field matches one of the known ``bad'' (or common) patterns the message is rejected.

The new rules set is shown next, followed by a discussion of how it works.

ruleset Email 
{
  import java.util.List;
  import email2.EmailMessage;
  import email2.EmailRuleUtils;
  
  variables
  {
    EmailRuleUtils emailRuleUtils = new EmailRuleUtils();
    EmailMessage message = new EmailMessage();
    String result = new String("");
    List subjectFieldRejectionPatterns;
    List whitelist;
  } 

  inputs{message,subjectFieldRejectionPatterns,whitelist};

  outputs{result};

  void process() using Forward 
  {
  
    // do this so a maximum of one rule is fired
    : setControlParameter(ARL.ControlStrategy, ARL.FIRE_ONE_RULE);
    
    SenderUnknownAndSubjectIsEmpty [1000]: 
      If ( emailRuleUtils.senderIsUnknown(message.getFrom(),whitelist) and emailRuleUtils.isBlankOrNull(message.getSubject()) )
      result = "SPAM (SenderUnknownAndSubjectIsEmpty)";

    SenderUnknownAndAttachment [990]:
      If (emailRuleUtils.senderIsUnknown(message.getFrom(),whitelist) and message.hasAttachment())
      result = "SPAM (SenderUnknownAndAttachment)";

    SubjectFieldMatchesPattern [900]: 
      If (emailRuleUtils.fieldMatchesSpamPattern(message.getSubject(),subjectFieldRejectionPatterns))
      result = "SPAM (SubjectFieldMatchesPattern)";
      
    Default :
      result = "NOT SPAM";

   }

  void postProcess() 
  {
     :  println("Ruleset concludes the email is a " + result + " email.");
  }
}

A simple utility class  

The last set of changes I've made is to create a new utility class named EmailRuleUtils. This class contains two methods that can be called from the rules engine, fieldMatchesSpamPattern and senderIsUnknown. I've put this logic in a separate class for several reasons. First, I prefer the obvious separation of concerns between the application, the rules engine, and these helper methods. Second, I like to have these methods in an IDE like JBuilder, where I can develop code more quickly and efficiently. Third, I like to be able to create unit tests against simple helper methods like these.

The source code for the EmailRuleUtils class is shown next.

package email2;
import java.util.regex.Pattern;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.List;

public class EmailRuleUtils
{
  public boolean isBlankOrNull(String string)
  {
    if ( string==null || string.trim().equals("") ) return true;
    else return false;
  }

  /**
   * If the given field matches any of the patterns in the supplied list of patterns,
   * this method will return true.
   * @param currentField Pass in things like the Subject field, From field, Reply-to, etc.
   * @param knownPatternsToReject A List of patterns that the supplied field will be tested against.
   * @return boolean
   */
  public boolean fieldMatchesSpamPattern(String currentField, List knownPatternsToReject)
  {
    if ( currentField == null ) return true;
    currentField = currentField.toLowerCase();

    Iterator it = knownPatternsToReject.iterator();
    while ( it.hasNext() )
    {
      String keywordToReject = ((String)it.next()).toLowerCase();
      Pattern aPattern = Pattern.compile(keywordToReject,Pattern.CASE_INSENSITIVE);
      Matcher aMatcher = aPattern.matcher(currentField);
      if ( aMatcher.find() ) return true;
    }

    // the given string didn't match a rejection pattern? return false.
    return false;
  }

  public boolean senderIsUnknown(String sender, List whitelist)
  {
    if ( sender == null ) return true;
    sender = sender.toLowerCase();

    Iterator it = whitelist.iterator();
    while ( it.hasNext() )
    {
      String emailAddress = ((String)it.next()).toLowerCase();
      Pattern aPattern = Pattern.compile(emailAddress,Pattern.CASE_INSENSITIVE);
      Matcher aMatcher = aPattern.matcher(sender);
      if ( aMatcher.find() ) return false;
    }
    return true;
  }
}

Running this example

Looking at the code samples provided can you guess what the output of the program will be? In this particular example the output is ``Output is: SPAM (SenderUnknownAndSubjectIsEmpty)``. This is because this rule was set to the highest priority and it fired first. Once it fired, the process() method exited.

Create your own tests

For your own exercise you can try different things like changing the priority of the rules, and varying the inputs to see how to make the other rules fire. The important thing for me is that once you get through these basic hurdles, you can start enjoying ABLE faster, and start experimenting with some of the really powerful things that are under the hood.


up previous next
Next: More of the ABLE Up: AI Previous: Getting started with a