RemoteDeliverypublic class RemoteDelivery extends org.apache.mailet.GenericMailet implements RunnableReceives a MessageContainer from JamesSpoolManager and takes care of delivery
the message to remote hosts. If for some reason mail can't be delivered
store it in the "outgoing" Repository and set an Alarm. After the next "delayTime" the
Alarm will wake the servlet that will try to send it again. After "maxRetries"
the mail will be considered undeliverable and will be returned to sender.
TO DO (in priority):
1. Support a gateway (a single server where all mail will be delivered) (DONE)
2. Provide better failure messages (DONE)
3. More efficiently handle numerous recipients
4. Migrate to use Phoenix for the delivery threads
You really want to read the JavaMail documentation if you are
working in here, and you will want to view the list of JavaMail
attributes, which are documented here:
http://java.sun.com/products/javamail/1.3/docs/javadocs/com/sun/mail/smtp/package-summary.html
as well as other places. |
Fields Summary |
---|
private static final long | DEFAULT_DELAY_TIME | private static final String | PATTERN_STRING | private static org.apache.oro.text.regex.Pattern | PATTERN | private static final HashMap | MULTIPLIERS | private boolean | isDebugControls certain log messages | private org.apache.james.services.SpoolRepository | outgoing | private long[] | delayTimes | private int | maxRetries | private long | smtpTimeout | private boolean | sendPartial | private int | connectionTimeout | private int | deliveryThreadCount | private Collection | gatewayServer | private String | authUser | private String | authPass | private String | bindAddress | private boolean | isBindUsed | private Collection | deliveryThreads | private volatile boolean | destroyed | private String | bounceProcessor | private org.apache.oro.text.regex.Perl5Matcher | delayTimeMatcher | private MultipleDelayFilter | delayFilter | private Properties | defprops |
Methods Summary |
---|
private void | bounce(org.apache.mailet.Mail mail, javax.mail.MessagingException ex)
StringWriter sout = new StringWriter();
PrintWriter out = new PrintWriter(sout, true);
String machine = "[unknown]";
try {
InetAddress me = InetAddress.getLocalHost();
machine = me.getHostName();
} catch(Exception e){
machine = "[address unknown]";
}
StringBuffer bounceBuffer =
new StringBuffer(128)
.append("Hi. This is the James mail server at ")
.append(machine)
.append(".");
out.println(bounceBuffer.toString());
out.println("I'm afraid I wasn't able to deliver your message to the following addresses.");
out.println("This is a permanent error; I've given up. Sorry it didn't work out. Below");
out.println("I include the list of recipients and the reason why I was unable to deliver");
out.println("your message.");
out.println();
for (Iterator i = mail.getRecipients().iterator(); i.hasNext(); ) {
out.println(i.next());
}
if (ex.getNextException() == null) {
out.println(ex.getMessage().trim());
} else {
Exception ex1 = ex.getNextException();
if (ex1 instanceof SendFailedException) {
out.println("Remote mail server told me: " + ex1.getMessage().trim());
} else if (ex1 instanceof UnknownHostException) {
out.println("Unknown host: " + ex1.getMessage().trim());
out.println("This could be a DNS server error, a typo, or a problem with the recipient's mail server.");
} else if (ex1 instanceof ConnectException) {
//Already formatted as "Connection timed out: connect"
out.println(ex1.getMessage().trim());
} else if (ex1 instanceof SocketException) {
out.println("Socket exception: " + ex1.getMessage().trim());
} else {
out.println(ex1.getMessage().trim());
}
}
out.println();
log("Sending failure message " + mail.getName());
try {
getMailetContext().bounce(mail, sout.toString());
} catch (MessagingException me) {
log("Encountered unexpected messaging exception while bouncing message: " + me.getMessage());
} catch (Exception e) {
log("Encountered unexpected exception while bouncing message: " + e.getMessage());
}
| private int | calcTotalAttempts(java.util.ArrayList list)
int sum = 0;
Iterator i = list.iterator();
while (i.hasNext()) {
Delay delay = (Delay)i.next();
sum += delay.getAttempts();
}
return sum;
| private void | convertTo7Bit(javax.mail.internet.MimePart part)Converts a message to 7 bit.
if (part.isMimeType("multipart/*")) {
MimeMultipart parts = (MimeMultipart) part.getContent();
int count = parts.getCount();
for (int i = 0; i < count; i++) {
convertTo7Bit((MimePart)parts.getBodyPart(i));
}
} else {
if (part.isMimeType("text/*")) {
part.setHeader("Content-Transfer-Encoding", "quoted-printable");
part.addHeader("X-MIME-Autoconverted", "from 8bit to quoted-printable by "+getMailetContext().getServerInfo());
} else {
// if the part doesn't contain text it will be base64 encoded.
part.setHeader("Content-Transfer-Encoding", "base64");
part.addHeader("X-MIME-Autoconverted", "from 8bit to base64 by "+getMailetContext().getServerInfo());
}
}
| private boolean | deliver(org.apache.mailet.Mail mail, javax.mail.Session session)We can assume that the recipients of this message are all going to the same
mail server. We will now rely on the DNS server to do DNS MX record lookup
and try to deliver to the multiple mail servers. If it fails, it should
throw an exception.
Creation date: (2/24/00 11:25:00 PM)
try {
if (isDebug) {
log("Attempting to deliver " + mail.getName());
}
MimeMessage message = mail.getMessage();
//Create an array of the recipients as InternetAddress objects
Collection recipients = mail.getRecipients();
InternetAddress addr[] = new InternetAddress[recipients.size()];
int j = 0;
for (Iterator i = recipients.iterator(); i.hasNext(); j++) {
MailAddress rcpt = (MailAddress)i.next();
addr[j] = rcpt.toInternetAddress();
}
if (addr.length <= 0) {
log("No recipients specified... not sure how this could have happened.");
return true;
}
//Figure out which servers to try to send to. This collection
// will hold all the possible target servers
Iterator targetServers = null;
if (gatewayServer == null) {
MailAddress rcpt = (MailAddress) recipients.iterator().next();
String host = rcpt.getHost();
//Lookup the possible targets
targetServers = getMailetContext().getSMTPHostAddresses(host);
if (!targetServers.hasNext()) {
log("No mail server found for: " + host);
StringBuffer exceptionBuffer =
new StringBuffer(128)
.append("There are no DNS entries for the hostname ")
.append(host)
.append(". I cannot determine where to send this message.");
return failMessage(mail, new MessagingException(exceptionBuffer.toString()), false);
}
} else {
targetServers = getGatewaySMTPHostAddresses(gatewayServer);
}
MessagingException lastError = null;
while ( targetServers.hasNext()) {
try {
HostAddress outgoingMailServer = (HostAddress) targetServers.next();
StringBuffer logMessageBuffer =
new StringBuffer(256)
.append("Attempting delivery of ")
.append(mail.getName())
.append(" to host ")
.append(outgoingMailServer.getHostName())
.append(" at ")
.append(outgoingMailServer.getHost())
.append(" for addresses ")
.append(Arrays.asList(addr));
log(logMessageBuffer.toString());
Properties props = session.getProperties();
if (mail.getSender() == null) {
props.put("mail.smtp.from", "<>");
} else {
String sender = mail.getSender().toString();
props.put("mail.smtp.from", sender);
}
//Many of these properties are only in later JavaMail versions
//"mail.smtp.ehlo" //default true
//"mail.smtp.auth" //default false
//"mail.smtp.dsn.ret" //default to nothing... appended as RET= after MAIL FROM line.
//"mail.smtp.dsn.notify" //default to nothing...appended as NOTIFY= after RCPT TO line.
Transport transport = null;
try {
transport = session.getTransport(outgoingMailServer);
try {
if (authUser != null) {
transport.connect(outgoingMailServer.getHostName(), authUser, authPass);
} else {
transport.connect();
}
} catch (MessagingException me) {
// Any error on connect should cause the mailet to attempt
// to connect to the next SMTP server associated with this
// MX record. Just log the exception. We'll worry about
// failing the message at the end of the loop.
log(me.getMessage());
continue;
}
// if the transport is a SMTPTransport (from sun) some
// performance enhancement can be done.
if (transport instanceof SMTPTransport) {
SMTPTransport smtpTransport = (SMTPTransport) transport;
// if the message is alredy 8bit or binary and the
// server doesn't support the 8bit extension it has
// to be converted to 7bit. Javamail api doesn't perform
// that conversion, but it is required to be a
// rfc-compliant smtp server.
// Temporarily disabled. See JAMES-638
/*
if (!smtpTransport.supportsExtension("8BITMIME")) {
try {
convertTo7Bit(message);
} catch (IOException e) {
// An error has occured during the 7bit conversion.
// The error is logged and the message is sent anyway.
log("Error during the conversion to 7 bit.", e);
}
}
*/
/*
* Workaround for a javamail 1.3.2 bug: if
* a message is sent without encoding information
* and the 8bit allow property is set an exception
* is trown during the mail delivery.
*/
try {
setEncodingIfMissing(message);
} catch (IOException e) {
log("Error while adding encoding information to the message", e);
}
} else {
// If the transport is not the one
// developed by Sun we are not sure of how it
// handles the 8 bit mime stuff,
// so I convert the message to 7bit.
try {
convertTo7Bit(message);
} catch (IOException e) {
log("Error during the conversion to 7 bit.", e);
}
}
transport.sendMessage(message, addr);
} finally {
if (transport != null) {
transport.close();
transport = null;
}
}
logMessageBuffer =
new StringBuffer(256)
.append("Mail (")
.append(mail.getName())
.append(") sent successfully to ")
.append(outgoingMailServer.getHostName())
.append(" at ")
.append(outgoingMailServer.getHost())
.append(" for ")
.append(mail.getRecipients());
log(logMessageBuffer.toString());
return true;
} catch (SendFailedException sfe) {
logSendFailedException(sfe);
if (sfe.getValidSentAddresses() != null) {
Address[] validSent = sfe.getValidSentAddresses();
if (validSent.length > 0) {
StringBuffer logMessageBuffer =
new StringBuffer(256)
.append("Mail (")
.append(mail.getName())
.append(") sent successfully for ")
.append(Arrays.asList(validSent));
log(logMessageBuffer.toString());
}
}
/* SMTPSendFailedException introduced in JavaMail 1.3.2, and provides detailed protocol reply code for the operation */
if (sfe instanceof SMTPSendFailedException) {
SMTPSendFailedException ssfe = (SMTPSendFailedException) sfe;
// if 5xx, terminate this delivery attempt by re-throwing the exception.
if (ssfe.getReturnCode() >= 500 && ssfe.getReturnCode() <= 599) throw sfe;
}
if (sfe.getValidUnsentAddresses() != null
&& sfe.getValidUnsentAddresses().length > 0) {
if (isDebug) log("Send failed, " + sfe.getValidUnsentAddresses().length + " valid addresses remain, continuing with any other servers");
lastError = sfe;
continue;
} else {
// There are no valid addresses left to send, so rethrow
throw sfe;
}
} catch (MessagingException me) {
//MessagingException are horribly difficult to figure out what actually happened.
StringBuffer exceptionBuffer =
new StringBuffer(256)
.append("Exception delivering message (")
.append(mail.getName())
.append(") - ")
.append(me.getMessage());
log(exceptionBuffer.toString());
if ((me.getNextException() != null) &&
(me.getNextException() instanceof java.io.IOException)) {
//This is more than likely a temporary failure
// If it's an IO exception with no nested exception, it's probably
// some socket or weird I/O related problem.
lastError = me;
continue;
}
// This was not a connection or I/O error particular to one
// SMTP server of an MX set. Instead, it is almost certainly
// a protocol level error. In this case we assume that this
// is an error we'd encounter with any of the SMTP servers
// associated with this MX record, and we pass the exception
// to the code in the outer block that determines its severity.
throw me;
}
} // end while
//If we encountered an exception while looping through,
//throw the last MessagingException we caught. We only
//do this if we were unable to send the message to any
//server. If sending eventually succeeded, we exit
//deliver() though the return at the end of the try
//block.
if (lastError != null) {
throw lastError;
}
} catch (SendFailedException sfe) {
logSendFailedException(sfe);
Collection recipients = mail.getRecipients();
boolean deleteMessage = false;
/*
* If you send a message that has multiple invalid
* addresses, you'll get a top-level SendFailedException
* that that has the valid, valid-unsent, and invalid
* address lists, with all of the server response messages
* will be contained within the nested exceptions. [Note:
* the content of the nested exceptions is implementation
* dependent.]
*
* sfe.getInvalidAddresses() should be considered permanent.
* sfe.getValidUnsentAddresses() should be considered temporary.
*
* JavaMail v1.3 properly populates those collections based
* upon the 4xx and 5xx response codes to RCPT TO. Some
* servers, such as Yahoo! don't respond to the RCPT TO,
* and provide a 5xx reply after DATA. In that case, we
* will pick up the failure from SMTPSendFailedException.
*
*/
/* SMTPSendFailedException introduced in JavaMail 1.3.2, and provides detailed protocol reply code for the operation */
if (sfe instanceof SMTPSendFailedException) {
// If we got an SMTPSendFailedException, use its RetCode to determine default permanent/temporary failure
SMTPSendFailedException ssfe = (SMTPSendFailedException) sfe;
deleteMessage = (ssfe.getReturnCode() >= 500 && ssfe.getReturnCode() <= 599);
} else {
// Sometimes we'll get a normal SendFailedException with nested SMTPAddressFailedException, so use the latter RetCode
MessagingException me = sfe;
Exception ne;
while ((ne = me.getNextException()) != null && ne instanceof MessagingException) {
me = (MessagingException)ne;
if (me instanceof SMTPAddressFailedException) {
SMTPAddressFailedException ssfe = (SMTPAddressFailedException)me;
deleteMessage = (ssfe.getReturnCode() >= 500 && ssfe.getReturnCode() <= 599);
}
}
}
// log the original set of intended recipients
if (isDebug) log("Recipients: " + recipients);
if (sfe.getInvalidAddresses() != null) {
Address[] address = sfe.getInvalidAddresses();
if (address.length > 0) {
recipients.clear();
for (int i = 0; i < address.length; i++) {
try {
recipients.add(new MailAddress(address[i].toString()));
} catch (ParseException pe) {
// this should never happen ... we should have
// caught malformed addresses long before we
// got to this code.
log("Can't parse invalid address: " + pe.getMessage());
}
}
if (isDebug) log("Invalid recipients: " + recipients);
deleteMessage = failMessage(mail, sfe, true);
}
}
if (sfe.getValidUnsentAddresses() != null) {
Address[] address = sfe.getValidUnsentAddresses();
if (address.length > 0) {
recipients.clear();
for (int i = 0; i < address.length; i++) {
try {
recipients.add(new MailAddress(address[i].toString()));
} catch (ParseException pe) {
// this should never happen ... we should have
// caught malformed addresses long before we
// got to this code.
log("Can't parse unsent address: " + pe.getMessage());
}
}
if (isDebug) log("Unsent recipients: " + recipients);
if (sfe instanceof SMTPSendFailedException) {
SMTPSendFailedException ssfe = (SMTPSendFailedException) sfe;
deleteMessage = failMessage(mail, sfe, ssfe.getReturnCode() >= 500 && ssfe.getReturnCode() <= 599);
} else {
deleteMessage = failMessage(mail, sfe, false);
}
}
}
return deleteMessage;
} catch (MessagingException ex) {
// We should do a better job checking this... if the failure is a general
// connect exception, this is less descriptive than more specific SMTP command
// failure... have to lookup and see what are the various Exception
// possibilities
// Unable to deliver message after numerous tries... fail accordingly
// We check whether this is a 5xx error message, which
// indicates a permanent failure (like account doesn't exist
// or mailbox is full or domain is setup wrong).
// We fail permanently if this was a 5xx error
return failMessage(mail, ex, ('5" == ex.getMessage().charAt(0)));
}
/* If we get here, we've exhausted the loop of servers without
* sending the message or throwing an exception. One case
* where this might happen is if we get a MessagingException on
* each transport.connect(), e.g., if there is only one server
* and we get a connect exception.
*/
return failMessage(mail, new MessagingException("No mail server(s) available at this time."), false);
| public synchronized void | destroy()
//Mark flag so threads from this mailet stop themselves
destroyed = true;
//Wake up all threads from waiting for an accept
for (Iterator i = deliveryThreads.iterator(); i.hasNext(); ) {
Thread t = (Thread)i.next();
t.interrupt();
}
notifyAll();
| private long[] | expandDelays(java.util.ArrayList list)This method expands an ArrayList containing Delay objects into an array holding the
only delaytime in the order.
So if the list has 2 Delay objects the first having attempts=2 and delaytime 4000
the second having attempts=1 and delaytime=300000 will be expanded into this array:
long[0] = 4000
long[1] = 4000
long[2] = 300000
long[] delays = new long [calcTotalAttempts(list)];
Iterator i = list.iterator();
int idx = 0;
while (i.hasNext()) {
Delay delay = (Delay)i.next();
for (int j=0; j<delay.getAttempts(); j++) {
delays[idx++]= delay.getDelayTime();
}
}
return delays;
| private boolean | failMessage(org.apache.mailet.Mail mail, javax.mail.MessagingException ex, boolean permanent)Insert the method's description here.
Creation date: (2/25/00 1:14:18 AM)
StringWriter sout = new StringWriter();
PrintWriter out = new PrintWriter(sout, true);
if (permanent) {
out.print("Permanent");
} else {
out.print("Temporary");
}
StringBuffer logBuffer =
new StringBuffer(64)
.append(" exception delivering mail (")
.append(mail.getName())
.append(": ");
out.print(logBuffer.toString());
if (isDebug) ex.printStackTrace(out);
log(sout.toString());
if (!permanent) {
if (!mail.getState().equals(Mail.ERROR)) {
mail.setState(Mail.ERROR);
mail.setErrorMessage("0");
mail.setLastUpdated(new Date());
}
int retries = Integer.parseInt(mail.getErrorMessage());
if (retries < maxRetries) {
logBuffer =
new StringBuffer(128)
.append("Storing message ")
.append(mail.getName())
.append(" into outgoing after ")
.append(retries)
.append(" retries");
log(logBuffer.toString());
++retries;
mail.setErrorMessage(retries + "");
mail.setLastUpdated(new Date());
return false;
} else {
logBuffer =
new StringBuffer(128)
.append("Bouncing message ")
.append(mail.getName())
.append(" after ")
.append(retries)
.append(" retries");
log(logBuffer.toString());
}
}
if (mail.getSender() == null) {
log("Null Sender: no bounce will be generated for " + mail.getName());
return true;
}
if (bounceProcessor != null) {
// do the new DSN bounce
// setting attributes for DSN mailet
mail.setAttribute("delivery-error", ex);
mail.setState(bounceProcessor);
// re-insert the mail into the spool for getting it passed to the dsn-processor
MailetContext mc = getMailetContext();
try {
mc.sendMail(mail);
} catch (MessagingException e) {
// we shouldn't get an exception, because the mail was already processed
log("Exception re-inserting failed mail: ", e);
}
} else {
// do an old style bounce
bounce(mail, ex);
}
return true;
| private java.util.Iterator | getGatewaySMTPHostAddresses(java.util.Collection gatewayServers)
return new Iterator() {
private Iterator gateways = gatewayServers.iterator();
private Iterator addresses = null;
public boolean hasNext() {
/* Make sure that when next() is called, that we can
* provide a HostAddress. This means that we need to
* have an inner iterator, and verify that it has
* addresses. We could, for example, run into a
* situation where the next gateway didn't have any
* valid addresses.
*/
if (!hasNextAddress() && gateways.hasNext()) {
do {
String server = (String) gateways.next();
String port = "25";
int idx = server.indexOf(':");
if ( idx > 0) {
port = server.substring(idx+1);
server = server.substring(0,idx);
}
final String nextGateway = server;
final String nextGatewayPort = port;
try {
final InetAddress[] ips = org.apache.james.dnsserver.DNSServer.getAllByName(nextGateway);
addresses = new Iterator() {
private InetAddress[] ipAddresses = ips;
int i = 0;
public boolean hasNext() {
return i < ipAddresses.length;
}
public Object next() {
return new org.apache.mailet.HostAddress(nextGateway, "smtp://" + (ipAddresses[i++]).getHostAddress() + ":" + nextGatewayPort);
}
public void remove() {
throw new UnsupportedOperationException ("remove not supported by this iterator");
}
};
}
catch (java.net.UnknownHostException uhe) {
log("Unknown gateway host: " + uhe.getMessage().trim());
log("This could be a DNS server error or configuration error.");
}
} while (!hasNextAddress() && gateways.hasNext());
}
return hasNextAddress();
}
private boolean hasNextAddress() {
return addresses != null && addresses.hasNext();
}
public Object next() {
return (addresses != null) ? addresses.next() : null;
}
public void remove() {
throw new UnsupportedOperationException ("remove not supported by this iterator");
}
};
| public java.lang.String | getMailetInfo()
return "RemoteDelivery Mailet";
| private long | getNextDelay(int retry_count)This method returns, given a retry-count, the next delay time to use.
if (retry_count > delayTimes.length) {
return DEFAULT_DELAY_TIME;
}
return delayTimes[retry_count-1];
| public void | init()Initialize the mailet // default properties for the javamail Session
isDebug = (getInitParameter("debug") == null) ? false : new Boolean(getInitParameter("debug")).booleanValue();
ArrayList delay_times_list = new ArrayList();
try {
if (getInitParameter("delayTime") != null) {
delayTimeMatcher = new Perl5Matcher();
String delay_times = getInitParameter("delayTime");
//split on comma's
StringTokenizer st = new StringTokenizer (delay_times,",");
while (st.hasMoreTokens()) {
String delay_time = st.nextToken();
delay_times_list.add (new Delay(delay_time));
}
} else {
//use default delayTime.
delay_times_list.add (new Delay());
}
} catch (Exception e) {
log("Invalid delayTime setting: " + getInitParameter("delayTime"));
}
try {
if (getInitParameter("maxRetries") != null) {
maxRetries = Integer.parseInt(getInitParameter("maxRetries"));
}
//check consistency with delay_times_list attempts
int total_attempts = calcTotalAttempts (delay_times_list);
if (total_attempts > maxRetries) {
log("Total number of delayTime attempts exceeds maxRetries specified. Increasing maxRetries from "+maxRetries+" to "+total_attempts);
maxRetries = total_attempts;
} else {
int extra = maxRetries - total_attempts;
if (extra != 0) {
log("maxRetries is larger than total number of attempts specified. Increasing last delayTime with "+extra+" attempts ");
if (delay_times_list.size() != 0) {
Delay delay = (Delay)delay_times_list.get (delay_times_list.size()-1); //last Delay
delay.setAttempts (delay.getAttempts()+extra);
log("Delay of "+delay.getDelayTime()+" msecs is now attempted: "+delay.getAttempts()+" times");
} else {
log ("NO, delaytimes cannot continue");
}
}
}
delayTimes = expandDelays (delay_times_list);
} catch (Exception e) {
log("Invalid maxRetries setting: " + getInitParameter("maxRetries"));
}
try {
if (getInitParameter("timeout") != null) {
smtpTimeout = Integer.parseInt(getInitParameter("timeout"));
}
} catch (Exception e) {
log("Invalid timeout setting: " + getInitParameter("timeout"));
}
try {
if (getInitParameter("connectiontimeout") != null) {
connectionTimeout = Integer.parseInt(getInitParameter("connectiontimeout"));
}
} catch (Exception e) {
log("Invalid timeout setting: " + getInitParameter("timeout"));
}
sendPartial = (getInitParameter("sendpartial") == null) ? false : new Boolean(getInitParameter("sendpartial")).booleanValue();
bounceProcessor = getInitParameter("bounceProcessor");
String gateway = getInitParameter("gateway");
String gatewayPort = getInitParameter("gatewayPort");
if (gateway != null) {
gatewayServer = new ArrayList();
StringTokenizer st = new StringTokenizer(gateway, ",") ;
while (st.hasMoreTokens()) {
String server = st.nextToken().trim() ;
if (server.indexOf(':") < 0 && gatewayPort != null) {
server += ":";
server += gatewayPort;
}
if (isDebug) log("Adding SMTP gateway: " + server) ;
gatewayServer.add(server);
}
authUser = getInitParameter("gatewayusername");
authPass = getInitParameter("gatewayPassword");
}
ServiceManager compMgr = (ServiceManager)getMailetContext().getAttribute(Constants.AVALON_COMPONENT_MANAGER);
String outgoingPath = getInitParameter("outgoing");
if (outgoingPath == null) {
outgoingPath = "file:///../var/mail/outgoing";
}
try {
// Instantiate the a MailRepository for outgoing mails
Store mailstore = (Store) compMgr.lookup(Store.ROLE);
DefaultConfiguration spoolConf
= new DefaultConfiguration("repository", "generated:RemoteDelivery.java");
spoolConf.setAttribute("destinationURL", outgoingPath);
spoolConf.setAttribute("type", "SPOOL");
outgoing = (SpoolRepository) mailstore.select(spoolConf);
} catch (ServiceException cnfe) {
log("Failed to retrieve Store component:" + cnfe.getMessage());
} catch (Exception e) {
log("Failed to retrieve Store component:" + e.getMessage());
}
//Start up a number of threads
try {
deliveryThreadCount = Integer.parseInt(getInitParameter("deliveryThreads"));
} catch (Exception e) {
}
for (int i = 0; i < deliveryThreadCount; i++) {
StringBuffer nameBuffer =
new StringBuffer(32)
.append("Remote delivery thread (")
.append(i)
.append(")");
Thread t = new Thread(this, nameBuffer.toString());
t.start();
deliveryThreads.add(t);
}
bindAddress = getInitParameter("bind");
isBindUsed = bindAddress != null;
try {
if (isBindUsed) RemoteDeliverySocketFactory.setBindAdress(bindAddress);
} catch (UnknownHostException e) {
log("Invalid bind setting (" + bindAddress + "): " + e.toString());
}
Iterator i = getInitParameterNames();
while (i.hasNext()) {
String name = (String) i.next();
if (name.startsWith("mail.")) {
defprops.put(name,getInitParameter(name));
}
}
| private void | logSendFailedException(javax.mail.SendFailedException sfe)
if (isDebug) {
MessagingException me = sfe;
if (me instanceof SMTPSendFailedException) {
SMTPSendFailedException ssfe = (SMTPSendFailedException)me;
log("SMTP SEND FAILED:");
log(ssfe.toString());
log(" Command: " + ssfe.getCommand());
log(" RetCode: " + ssfe.getReturnCode());
log(" Response: " + ssfe.getMessage());
} else {
log("Send failed: " + me.toString());
}
Exception ne;
while ((ne = me.getNextException()) != null && ne instanceof MessagingException) {
me = (MessagingException)ne;
if (me instanceof SMTPAddressFailedException) {
SMTPAddressFailedException e = (SMTPAddressFailedException)me;
log("ADDRESS FAILED:");
log(e.toString());
log(" Address: " + e.getAddress());
log(" Command: " + e.getCommand());
log(" RetCode: " + e.getReturnCode());
log(" Response: " + e.getMessage());
} else if (me instanceof SMTPAddressSucceededException) {
log("ADDRESS SUCCEEDED:");
SMTPAddressSucceededException e = (SMTPAddressSucceededException)me;
log(e.toString());
log(" Address: " + e.getAddress());
log(" Command: " + e.getCommand());
log(" RetCode: " + e.getReturnCode());
log(" Response: " + e.getMessage());
}
}
}
| public void | run()Handles checking the outgoing spool for new mail and delivering them if
there are any
/* TODO: CHANGE ME!!! The problem is that we need to wait for James to
* finish initializing. We expect the HELLO_NAME to be put into
* the MailetContext, but in the current configuration we get
* started before the SMTP Server, which establishes the value.
* Since there is no contractual guarantee that there will be a
* HELLO_NAME value, we can't just wait for it. As a temporary
* measure, I'm inserting this philosophically unsatisfactory
* fix.
*/
long stop = System.currentTimeMillis() + 60000;
while ((getMailetContext().getAttribute(Constants.HELLO_NAME) == null)
&& stop > System.currentTimeMillis()) {
try {
Thread.sleep(1000);
} catch (Exception ignored) {} // wait for James to finish initializing
}
//Checks the pool and delivers a mail message
Properties props = new Properties();
//Not needed for production environment
props.put("mail.debug", "false");
// Reactivated: javamail 1.3.2 should no more have problems with "250 OK"
// messages (WAS "false": Prevents problems encountered with 250 OK Messages)
props.put("mail.smtp.ehlo", "true");
// By setting this property to true the transport is allowed to
// send 8 bit data to the server (if it supports the 8bitmime extension).
// 2006/03/01 reverted to false because of a javamail bug converting to 8bit
// messages created by an inputstream.
props.setProperty("mail.smtp.allow8bitmime", "false");
//Sets timeout on going connections
props.put("mail.smtp.timeout", smtpTimeout + "");
props.put("mail.smtp.connectiontimeout", connectionTimeout + "");
props.put("mail.smtp.sendpartial",String.valueOf(sendPartial));
//Set the hostname we'll use as this server
if (getMailetContext().getAttribute(Constants.HELLO_NAME) != null) {
props.put("mail.smtp.localhost", getMailetContext().getAttribute(Constants.HELLO_NAME));
} else {
String defaultDomain = (String) getMailetContext().getAttribute(Constants.DEFAULT_DOMAIN);
if (defaultDomain != null) {
props.put("mail.smtp.localhost", defaultDomain);
}
}
if (isBindUsed) {
// undocumented JavaMail 1.2 feature, smtp transport will use
// our socket factory, which will also set the local address
props.put("mail.smtp.socketFactory.class",
"org.apache.james.transport.mailets.RemoteDeliverySocketFactory");
// Don't fallback to the standard socket factory on error, do throw an exception
props.put("mail.smtp.socketFactory.fallback", "false");
}
if (authUser != null) {
props.put("mail.smtp.auth","true");
}
props.putAll(defprops);
Session session = Session.getInstance(props, null);
try {
while (!Thread.interrupted() && !destroyed) {
try {
Mail mail = (Mail)outgoing.accept(delayFilter);
String key = mail.getName();
try {
if (isDebug) {
StringBuffer logMessageBuffer =
new StringBuffer(128)
.append(Thread.currentThread().getName())
.append(" will process mail ")
.append(key);
log(logMessageBuffer.toString());
}
if (deliver(mail, session)) {
//Message was successfully delivered/fully failed... delete it
ContainerUtil.dispose(mail);
outgoing.remove(key);
} else {
//Something happened that will delay delivery. Store any updates
outgoing.store(mail);
ContainerUtil.dispose(mail);
// This is an update, we have to unlock and notify or this mail
// is kept locked by this thread
outgoing.unlock(key);
// We do not notify because we updated an already existing mail
// and we are now free to handle more mails.
// Furthermore this mail should not be processed now because we
// have a retry time scheduling.
}
//Clear the object handle to make sure it recycles this object.
mail = null;
} catch (Exception e) {
// Prevent unexpected exceptions from causing looping by removing
// message from outgoing.
// DO NOT CHNANGE THIS to catch Error! For example, if there were an OutOfMemory condition
// caused because something else in the server was abusing memory, we would not want to
// start purging the outgoing spool!
ContainerUtil.dispose(mail);
outgoing.remove(key);
throw e;
}
} catch (Throwable e) {
if (!destroyed) log("Exception caught in RemoteDelivery.run()", e);
}
}
} finally {
// Restore the thread state to non-interrupted.
Thread.interrupted();
}
| public void | service(org.apache.mailet.Mail mail)For this message, we take the list of recipients, organize these into distinct
servers, and duplicate the message for each of these servers, and then call
the deliver (messagecontainer) method for each server-specific
messagecontainer ... that will handle storing it in the outgoing queue if needed.
// Do I want to give the internal key, or the message's Message ID
if (isDebug) {
log("Remotely delivering mail " + mail.getName());
}
Collection recipients = mail.getRecipients();
if (gatewayServer == null) {
// Must first organize the recipients into distinct servers (name made case insensitive)
Hashtable targets = new Hashtable();
for (Iterator i = recipients.iterator(); i.hasNext();) {
MailAddress target = (MailAddress)i.next();
String targetServer = target.getHost().toLowerCase(Locale.US);
Collection temp = (Collection)targets.get(targetServer);
if (temp == null) {
temp = new ArrayList();
targets.put(targetServer, temp);
}
temp.add(target);
}
//We have the recipients organized into distinct servers... put them into the
//delivery store organized like this... this is ultra inefficient I think...
// Store the new message containers, organized by server, in the outgoing mail repository
String name = mail.getName();
for (Iterator i = targets.keySet().iterator(); i.hasNext(); ) {
String host = (String) i.next();
Collection rec = (Collection) targets.get(host);
if (isDebug) {
StringBuffer logMessageBuffer =
new StringBuffer(128)
.append("Sending mail to ")
.append(rec)
.append(" on host ")
.append(host);
log(logMessageBuffer.toString());
}
mail.setRecipients(rec);
StringBuffer nameBuffer =
new StringBuffer(128)
.append(name)
.append("-to-")
.append(host);
mail.setName(nameBuffer.toString());
outgoing.store(mail);
//Set it to try to deliver (in a separate thread) immediately (triggered by storage)
}
} else {
// Store the mail unaltered for processing by the gateway server(s)
if (isDebug) {
StringBuffer logMessageBuffer =
new StringBuffer(128)
.append("Sending mail to ")
.append(mail.getRecipients())
.append(" via ")
.append(gatewayServer);
log(logMessageBuffer.toString());
}
//Set it to try to deliver (in a separate thread) immediately (triggered by storage)
outgoing.store(mail);
}
mail.setState(Mail.GHOST);
| private void | setEncodingIfMissing(javax.mail.internet.MimePart part)Adds an encoding information to each text mime part. This is a workaround
for a javamail 1.3.2 bug: if a message is sent without encoding
information a null pointer exception is thrown during the message
delivery.
if (part.isMimeType("text/*")) {
String enc = part.getEncoding();
if (enc == null) part.setHeader("Content-Transfer-Encoding", "7bit");
} else if (part.isMimeType("multipart/*")) {
Object content = part.getContent();
if (content instanceof MimeMultipart) {
MimeMultipart parts = (MimeMultipart) content;
int count = parts.getCount();
for (int i = 0; i < count; i++) {
setEncodingIfMissing((MimePart)parts.getBodyPart(i));
}
}
}
|
|