LinearProcessorpublic class LinearProcessor extends org.apache.avalon.framework.logger.AbstractLogEnabled implements org.apache.avalon.framework.activity.Disposable, org.apache.avalon.framework.activity.InitializableImplements a processor for mails, directing the mail down
the chain of matchers/mailets.
SAMPLE CONFIGURATION
<processor name="try" onerror="return,log">
<mailet match="RecipientIsLocal" class="LocalDelivery">
</mailet>
<mailet match="All" class="RemoteDelivery">
<delayTime>21600000</delayTime>
<maxRetries>5</maxRetries>
</mailet>
</processor>
Note that the 'onerror' attribute is not yet supported.
As of James v2.2.0a5, 'onerror' functionality is implemented, but
it is implemented on the <mailet> tag. The specification is:
<mailet match="..." class="..."
[onMatchException="{noMatch|matchAll|error|<aProcessorName>}"]
[onMailetException="{ignore|error|<aProcessorName>}"]>
noMatch: no addresses are considered to match
matchAll: all addresses are considered to match
error: as before, send the message to the ERROR processor
Otherwise, a processor name can be specified, and the message will
be sent there.
CVS $Id: LinearProcessor.java 494012 2007-01-08 10:23:58Z norman $ |
Fields Summary |
---|
private static final Random | random | private static final String | TERMINATING_MATCHER_NAMEThe name of the matcher used to terminate the matcher chain. The
end of the matcher/mailet chain must be a matcher that matches
all mails and a mailet that sets every mail to GHOST status.
This is necessary to ensure that mails are removed from the spool
in an orderly fashion. | private static final String | TERMINATING_MAILET_NAMEThe name of the mailet used to terminate the mailet chain. The
end of the matcher/mailet chain must be a matcher that matches
all mails and a mailet that sets every mail to GHOST status.
This is necessary to ensure that mails are removed from the spool
in an orderly fashion. | private List | mailets | private List | matchers | private volatile boolean | listsClosed | private org.apache.james.services.SpoolRepository | spool |
Methods Summary |
---|
public synchronized void | add(org.apache.mailet.Matcher matcher, org.apache.mailet.Mailet mailet)Adds a new Matcher / Mailet pair
to the processor. Checks to ensure that the matcher and
mailet passed in are not null. Synchronized to ensure that
the matchers and mailets are kept in sync.
It is an essential part of the contract of the LinearProcessor
that a particular matcher/mailet combination be used to
terminate the processor chain. This is done by calling the
closeProcessorList method.
Once the closeProcessorList has been called any subsequent
call to the add method will result in an IllegalStateException.
This method is synchronized to protect against corruption of
matcher/mailets lists
if (matcher == null) {
throw new IllegalArgumentException("Null valued matcher passed to LinearProcessor.");
}
if (mailet == null) {
throw new IllegalArgumentException("Null valued mailet passed to LinearProcessor.");
}
if (listsClosed) {
throw new IllegalStateException("Attempt to add matcher/mailet after lists have been closed");
}
matchers.add(matcher);
mailets.add(mailet);
| public synchronized void | closeProcessorLists()Closes the processor matcher/mailet list.
This method is synchronized to protect against corruption of
matcher/mailets lists
if (listsClosed) {
throw new IllegalStateException("Processor's matcher/mailet lists have already been closed.");
}
Matcher terminatingMatcher =
new GenericMatcher() {
public Collection match(Mail mail) {
return mail.getRecipients();
}
public String getMatcherInfo() {
return TERMINATING_MATCHER_NAME;
}
};
Mailet terminatingMailet =
new GenericMailet() {
public void service(Mail mail) {
if (!(Mail.ERROR.equals(mail.getState()))) {
// Don't complain if we fall off the end of the
// error processor. That is currently the
// normal situation for James, and the message
// will show up in the error store.
StringBuffer warnBuffer = new StringBuffer(256)
.append("Message ")
.append(mail.getName())
.append(" reached the end of this processor, and is automatically deleted. This may indicate a configuration error.");
LinearProcessor.this.getLogger().warn(warnBuffer.toString());
}
mail.setState(Mail.GHOST);
}
public String getMailetInfo() {
return getMailetName();
}
public String getMailetName() {
return TERMINATING_MAILET_NAME;
}
};
add(terminatingMatcher, terminatingMailet);
listsClosed = true;
| public void | dispose()The dispose operation is called at the end of a components lifecycle.
Instances of this class use this method to release and destroy any
resources that they own.
This implementation disposes of all the mailet instances added to the
processor
Iterator it = mailets.iterator();
boolean debugEnabled = getLogger().isDebugEnabled();
while (it.hasNext()) {
Mailet mailet = (Mailet)it.next();
if (debugEnabled) {
getLogger().debug("Shutdown mailet " + mailet.getMailetInfo());
}
mailet.destroy();
}
| private void | handleException(javax.mail.MessagingException me, org.apache.mailet.Mail mail, java.lang.String offendersName, java.lang.String nextState)This is a helper method that updates the state of the mail object to
Mail.ERROR as well as recording the exception to the log
System.err.println("exception! " + me);
mail.setState(nextState);
StringWriter sout = new StringWriter();
PrintWriter out = new PrintWriter(sout, true);
StringBuffer exceptionBuffer =
new StringBuffer(128)
.append("Exception calling ")
.append(offendersName)
.append(": ")
.append(me.getMessage());
out.println(exceptionBuffer.toString());
Exception e = me;
while (e != null) {
e.printStackTrace(out);
if (e instanceof MessagingException) {
e = ((MessagingException)e).getNextException();
} else {
e = null;
}
}
String errorString = sout.toString();
mail.setErrorMessage(errorString);
getLogger().error(errorString);
throw me;
| public void | initialize()
matchers = new ArrayList();
mailets = new ArrayList();
| private java.lang.String | newName(org.apache.mailet.Mail mail)Create a unique new primary key name.
StringBuffer nameBuffer =
new StringBuffer(64)
.append(mail.getName())
.append("-!")
.append(random.nextInt(1048576));
return nameBuffer.toString();
| public void | service(org.apache.mailet.Mail mail)Processes a single mail message through the chain of matchers and mailets.
Calls to this method before setSpool has been called with a non-null argument
will result in an IllegalStateException .
If the matcher/mailet lists have not been closed by a call to the closeProcessorLists
method then a call to this method will result in an IllegalStateException .
The end of the matcher/mailet chain must be a matcher that matches all mails and
a mailet that sets every mail to GHOST status. This is necessary to ensure that
mails are removed from the spool in an orderly fashion. The closeProcessorLists method
ensures this.
if (spool == null) {
throw new IllegalStateException("Attempt to service mail before the spool has been set to a non-null value");
}
if (!listsClosed) {
throw new IllegalStateException("Attempt to service mail before matcher/mailet lists have been closed");
}
if (getLogger().isDebugEnabled()) {
getLogger().debug("Servicing mail: " + mail.getName());
}
// unprocessed is an array of Lists of Mail objects
// the array indicates which matcher/mailet (stage in the linear
// processor) that this Mail needs to be processed.
// e.g., a Mail in unprocessed[0] needs to be
// processed by the first matcher/mailet.
//
// It is a List of Mail objects at each array spot as multiple Mail
// objects could be at the same stage.
//
// Note that every Mail object in this array will either be the
// original Mail object passed in, or a result of this method's
// (and hence this thread's) processing.
List[] unprocessed = new List[matchers.size() + 1];
for (int i = 0; i < unprocessed.length; i++) {
// No need to use synchronization, as this is totally
// local to the method
unprocessed[i] = new LinkedList();
}
//Add the object to the bottom of the list
unprocessed[0].add(mail);
//This is the original state of the message
String originalState = mail.getState();
// The original mail: we should not care to save this mail.
// This should be saved in the spoolmanager.
Mail originalMail = mail;
//We'll use these as temporary variables in the loop
mail = null; // the message we're currently processing
int i = 0; // where in the stage we're looking
while (true) {
// The last element in the unprocessed array has mail messages
// that have completed all stages. We want them to just die,
// so we clear that spot to allow garbage collection of the
// objects.
//
// Please note that the presence of the terminating mailet at the end
// of the chain is critical to the proper operation
// of the LinearProcessor code. If this mailet is not placed
// at the end of the chain with a terminating matcher, there is a
// potential for configuration or implementation errors to
// lead to mails trapped in the spool. This matcher/mailet
// combination is added when the closeProcessorList method
// is called.
unprocessed[unprocessed.length - 1].clear();
//initialize the mail reference we will be searching on
mail = null;
//Scan through all stages, trying to find a message to process
for (i = 0; i < unprocessed.length; i++) {
if (unprocessed[i].size() > 0) {
//Get the first element from the queue, and remove it from there
mail = (Mail)unprocessed[i].remove(0);
break;
}
}
//Check it we found anything
if (mail == null) {
//We found no messages to process... we're done servicing the mail object
return;
}
//Call the matcher and find what recipients match
Collection recipients = null;
Matcher matcher = (Matcher) matchers.get(i);
StringBuffer logMessageBuffer = null;
if (getLogger().isDebugEnabled()) {
logMessageBuffer =
new StringBuffer(128)
.append("Checking ")
.append(mail.getName())
.append(" with ")
.append(matcher);
getLogger().debug(logMessageBuffer.toString());
}
try {
recipients = matcher.match(mail);
if (recipients == null) {
//In case the matcher returned null, create an empty Collection
recipients = new ArrayList(0);
} else if (recipients != mail.getRecipients()) {
//Make sure all the objects are MailAddress objects
verifyMailAddresses(recipients);
}
} catch (MessagingException me) {
// look in the matcher's mailet's init attributes
MailetConfig mailetConfig = ((Mailet) mailets.get(i)).getMailetConfig();
String onMatchException = ((MailetConfigImpl) mailetConfig).getInitAttribute("onMatchException");
if (onMatchException == null) {
onMatchException = Mail.ERROR;
} else {
onMatchException = onMatchException.trim().toLowerCase(Locale.US);
}
if (onMatchException.compareTo("nomatch") == 0) {
//In case the matcher returned null, create an empty Collection
recipients = new ArrayList(0);
} else if (onMatchException.compareTo("matchall") == 0) {
recipients = mail.getRecipients();
// no need to verify addresses
} else {
handleException(me, mail, matcher.getMatcherConfig().getMatcherName(), onMatchException);
}
}
// Split the recipients into two pools. notRecipients will contain the
// recipients on the message that the matcher did not return.
Collection notRecipients;
if (recipients == mail.getRecipients() || recipients.size() == 0) {
notRecipients = new ArrayList(0);
} else {
notRecipients = new ArrayList(mail.getRecipients());
notRecipients.removeAll(recipients);
}
if (recipients.size() == 0) {
//Everything was not a match... store it in the next spot in the array
unprocessed[i + 1].add(mail);
continue;
}
if (notRecipients.size() != 0) {
// There are a mix of recipients and not recipients.
// We need to clone this message, put the notRecipients on the clone
// and store it in the next spot
Mail notMail = new MailImpl(mail,newName(mail));
notMail.setRecipients(notRecipients);
// set the state to the current processor
notMail.setState(originalState);
unprocessed[i + 1].add(notMail);
//We have to set the reduce possible recipients on the old message
mail.setRecipients(recipients);
}
// We have messages that need to process... time to run the mailet.
Mailet mailet = (Mailet) mailets.get(i);
if (getLogger().isDebugEnabled()) {
logMessageBuffer =
new StringBuffer(128)
.append("Servicing ")
.append(mail.getName())
.append(" by ")
.append(mailet.getMailetInfo());
getLogger().debug(logMessageBuffer.toString());
}
try {
mailet.service(mail);
// Make sure all the recipients are still MailAddress objects
verifyMailAddresses(mail.getRecipients());
} catch (MessagingException me) {
MailetConfig mailetConfig = mailet.getMailetConfig();
String onMailetException = ((MailetConfigImpl) mailetConfig).getInitAttribute("onMailetException");
if (onMailetException == null) {
onMailetException = Mail.ERROR;
} else {
onMailetException = onMailetException.trim().toLowerCase(Locale.US);
}
if (onMailetException.compareTo("ignore") == 0) {
// ignore the exception and continue
// this option should not be used if the mail object can be changed by the mailet
verifyMailAddresses(mail.getRecipients());
} else {
handleException(me, mail, mailet.getMailetConfig().getMailetName(), onMailetException);
}
}
// See if the state was changed by the mailet
if (!mail.getState().equals(originalState)) {
//If this message was ghosted, we just want to let it die
if (mail.getState().equals(Mail.GHOST)) {
// let this instance die...
ContainerUtil.dispose(mail);
mail = null;
continue;
}
// This was just set to another state requiring further processing...
// Store this back in the spool and it will get picked up and
// run in that processor
// We store only mails created by the matcher "splitting"
// The original mail will be "stored" by the caller.
if (originalMail != mail) {
spool.store(mail);
ContainerUtil.dispose(mail);
}
mail = null;
continue;
} else {
// Ok, we made it through with the same state... move it to the next
// spot in the array
unprocessed[i + 1].add(mail);
}
}
| public void | setSpool(org.apache.james.services.SpoolRepository spool)Set the spool to be used by this LinearProcessor. // The spool on which this processor is acting
if (spool == null) {
throw new IllegalArgumentException("The spool cannot be null");
}
this.spool = spool;
| private void | verifyMailAddresses(java.util.Collection col)Checks that all objects in this class are of the form MailAddress.
try {
MailAddress addresses[] = (MailAddress[])col.toArray(new MailAddress[0]);
// Why is this here? According to the javadoc for
// java.util.Collection.toArray(Object[]), this should
// never happen. The exception will be thrown.
if (addresses.length != col.size()) {
throw new MailetException("The recipient list contains objects other than MailAddress objects");
}
} catch (ArrayStoreException ase) {
throw new MailetException("The recipient list contains objects other than MailAddress objects");
}
|
|