20040826

James Drools

Take two top class bits of Java technology, James apache's mail server and Drools from Codehaus. Mix them together and what do you get, a powerful rule engine for email processing.

The way to do it is to take the Mailet interface of James and write a DroolsMailet like this:


public class DroolsMailet extends GenericMailet {

private WorkingMemory memory = null;

public void init() throws MessagingException {
super.init();
MailetConfig config = getMailetConfig();
String rules = config.getInitParameter("rules");
RuleBase ruleBase = RuleBaseBuilder.buildFromUrl(
this.getClass().getResource(rules));
memory = ruleBase.newWorkingMemory();
}

public void service(Mail mail) throws MessagingException {
...
}
}


You may want to persist the working memory or have some kind of rule reload, but this simple implementation should do for most things.

When James calls the service method you want to trigger Drools to work on the new message. So the service method in its basic form looks like this:


memory.assertObject(mail);
memory.fireAllRules();


But there is a problem with that, your working memory will slowly fill up with all the messages that go through James. So you want to have some way to limit the number of messages in your working memory. You could do this by timestamp, have a Drools rule that retracts messages where the lastUpdated timestamp is more than a certain time ago. But if you get a flood of emails through your server then you could still blow its memory capacity. So instead you should fix a limited number of message to hold, like this:


private final LinkedList messageList = new LinkedList();
private int maxMessagesToHold = 100; // some default size


then in your init method:


maxMessagesToHold = Integer.parseInt(config.getInitParameter("windowSize"));


and in the service method:


messageList.addLast(mail);
if (messageList.size() > maxMessagesToHold) {
Mail oldMessage = (Mail) messageList.removeFirst();
memory.retractObject(oldMessage);
}


So now your Drools working memory holds the last X messages in it, so what! This is where you start being able to write some powerful rules. Most people are probably thinking of various spam filtering techniques. But how about this for a different kind of rule:


<rule name="ClientRecordIncoming">
<parameter identifier="message">
<java:class>org.apache.mailet.Mail</java:class>
</parameter>
<parameter identifier="clients">
<java:class>com.mycorp.ClientDatabase</java:class>
</parameter>
<java:condition>clients.isClientAddress(message.getSender())</java:condition>
<java:consequence>
Client client = clients.getClientByAddress(message.getSender());
clients.recordMessage(client, message);
</java:consequence>
</rule>


This rule will record all incoming messages from a recognised client in your client database, assuming you can write the ClientDatabase class and load it into working memory in the mailet init method. You could just as easily write this into the mailet, but the power of Drools really shows when you start cross relating emails.


<rule name="RelationshipMapper">
<parameter identifier="first">
<java:class>org.apache.mailet.Mail</java:class>
</parameter>
<parameter identifier="second">
<java:class>org.apache.mailet.Mail</java:class>
</parameter>
<java:condition>first.getRecipients().size() == 1</java:condition>
<java:condition>second.getRecipients().size() == 1</java:condition>
<java:condition>first.getRecipients().contains(second.getSender())</java:condition>
<java:condition>second.getRecipients().contains(first.getSender())</java:condition>
<java:consequence>
EmailRelationship related = Relationships.get(first.getSender(), second.getSender());
if (related.isNew()) {
drools.assertObject(related);
} else {
drools.modifyObject(related);
}
</java:consequence>
</rule>


This rule will fire when there is an exchange of emails between two addresses within the window. This way you could track the business relationships of your staff through their emails.


<rule name="DepartmentalRelationshipMapper">
<parameter identifier="related">
<java:class>com.mycopr.EmailRelationship</java:class>
</parameter>
<parameter identifier="staff">
<java:class>com.mycorp.StaffDatabase</java:class>
</parameter>
<java:condition>staff.deptOf(related.first) != staff.deptOf(related.second)</java:condition>
<java:consequence>
...
</java:consequence>
</rule>


And the list goes on...
It reminds me of chinese wall compliance tools.

1 comment:

straun said...

A bit of clarification:

If you are processing mail on an individual basis then keep that code in the mailet, you don't need the overhead of Drools.

If you are just watching what is going on, and don't need to take any actions (redirecting or blocking emails) then you should log the messages and use Drools offline as a post processing tool.