Exception Handling in James v2.2 and later

This is a summary to the changes in James related to exception management, as could be of interest to all users.


Administrative Control

Prior to the James 2.2.0a5 test build, the Mail Administrator was unable to control exception management, and may need to, as the Administrator is the only person knowing if matching because of an exception is the correct application domain semantic. In 2.2.0a5 we added a mechanism (the "onException") that provides administrative control, optionally coding (the attribute values are case insensitive):

  <mailet match="..." class="..."
         [onMatchException="{noMatch|matchAll|error|<aProcessorName>}"]
         [onMailetException="{ignore|error|<aProcessorName>}"]>

The meaning of the values is as follows:

The default is equivalent to code on{Match|Mailet}Exception="error", that means sending to the error processor, as has always been before 2.2.0a5.

This config.xml "code" catches any MessagingException thrown by the underlying matcher or mailet (that may embed any Throwable catched below), but if a RuntimeException or an Error flows up it will not be considered here. [Technical note: It is an open issue whether to catch here also RuntimeException or even Throwable. Any comments? See also Overview of Exception Handling in the Mailet Pipeline, below.]


An Example

An example taken from a system using the James 2.3.0 build is the following:

         <!-- Check attachment extensions for possible viruses -->
         <!-- The "-z" option requests the check to be non-recursively applied -->
         <!-- to the contents of any attached '*.zip' file. -->
         <mailet match="AttachmentFileNameIs=-d -z *.exe *.com *.bat *.cmd *.pif *.scr *.vbs *.avi *.mp3 *.mpeg *.shs"
                 class="Bounce" onMatchException="error">
            <inline>heads</inline>
            <attachment>none</attachment>
            <passThrough>false</passThrough>
            <prefix>[REJECTED]</prefix>
            <notice>
The Security Policy of XXX does not allow to forward messages containing attachments having any of the extensions .exe, .com, .bat, .cmd, .pif, .scr, .vbs, .avi, .mp3, .mpeg, .shs, therefore your message has been rejected.

Please don't reply to this e-mail as it has been automatically sent by the antivirus system.

Regards, Postmaster XXX.YYY
.....................................
            </notice>
         </mailet>

The onMailetException attributes were coded because some malformed messages, typical of viruses, are causing some javax methods to throw exceptions; my choice was to consider the message in error, but it could be done differently, perhaps the first way using noMatch, and the second way sending the message to a special "may-contain-executables" processor, that bounces back a less "resolute" message.


Overview of Exception Handling in the Mailet Pipeline

To recall (for the non Java programmers), here is the class hierarchy of Throwable:

Throwable
        Exception
                ...
                MessagingException
                ...
                RuntimeException
                ...
        Error
                ...

The major distinctions are (quoting from Sun Javadoc):

A standalone program should just not catch it and terminate. A server application like James should catch it and, if "fatal" (i.e. of general scope and not just related to the processing of a single Mail object for example) should terminate, otherwise should recover as possible (for instance sending the offending/offended Mail object to the error processor) and continue.

With the exclusion of RuntimeException (see below), an Exception arise when an un-anticipated "event", caused by "external agents" (like IO error) occurs that can and should be managed somewhere in the code stack.

The "best practice" should consist in having the code catch the exception as down as possible in the code stack and take an appropriate action as specifically as possible. Such action can also consist, if appropriate, in rethrowing up the exception or in embedding in a new exception and throw it. The java compiler helps, compelling a class either to catch an exception if thrown from below (unless it is a RuntimeException) or to declare a "throws".

It looks similar to Error above, but the key point here is that a RuntimeException is (or should be) a programming error: something that can be fixed in the code, as for example an IndexOutOfBoundsException that appropriate controls in the code can avoid.

Very generic.

The key point for James is that the Matcher and the Mailet interfaces declare respectively the match(Mail) and the service(Mail) methods as throwing MessagingException. So it is the communication channel for exceptions between mailets and matchers and the rest of James.

Now, talking about matchers and mailets, there are three types of people involved in coding in a James environment: (i) the James developers, (ii) the Matcher and Mailet developers, and (iii) the administrators that deploy a server and maintain their own config.xml files.

The James developers, taken in a broad sense, can be considered as a coordinated group of people, and we can consider exception management in the James code as following the same guidelines (to improve/enforce/cleanup perhaps).

The James code invokes matchers and mailets, that can be foreign code. So James must protect itself from a Throwable arising from below, from code that not necessarily follows all the rules. Each matcher or mailet follows its own exception management rules (those in the James distribution should be coherent with the rest of the James code.

At this level the following is anyhow always true: as said before, the Matcher and the Mailet interfaces declare respectively the match(Mail) and the service(Mail) methods as throwing MessagingException. As a consequence, when a matcher or a mailet gets an exception while processing a Mail object (not during initialization - this is another story) it can do the following things: if the exception is a MessagingException it can either catch it or let it flow up (to LinearProcessor, part of James' spool management); if the exception is any other Exception (but not some kind of RuntimeException), the java compiler forces the programmer to catch it somewhere and either take the appropriate action, or to embed it in a new MessagingException and and throw it up.

In the middle stays the administrator of the James server instance deployed, that coding his config.xml decides to use or not a specific matcher or mailet, and is coding a kind of "program" using <mailet match="..." class="...">, equivalent to if (...) {...}. As said above, since 2.2.0a5 he is able to exercise some control using the "onException" mechanism.


Other error conditions

Now, what happens if a RuntimeError flows up? It will not be catched at config.xml level, but JamesSpoolManager.process(MailImpl) will catch it and have the Mail object sent to the error processor.

Finally, what happens if an Error flows up? Before the James 2.2.0a7 test build, it was catched a level up in the stack, in JamesSpoolManager.run(), which reported the exception and left the message in the spool. This could lead to some problem conditions.

In James 2.2.0a7 and later JamesSpoolManager.process(MailImpl mail) will catch any Throwable, and have the Mail object sent to the error processor. JamesSpoolManager.run() still can catch Throwables that flow up from other spool related operations, but will no longer receive from below any Throwable originating from matchers or mailets.

HandlingExceptions (last edited 2009-09-20 22:58:34 by localhost)