0001: /****************************************************************
0002: * Licensed to the Apache Software Foundation (ASF) under one *
0003: * or more contributor license agreements. See the NOTICE file *
0004: * distributed with this work for additional information *
0005: * regarding copyright ownership. The ASF licenses this file *
0006: * to you under the Apache License, Version 2.0 (the *
0007: * "License"); you may not use this file except in compliance *
0008: * with the License. You may obtain a copy of the License at *
0009: * *
0010: * http://www.apache.org/licenses/LICENSE-2.0 *
0011: * *
0012: * Unless required by applicable law or agreed to in writing, *
0013: * software distributed under the License is distributed on an *
0014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
0015: * KIND, either express or implied. See the License for the *
0016: * specific language governing permissions and limitations *
0017: * under the License. *
0018: ****************************************************************/package org.apache.james.transport.mailets;
0019:
0020: import org.apache.avalon.cornerstone.services.store.Store;
0021: import org.apache.avalon.framework.configuration.DefaultConfiguration;
0022: import org.apache.avalon.framework.container.ContainerUtil;
0023: import org.apache.avalon.framework.service.ServiceException;
0024: import org.apache.avalon.framework.service.ServiceManager;
0025: import org.apache.james.Constants;
0026: import org.apache.james.services.SpoolRepository;
0027: import org.apache.mailet.GenericMailet;
0028: import org.apache.mailet.HostAddress;
0029: import org.apache.mailet.Mail;
0030: import org.apache.mailet.MailAddress;
0031: import org.apache.mailet.MailetContext;
0032: import org.apache.oro.text.regex.MalformedPatternException;
0033: import org.apache.oro.text.regex.MatchResult;
0034: import org.apache.oro.text.regex.Pattern;
0035: import org.apache.oro.text.regex.Perl5Compiler;
0036: import org.apache.oro.text.regex.Perl5Matcher;
0037:
0038: import com.sun.mail.smtp.SMTPAddressFailedException;
0039: import com.sun.mail.smtp.SMTPAddressSucceededException;
0040: import com.sun.mail.smtp.SMTPSendFailedException;
0041: import com.sun.mail.smtp.SMTPTransport;
0042:
0043: import javax.mail.Address;
0044: import javax.mail.MessagingException;
0045: import javax.mail.SendFailedException;
0046: import javax.mail.Session;
0047: import javax.mail.Transport;
0048: import javax.mail.internet.InternetAddress;
0049: import javax.mail.internet.MimeMessage;
0050: import javax.mail.internet.MimeMultipart;
0051: import javax.mail.internet.MimePart;
0052: import javax.mail.internet.ParseException;
0053:
0054: import java.io.IOException;
0055: import java.io.PrintWriter;
0056: import java.io.StringWriter;
0057: import java.net.ConnectException;
0058: import java.net.InetAddress;
0059: import java.net.SocketException;
0060: import java.net.UnknownHostException;
0061: import java.util.ArrayList;
0062: import java.util.Arrays;
0063: import java.util.Collection;
0064: import java.util.Date;
0065: import java.util.HashMap;
0066: import java.util.Hashtable;
0067: import java.util.Iterator;
0068: import java.util.Locale;
0069: import java.util.Properties;
0070: import java.util.StringTokenizer;
0071: import java.util.Vector;
0072:
0073: /**
0074: * Receives a MessageContainer from JamesSpoolManager and takes care of delivery
0075: * the message to remote hosts. If for some reason mail can't be delivered
0076: * store it in the "outgoing" Repository and set an Alarm. After the next "delayTime" the
0077: * Alarm will wake the servlet that will try to send it again. After "maxRetries"
0078: * the mail will be considered undeliverable and will be returned to sender.
0079: *
0080: * TO DO (in priority):
0081: * 1. Support a gateway (a single server where all mail will be delivered) (DONE)
0082: * 2. Provide better failure messages (DONE)
0083: * 3. More efficiently handle numerous recipients
0084: * 4. Migrate to use Phoenix for the delivery threads
0085: *
0086: * You really want to read the JavaMail documentation if you are
0087: * working in here, and you will want to view the list of JavaMail
0088: * attributes, which are documented here:
0089: *
0090: * http://java.sun.com/products/javamail/1.3/docs/javadocs/com/sun/mail/smtp/package-summary.html
0091: *
0092: * as well as other places.
0093: *
0094: * @version CVS $Revision: 494056 $ $Date: 2007-01-08 14:15:03 +0100 (Mo, 08 Jan 2007) $
0095: */
0096: public class RemoteDelivery extends GenericMailet implements Runnable {
0097:
0098: private static final long DEFAULT_DELAY_TIME = 21600000; // default is 6*60*60*1000 millis (6 hours)
0099: private static final String PATTERN_STRING = "\\s*([0-9]*\\s*[\\*])?\\s*([0-9]+)\\s*([a-z,A-Z]*)\\s*";//pattern to match
0100: //[attempts*]delay[units]
0101:
0102: private static Pattern PATTERN = null; //the compiled pattern of the above String
0103: private static final HashMap MULTIPLIERS = new HashMap(10); //holds allowed units for delaytime together with
0104: //the factor to turn it into the equivalent time in msec
0105:
0106: /*
0107: * Static initializer.<p>
0108: * Compiles pattern for processing delaytime entries.<p>
0109: * Initializes MULTIPLIERS with the supported unit quantifiers
0110: */
0111: static {
0112: try {
0113: Perl5Compiler compiler = new Perl5Compiler();
0114: PATTERN = compiler.compile(PATTERN_STRING,
0115: Perl5Compiler.READ_ONLY_MASK);
0116: } catch (MalformedPatternException mpe) {
0117: //this should not happen as the pattern string is hardcoded.
0118: System.err.println("Malformed pattern: " + PATTERN_STRING);
0119: mpe.printStackTrace(System.err);
0120: }
0121: //add allowed units and their respective multiplier
0122: MULTIPLIERS.put("msec", new Integer(1));
0123: MULTIPLIERS.put("msecs", new Integer(1));
0124: MULTIPLIERS.put("sec", new Integer(1000));
0125: MULTIPLIERS.put("secs", new Integer(1000));
0126: MULTIPLIERS.put("minute", new Integer(1000 * 60));
0127: MULTIPLIERS.put("minutes", new Integer(1000 * 60));
0128: MULTIPLIERS.put("hour", new Integer(1000 * 60 * 60));
0129: MULTIPLIERS.put("hours", new Integer(1000 * 60 * 60));
0130: MULTIPLIERS.put("day", new Integer(1000 * 60 * 60 * 24));
0131: MULTIPLIERS.put("days", new Integer(1000 * 60 * 60 * 24));
0132: }
0133:
0134: /**
0135: * This filter is used in the accept call to the spool.
0136: * It will select the next mail ready for processing according to the mails
0137: * retrycount and lastUpdated time
0138: **/
0139: private class MultipleDelayFilter implements
0140: SpoolRepository.AcceptFilter {
0141: /**
0142: * holds the time to wait for the youngest mail to get ready for processing
0143: **/
0144: long youngest = 0;
0145:
0146: /**
0147: * Uses the getNextDelay to determine if a mail is ready for processing based on the delivered parameters
0148: * errorMessage (which holds the retrycount), lastUpdated and state
0149: * @param key the name/key of the message
0150: * @param state the mails state
0151: * @param lastUpdated the mail was last written to the spool at this time.
0152: * @param errorMessage actually holds the retrycount as a string (see failMessage below)
0153: **/
0154: public boolean accept(String key, String state,
0155: long lastUpdated, String errorMessage) {
0156: if (state.equals(Mail.ERROR)) {
0157: //Test the time...
0158: int retries = Integer.parseInt(errorMessage);
0159:
0160: // If the retries count is 0 we should try to send the mail now!
0161: if (retries == 0)
0162: return true;
0163:
0164: long delay = getNextDelay(retries);
0165: long timeToProcess = delay + lastUpdated;
0166:
0167: if (System.currentTimeMillis() > timeToProcess) {
0168: //We're ready to process this again
0169: return true;
0170: } else {
0171: //We're not ready to process this.
0172: if (youngest == 0 || youngest > timeToProcess) {
0173: //Mark this as the next most likely possible mail to process
0174: youngest = timeToProcess;
0175: }
0176: return false;
0177: }
0178: } else {
0179: //This mail is good to go... return the key
0180: return true;
0181: }
0182: }
0183:
0184: /**
0185: * @return the optimal time the SpoolRepository.accept(AcceptFilter) method should wait before
0186: * trying to find a mail ready for processing again.
0187: **/
0188: public long getWaitTime() {
0189: if (youngest == 0) {
0190: return 0;
0191: } else {
0192: long duration = youngest - System.currentTimeMillis();
0193: youngest = 0; //get ready for next run
0194: return duration <= 0 ? 1 : duration;
0195: }
0196: }
0197: }
0198:
0199: /**
0200: * Controls certain log messages
0201: */
0202: private boolean isDebug = false;
0203:
0204: private SpoolRepository outgoing; // The spool of outgoing mail
0205: private long[] delayTimes; //holds expanded delayTimes
0206: private int maxRetries = 5; // default number of retries
0207: private long smtpTimeout = 180000; //default number of ms to timeout on smtp delivery
0208: private boolean sendPartial = false; // If false then ANY address errors will cause the transmission to fail
0209: private int connectionTimeout = 60000; // The amount of time JavaMail will wait before giving up on a socket connect()
0210: private int deliveryThreadCount = 1; // default number of delivery threads
0211: private Collection gatewayServer = null; // the server(s) to send all email to
0212: private String authUser = null; // auth for gateway server
0213: private String authPass = null; // password for gateway server
0214: private String bindAddress = null; // JavaMail delivery socket binds to this local address. If null the JavaMail default will be used.
0215: private boolean isBindUsed = false; // true, if the bind configuration
0216: // parameter is supplied, RemoteDeliverySocketFactory
0217: // will be used in this case
0218: private Collection deliveryThreads = new Vector();
0219: private volatile boolean destroyed = false; //Flag that the run method will check and end itself if set to true
0220: private String bounceProcessor = null; // the processor for creating Bounces
0221:
0222: private Perl5Matcher delayTimeMatcher; //matcher use at init time to parse delaytime parameters
0223: private MultipleDelayFilter delayFilter = new MultipleDelayFilter();//used by accept to selcet the next mail ready for processing
0224: private Properties defprops = new Properties(); // default properties for the javamail Session
0225:
0226: /**
0227: * Initialize the mailet
0228: */
0229: public void init() throws MessagingException {
0230: isDebug = (getInitParameter("debug") == null) ? false
0231: : new Boolean(getInitParameter("debug")).booleanValue();
0232: ArrayList delay_times_list = new ArrayList();
0233: try {
0234: if (getInitParameter("delayTime") != null) {
0235: delayTimeMatcher = new Perl5Matcher();
0236: String delay_times = getInitParameter("delayTime");
0237: //split on comma's
0238: StringTokenizer st = new StringTokenizer(delay_times,
0239: ",");
0240: while (st.hasMoreTokens()) {
0241: String delay_time = st.nextToken();
0242: delay_times_list.add(new Delay(delay_time));
0243: }
0244: } else {
0245: //use default delayTime.
0246: delay_times_list.add(new Delay());
0247: }
0248: } catch (Exception e) {
0249: log("Invalid delayTime setting: "
0250: + getInitParameter("delayTime"));
0251: }
0252: try {
0253: if (getInitParameter("maxRetries") != null) {
0254: maxRetries = Integer
0255: .parseInt(getInitParameter("maxRetries"));
0256: }
0257: //check consistency with delay_times_list attempts
0258: int total_attempts = calcTotalAttempts(delay_times_list);
0259: if (total_attempts > maxRetries) {
0260: log("Total number of delayTime attempts exceeds maxRetries specified. Increasing maxRetries from "
0261: + maxRetries + " to " + total_attempts);
0262: maxRetries = total_attempts;
0263: } else {
0264: int extra = maxRetries - total_attempts;
0265: if (extra != 0) {
0266: log("maxRetries is larger than total number of attempts specified. Increasing last delayTime with "
0267: + extra + " attempts ");
0268:
0269: if (delay_times_list.size() != 0) {
0270: Delay delay = (Delay) delay_times_list
0271: .get(delay_times_list.size() - 1); //last Delay
0272: delay.setAttempts(delay.getAttempts() + extra);
0273: log("Delay of " + delay.getDelayTime()
0274: + " msecs is now attempted: "
0275: + delay.getAttempts() + " times");
0276: } else {
0277: log("NO, delaytimes cannot continue");
0278: }
0279: }
0280: }
0281: delayTimes = expandDelays(delay_times_list);
0282:
0283: } catch (Exception e) {
0284: log("Invalid maxRetries setting: "
0285: + getInitParameter("maxRetries"));
0286: }
0287: try {
0288: if (getInitParameter("timeout") != null) {
0289: smtpTimeout = Integer
0290: .parseInt(getInitParameter("timeout"));
0291: }
0292: } catch (Exception e) {
0293: log("Invalid timeout setting: "
0294: + getInitParameter("timeout"));
0295: }
0296:
0297: try {
0298: if (getInitParameter("connectiontimeout") != null) {
0299: connectionTimeout = Integer
0300: .parseInt(getInitParameter("connectiontimeout"));
0301: }
0302: } catch (Exception e) {
0303: log("Invalid timeout setting: "
0304: + getInitParameter("timeout"));
0305: }
0306: sendPartial = (getInitParameter("sendpartial") == null) ? false
0307: : new Boolean(getInitParameter("sendpartial"))
0308: .booleanValue();
0309:
0310: bounceProcessor = getInitParameter("bounceProcessor");
0311:
0312: String gateway = getInitParameter("gateway");
0313: String gatewayPort = getInitParameter("gatewayPort");
0314:
0315: if (gateway != null) {
0316: gatewayServer = new ArrayList();
0317: StringTokenizer st = new StringTokenizer(gateway, ",");
0318: while (st.hasMoreTokens()) {
0319: String server = st.nextToken().trim();
0320: if (server.indexOf(':') < 0 && gatewayPort != null) {
0321: server += ":";
0322: server += gatewayPort;
0323: }
0324:
0325: if (isDebug)
0326: log("Adding SMTP gateway: " + server);
0327: gatewayServer.add(server);
0328: }
0329: authUser = getInitParameter("gatewayusername");
0330: authPass = getInitParameter("gatewayPassword");
0331: }
0332:
0333: ServiceManager compMgr = (ServiceManager) getMailetContext()
0334: .getAttribute(Constants.AVALON_COMPONENT_MANAGER);
0335: String outgoingPath = getInitParameter("outgoing");
0336: if (outgoingPath == null) {
0337: outgoingPath = "file:///../var/mail/outgoing";
0338: }
0339:
0340: try {
0341: // Instantiate the a MailRepository for outgoing mails
0342: Store mailstore = (Store) compMgr.lookup(Store.ROLE);
0343:
0344: DefaultConfiguration spoolConf = new DefaultConfiguration(
0345: "repository", "generated:RemoteDelivery.java");
0346: spoolConf.setAttribute("destinationURL", outgoingPath);
0347: spoolConf.setAttribute("type", "SPOOL");
0348: outgoing = (SpoolRepository) mailstore.select(spoolConf);
0349: } catch (ServiceException cnfe) {
0350: log("Failed to retrieve Store component:"
0351: + cnfe.getMessage());
0352: } catch (Exception e) {
0353: log("Failed to retrieve Store component:" + e.getMessage());
0354: }
0355:
0356: //Start up a number of threads
0357: try {
0358: deliveryThreadCount = Integer
0359: .parseInt(getInitParameter("deliveryThreads"));
0360: } catch (Exception e) {
0361: }
0362: for (int i = 0; i < deliveryThreadCount; i++) {
0363: StringBuffer nameBuffer = new StringBuffer(32).append(
0364: "Remote delivery thread (").append(i).append(")");
0365: Thread t = new Thread(this , nameBuffer.toString());
0366: t.start();
0367: deliveryThreads.add(t);
0368: }
0369:
0370: bindAddress = getInitParameter("bind");
0371: isBindUsed = bindAddress != null;
0372: try {
0373: if (isBindUsed)
0374: RemoteDeliverySocketFactory.setBindAdress(bindAddress);
0375: } catch (UnknownHostException e) {
0376: log("Invalid bind setting (" + bindAddress + "): "
0377: + e.toString());
0378: }
0379:
0380: Iterator i = getInitParameterNames();
0381: while (i.hasNext()) {
0382: String name = (String) i.next();
0383: if (name.startsWith("mail.")) {
0384: defprops.put(name, getInitParameter(name));
0385: }
0386: }
0387: }
0388:
0389: /*
0390: * private method to log the extended SendFailedException introduced in JavaMail 1.3.2.
0391: */
0392: private void logSendFailedException(SendFailedException sfe) {
0393: if (isDebug) {
0394: MessagingException me = sfe;
0395: if (me instanceof SMTPSendFailedException) {
0396: SMTPSendFailedException ssfe = (SMTPSendFailedException) me;
0397: log("SMTP SEND FAILED:");
0398: log(ssfe.toString());
0399: log(" Command: " + ssfe.getCommand());
0400: log(" RetCode: " + ssfe.getReturnCode());
0401: log(" Response: " + ssfe.getMessage());
0402: } else {
0403: log("Send failed: " + me.toString());
0404: }
0405: Exception ne;
0406: while ((ne = me.getNextException()) != null
0407: && ne instanceof MessagingException) {
0408: me = (MessagingException) ne;
0409: if (me instanceof SMTPAddressFailedException) {
0410: SMTPAddressFailedException e = (SMTPAddressFailedException) me;
0411: log("ADDRESS FAILED:");
0412: log(e.toString());
0413: log(" Address: " + e.getAddress());
0414: log(" Command: " + e.getCommand());
0415: log(" RetCode: " + e.getReturnCode());
0416: log(" Response: " + e.getMessage());
0417: } else if (me instanceof SMTPAddressSucceededException) {
0418: log("ADDRESS SUCCEEDED:");
0419: SMTPAddressSucceededException e = (SMTPAddressSucceededException) me;
0420: log(e.toString());
0421: log(" Address: " + e.getAddress());
0422: log(" Command: " + e.getCommand());
0423: log(" RetCode: " + e.getReturnCode());
0424: log(" Response: " + e.getMessage());
0425: }
0426: }
0427: }
0428: }
0429:
0430: /**
0431: * We can assume that the recipients of this message are all going to the same
0432: * mail server. We will now rely on the DNS server to do DNS MX record lookup
0433: * and try to deliver to the multiple mail servers. If it fails, it should
0434: * throw an exception.
0435: *
0436: * Creation date: (2/24/00 11:25:00 PM)
0437: * @param mail org.apache.james.core.MailImpl
0438: * @param session javax.mail.Session
0439: * @return boolean Whether the delivery was successful and the message can be deleted
0440: */
0441: private boolean deliver(Mail mail, Session session) {
0442: try {
0443: if (isDebug) {
0444: log("Attempting to deliver " + mail.getName());
0445: }
0446: MimeMessage message = mail.getMessage();
0447:
0448: //Create an array of the recipients as InternetAddress objects
0449: Collection recipients = mail.getRecipients();
0450: InternetAddress addr[] = new InternetAddress[recipients
0451: .size()];
0452: int j = 0;
0453: for (Iterator i = recipients.iterator(); i.hasNext(); j++) {
0454: MailAddress rcpt = (MailAddress) i.next();
0455: addr[j] = rcpt.toInternetAddress();
0456: }
0457:
0458: if (addr.length <= 0) {
0459: log("No recipients specified... not sure how this could have happened.");
0460: return true;
0461: }
0462:
0463: //Figure out which servers to try to send to. This collection
0464: // will hold all the possible target servers
0465: Iterator targetServers = null;
0466: if (gatewayServer == null) {
0467: MailAddress rcpt = (MailAddress) recipients.iterator()
0468: .next();
0469: String host = rcpt.getHost();
0470:
0471: //Lookup the possible targets
0472: targetServers = getMailetContext()
0473: .getSMTPHostAddresses(host);
0474: if (!targetServers.hasNext()) {
0475: log("No mail server found for: " + host);
0476: StringBuffer exceptionBuffer = new StringBuffer(128)
0477: .append(
0478: "There are no DNS entries for the hostname ")
0479: .append(host)
0480: .append(
0481: ". I cannot determine where to send this message.");
0482: return failMessage(mail, new MessagingException(
0483: exceptionBuffer.toString()), false);
0484: }
0485: } else {
0486: targetServers = getGatewaySMTPHostAddresses(gatewayServer);
0487: }
0488:
0489: MessagingException lastError = null;
0490:
0491: while (targetServers.hasNext()) {
0492: try {
0493: HostAddress outgoingMailServer = (HostAddress) targetServers
0494: .next();
0495: StringBuffer logMessageBuffer = new StringBuffer(
0496: 256).append("Attempting delivery of ")
0497: .append(mail.getName()).append(" to host ")
0498: .append(outgoingMailServer.getHostName())
0499: .append(" at ").append(
0500: outgoingMailServer.getHost())
0501: .append(" for addresses ").append(
0502: Arrays.asList(addr));
0503: log(logMessageBuffer.toString());
0504:
0505: Properties props = session.getProperties();
0506: if (mail.getSender() == null) {
0507: props.put("mail.smtp.from", "<>");
0508: } else {
0509: String sender = mail.getSender().toString();
0510: props.put("mail.smtp.from", sender);
0511: }
0512:
0513: //Many of these properties are only in later JavaMail versions
0514: //"mail.smtp.ehlo" //default true
0515: //"mail.smtp.auth" //default false
0516: //"mail.smtp.dsn.ret" //default to nothing... appended as RET= after MAIL FROM line.
0517: //"mail.smtp.dsn.notify" //default to nothing...appended as NOTIFY= after RCPT TO line.
0518:
0519: Transport transport = null;
0520: try {
0521: transport = session
0522: .getTransport(outgoingMailServer);
0523: try {
0524: if (authUser != null) {
0525: transport.connect(outgoingMailServer
0526: .getHostName(), authUser,
0527: authPass);
0528: } else {
0529: transport.connect();
0530: }
0531: } catch (MessagingException me) {
0532: // Any error on connect should cause the mailet to attempt
0533: // to connect to the next SMTP server associated with this
0534: // MX record. Just log the exception. We'll worry about
0535: // failing the message at the end of the loop.
0536: log(me.getMessage());
0537: continue;
0538: }
0539: // if the transport is a SMTPTransport (from sun) some
0540: // performance enhancement can be done.
0541: if (transport instanceof SMTPTransport) {
0542: SMTPTransport smtpTransport = (SMTPTransport) transport;
0543:
0544: // if the message is alredy 8bit or binary and the
0545: // server doesn't support the 8bit extension it has
0546: // to be converted to 7bit. Javamail api doesn't perform
0547: // that conversion, but it is required to be a
0548: // rfc-compliant smtp server.
0549:
0550: // Temporarily disabled. See JAMES-638
0551: /*
0552: if (!smtpTransport.supportsExtension("8BITMIME")) {
0553: try {
0554: convertTo7Bit(message);
0555: } catch (IOException e) {
0556: // An error has occured during the 7bit conversion.
0557: // The error is logged and the message is sent anyway.
0558:
0559: log("Error during the conversion to 7 bit.", e);
0560: }
0561: }
0562: */
0563:
0564: /*
0565: * Workaround for a javamail 1.3.2 bug: if
0566: * a message is sent without encoding information
0567: * and the 8bit allow property is set an exception
0568: * is trown during the mail delivery.
0569: */
0570:
0571: try {
0572: setEncodingIfMissing(message);
0573: } catch (IOException e) {
0574: log(
0575: "Error while adding encoding information to the message",
0576: e);
0577: }
0578: } else {
0579: // If the transport is not the one
0580: // developed by Sun we are not sure of how it
0581: // handles the 8 bit mime stuff,
0582: // so I convert the message to 7bit.
0583: try {
0584: convertTo7Bit(message);
0585: } catch (IOException e) {
0586: log(
0587: "Error during the conversion to 7 bit.",
0588: e);
0589: }
0590: }
0591: transport.sendMessage(message, addr);
0592: } finally {
0593: if (transport != null) {
0594: transport.close();
0595: transport = null;
0596: }
0597: }
0598: logMessageBuffer = new StringBuffer(256).append(
0599: "Mail (").append(mail.getName()).append(
0600: ") sent successfully to ").append(
0601: outgoingMailServer.getHostName()).append(
0602: " at ")
0603: .append(outgoingMailServer.getHost())
0604: .append(" for ").append(
0605: mail.getRecipients());
0606: log(logMessageBuffer.toString());
0607: return true;
0608: } catch (SendFailedException sfe) {
0609: logSendFailedException(sfe);
0610:
0611: if (sfe.getValidSentAddresses() != null) {
0612: Address[] validSent = sfe
0613: .getValidSentAddresses();
0614: if (validSent.length > 0) {
0615: StringBuffer logMessageBuffer = new StringBuffer(
0616: 256).append("Mail (").append(
0617: mail.getName()).append(
0618: ") sent successfully for ").append(
0619: Arrays.asList(validSent));
0620: log(logMessageBuffer.toString());
0621: }
0622: }
0623:
0624: /* SMTPSendFailedException introduced in JavaMail 1.3.2, and provides detailed protocol reply code for the operation */
0625: if (sfe instanceof SMTPSendFailedException) {
0626: SMTPSendFailedException ssfe = (SMTPSendFailedException) sfe;
0627: // if 5xx, terminate this delivery attempt by re-throwing the exception.
0628: if (ssfe.getReturnCode() >= 500
0629: && ssfe.getReturnCode() <= 599)
0630: throw sfe;
0631: }
0632:
0633: if (sfe.getValidUnsentAddresses() != null
0634: && sfe.getValidUnsentAddresses().length > 0) {
0635: if (isDebug)
0636: log("Send failed, "
0637: + sfe.getValidUnsentAddresses().length
0638: + " valid addresses remain, continuing with any other servers");
0639: lastError = sfe;
0640: continue;
0641: } else {
0642: // There are no valid addresses left to send, so rethrow
0643: throw sfe;
0644: }
0645: } catch (MessagingException me) {
0646: //MessagingException are horribly difficult to figure out what actually happened.
0647: StringBuffer exceptionBuffer = new StringBuffer(256)
0648: .append("Exception delivering message (")
0649: .append(mail.getName()).append(") - ")
0650: .append(me.getMessage());
0651: log(exceptionBuffer.toString());
0652: if ((me.getNextException() != null)
0653: && (me.getNextException() instanceof java.io.IOException)) {
0654: //This is more than likely a temporary failure
0655:
0656: // If it's an IO exception with no nested exception, it's probably
0657: // some socket or weird I/O related problem.
0658: lastError = me;
0659: continue;
0660: }
0661: // This was not a connection or I/O error particular to one
0662: // SMTP server of an MX set. Instead, it is almost certainly
0663: // a protocol level error. In this case we assume that this
0664: // is an error we'd encounter with any of the SMTP servers
0665: // associated with this MX record, and we pass the exception
0666: // to the code in the outer block that determines its severity.
0667: throw me;
0668: }
0669: } // end while
0670: //If we encountered an exception while looping through,
0671: //throw the last MessagingException we caught. We only
0672: //do this if we were unable to send the message to any
0673: //server. If sending eventually succeeded, we exit
0674: //deliver() though the return at the end of the try
0675: //block.
0676: if (lastError != null) {
0677: throw lastError;
0678: }
0679: } catch (SendFailedException sfe) {
0680: logSendFailedException(sfe);
0681:
0682: Collection recipients = mail.getRecipients();
0683:
0684: boolean deleteMessage = false;
0685:
0686: /*
0687: * If you send a message that has multiple invalid
0688: * addresses, you'll get a top-level SendFailedException
0689: * that that has the valid, valid-unsent, and invalid
0690: * address lists, with all of the server response messages
0691: * will be contained within the nested exceptions. [Note:
0692: * the content of the nested exceptions is implementation
0693: * dependent.]
0694: *
0695: * sfe.getInvalidAddresses() should be considered permanent.
0696: * sfe.getValidUnsentAddresses() should be considered temporary.
0697: *
0698: * JavaMail v1.3 properly populates those collections based
0699: * upon the 4xx and 5xx response codes to RCPT TO. Some
0700: * servers, such as Yahoo! don't respond to the RCPT TO,
0701: * and provide a 5xx reply after DATA. In that case, we
0702: * will pick up the failure from SMTPSendFailedException.
0703: *
0704: */
0705:
0706: /* SMTPSendFailedException introduced in JavaMail 1.3.2, and provides detailed protocol reply code for the operation */
0707: if (sfe instanceof SMTPSendFailedException) {
0708: // If we got an SMTPSendFailedException, use its RetCode to determine default permanent/temporary failure
0709: SMTPSendFailedException ssfe = (SMTPSendFailedException) sfe;
0710: deleteMessage = (ssfe.getReturnCode() >= 500 && ssfe
0711: .getReturnCode() <= 599);
0712: } else {
0713: // Sometimes we'll get a normal SendFailedException with nested SMTPAddressFailedException, so use the latter RetCode
0714: MessagingException me = sfe;
0715: Exception ne;
0716: while ((ne = me.getNextException()) != null
0717: && ne instanceof MessagingException) {
0718: me = (MessagingException) ne;
0719: if (me instanceof SMTPAddressFailedException) {
0720: SMTPAddressFailedException ssfe = (SMTPAddressFailedException) me;
0721: deleteMessage = (ssfe.getReturnCode() >= 500 && ssfe
0722: .getReturnCode() <= 599);
0723: }
0724: }
0725: }
0726:
0727: // log the original set of intended recipients
0728: if (isDebug)
0729: log("Recipients: " + recipients);
0730:
0731: if (sfe.getInvalidAddresses() != null) {
0732: Address[] address = sfe.getInvalidAddresses();
0733: if (address.length > 0) {
0734: recipients.clear();
0735: for (int i = 0; i < address.length; i++) {
0736: try {
0737: recipients.add(new MailAddress(address[i]
0738: .toString()));
0739: } catch (ParseException pe) {
0740: // this should never happen ... we should have
0741: // caught malformed addresses long before we
0742: // got to this code.
0743: log("Can't parse invalid address: "
0744: + pe.getMessage());
0745: }
0746: }
0747: if (isDebug)
0748: log("Invalid recipients: " + recipients);
0749: deleteMessage = failMessage(mail, sfe, true);
0750: }
0751: }
0752:
0753: if (sfe.getValidUnsentAddresses() != null) {
0754: Address[] address = sfe.getValidUnsentAddresses();
0755: if (address.length > 0) {
0756: recipients.clear();
0757: for (int i = 0; i < address.length; i++) {
0758: try {
0759: recipients.add(new MailAddress(address[i]
0760: .toString()));
0761: } catch (ParseException pe) {
0762: // this should never happen ... we should have
0763: // caught malformed addresses long before we
0764: // got to this code.
0765: log("Can't parse unsent address: "
0766: + pe.getMessage());
0767: }
0768: }
0769: if (isDebug)
0770: log("Unsent recipients: " + recipients);
0771: if (sfe instanceof SMTPSendFailedException) {
0772: SMTPSendFailedException ssfe = (SMTPSendFailedException) sfe;
0773: deleteMessage = failMessage(mail, sfe, ssfe
0774: .getReturnCode() >= 500
0775: && ssfe.getReturnCode() <= 599);
0776: } else {
0777: deleteMessage = failMessage(mail, sfe, false);
0778: }
0779: }
0780: }
0781:
0782: return deleteMessage;
0783: } catch (MessagingException ex) {
0784: // We should do a better job checking this... if the failure is a general
0785: // connect exception, this is less descriptive than more specific SMTP command
0786: // failure... have to lookup and see what are the various Exception
0787: // possibilities
0788:
0789: // Unable to deliver message after numerous tries... fail accordingly
0790:
0791: // We check whether this is a 5xx error message, which
0792: // indicates a permanent failure (like account doesn't exist
0793: // or mailbox is full or domain is setup wrong).
0794: // We fail permanently if this was a 5xx error
0795: return failMessage(mail, ex, ('5' == ex.getMessage()
0796: .charAt(0)));
0797: }
0798:
0799: /* If we get here, we've exhausted the loop of servers without
0800: * sending the message or throwing an exception. One case
0801: * where this might happen is if we get a MessagingException on
0802: * each transport.connect(), e.g., if there is only one server
0803: * and we get a connect exception.
0804: */
0805: return failMessage(mail, new MessagingException(
0806: "No mail server(s) available at this time."), false);
0807: }
0808:
0809: /**
0810: * Converts a message to 7 bit.
0811: *
0812: * @param message
0813: * @return
0814: */
0815: private void convertTo7Bit(MimePart part)
0816: throws MessagingException, IOException {
0817: if (part.isMimeType("multipart/*")) {
0818: MimeMultipart parts = (MimeMultipart) part.getContent();
0819: int count = parts.getCount();
0820: for (int i = 0; i < count; i++) {
0821: convertTo7Bit((MimePart) parts.getBodyPart(i));
0822: }
0823: } else {
0824: if (part.isMimeType("text/*")) {
0825: part.setHeader("Content-Transfer-Encoding",
0826: "quoted-printable");
0827: part.addHeader("X-MIME-Autoconverted",
0828: "from 8bit to quoted-printable by "
0829: + getMailetContext().getServerInfo());
0830: } else {
0831: // if the part doesn't contain text it will be base64 encoded.
0832: part.setHeader("Content-Transfer-Encoding", "base64");
0833: part.addHeader("X-MIME-Autoconverted",
0834: "from 8bit to base64 by "
0835: + getMailetContext().getServerInfo());
0836: }
0837: }
0838: }
0839:
0840: /**
0841: * Adds an encoding information to each text mime part. This is a workaround
0842: * for a javamail 1.3.2 bug: if a message is sent without encoding
0843: * information a null pointer exception is thrown during the message
0844: * delivery.
0845: *
0846: * @param part
0847: * @throws MessagingException
0848: * @throws IOException
0849: */
0850: private void setEncodingIfMissing(MimePart part)
0851: throws MessagingException, IOException {
0852: if (part.isMimeType("text/*")) {
0853: String enc = part.getEncoding();
0854: if (enc == null)
0855: part.setHeader("Content-Transfer-Encoding", "7bit");
0856: } else if (part.isMimeType("multipart/*")) {
0857: Object content = part.getContent();
0858: if (content instanceof MimeMultipart) {
0859: MimeMultipart parts = (MimeMultipart) content;
0860: int count = parts.getCount();
0861: for (int i = 0; i < count; i++) {
0862: setEncodingIfMissing((MimePart) parts
0863: .getBodyPart(i));
0864: }
0865: }
0866: }
0867: }
0868:
0869: /**
0870: * Insert the method's description here.
0871: * Creation date: (2/25/00 1:14:18 AM)
0872: * @param mail org.apache.james.core.MailImpl
0873: * @param exception javax.mail.MessagingException
0874: * @param boolean permanent
0875: * @return boolean Whether the message failed fully and can be deleted
0876: */
0877: private boolean failMessage(Mail mail, MessagingException ex,
0878: boolean permanent) {
0879: StringWriter sout = new StringWriter();
0880: PrintWriter out = new PrintWriter(sout, true);
0881: if (permanent) {
0882: out.print("Permanent");
0883: } else {
0884: out.print("Temporary");
0885: }
0886: StringBuffer logBuffer = new StringBuffer(64).append(
0887: " exception delivering mail (").append(mail.getName())
0888: .append(": ");
0889: out.print(logBuffer.toString());
0890: if (isDebug)
0891: ex.printStackTrace(out);
0892: log(sout.toString());
0893: if (!permanent) {
0894: if (!mail.getState().equals(Mail.ERROR)) {
0895: mail.setState(Mail.ERROR);
0896: mail.setErrorMessage("0");
0897: mail.setLastUpdated(new Date());
0898: }
0899: int retries = Integer.parseInt(mail.getErrorMessage());
0900: if (retries < maxRetries) {
0901: logBuffer = new StringBuffer(128).append(
0902: "Storing message ").append(mail.getName())
0903: .append(" into outgoing after ")
0904: .append(retries).append(" retries");
0905: log(logBuffer.toString());
0906: ++retries;
0907: mail.setErrorMessage(retries + "");
0908: mail.setLastUpdated(new Date());
0909: return false;
0910: } else {
0911: logBuffer = new StringBuffer(128).append(
0912: "Bouncing message ").append(mail.getName())
0913: .append(" after ").append(retries).append(
0914: " retries");
0915: log(logBuffer.toString());
0916: }
0917: }
0918:
0919: if (mail.getSender() == null) {
0920: log("Null Sender: no bounce will be generated for "
0921: + mail.getName());
0922: return true;
0923: }
0924:
0925: if (bounceProcessor != null) {
0926: // do the new DSN bounce
0927: // setting attributes for DSN mailet
0928: mail.setAttribute("delivery-error", ex);
0929: mail.setState(bounceProcessor);
0930: // re-insert the mail into the spool for getting it passed to the dsn-processor
0931: MailetContext mc = getMailetContext();
0932: try {
0933: mc.sendMail(mail);
0934: } catch (MessagingException e) {
0935: // we shouldn't get an exception, because the mail was already processed
0936: log("Exception re-inserting failed mail: ", e);
0937: }
0938: } else {
0939: // do an old style bounce
0940: bounce(mail, ex);
0941: }
0942: return true;
0943: }
0944:
0945: private void bounce(Mail mail, MessagingException ex) {
0946: StringWriter sout = new StringWriter();
0947: PrintWriter out = new PrintWriter(sout, true);
0948: String machine = "[unknown]";
0949: try {
0950: InetAddress me = InetAddress.getLocalHost();
0951: machine = me.getHostName();
0952: } catch (Exception e) {
0953: machine = "[address unknown]";
0954: }
0955: StringBuffer bounceBuffer = new StringBuffer(128).append(
0956: "Hi. This is the James mail server at ")
0957: .append(machine).append(".");
0958: out.println(bounceBuffer.toString());
0959: out
0960: .println("I'm afraid I wasn't able to deliver your message to the following addresses.");
0961: out
0962: .println("This is a permanent error; I've given up. Sorry it didn't work out. Below");
0963: out
0964: .println("I include the list of recipients and the reason why I was unable to deliver");
0965: out.println("your message.");
0966: out.println();
0967: for (Iterator i = mail.getRecipients().iterator(); i.hasNext();) {
0968: out.println(i.next());
0969: }
0970: if (ex.getNextException() == null) {
0971: out.println(ex.getMessage().trim());
0972: } else {
0973: Exception ex1 = ex.getNextException();
0974: if (ex1 instanceof SendFailedException) {
0975: out.println("Remote mail server told me: "
0976: + ex1.getMessage().trim());
0977: } else if (ex1 instanceof UnknownHostException) {
0978: out.println("Unknown host: " + ex1.getMessage().trim());
0979: out
0980: .println("This could be a DNS server error, a typo, or a problem with the recipient's mail server.");
0981: } else if (ex1 instanceof ConnectException) {
0982: //Already formatted as "Connection timed out: connect"
0983: out.println(ex1.getMessage().trim());
0984: } else if (ex1 instanceof SocketException) {
0985: out.println("Socket exception: "
0986: + ex1.getMessage().trim());
0987: } else {
0988: out.println(ex1.getMessage().trim());
0989: }
0990: }
0991: out.println();
0992:
0993: log("Sending failure message " + mail.getName());
0994: try {
0995: getMailetContext().bounce(mail, sout.toString());
0996: } catch (MessagingException me) {
0997: log("Encountered unexpected messaging exception while bouncing message: "
0998: + me.getMessage());
0999: } catch (Exception e) {
1000: log("Encountered unexpected exception while bouncing message: "
1001: + e.getMessage());
1002: }
1003: }
1004:
1005: public String getMailetInfo() {
1006: return "RemoteDelivery Mailet";
1007: }
1008:
1009: /**
1010: * For this message, we take the list of recipients, organize these into distinct
1011: * servers, and duplicate the message for each of these servers, and then call
1012: * the deliver (messagecontainer) method for each server-specific
1013: * messagecontainer ... that will handle storing it in the outgoing queue if needed.
1014: *
1015: * @param mail org.apache.mailet.Mail
1016: */
1017: public void service(Mail mail) throws MessagingException {
1018: // Do I want to give the internal key, or the message's Message ID
1019: if (isDebug) {
1020: log("Remotely delivering mail " + mail.getName());
1021: }
1022: Collection recipients = mail.getRecipients();
1023:
1024: if (gatewayServer == null) {
1025: // Must first organize the recipients into distinct servers (name made case insensitive)
1026: Hashtable targets = new Hashtable();
1027: for (Iterator i = recipients.iterator(); i.hasNext();) {
1028: MailAddress target = (MailAddress) i.next();
1029: String targetServer = target.getHost().toLowerCase(
1030: Locale.US);
1031: Collection temp = (Collection) targets
1032: .get(targetServer);
1033: if (temp == null) {
1034: temp = new ArrayList();
1035: targets.put(targetServer, temp);
1036: }
1037: temp.add(target);
1038: }
1039:
1040: //We have the recipients organized into distinct servers... put them into the
1041: //delivery store organized like this... this is ultra inefficient I think...
1042:
1043: // Store the new message containers, organized by server, in the outgoing mail repository
1044: String name = mail.getName();
1045: for (Iterator i = targets.keySet().iterator(); i.hasNext();) {
1046: String host = (String) i.next();
1047: Collection rec = (Collection) targets.get(host);
1048: if (isDebug) {
1049: StringBuffer logMessageBuffer = new StringBuffer(
1050: 128).append("Sending mail to ").append(rec)
1051: .append(" on host ").append(host);
1052: log(logMessageBuffer.toString());
1053: }
1054: mail.setRecipients(rec);
1055: StringBuffer nameBuffer = new StringBuffer(128).append(
1056: name).append("-to-").append(host);
1057: mail.setName(nameBuffer.toString());
1058: outgoing.store(mail);
1059: //Set it to try to deliver (in a separate thread) immediately (triggered by storage)
1060: }
1061: } else {
1062: // Store the mail unaltered for processing by the gateway server(s)
1063: if (isDebug) {
1064: StringBuffer logMessageBuffer = new StringBuffer(128)
1065: .append("Sending mail to ").append(
1066: mail.getRecipients()).append(" via ")
1067: .append(gatewayServer);
1068: log(logMessageBuffer.toString());
1069: }
1070:
1071: //Set it to try to deliver (in a separate thread) immediately (triggered by storage)
1072: outgoing.store(mail);
1073: }
1074: mail.setState(Mail.GHOST);
1075: }
1076:
1077: // Need to synchronize to get object monitor for notifyAll()
1078: public synchronized void destroy() {
1079: //Mark flag so threads from this mailet stop themselves
1080: destroyed = true;
1081: //Wake up all threads from waiting for an accept
1082: for (Iterator i = deliveryThreads.iterator(); i.hasNext();) {
1083: Thread t = (Thread) i.next();
1084: t.interrupt();
1085: }
1086: notifyAll();
1087: }
1088:
1089: /**
1090: * Handles checking the outgoing spool for new mail and delivering them if
1091: * there are any
1092: */
1093: public void run() {
1094: /* TODO: CHANGE ME!!! The problem is that we need to wait for James to
1095: * finish initializing. We expect the HELLO_NAME to be put into
1096: * the MailetContext, but in the current configuration we get
1097: * started before the SMTP Server, which establishes the value.
1098: * Since there is no contractual guarantee that there will be a
1099: * HELLO_NAME value, we can't just wait for it. As a temporary
1100: * measure, I'm inserting this philosophically unsatisfactory
1101: * fix.
1102: */
1103: long stop = System.currentTimeMillis() + 60000;
1104: while ((getMailetContext().getAttribute(Constants.HELLO_NAME) == null)
1105: && stop > System.currentTimeMillis()) {
1106:
1107: try {
1108: Thread.sleep(1000);
1109: } catch (Exception ignored) {
1110: } // wait for James to finish initializing
1111: }
1112:
1113: //Checks the pool and delivers a mail message
1114: Properties props = new Properties();
1115: //Not needed for production environment
1116: props.put("mail.debug", "false");
1117: // Reactivated: javamail 1.3.2 should no more have problems with "250 OK"
1118: // messages (WAS "false": Prevents problems encountered with 250 OK Messages)
1119: props.put("mail.smtp.ehlo", "true");
1120: // By setting this property to true the transport is allowed to
1121: // send 8 bit data to the server (if it supports the 8bitmime extension).
1122: // 2006/03/01 reverted to false because of a javamail bug converting to 8bit
1123: // messages created by an inputstream.
1124: props.setProperty("mail.smtp.allow8bitmime", "false");
1125: //Sets timeout on going connections
1126: props.put("mail.smtp.timeout", smtpTimeout + "");
1127:
1128: props
1129: .put("mail.smtp.connectiontimeout", connectionTimeout
1130: + "");
1131: props.put("mail.smtp.sendpartial", String.valueOf(sendPartial));
1132:
1133: //Set the hostname we'll use as this server
1134: if (getMailetContext().getAttribute(Constants.HELLO_NAME) != null) {
1135: props.put("mail.smtp.localhost", getMailetContext()
1136: .getAttribute(Constants.HELLO_NAME));
1137: } else {
1138: String defaultDomain = (String) getMailetContext()
1139: .getAttribute(Constants.DEFAULT_DOMAIN);
1140: if (defaultDomain != null) {
1141: props.put("mail.smtp.localhost", defaultDomain);
1142: }
1143: }
1144:
1145: if (isBindUsed) {
1146: // undocumented JavaMail 1.2 feature, smtp transport will use
1147: // our socket factory, which will also set the local address
1148: props
1149: .put("mail.smtp.socketFactory.class",
1150: "org.apache.james.transport.mailets.RemoteDeliverySocketFactory");
1151: // Don't fallback to the standard socket factory on error, do throw an exception
1152: props.put("mail.smtp.socketFactory.fallback", "false");
1153: }
1154:
1155: if (authUser != null) {
1156: props.put("mail.smtp.auth", "true");
1157: }
1158:
1159: props.putAll(defprops);
1160:
1161: Session session = Session.getInstance(props, null);
1162: try {
1163: while (!Thread.interrupted() && !destroyed) {
1164: try {
1165: Mail mail = (Mail) outgoing.accept(delayFilter);
1166: String key = mail.getName();
1167: try {
1168: if (isDebug) {
1169: StringBuffer logMessageBuffer = new StringBuffer(
1170: 128).append(
1171: Thread.currentThread().getName())
1172: .append(" will process mail ")
1173: .append(key);
1174: log(logMessageBuffer.toString());
1175: }
1176: if (deliver(mail, session)) {
1177: //Message was successfully delivered/fully failed... delete it
1178: ContainerUtil.dispose(mail);
1179: outgoing.remove(key);
1180: } else {
1181: //Something happened that will delay delivery. Store any updates
1182: outgoing.store(mail);
1183: ContainerUtil.dispose(mail);
1184: // This is an update, we have to unlock and notify or this mail
1185: // is kept locked by this thread
1186: outgoing.unlock(key);
1187: // We do not notify because we updated an already existing mail
1188: // and we are now free to handle more mails.
1189: // Furthermore this mail should not be processed now because we
1190: // have a retry time scheduling.
1191: }
1192: //Clear the object handle to make sure it recycles this object.
1193: mail = null;
1194: } catch (Exception e) {
1195: // Prevent unexpected exceptions from causing looping by removing
1196: // message from outgoing.
1197: // DO NOT CHNANGE THIS to catch Error! For example, if there were an OutOfMemory condition
1198: // caused because something else in the server was abusing memory, we would not want to
1199: // start purging the outgoing spool!
1200: ContainerUtil.dispose(mail);
1201: outgoing.remove(key);
1202: throw e;
1203: }
1204: } catch (Throwable e) {
1205: if (!destroyed)
1206: log("Exception caught in RemoteDelivery.run()",
1207: e);
1208: }
1209: }
1210: } finally {
1211: // Restore the thread state to non-interrupted.
1212: Thread.interrupted();
1213: }
1214: }
1215:
1216: /**
1217: * @param list holding Delay objects
1218: * @return the total attempts for all delays
1219: **/
1220: private int calcTotalAttempts(ArrayList list) {
1221: int sum = 0;
1222: Iterator i = list.iterator();
1223: while (i.hasNext()) {
1224: Delay delay = (Delay) i.next();
1225: sum += delay.getAttempts();
1226: }
1227: return sum;
1228: }
1229:
1230: /**
1231: * This method expands an ArrayList containing Delay objects into an array holding the
1232: * only delaytime in the order.<p>
1233: * So if the list has 2 Delay objects the first having attempts=2 and delaytime 4000
1234: * the second having attempts=1 and delaytime=300000 will be expanded into this array:<p>
1235: * long[0] = 4000<p>
1236: * long[1] = 4000<p>
1237: * long[2] = 300000<p>
1238: * @param list the list to expand
1239: * @return the expanded list
1240: **/
1241: private long[] expandDelays(ArrayList list) {
1242: long[] delays = new long[calcTotalAttempts(list)];
1243: Iterator i = list.iterator();
1244: int idx = 0;
1245: while (i.hasNext()) {
1246: Delay delay = (Delay) i.next();
1247: for (int j = 0; j < delay.getAttempts(); j++) {
1248: delays[idx++] = delay.getDelayTime();
1249: }
1250: }
1251: return delays;
1252: }
1253:
1254: /**
1255: * This method returns, given a retry-count, the next delay time to use.
1256: * @param retry_count the current retry_count.
1257: * @return the next delay time to use, given the retry count
1258: **/
1259: private long getNextDelay(int retry_count) {
1260: if (retry_count > delayTimes.length) {
1261: return DEFAULT_DELAY_TIME;
1262: }
1263: return delayTimes[retry_count - 1];
1264: }
1265:
1266: /**
1267: * This class is used to hold a delay time and its corresponding number
1268: * of retries.
1269: **/
1270: private class Delay {
1271: private int attempts = 1;
1272: private long delayTime = DEFAULT_DELAY_TIME;
1273:
1274: /**
1275: * This constructor expects Strings of the form "[attempt\*]delaytime[unit]". <p>
1276: * The optional attempt is the number of tries this delay should be used (default = 1)
1277: * The unit if present must be one of (msec,sec,minute,hour,day) (default = msec)
1278: * The constructor multiplies the delaytime by the relevant multiplier for the unit,
1279: * so the delayTime instance variable is always in msec.
1280: * @param init_string the string to initialize this Delay object from
1281: **/
1282: public Delay(String init_string) throws MessagingException {
1283: String unit = "msec"; //default unit
1284: if (delayTimeMatcher.matches(init_string, PATTERN)) {
1285: MatchResult res = delayTimeMatcher.getMatch();
1286: //the capturing groups will now hold
1287: //at 1: attempts * (if present)
1288: //at 2: delaytime
1289: //at 3: unit (if present)
1290:
1291: if (res.group(1) != null && !res.group(1).equals("")) {
1292: //we have an attempt *
1293: String attempt_match = res.group(1);
1294: //strip the * and whitespace
1295: attempt_match = attempt_match.substring(0,
1296: attempt_match.length() - 1).trim();
1297: attempts = Integer.parseInt(attempt_match);
1298: }
1299:
1300: delayTime = Long.parseLong(res.group(2));
1301:
1302: if (!res.group(3).equals("")) {
1303: //we have a unit
1304: unit = res.group(3).toLowerCase(Locale.US);
1305: }
1306: } else {
1307: throw new MessagingException(init_string
1308: + " does not match " + PATTERN_STRING);
1309: }
1310: if (MULTIPLIERS.get(unit) != null) {
1311: int multiplier = ((Integer) MULTIPLIERS.get(unit))
1312: .intValue();
1313: delayTime *= multiplier;
1314: } else {
1315: throw new MessagingException("Unknown unit: " + unit);
1316: }
1317: }
1318:
1319: /**
1320: * This constructor makes a default Delay object, ie. attempts=1 and delayTime=DEFAULT_DELAY_TIME
1321: **/
1322: public Delay() {
1323: }
1324:
1325: /**
1326: * @return the delayTime for this Delay
1327: **/
1328: public long getDelayTime() {
1329: return delayTime;
1330: }
1331:
1332: /**
1333: * @return the number attempts this Delay should be used.
1334: **/
1335: public int getAttempts() {
1336: return attempts;
1337: }
1338:
1339: /**
1340: * Set the number attempts this Delay should be used.
1341: **/
1342: public void setAttempts(int value) {
1343: attempts = value;
1344: }
1345:
1346: /**
1347: * Pretty prints this Delay
1348: **/
1349: public String toString() {
1350: StringBuffer buf = new StringBuffer(15);
1351: buf.append(getAttempts());
1352: buf.append('*');
1353: buf.append(getDelayTime());
1354: buf.append("msec");
1355: return buf.toString();
1356: }
1357: }
1358:
1359: /*
1360: * Returns an Iterator over org.apache.mailet.HostAddress, a
1361: * specialized subclass of javax.mail.URLName, which provides
1362: * location information for servers that are specified as mail
1363: * handlers for the given hostname. If no host is found, the
1364: * Iterator returned will be empty and the first call to hasNext()
1365: * will return false. The Iterator is a nested iterator: the outer
1366: * iteration is over each gateway, and the inner iteration is over
1367: * potentially multiple A records for each gateway.
1368: *
1369: * @see org.apache.james.DNSServer#getSMTPHostAddresses(String)
1370: * @since v2.2.0a16-unstable
1371: * @param gatewayServers - Collection of host[:port] Strings
1372: * @return an Iterator over HostAddress instances, sorted by priority
1373: */
1374: private Iterator getGatewaySMTPHostAddresses(
1375: final Collection gatewayServers) {
1376: return new Iterator() {
1377: private Iterator gateways = gatewayServers.iterator();
1378: private Iterator addresses = null;
1379:
1380: public boolean hasNext() {
1381: /* Make sure that when next() is called, that we can
1382: * provide a HostAddress. This means that we need to
1383: * have an inner iterator, and verify that it has
1384: * addresses. We could, for example, run into a
1385: * situation where the next gateway didn't have any
1386: * valid addresses.
1387: */
1388: if (!hasNextAddress() && gateways.hasNext()) {
1389: do {
1390: String server = (String) gateways.next();
1391: String port = "25";
1392:
1393: int idx = server.indexOf(':');
1394: if (idx > 0) {
1395: port = server.substring(idx + 1);
1396: server = server.substring(0, idx);
1397: }
1398:
1399: final String nextGateway = server;
1400: final String nextGatewayPort = port;
1401: try {
1402: final InetAddress[] ips = org.apache.james.dnsserver.DNSServer
1403: .getAllByName(nextGateway);
1404: addresses = new Iterator() {
1405: private InetAddress[] ipAddresses = ips;
1406: int i = 0;
1407:
1408: public boolean hasNext() {
1409: return i < ipAddresses.length;
1410: }
1411:
1412: public Object next() {
1413: return new org.apache.mailet.HostAddress(
1414: nextGateway,
1415: "smtp://"
1416: + (ipAddresses[i++])
1417: .getHostAddress()
1418: + ":"
1419: + nextGatewayPort);
1420: }
1421:
1422: public void remove() {
1423: throw new UnsupportedOperationException(
1424: "remove not supported by this iterator");
1425: }
1426: };
1427: } catch (java.net.UnknownHostException uhe) {
1428: log("Unknown gateway host: "
1429: + uhe.getMessage().trim());
1430: log("This could be a DNS server error or configuration error.");
1431: }
1432: } while (!hasNextAddress() && gateways.hasNext());
1433: }
1434:
1435: return hasNextAddress();
1436: }
1437:
1438: private boolean hasNextAddress() {
1439: return addresses != null && addresses.hasNext();
1440: }
1441:
1442: public Object next() {
1443: return (addresses != null) ? addresses.next() : null;
1444: }
1445:
1446: public void remove() {
1447: throw new UnsupportedOperationException(
1448: "remove not supported by this iterator");
1449: }
1450: };
1451: }
1452: }
|