Implementing a custom matcher is generally a simple task, most of whose complexity lies in coding the actual work to be done by the matcher. This is largely due to the simplicity of the Matcher interface and the fact that a couple of abstract Matcher template classes are provided in the Mailet package. These two classes, GenericMatcher and GenericRecipientMatcher, greatly simplfy the task of Matcher authoring.
As discussed elsewhere in this manual, the Matcher interface does not simply match or not match a particular message. Rather, it returns some subset of the original message recipients as a result of the match(Mail) method. This leads to the two different abstract Matcher implementations.
The first, GenericMatcher, is intended for matchers where recipient evaluation is not necessary. Basically, you should subclass this implementation if your matcher is going to return all or none of the recipients.
When subclassing this class, there are four methods that potentially need to be overridden. These are getMatcherInfo(), init(), match(Mail), and destroy(). More on these anon.
The second implementation, GenericRecipientMatcher, is intended for those matchers where each recipient is evaluated individually. It is a subclass of GenericMatcher, and inherits most of its behavior from that class. The only major difference is that subclasses are expected to override matchRecipient(MailAddress) rather than match(Mail).
Matchers are passed a single String as part of their configuration. Interpretation of this list is left entirely to the body of the Matcher. This String value is available in the body of the Matcher through use of the getCondition() method of the GenericMatcher class. This method returns the String value passed to the Matcher, and returns null if no value is set. The method getCondition() is available inside the init(), destroy(), match(Mail), and matchRecipient(MailAddress) methods.
There is a simple logging mechanism provided by the Mailet API. It does not support logging levels, so any log filtering will have to be implemented in the Matcher code. Logging is done by calling one of the two logging methods on GenericMatcher/GenericRecipientMatcher - log(String) or log(String,Throwable). Logging is available inside the init(), destroy(), match(Mail), and matchRecipient(MailAddress) methods.
The value of getMatcherInfo() for the Matcher is prepended to the log entries for that Matcher. So it may be desirable for you to override this method so you can distinguish Matcher log entries by Matcher.
As part of the Matcher lifecycle, a Matcher is guaranteed to be initialized immediately after being instantiated. This happens once and only once for each Matcher instance. The Initialization phase is where configuration parsing and per-Matcher resource creation generally take place. Depending on your Matcher, it may or may not be necessary to do any initialization of the Matcher. Initialization logic is implemented by overriding the init() method of GenericMatcher/GenericRecipientMatcher.
It is the matching phase where the Matcher's work is done. The exact form of this phase largely depends on which Matcher superclass is subclassed.
If GenericMatcher is being subclassed, it is the match(Mail) that is implemented. As described above, this method returns a Collection of MailAddresses that is a subset of the original recipients for the Mail object.
If it is a purely recipient-filtering Matcher, then the GenericRecipientMatcher should be subclassed. In this case, developers must provide an implementation of the matchRecipient(MailAddress) method. This method returns true if the recipient matches, and false otherwise.
As part of the Matcher lifecycle, a Matcher is guaranteed to be destroyed when the container cleans up the Matcher. This happens once and only once for each Matcher instance. The Destruction phase is where per-Matcher resource release generally takes place. Depending on your Matcher, it may or may not be necessary to do any destruction of the Matcher. Destruction logic is implemented by overriding the destroy() method of GenericMatcher/GenericRecipientMatcher.
Once a Matcher has been successfully implemented there are only a couple of additional steps necessary to actually deploy the Matcher.
The Matcher must be added to James' classpath so that the Matcher can be loaded by James. There are two ways to add a custom Matcher to the classpath so that James will be able to load the Matcher. These are:
1a. Download the source distribution, add a jar file containing the custom files to the lib directory of the unpacked source distribution, and build a new .sar file by following the directions here. This new .sar file will now include your custom classes.
or
1b. Place a jar file containing the custom class files in the lib subdirectory of the James installation. It will also be necessary to unpack the JavaMail and James jar files from the provided .sar file and add them to this directory.
or
1c. Place a jar file containing the custom class files in /path/to/james/conf/lib/ subdirectory with JAMES-Spring or /path/to/james/conf/extensions-jars/ subdirectory with JAMES-Guice.
2. After this is done get sure you add the matcher package to the mailetcontainer.xml. For example:
<!-- Set the Java packages from which to load mailets and matchers --> <matcherpackages> <matcherpackage>org.apache.james.transport.matchers</matcherpackage> <matcherpackage>org.apache.james.transport.matchers.smime</matcherpackage> <matcherpackage>your.costum.package.transport-matchers</matcherpackage> </matcherpackages>
Upon injections, the user can reference additional guice modules, that are going to be used only upon extensions instantiation. In order to do that:
1. Place the jar containing the guice module that should be used to instantiate your extensions within the /extensions-jars folder
2. Register your module fully qualified class name within
extensions.properties under the guice.extension.module
key.
Configuration of the processor chain is discussed elsewhere in this documentation. The details of configuring matcher deployment is discussed at length. Here we will only comment that it is important to add the appropriate matcher package for your custom matcher to the <matcherpackages> list and that the name of your matcher should not conflict with any of the matchers described here.