JAMES-1877 Extract DeliveryRunnable from RemoteDelivery
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/4a5a4ba6 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/4a5a4ba6 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/4a5a4ba6 Branch: refs/heads/master Commit: 4a5a4ba660ebb8e9a6f1e2446a65527e1fa8fd37 Parents: 69f66c7 Author: Benoit Tellier <btell...@linagora.com> Authored: Wed Nov 30 19:30:37 2016 +0700 Committer: Benoit Tellier <btell...@linagora.com> Committed: Tue Jan 10 15:04:32 2017 +0700 ---------------------------------------------------------------------- .../james/transport/mailets/RemoteDelivery.java | 931 +----------------- .../remoteDelivery/DeliveryRunnable.java | 963 +++++++++++++++++++ .../remoteDelivery/VolatileIsDestroyed.java | 36 + .../remoteDelivery/VolatileIsDestroyedTest.java | 41 + 4 files changed, 1055 insertions(+), 916 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/4a5a4ba6/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/RemoteDelivery.java ---------------------------------------------------------------------- diff --git a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/RemoteDelivery.java b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/RemoteDelivery.java index c4c18d2..e7db2c8 100644 --- a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/RemoteDelivery.java +++ b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/RemoteDelivery.java @@ -19,61 +19,34 @@ package org.apache.james.transport.mailets; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.ConnectException; -import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Date; import java.util.Hashtable; -import java.util.Iterator; import java.util.Locale; import java.util.Map; -import java.util.Properties; import java.util.Vector; -import java.util.concurrent.TimeUnit; import javax.inject.Inject; -import javax.mail.Address; import javax.mail.MessagingException; -import javax.mail.SendFailedException; -import javax.mail.Session; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMultipart; -import javax.mail.internet.MimePart; -import javax.mail.internet.ParseException; import org.apache.james.dnsservice.api.DNSService; -import org.apache.james.dnsservice.api.TemporaryResolutionException; -import org.apache.james.dnsservice.library.MXHostAddressIterator; import org.apache.james.domainlist.api.DomainList; -import org.apache.james.lifecycle.api.LifecycleUtil; import org.apache.james.metrics.api.Metric; import org.apache.james.metrics.api.MetricFactory; import org.apache.james.queue.api.MailPrioritySupport; import org.apache.james.queue.api.MailQueue; import org.apache.james.queue.api.MailQueue.MailQueueException; -import org.apache.james.queue.api.MailQueue.MailQueueItem; import org.apache.james.queue.api.MailQueueFactory; -import org.apache.james.transport.mailets.remoteDelivery.Delay; +import org.apache.james.transport.mailets.remoteDelivery.DeliveryRunnable; import org.apache.james.transport.mailets.remoteDelivery.RemoteDeliveryConfiguration; import org.apache.james.transport.mailets.remoteDelivery.RemoteDeliverySocketFactory; -import org.apache.mailet.HostAddress; +import org.apache.james.transport.mailets.remoteDelivery.VolatileIsDestroyed; import org.apache.mailet.Mail; import org.apache.mailet.MailAddress; -import org.apache.mailet.MailetContext; import org.apache.mailet.base.GenericMailet; import org.slf4j.Logger; -import com.sun.mail.smtp.SMTPTransport; - /** * <p>The RemoteDelivery mailet delivers messages to a remote SMTP server able to deliver or forward messages to their final * destination. @@ -144,8 +117,7 @@ import com.sun.mail.smtp.SMTPTransport; * <li><b>debug</b> (optional) - a Boolean (true/false) indicating whether debugging is on. Default is false.</li> * </ul> */ -@SuppressWarnings("deprecation") -public class RemoteDelivery extends GenericMailet implements Runnable { +public class RemoteDelivery extends GenericMailet { private static final String OUTGOING_MAILS = "outgoingMails"; @@ -154,16 +126,12 @@ public class RemoteDelivery extends GenericMailet implements Runnable { private final MailQueueFactory queueFactory; private final Metric outgoingMailsMetric; private final Collection<Thread> workersThreads; + private final VolatileIsDestroyed volatileIsDestroyed; private MailQueue queue; private Logger logger; private RemoteDeliveryConfiguration configuration; - /** - * Flag used by 'run' method to end itself. - */ - private volatile boolean destroyed = false; - @Inject public RemoteDelivery(DNSService dnsServer, DomainList domainList, MailQueueFactory queueFactory, MetricFactory metricFactory) { this.dnsServer = dnsServer; @@ -171,6 +139,7 @@ public class RemoteDelivery extends GenericMailet implements Runnable { this.queueFactory = queueFactory; this.outgoingMailsMetric = metricFactory.generate(OUTGOING_MAILS); this.workersThreads = new Vector<Thread>(); + this.volatileIsDestroyed = new VolatileIsDestroyed(); } /** @@ -195,25 +164,20 @@ public class RemoteDelivery extends GenericMailet implements Runnable { private void initDeliveryThreads() { for (int a = 0; a < configuration.getWorkersThreadCount(); a++) { String threadName = "Remote delivery thread (" + a + ")"; - Thread t = new Thread(this, threadName); + Thread t = new Thread( + new DeliveryRunnable(queue, + configuration, + dnsServer, + outgoingMailsMetric, + logger, + getMailetContext(), + volatileIsDestroyed), + threadName); t.start(); workersThreads.add(t); } } - /** - * This method returns, given a retry-count, the next delay time to use. - * - * @param retry_count the current retry_count. - * @return the next delay time to use, given the retry count - */ - private long getNextDelay(int retry_count) { - if (retry_count > configuration.getDelayTimes().size()) { - return Delay.DEFAULT_DELAY_TIME; - } - return configuration.getDelayTimes().get(retry_count - 1); - } - @Override public String getMailetInfo() { return "RemoteDelivery Mailet"; @@ -302,9 +266,7 @@ public class RemoteDelivery extends GenericMailet implements Runnable { */ @Override public synchronized void destroy() { - // Mark flag so threads from this Mailet stop themselves - destroyed = true; - + volatileIsDestroyed.markAsDestroyed(); // Wake up all threads from waiting for an accept for (Thread t : workersThreads) { t.interrupt(); @@ -312,867 +274,4 @@ public class RemoteDelivery extends GenericMailet implements Runnable { notifyAll(); } - /** - * Handles checking the outgoing spool for new mail and delivering them if - * there are any - */ - @Override - public void run() { - final Session session = obtainSession(configuration.createFinalJavaxProperties()); - try { - while (!Thread.interrupted() && !destroyed) { - try { - // Get the 'mail' object that is ready for deliverying. If - // no - // message is - // ready, the 'accept' will block until message is ready. - // The amount - // of time to block is determined by the 'getWaitTime' - // method of the - // MultipleDelayFilter. - MailQueueItem queueItem = queue.deQueue(); - Mail mail = queueItem.getMail(); - - String key = mail.getName(); - - try { - if (configuration.isDebug()) { - String message = Thread.currentThread().getName() + " will process mail " + key; - log(message); - } - - // Deliver message - if (deliver(mail, session)) { - // Message was successfully delivered/fully - // failed... - // delete it - LifecycleUtil.dispose(mail); - // workRepository.remove(key); - } else { - // Something happened that will delay delivery. - // Store it back in the retry repository. - // workRepository.store(mail); - int retries = 0; - try { - retries = Integer.parseInt(mail.getErrorMessage()); - } catch (NumberFormatException e) { - // Something strange was happen with the - // errorMessage.. - } - - long delay = getNextDelay(retries); - - if (configuration.isUsePriority()) { - // Use lowest priority for retries. See JAMES-1311 - mail.setAttribute(MailPrioritySupport.MAIL_PRIORITY, MailPrioritySupport.LOW_PRIORITY); - } - queue.enQueue(mail, delay, TimeUnit.MILLISECONDS); - LifecycleUtil.dispose(mail); - - // This is an update, so we have to unlock and - // notify or this mail is kept locked by this - // thread. - // workRepository.unlock(key); - - // Note: 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; - queueItem.done(true); - } catch (Exception e) { - // Prevent unexpected exceptions from causing looping by - // removing message from outgoing. - // DO NOT CHANGE 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 retrying spool! - log("Exception caught in RemoteDelivery.run()", e); - LifecycleUtil.dispose(mail); - // workRepository.remove(key); - queueItem.done(false); - throw new MailQueueException("Unable to perform dequeue", e); - } - - } catch (Throwable e) { - if (!destroyed) { - log("Exception caught in RemoteDelivery.run()", e); - } - } - } - } finally { - // Restore the thread state to non-interrupted. - Thread.interrupted(); - } - } - - /** - * 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. - * - * @param mail org.apache.james.core.MailImpl - * @param session javax.mail.Session - * @return boolean Whether the delivery was successful and the message can - * be deleted - */ - private boolean deliver(Mail mail, Session session) { - try { - if (configuration.isDebug()) { - log("Attempting to deliver " + mail.getName()); - } - MimeMessage message = mail.getMessage(); - - // Create an array of the recipients as InternetAddress objects - Collection<MailAddress> recipients = mail.getRecipients(); - InternetAddress addr[] = new InternetAddress[recipients.size()]; - int j = 0; - for (Iterator<MailAddress> i = recipients.iterator(); i.hasNext(); j++) { - MailAddress rcpt = 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<HostAddress> targetServers; - if (configuration.getGatewayServer() == null) { - MailAddress rcpt = recipients.iterator().next(); - String host = rcpt.getDomain(); - - // Lookup the possible targets - try { - targetServers = new MXHostAddressIterator(dnsServer.findMXRecords(host).iterator(), dnsServer, false, logger); - } catch (TemporaryResolutionException e) { - log("Temporary problem looking up mail server for host: " + host); - String exceptionBuffer = "Temporary problem looking up mail server for host: " + host + ". I cannot determine where to send this message."; - - // temporary problems - return failMessage(mail, new MessagingException(exceptionBuffer), false); - } - if (!targetServers.hasNext()) { - log("No mail server found for: " + host); - String exceptionBuffer = "There are no DNS entries for the hostname " + host + ". I cannot determine where to send this message."; - - int retry = 0; - try { - retry = Integer.parseInt(mail.getErrorMessage()); - } catch (NumberFormatException e) { - // Unable to parse retryCount - } - if (retry == 0 || retry > configuration.getDnsProblemRetry()) { - // The domain has no dns entry.. Return a permanent - // error - return failMessage(mail, new MessagingException(exceptionBuffer), true); - } else { - return failMessage(mail, new MessagingException(exceptionBuffer), false); - } - } - } else { - targetServers = getGatewaySMTPHostAddresses(configuration.getGatewayServer()); - } - - MessagingException lastError = null; - - while (targetServers.hasNext()) { - try { - - 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); - } - - HostAddress outgoingMailServer = targetServers.next(); - StringBuilder logMessageBuffer = new StringBuilder(256).append("Attempting delivery of ").append(mail.getName()).append(" to host ").append(outgoingMailServer.getHostName()).append(" at ").append(outgoingMailServer.getHost()).append(" from ").append(props.get("mail.smtp.from")) - .append(" for addresses ").append(Arrays.asList(addr)); - log(logMessageBuffer.toString()); - - // 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. - - SMTPTransport transport = null; - try { - transport = (SMTPTransport) session.getTransport(outgoingMailServer); - transport.setLocalHost( props.getProperty("mail.smtp.localhost", configuration.getHeloNameProvider().getHeloName()) ); - try { - if (configuration.getAuthUser() != null) { - transport.connect(outgoingMailServer.getHostName(), configuration.getAuthUser(), configuration.getAuthPass()); - } 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. - - // Also include the stacktrace if debug is enabled. See JAMES-1257 - if (configuration.isDebug()) { - log(me.getMessage(), me.getCause()); - } else { - log(me.getMessage()); - } - continue; - } - // if the transport is a SMTPTransport (from sun) some - // performance enhancement can be done. - if (transport.getClass().getName().endsWith(".SMTPTransport")) { - boolean supports8bitmime = false; - try { - Method supportsExtension = transport.getClass().getMethod("supportsExtension", new Class[]{String.class}); - supports8bitmime = (Boolean) supportsExtension.invoke(transport, "8BITMIME"); - } catch (NoSuchMethodException nsme) { - // An SMTPAddressFailedException with no - // getAddress method. - } catch (IllegalAccessException iae) { - } catch (IllegalArgumentException iae) { - } catch (InvocationTargetException ite) { - // Other issues with getAddress invokation. - } - - // 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 (!supports8bitmime) { - 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); - } - } - } 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) { - try { - // James-899: transport.close() sends QUIT to - // the server; if that fails - // (e.g. because the server has already closed - // the connection) the message - // should be considered to be delivered because - // the error happened outside - // of the mail transaction (MAIL, RCPT, DATA). - transport.close(); - } catch (MessagingException e) { - log("Warning: could not close the SMTP transport after sending mail (" + mail.getName() + ") to " + outgoingMailServer.getHostName() + " at " + outgoingMailServer.getHost() + " for " + mail.getRecipients() + "; probably the server has already closed the " - + "connection. Message is considered to be delivered. Exception: " + e.getMessage()); - } - transport = null; - } - } - logMessageBuffer = new StringBuilder(256).append("Mail (").append(mail.getName()).append(") sent successfully to ").append(outgoingMailServer.getHostName()).append(" at ").append(outgoingMailServer.getHost()).append(" from ").append(props.get("mail.smtp.from")).append(" for ") - .append(mail.getRecipients()); - log(logMessageBuffer.toString()); - outgoingMailsMetric.increment(); - return true; - } catch (SendFailedException sfe) { - logSendFailedException(sfe); - - if (sfe.getValidSentAddresses() != null) { - Address[] validSent = sfe.getValidSentAddresses(); - if (validSent.length > 0) { - String logMessageBuffer = "Mail (" + mail.getName() + ") sent successfully for " + Arrays.asList(validSent); - log(logMessageBuffer); - } - } - - /* - * SMTPSendFailedException introduced in JavaMail 1.3.2, and - * provides detailed protocol reply code for the operation - */ - if (sfe.getClass().getName().endsWith(".SMTPSendFailedException")) { - try { - int returnCode = (Integer) invokeGetter(sfe, "getReturnCode"); - // if 5xx, terminate this delivery attempt by - // re-throwing the exception. - if (returnCode >= 500 && returnCode <= 599) - throw sfe; - } catch (ClassCastException cce) { - } catch (IllegalArgumentException iae) { - } - } - - if (sfe.getValidUnsentAddresses() != null && sfe.getValidUnsentAddresses().length > 0) { - if (configuration.isDebug()) - log("Send failed, " + sfe.getValidUnsentAddresses().length + " valid addresses remain, continuing with any other servers"); - lastError = sfe; - } 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. - String exceptionBuffer = "Exception delivering message (" + mail.getName() + ") - " + me.getMessage(); - log(exceptionBuffer); - 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); - - // Copy the recipients as direct modification may not be possible - Collection<MailAddress> recipients = new ArrayList<MailAddress>(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 - */ - try { - if (sfe.getClass().getName().endsWith(".SMTPSendFailedException")) { - int returnCode = (Integer) invokeGetter(sfe, "getReturnCode"); - // If we got an SMTPSendFailedException, use its RetCode to - // determine default permanent/temporary failure - deleteMessage = (returnCode >= 500 && returnCode <= 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.getClass().getName().endsWith(".SMTPAddressFailedException")) { - int returnCode = (Integer) invokeGetter(me, "getReturnCode"); - deleteMessage = (returnCode >= 500 && returnCode <= 599); - } - } - } - } catch (IllegalStateException ise) { - // unexpected exception (not a compatible javamail - // implementation) - } catch (ClassCastException cce) { - // unexpected exception (not a compatible javamail - // implementation) - } - - // log the original set of intended recipients - if (configuration.isDebug()) - log("Recipients: " + recipients); - - if (sfe.getInvalidAddresses() != null) { - Address[] address = sfe.getInvalidAddresses(); - if (address.length > 0) { - recipients.clear(); - for (Address addres : address) { - try { - recipients.add(new MailAddress(addres.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()); - } - } - // Set the recipients for the mail - mail.setRecipients(recipients); - - if (configuration.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 (Address addres : address) { - try { - recipients.add(new MailAddress(addres.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()); - } - } - // Set the recipients for the mail - mail.setRecipients(recipients); - if (configuration.isDebug()) - log("Unsent recipients: " + recipients); - if (sfe.getClass().getName().endsWith(".SMTPSendFailedException")) { - int returnCode = (Integer) invokeGetter(sfe, "getReturnCode"); - deleteMessage = failMessage(mail, sfe, returnCode >= 500 && returnCode <= 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))); - } catch (Exception ex) { - log("Generic exception = permanent failure: "+ex.getMessage(), ex); - // Generic exception = permanent failure - return failMessage(mail, ex, true); - } - - /* - * 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); - } - - /** - * Try to return a usefull logString created of the Exception which was - * given. Return null if nothing usefull could be done - * - * @param e The MessagingException to use - * @return logString - */ - private String exceptionToLogString(Exception e) { - if (e.getClass().getName().endsWith(".SMTPSendFailedException")) { - return "RemoteHost said: " + e.getMessage(); - } else if (e instanceof SendFailedException) { - SendFailedException exception = (SendFailedException) e; - - // No error - if (exception.getInvalidAddresses().length == 0 && exception.getValidUnsentAddresses().length == 0) - return null; - - Exception ex; - StringBuilder sb = new StringBuilder(); - boolean smtpExFound = false; - sb.append("RemoteHost said:"); - - if (e instanceof MessagingException) - while ((ex = ((MessagingException) e).getNextException()) != null && ex instanceof MessagingException) { - e = ex; - if (ex.getClass().getName().endsWith(".SMTPAddressFailedException")) { - try { - InternetAddress ia = (InternetAddress) invokeGetter(ex, "getAddress"); - sb.append(" ( ").append(ia).append(" - [").append(ex.getMessage().replaceAll("\\n", "")).append("] )"); - smtpExFound = true; - } catch (IllegalStateException ise) { - // Error invoking the getAddress method - } catch (ClassCastException cce) { - // The getAddress method returned something - // different than InternetAddress - } - } - } - if (!smtpExFound) { - boolean invalidAddr = false; - sb.append(" ( "); - - if (exception.getInvalidAddresses().length > 0) { - sb.append(Arrays.toString(exception.getInvalidAddresses())); - invalidAddr = true; - } - if (exception.getValidUnsentAddresses().length > 0) { - if (invalidAddr) - sb.append(" "); - sb.append(Arrays.toString(exception.getValidUnsentAddresses())); - } - sb.append(" - ["); - sb.append(exception.getMessage().replaceAll("\\n", "")); - sb.append("] )"); - } - return sb.toString(); - } - return null; - } - - /** - * Utility method used to invoke getters for javamail implementation - * specific classes. - * - * @param target the object whom method will be invoked - * @param getter the no argument method name - * @return the result object - * @throws IllegalStateException on invocation error - */ - private Object invokeGetter(Object target, String getter) { - try { - Method getAddress = target.getClass().getMethod(getter); - return getAddress.invoke(target); - } catch (NoSuchMethodException nsme) { - // An SMTPAddressFailedException with no getAddress method. - } catch (IllegalAccessException iae) { - } catch (IllegalArgumentException iae) { - } catch (InvocationTargetException ite) { - // Other issues with getAddress invokation. - } - return new IllegalStateException("Exception invoking " + getter + " on a " + target.getClass() + " object"); - } - - /* - * private method to log the extended SendFailedException introduced in - * JavaMail 1.3.2. - */ - private void logSendFailedException(SendFailedException sfe) { - if (configuration.isDebug()) { - MessagingException me = sfe; - if (me.getClass().getName().endsWith(".SMTPSendFailedException")) { - try { - String command = (String) invokeGetter(sfe, "getCommand"); - Integer returnCode = (Integer) invokeGetter(sfe, "getReturnCode"); - log("SMTP SEND FAILED:"); - log(sfe.toString()); - log(" Command: " + command); - log(" RetCode: " + returnCode); - log(" Response: " + sfe.getMessage()); - } catch (IllegalStateException ise) { - // Error invoking the getAddress method - log("Send failed: " + me.toString()); - } catch (ClassCastException cce) { - // The getAddress method returned something different than - // InternetAddress - log("Send failed: " + me.toString()); - } - } else { - log("Send failed: " + me.toString()); - } - Exception ne; - while ((ne = me.getNextException()) != null && ne instanceof MessagingException) { - me = (MessagingException) ne; - if (me.getClass().getName().endsWith(".SMTPAddressFailedException") || me.getClass().getName().endsWith(".SMTPAddressSucceededException")) { - try { - String action = me.getClass().getName().endsWith(".SMTPAddressFailedException") ? "FAILED" : "SUCCEEDED"; - InternetAddress address = (InternetAddress) invokeGetter(me, "getAddress"); - String command = (String) invokeGetter(me, "getCommand"); - Integer returnCode = (Integer) invokeGetter(me, "getReturnCode"); - log("ADDRESS " + action + ":"); - log(me.toString()); - log(" Address: " + address); - log(" Command: " + command); - log(" RetCode: " + returnCode); - log(" Response: " + me.getMessage()); - } catch (IllegalStateException ise) { - // Error invoking the getAddress method - } catch (ClassCastException cce) { - // A method returned something different than expected - } - } - } - } - } - - /** - * Converts a message to 7 bit. - * - * @param part - */ - private void convertTo7Bit(MimePart part) throws MessagingException, IOException { - 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 ("8bit".equals(part.getEncoding())) { - // The content may already be in encoded the form (likely with mail - // created from a - // stream). In that case, just changing the encoding to - // quoted-printable will mangle - // the result when this is transmitted. We must first convert the - // content into its - // native format, set it back, and only THEN set the transfer - // encoding to force the - // content to be encoded appropriately. - - // if the part doesn't contain text it will be base64 encoded. - String contentTransferEncoding = part.isMimeType("text/*") ? "quoted-printable" : "base64"; - part.setContent(part.getContent(), part.getContentType()); - part.setHeader("Content-Transfer-Encoding", contentTransferEncoding); - part.addHeader("X-MIME-Autoconverted", "from 8bit to " + contentTransferEncoding + " by " + getMailetContext().getServerInfo()); - } - } - - /** - * Insert the method's description here. - * - * @param mail org.apache.james.core.MailImpl - * @param ex javax.mail.MessagingException - * @param permanent - * @return boolean Whether the message failed fully and can be deleted - */ - private boolean failMessage(Mail mail, Exception ex, boolean permanent) { - StringWriter sout = new StringWriter(); - PrintWriter out = new PrintWriter(sout, true); - if (permanent) { - out.print("Permanent"); - } else { - out.print("Temporary"); - } - - String exceptionLog = exceptionToLogString(ex); - - StringBuilder logBuffer = new StringBuilder(64).append(" exception delivering mail (").append(mail.getName()); - - if (exceptionLog != null) { - logBuffer.append(". "); - logBuffer.append(exceptionLog); - } - - logBuffer.append(": "); - out.print(logBuffer.toString()); - if (configuration.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 = 0; - try { - retries = Integer.parseInt(mail.getErrorMessage()); - } catch (NumberFormatException e) { - // Something strange was happen with the errorMessage.. - } - - if (retries < configuration.getMaxRetries()) { - logBuffer = new StringBuilder(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 StringBuilder(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 (configuration.getBounceProcessor() != null) { - // do the new DSN bounce - // setting attributes for DSN mailet - String cause; - if (ex instanceof MessagingException) { - cause = getErrorMsg((MessagingException) ex); - } else { - cause = ex.getMessage(); - } - mail.setAttribute("delivery-error", cause); - mail.setState(configuration.getBounceProcessor()); - // 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; - } - - /** - * Utility method for getting the error message from the (nested) exception. - * - * @param me MessagingException - * @return error message - */ - protected String getErrorMsg(MessagingException me) { - if (me.getNextException() == null) { - return me.getMessage().trim(); - } else { - Exception ex1 = me.getNextException(); - return ex1.getMessage().trim(); - } - } - - private void bounce(Mail mail, Exception ex) { - StringWriter sout = new StringWriter(); - PrintWriter out = new PrintWriter(sout, true); - String machine; - try { - machine = configuration.getHeloNameProvider().getHeloName(); - - } catch (Exception e) { - machine = "[address unknown]"; - } - String bounceBuffer = "Hi. This is the James mail server at " + machine + "."; - out.println(bounceBuffer); - 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 (MailAddress mailAddress : mail.getRecipients()) { - out.println(mailAddress); - } - if (ex instanceof MessagingException) { - if (((MessagingException) ex).getNextException() == null) { - out.println(ex.getMessage().trim()); - } else { - Exception ex1 = ((MessagingException) 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()); - } - } - - /** - * Returns the javamail Session object. - * - * @param props - * @return the java mail session - */ - protected Session obtainSession(Properties props) { - return Session.getInstance(props); - } - - /** - * Returns an Iterator over org.apache.mailet.HostAddress, a specialized - * subclass of javax.mail.URLName, which provides location information for - * servers that are specified as mail handlers for the given hostname. If no - * host is found, the Iterator returned will be empty and the first call to - * hasNext() will return false. The Iterator is a nested iterator: the outer - * iteration is over each gateway, and the inner iteration is over - * potentially multiple A records for each gateway. - * - * @param gatewayServers - Collection of host[:port] Strings - * @return an Iterator over HostAddress instances, sorted by priority - * @since v2.2.0a16-unstable - */ - private Iterator<HostAddress> getGatewaySMTPHostAddresses(Collection<String> gatewayServers) { - Iterator<String> gateways = gatewayServers.iterator(); - - return new MXHostAddressIterator(gateways, dnsServer, false, logger); - } - } --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org