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.

In 2.2.0a5 we added the following optional configurations,  onMatchException  and  onMailetException :

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

The meaning of the values is as follows:

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

My take on this would be that RuntimeException and Error are caused by problems that cutting across functional concern and should therefore be handled by configuration. Not sure of the solution - it seems like it perhaps should be a mechanism in its own right, but the above would work with the option of providing standard processors. Chanoch


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.

This email is system generated. Replies to it will be ignored.

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

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 terminate without catching it. A server application like James should catch it and, if "fatal" should terminate (i.e. if it is of general scope and not just related to the processing of a single Mail object), otherwise it should recover if possible and continue (for instance sending the offending/offended Mail object to the error processor).

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 close to the event as possible and take appropriate action as specifically as possible. Such action can also consist, if appropriate, in rethrowing the exception or in embedding it in a new exception which it throws. The java compiler helps with checked exceptions, compelling a method to catch the exception or to declare it in it's signature.

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. It is not desirable that the code deal with this type of error, as this would incorporate exceptions as part of the normal application flow. From James' perspective, however, it would be desirable to handle this in such a way that the user is buffered from the consequences. (is given a helpful message)

Generic exception belonging to javax.mail which extends Exception (i.e. it is not a RuntimeException)

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 may not be following the rules. Each matcher or mailet follows its own exception management rules (those in the James distribution should be consistent with the rest of the James code.

At this level the following is 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 (though not during initialization - that 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 checked Exception, the java compiler forces the programmer to catch it and process it, including the option of embedding it in a new MessagingException to throw upwards.


Other error conditions

If a RuntimeError flows up, it will not be caught at config.xml level, but JamesSpoolManager.process(MailImpl) will catch it and send the Mail object to the error processor.

In case of an Error, before the James 2.2.0a7 test build it was caught a level up in the stack, in JamesSpoolManager.run(). This then reported the Throwable 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) catches any Throwables, and sends the Mail object to the error processor. JamesSpoolManager.run() can still 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 2010-11-19 14:08:57 by ChanochWiggers)