View Javadoc

1   /************************************************************************
2    * Copyright (c) 2000-2006 The Apache Software Foundation.             *
3    * All rights reserved.                                                *
4    * ------------------------------------------------------------------- *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you *
6    * may not use this file except in compliance with the License. You    *
7    * may obtain a copy of the License at:                                *
8    *                                                                     *
9    *     http://www.apache.org/licenses/LICENSE-2.0                      *
10   *                                                                     *
11   * Unless required by applicable law or agreed to in writing, software *
12   * distributed under the License is distributed on an "AS IS" BASIS,   *
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or     *
14   * implied.  See the License for the specific language governing       *
15   * permissions and limitations under the License.                      *
16   ***********************************************************************/
17  
18  package org.apache.james.transport.mailets;
19  
20  import org.apache.avalon.cornerstone.services.store.Store;
21  import org.apache.avalon.framework.configuration.DefaultConfiguration;
22  import org.apache.avalon.framework.container.ContainerUtil;
23  import org.apache.avalon.framework.service.ServiceException;
24  import org.apache.avalon.framework.service.ServiceManager;
25  import org.apache.james.Constants;
26  import org.apache.james.services.SpoolRepository;
27  import org.apache.mailet.GenericMailet;
28  import org.apache.mailet.HostAddress;
29  import org.apache.mailet.Mail;
30  import org.apache.mailet.MailAddress;
31  import org.apache.mailet.MailetContext;
32  import org.apache.oro.text.regex.MalformedPatternException;
33  import org.apache.oro.text.regex.MatchResult;
34  import org.apache.oro.text.regex.Pattern;
35  import org.apache.oro.text.regex.Perl5Compiler;
36  import org.apache.oro.text.regex.Perl5Matcher;
37  
38  import com.sun.mail.smtp.SMTPAddressFailedException;
39  import com.sun.mail.smtp.SMTPAddressSucceededException;
40  import com.sun.mail.smtp.SMTPSendFailedException;
41  import com.sun.mail.smtp.SMTPTransport;
42  
43  import javax.mail.Address;
44  import javax.mail.MessagingException;
45  import javax.mail.SendFailedException;
46  import javax.mail.Session;
47  import javax.mail.Transport;
48  import javax.mail.internet.InternetAddress;
49  import javax.mail.internet.MimeMessage;
50  import javax.mail.internet.MimeMultipart;
51  import javax.mail.internet.MimePart;
52  import javax.mail.internet.ParseException;
53  
54  import java.io.IOException;
55  import java.io.PrintWriter;
56  import java.io.StringWriter;
57  import java.net.ConnectException;
58  import java.net.InetAddress;
59  import java.net.SocketException;
60  import java.net.UnknownHostException;
61  import java.util.ArrayList;
62  import java.util.Arrays;
63  import java.util.Collection;
64  import java.util.Date;
65  import java.util.HashMap;
66  import java.util.Hashtable;
67  import java.util.Iterator;
68  import java.util.Locale;
69  import java.util.Properties;
70  import java.util.StringTokenizer;
71  import java.util.Vector;
72  
73  
74  /***
75   * Receives a MessageContainer from JamesSpoolManager and takes care of delivery
76   * the message to remote hosts. If for some reason mail can't be delivered
77   * store it in the "outgoing" Repository and set an Alarm. After the next "delayTime" the
78   * Alarm will wake the servlet that will try to send it again. After "maxRetries"
79   * the mail will be considered undeliverable and will be returned to sender.
80   *
81   * TO DO (in priority):
82   * 1. Support a gateway (a single server where all mail will be delivered) (DONE)
83   * 2. Provide better failure messages (DONE)
84   * 3. More efficiently handle numerous recipients
85   * 4. Migrate to use Phoenix for the delivery threads
86   *
87   * You really want to read the JavaMail documentation if you are
88   * working in here, and you will want to view the list of JavaMail
89   * attributes, which are documented here:
90   *
91   * http://java.sun.com/products/javamail/1.3/docs/javadocs/com/sun/mail/smtp/package-summary.html
92   *
93   * as well as other places.
94   *
95   * @version CVS $Revision: 450877 $ $Date: 2006-09-28 14:49:38 +0000 (gio, 28 set 2006) $
96   */
97  public class RemoteDelivery extends GenericMailet implements Runnable {
98  
99      private static final long DEFAULT_DELAY_TIME = 21600000; // default is 6*60*60*1000 millis (6 hours)
100     private static final String PATTERN_STRING =
101         "//s*([0-9]*//s*[//*])?//s*([0-9]+)//s*([a-z,A-Z]*)//s*";//pattern to match
102                                                                  //[attempts*]delay[units]
103                                             
104     private static Pattern PATTERN = null; //the compiled pattern of the above String
105     private static final HashMap MULTIPLIERS = new HashMap (10); //holds allowed units for delaytime together with
106                                                                 //the factor to turn it into the equivalent time in msec
107 
108     /*
109      * Static initializer.<p>
110      * Compiles pattern for processing delaytime entries.<p>
111      * Initializes MULTIPLIERS with the supported unit quantifiers
112      */
113     static {
114         try {
115             Perl5Compiler compiler = new Perl5Compiler(); 
116             PATTERN = compiler.compile(PATTERN_STRING, Perl5Compiler.READ_ONLY_MASK);
117         } catch(MalformedPatternException mpe) {
118             //this should not happen as the pattern string is hardcoded.
119             System.err.println ("Malformed pattern: " + PATTERN_STRING);
120             mpe.printStackTrace (System.err);
121         }
122         //add allowed units and their respective multiplier
123         MULTIPLIERS.put ("msec", new Integer (1));
124         MULTIPLIERS.put ("msecs", new Integer (1));
125         MULTIPLIERS.put ("sec",  new Integer (1000));
126         MULTIPLIERS.put ("secs",  new Integer (1000));
127         MULTIPLIERS.put ("minute", new Integer (1000*60));
128         MULTIPLIERS.put ("minutes", new Integer (1000*60));
129         MULTIPLIERS.put ("hour", new Integer (1000*60*60));
130         MULTIPLIERS.put ("hours", new Integer (1000*60*60));
131         MULTIPLIERS.put ("day", new Integer (1000*60*60*24));
132         MULTIPLIERS.put ("days", new Integer (1000*60*60*24));
133     }
134     
135     /***
136      * This filter is used in the accept call to the spool.
137      * It will select the next mail ready for processing according to the mails
138      * retrycount and lastUpdated time
139      **/
140     private class MultipleDelayFilter implements SpoolRepository.AcceptFilter
141     {
142         /***
143          * holds the time to wait for the youngest mail to get ready for processing
144          **/
145         long youngest = 0;
146 
147         /***
148          * Uses the getNextDelay to determine if a mail is ready for processing based on the delivered parameters
149          * errorMessage (which holds the retrycount), lastUpdated and state
150          * @param key the name/key of the message
151          * @param state the mails state
152          * @param lastUpdated the mail was last written to the spool at this time.
153          * @param errorMessage actually holds the retrycount as a string (see failMessage below)
154          **/
155         public boolean accept (String key, String state, long lastUpdated, String errorMessage) {
156             if (state.equals(Mail.ERROR)) {
157                 //Test the time...
158                 int retries = Integer.parseInt(errorMessage);
159                 
160                 // If the retries count is 0 we should try to send the mail now!
161                 if (retries == 0) return true;
162                 
163                 long delay = getNextDelay (retries);
164                 long timeToProcess = delay + lastUpdated;
165 
166                 
167                 if (System.currentTimeMillis() > timeToProcess) {
168                     //We're ready to process this again
169                     return true;
170                 } else {
171                     //We're not ready to process this.
172                     if (youngest == 0 || youngest > timeToProcess) {
173                         //Mark this as the next most likely possible mail to process
174                         youngest = timeToProcess;
175                     }
176                     return false;
177                 }
178             } else {
179                 //This mail is good to go... return the key
180                 return true;
181             }
182         }
183 
184         /***
185          * @return the optimal time the SpoolRepository.accept(AcceptFilter) method should wait before
186          * trying to find a mail ready for processing again.
187          **/
188         public long getWaitTime () {
189             if (youngest == 0) {
190                 return 0;
191             } else {
192                 long duration = youngest - System.currentTimeMillis();
193                 youngest = 0; //get ready for next run
194                 return duration <= 0 ? 1 : duration;
195             }
196         }
197     }
198 
199     /***
200      * Controls certain log messages
201      */
202     private boolean isDebug = false;
203 
204     private SpoolRepository outgoing; // The spool of outgoing mail
205     private long[] delayTimes; //holds expanded delayTimes
206     private int maxRetries = 5; // default number of retries
207     private long smtpTimeout = 180000;  //default number of ms to timeout on smtp delivery
208     private boolean sendPartial = false; // If false then ANY address errors will cause the transmission to fail
209     private int connectionTimeout = 60000;  // The amount of time JavaMail will wait before giving up on a socket connect()
210     private int deliveryThreadCount = 1; // default number of delivery threads
211     private Collection gatewayServer = null; // the server(s) to send all email to
212     private String authUser = null; // auth for gateway server
213     private String authPass = null; // password for gateway server
214     private String bindAddress = null; // JavaMail delivery socket binds to this local address. If null the JavaMail default will be used.
215     private boolean isBindUsed = false; // true, if the bind configuration
216                                         // parameter is supplied, RemoteDeliverySocketFactory
217                                         // will be used in this case
218     private Collection deliveryThreads = new Vector();
219     private volatile boolean destroyed = false; //Flag that the run method will check and end itself if set to true
220     private String bounceProcessor = null; // the processor for creating Bounces
221 
222     private Perl5Matcher delayTimeMatcher; //matcher use at init time to parse delaytime parameters
223     private MultipleDelayFilter delayFilter = new MultipleDelayFilter ();//used by accept to selcet the next mail ready for processing
224 
225     
226     /***
227      * Initialize the mailet
228      */
229     public void init() throws MessagingException {
230         isDebug = (getInitParameter("debug") == null) ? false : new Boolean(getInitParameter("debug")).booleanValue();
231         ArrayList delay_times_list = new ArrayList();
232         try {
233             if (getInitParameter("delayTime") != null) {
234                 delayTimeMatcher = new Perl5Matcher();
235                 String delay_times = getInitParameter("delayTime");
236                 //split on comma's
237                 StringTokenizer st = new StringTokenizer (delay_times,",");
238                 while (st.hasMoreTokens()) {
239                     String delay_time = st.nextToken();
240                     delay_times_list.add (new Delay(delay_time));
241                 }
242             } else {
243                 //use default delayTime.
244                 delay_times_list.add (new Delay());
245             }
246         } catch (Exception e) {
247             log("Invalid delayTime setting: " + getInitParameter("delayTime"));
248         }
249         try {
250             if (getInitParameter("maxRetries") != null) {
251                 maxRetries = Integer.parseInt(getInitParameter("maxRetries"));
252             }
253             //check consistency with delay_times_list attempts
254             int total_attempts = calcTotalAttempts (delay_times_list);
255             if (total_attempts > maxRetries) {
256                 log("Total number of delayTime attempts exceeds maxRetries specified. Increasing maxRetries from "+maxRetries+" to "+total_attempts);
257                 maxRetries = total_attempts;
258             } else {
259                 int extra = maxRetries - total_attempts;
260                 if (extra != 0) {
261                     log("maxRetries is larger than total number of attempts specified. Increasing last delayTime with "+extra+" attempts ");
262 
263                     if (delay_times_list.size() != 0) { 
264                         Delay delay = (Delay)delay_times_list.get (delay_times_list.size()-1); //last Delay
265                         delay.setAttempts (delay.getAttempts()+extra);
266                         log("Delay of "+delay.getDelayTime()+" msecs is now attempted: "+delay.getAttempts()+" times");
267                     } else {
268                         log ("NO, delaytimes cannot continue");
269                     }
270                 }
271             }
272             delayTimes = expandDelays (delay_times_list);
273             
274         } catch (Exception e) {
275             log("Invalid maxRetries setting: " + getInitParameter("maxRetries"));
276         }
277         try {
278             if (getInitParameter("timeout") != null) {
279                 smtpTimeout = Integer.parseInt(getInitParameter("timeout"));
280             }
281         } catch (Exception e) {
282             log("Invalid timeout setting: " + getInitParameter("timeout"));
283         }
284 
285         try {
286             if (getInitParameter("connectiontimeout") != null) {
287                 connectionTimeout = Integer.parseInt(getInitParameter("connectiontimeout"));
288             }
289         } catch (Exception e) {
290             log("Invalid timeout setting: " + getInitParameter("timeout"));
291         }
292         sendPartial = (getInitParameter("sendpartial") == null) ? false : new Boolean(getInitParameter("sendpartial")).booleanValue();
293 
294         bounceProcessor = getInitParameter("bounceProcessor");
295 
296         String gateway = getInitParameter("gateway");
297         String gatewayPort = getInitParameter("gatewayPort");
298 
299         if (gateway != null) {
300             gatewayServer = new ArrayList();
301             StringTokenizer st = new StringTokenizer(gateway, ",") ;
302             while (st.hasMoreTokens()) {
303                 String server = st.nextToken().trim() ;
304                 if (server.indexOf(':') < 0 && gatewayPort != null) {
305                     server += ":";
306                     server += gatewayPort;
307                 }
308 
309                 if (isDebug) log("Adding SMTP gateway: " + server) ;
310                 gatewayServer.add(server);
311             }
312             authUser = getInitParameter("gatewayusername");
313             authPass = getInitParameter("gatewayPassword");
314         }
315 
316         ServiceManager compMgr = (ServiceManager)getMailetContext().getAttribute(Constants.AVALON_COMPONENT_MANAGER);
317         String outgoingPath = getInitParameter("outgoing");
318         if (outgoingPath == null) {
319             outgoingPath = "file:///../var/mail/outgoing";
320         }
321 
322         try {
323             // Instantiate the a MailRepository for outgoing mails
324             Store mailstore = (Store) compMgr.lookup(Store.ROLE);
325 
326             DefaultConfiguration spoolConf
327                 = new DefaultConfiguration("repository", "generated:RemoteDelivery.java");
328             spoolConf.setAttribute("destinationURL", outgoingPath);
329             spoolConf.setAttribute("type", "SPOOL");
330             outgoing = (SpoolRepository) mailstore.select(spoolConf);
331         } catch (ServiceException cnfe) {
332             log("Failed to retrieve Store component:" + cnfe.getMessage());
333         } catch (Exception e) {
334             log("Failed to retrieve Store component:" + e.getMessage());
335         }
336 
337         //Start up a number of threads
338         try {
339             deliveryThreadCount = Integer.parseInt(getInitParameter("deliveryThreads"));
340         } catch (Exception e) {
341         }
342         for (int i = 0; i < deliveryThreadCount; i++) {
343             StringBuffer nameBuffer =
344                 new StringBuffer(32)
345                         .append("Remote delivery thread (")
346                         .append(i)
347                         .append(")");
348             Thread t = new Thread(this, nameBuffer.toString());
349             t.start();
350             deliveryThreads.add(t);
351         }
352 
353         bindAddress = getInitParameter("bind");
354         isBindUsed = bindAddress != null;
355         try {
356             if (isBindUsed) RemoteDeliverySocketFactory.setBindAdress(bindAddress);
357         } catch (UnknownHostException e) {
358             log("Invalid bind setting (" + bindAddress + "): " + e.toString());
359         }
360     }
361 
362     /*
363      * private method to log the extended SendFailedException introduced in JavaMail 1.3.2.
364      */
365     private void logSendFailedException(SendFailedException sfe) {
366         if (isDebug) {
367             MessagingException me = sfe;
368             if (me instanceof SMTPSendFailedException) {
369                 SMTPSendFailedException ssfe = (SMTPSendFailedException)me;
370                 log("SMTP SEND FAILED:");
371                 log(ssfe.toString());
372                 log("  Command: " + ssfe.getCommand());
373                 log("  RetCode: " + ssfe.getReturnCode());
374                 log("  Response: " + ssfe.getMessage());
375             } else {
376                 log("Send failed: " + me.toString());
377             }
378             Exception ne;
379             while ((ne = me.getNextException()) != null && ne instanceof MessagingException) {
380                 me = (MessagingException)ne;
381                 if (me instanceof SMTPAddressFailedException) {
382                     SMTPAddressFailedException e = (SMTPAddressFailedException)me;
383                     log("ADDRESS FAILED:");
384                     log(e.toString());
385                     log("  Address: " + e.getAddress());
386                     log("  Command: " + e.getCommand());
387                     log("  RetCode: " + e.getReturnCode());
388                     log("  Response: " + e.getMessage());
389                 } else if (me instanceof SMTPAddressSucceededException) {
390                     log("ADDRESS SUCCEEDED:");
391                     SMTPAddressSucceededException e = (SMTPAddressSucceededException)me;
392                     log(e.toString());
393                     log("  Address: " + e.getAddress());
394                     log("  Command: " + e.getCommand());
395                     log("  RetCode: " + e.getReturnCode());
396                     log("  Response: " + e.getMessage());
397                 }
398             }
399         }
400     }
401 
402     /***
403      * We can assume that the recipients of this message are all going to the same
404      * mail server.  We will now rely on the DNS server to do DNS MX record lookup
405      * and try to deliver to the multiple mail servers.  If it fails, it should
406      * throw an exception.
407      *
408      * Creation date: (2/24/00 11:25:00 PM)
409      * @param mail org.apache.james.core.MailImpl
410      * @param session javax.mail.Session
411      * @return boolean Whether the delivery was successful and the message can be deleted
412      */
413     private boolean deliver(Mail mail, Session session) {
414         try {
415             if (isDebug) {
416                 log("Attempting to deliver " + mail.getName());
417             }
418             MimeMessage message = mail.getMessage();
419 
420             //Create an array of the recipients as InternetAddress objects
421             Collection recipients = mail.getRecipients();
422             InternetAddress addr[] = new InternetAddress[recipients.size()];
423             int j = 0;
424             for (Iterator i = recipients.iterator(); i.hasNext(); j++) {
425                 MailAddress rcpt = (MailAddress)i.next();
426                 addr[j] = rcpt.toInternetAddress();
427             }
428 
429             if (addr.length <= 0) {
430                 log("No recipients specified... not sure how this could have happened.");
431                 return true;
432             }
433 
434             //Figure out which servers to try to send to.  This collection
435             //  will hold all the possible target servers
436             Iterator targetServers = null;
437             if (gatewayServer == null) {
438                 MailAddress rcpt = (MailAddress) recipients.iterator().next();
439                 String host = rcpt.getHost();
440 
441                 //Lookup the possible targets
442                 targetServers = getMailetContext().getSMTPHostAddresses(host);
443                 if (!targetServers.hasNext()) {
444                     log("No mail server found for: " + host);
445                     StringBuffer exceptionBuffer =
446                         new StringBuffer(128)
447                         .append("There are no DNS entries for the hostname ")
448                         .append(host)
449                         .append(".  I cannot determine where to send this message.");
450                     return failMessage(mail, new MessagingException(exceptionBuffer.toString()), false);
451                 }
452             } else {
453                 targetServers = getGatewaySMTPHostAddresses(gatewayServer);
454             }
455 
456             MessagingException lastError = null;
457 
458             while ( targetServers.hasNext()) {
459                 try {
460                     HostAddress outgoingMailServer = (HostAddress) targetServers.next();
461                     StringBuffer logMessageBuffer =
462                         new StringBuffer(256)
463                         .append("Attempting delivery of ")
464                         .append(mail.getName())
465                         .append(" to host ")
466                         .append(outgoingMailServer.getHostName())
467                         .append(" at ")
468                         .append(outgoingMailServer.getHost())
469                         .append(" for addresses ")
470                         .append(Arrays.asList(addr));
471                     log(logMessageBuffer.toString());
472 
473                     Properties props = session.getProperties();
474                     if (mail.getSender() == null) {
475                         props.put("mail.smtp.from", "<>");
476                     } else {
477                         String sender = mail.getSender().toString();
478                         props.put("mail.smtp.from", sender);
479                     }
480 
481                     //Many of these properties are only in later JavaMail versions
482                     //"mail.smtp.ehlo"  //default true
483                     //"mail.smtp.auth"  //default false
484                     //"mail.smtp.dsn.ret"  //default to nothing... appended as RET= after MAIL FROM line.
485                     //"mail.smtp.dsn.notify" //default to nothing...appended as NOTIFY= after RCPT TO line.
486 
487                     Transport transport = null;
488                     try {
489                         transport = session.getTransport(outgoingMailServer);
490                         try {
491                             if (authUser != null) {
492                                 transport.connect(outgoingMailServer.getHostName(), authUser, authPass);
493                             } else {
494                                 transport.connect();
495                             }
496                         } catch (MessagingException me) {
497                             // Any error on connect should cause the mailet to attempt
498                             // to connect to the next SMTP server associated with this
499                             // MX record.  Just log the exception.  We'll worry about
500                             // failing the message at the end of the loop.
501                             log(me.getMessage());
502                             continue;
503                         }
504                         // if the transport is a SMTPTransport (from sun) some
505                         // performance enhancement can be done.
506                         if (transport instanceof SMTPTransport)  {
507                             SMTPTransport smtpTransport = (SMTPTransport) transport;
508                            
509                             // if the message is alredy 8bit or binary and the
510                             // server doesn't support the 8bit extension it has
511                             // to be converted to 7bit. Javamail api doesn't perform
512                             // that conversion, but it is required to be a
513                             // rfc-compliant smtp server.
514                             
515                             // Temporarily disabled. See JAMES-638
516                             /*
517                             if (!smtpTransport.supportsExtension("8BITMIME")) { 
518                                 try {
519                                     convertTo7Bit(message);
520                                 } catch (IOException e) {
521                                     // An error has occured during the 7bit conversion.
522                                     // The error is logged and the message is sent anyway.
523                                     
524                                     log("Error during the conversion to 7 bit.", e);
525                                 }
526                             }
527                             */
528                             
529                             /*
530                              * Workaround for a javamail 1.3.2 bug: if
531                              * a message is sent without encoding information
532                              * and the 8bit allow property is set an exception
533                              * is trown during the mail delivery.
534                              */
535                             
536                             try {
537                                 setEncodingIfMissing(message);
538                             } catch (IOException e) {
539                                 log("Error while adding encoding information to the message", e);
540                             }
541                         } else {
542                             // If the transport is not the one
543                             // developed by Sun we are not sure of how it
544                             // handles the 8 bit mime stuff,
545                             // so I convert the message to 7bit.
546                             try {
547                                 convertTo7Bit(message);
548                             } catch (IOException e) {
549                                 log("Error during the conversion to 7 bit.", e);
550                             }
551                         }
552                         transport.sendMessage(message, addr);
553                     } finally {
554                         if (transport != null) {
555                             transport.close();
556                             transport = null;
557                         }
558                     }
559                     logMessageBuffer =
560                                       new StringBuffer(256)
561                                       .append("Mail (")
562                                       .append(mail.getName())
563                                       .append(") sent successfully to ")
564                                       .append(outgoingMailServer.getHostName())
565                                       .append(" at ")
566                                       .append(outgoingMailServer.getHost())
567                                       .append(" for ")
568                                       .append(mail.getRecipients());
569                     log(logMessageBuffer.toString());
570                     return true;
571                 } catch (SendFailedException sfe) {
572                     logSendFailedException(sfe);
573 
574                     if (sfe.getValidSentAddresses() != null) {
575                         Address[] validSent = sfe.getValidSentAddresses();
576                         if (validSent.length > 0) {
577                             StringBuffer logMessageBuffer =
578                                 new StringBuffer(256)
579                                 .append("Mail (")
580                                 .append(mail.getName())
581                                 .append(") sent successfully for ")
582                                 .append(Arrays.asList(validSent));
583                             log(logMessageBuffer.toString());
584                         }
585                     }
586 
587                     /* SMTPSendFailedException introduced in JavaMail 1.3.2, and provides detailed protocol reply code for the operation */
588                     if (sfe instanceof SMTPSendFailedException) {
589                         SMTPSendFailedException ssfe = (SMTPSendFailedException) sfe;
590                         // if 5xx, terminate this delivery attempt by re-throwing the exception.
591                         if (ssfe.getReturnCode() >= 500 && ssfe.getReturnCode() <= 599) throw sfe;
592                     }
593 
594                     if (sfe.getValidUnsentAddresses() != null
595                         && sfe.getValidUnsentAddresses().length > 0) {
596                         if (isDebug) log("Send failed, " + sfe.getValidUnsentAddresses().length + " valid addresses remain, continuing with any other servers");
597                         lastError = sfe;
598                         continue;
599                     } else {
600                         // There are no valid addresses left to send, so rethrow
601                         throw sfe;
602                     }
603                 } catch (MessagingException me) {
604                     //MessagingException are horribly difficult to figure out what actually happened.
605                     StringBuffer exceptionBuffer =
606                         new StringBuffer(256)
607                         .append("Exception delivering message (")
608                         .append(mail.getName())
609                         .append(") - ")
610                         .append(me.getMessage());
611                     log(exceptionBuffer.toString());
612                     if ((me.getNextException() != null) &&
613                           (me.getNextException() instanceof java.io.IOException)) {
614                         //This is more than likely a temporary failure
615 
616                         // If it's an IO exception with no nested exception, it's probably
617                         // some socket or weird I/O related problem.
618                         lastError = me;
619                         continue;
620                     }
621                     // This was not a connection or I/O error particular to one
622                     // SMTP server of an MX set.  Instead, it is almost certainly
623                     // a protocol level error.  In this case we assume that this
624                     // is an error we'd encounter with any of the SMTP servers
625                     // associated with this MX record, and we pass the exception
626                     // to the code in the outer block that determines its severity.
627                     throw me;
628                 }
629             } // end while
630             //If we encountered an exception while looping through,
631             //throw the last MessagingException we caught.  We only
632             //do this if we were unable to send the message to any
633             //server.  If sending eventually succeeded, we exit
634             //deliver() though the return at the end of the try
635             //block.
636             if (lastError != null) {
637                 throw lastError;
638             }
639         } catch (SendFailedException sfe) {
640         logSendFailedException(sfe);
641 
642             Collection recipients = mail.getRecipients();
643 
644             boolean deleteMessage = false;
645 
646             /*
647              * If you send a message that has multiple invalid
648              * addresses, you'll get a top-level SendFailedException
649              * that that has the valid, valid-unsent, and invalid
650              * address lists, with all of the server response messages
651              * will be contained within the nested exceptions.  [Note:
652              * the content of the nested exceptions is implementation
653              * dependent.]
654              *
655              * sfe.getInvalidAddresses() should be considered permanent.
656              * sfe.getValidUnsentAddresses() should be considered temporary.
657              *
658              * JavaMail v1.3 properly populates those collections based
659              * upon the 4xx and 5xx response codes to RCPT TO.  Some
660              * servers, such as Yahoo! don't respond to the RCPT TO,
661              * and provide a 5xx reply after DATA.  In that case, we
662              * will pick up the failure from SMTPSendFailedException.
663              *
664              */
665 
666             /* SMTPSendFailedException introduced in JavaMail 1.3.2, and provides detailed protocol reply code for the operation */
667             if (sfe instanceof SMTPSendFailedException) {
668                 // If we got an SMTPSendFailedException, use its RetCode to determine default permanent/temporary failure
669                 SMTPSendFailedException ssfe = (SMTPSendFailedException) sfe;
670                 deleteMessage = (ssfe.getReturnCode() >= 500 && ssfe.getReturnCode() <= 599);
671             } else {
672                 // Sometimes we'll get a normal SendFailedException with nested SMTPAddressFailedException, so use the latter RetCode
673                 MessagingException me = sfe;
674                 Exception ne;
675                 while ((ne = me.getNextException()) != null && ne instanceof MessagingException) {
676                     me = (MessagingException)ne;
677                     if (me instanceof SMTPAddressFailedException) {
678                         SMTPAddressFailedException ssfe = (SMTPAddressFailedException)me;
679                         deleteMessage = (ssfe.getReturnCode() >= 500 && ssfe.getReturnCode() <= 599);
680                     }
681                 }
682             }
683 
684             // log the original set of intended recipients
685             if (isDebug) log("Recipients: " + recipients);
686 
687             if (sfe.getInvalidAddresses() != null) {
688                 Address[] address = sfe.getInvalidAddresses();
689                 if (address.length > 0) {
690                     recipients.clear();
691                     for (int i = 0; i < address.length; i++) {
692                         try {
693                             recipients.add(new MailAddress(address[i].toString()));
694                         } catch (ParseException pe) {
695                             // this should never happen ... we should have
696                             // caught malformed addresses long before we
697                             // got to this code.
698                             log("Can't parse invalid address: " + pe.getMessage());
699                         }
700                     }
701                     if (isDebug) log("Invalid recipients: " + recipients);
702                     deleteMessage = failMessage(mail, sfe, true);
703                 }
704             }
705 
706             if (sfe.getValidUnsentAddresses() != null) {
707                 Address[] address = sfe.getValidUnsentAddresses();
708                 if (address.length > 0) {
709                     recipients.clear();
710                     for (int i = 0; i < address.length; i++) {
711                         try {
712                             recipients.add(new MailAddress(address[i].toString()));
713                         } catch (ParseException pe) {
714                             // this should never happen ... we should have
715                             // caught malformed addresses long before we
716                             // got to this code.
717                             log("Can't parse unsent address: " + pe.getMessage());
718                         }
719                     }
720                     if (isDebug) log("Unsent recipients: " + recipients);
721                     if (sfe instanceof SMTPSendFailedException) {
722                         SMTPSendFailedException ssfe = (SMTPSendFailedException) sfe;
723                         deleteMessage = failMessage(mail, sfe, ssfe.getReturnCode() >= 500 && ssfe.getReturnCode() <= 599);
724                     } else {
725                         deleteMessage = failMessage(mail, sfe, false);
726                     }
727                 }
728             }
729 
730             return deleteMessage;
731         } catch (MessagingException ex) {
732             // We should do a better job checking this... if the failure is a general
733             // connect exception, this is less descriptive than more specific SMTP command
734             // failure... have to lookup and see what are the various Exception
735             // possibilities
736 
737             // Unable to deliver message after numerous tries... fail accordingly
738 
739             // We check whether this is a 5xx error message, which
740             // indicates a permanent failure (like account doesn't exist
741             // or mailbox is full or domain is setup wrong).
742             // We fail permanently if this was a 5xx error
743             return failMessage(mail, ex, ('5' == ex.getMessage().charAt(0)));
744         }
745 
746         /* If we get here, we've exhausted the loop of servers without
747          * sending the message or throwing an exception.  One case
748          * where this might happen is if we get a MessagingException on
749          * each transport.connect(), e.g., if there is only one server
750          * and we get a connect exception.
751          */
752         return failMessage(mail, new MessagingException("No mail server(s) available at this time."), false);
753     }
754 
755     /***
756      * Converts a message to 7 bit.
757      * 
758      * @param message
759      * @return
760      */
761     private void convertTo7Bit(MimePart part) throws MessagingException, IOException {
762         if (part.isMimeType("multipart/*")) {
763             MimeMultipart parts = (MimeMultipart) part.getContent();
764             int count = parts.getCount();
765             for (int i = 0; i < count; i++) {
766                 convertTo7Bit((MimePart)parts.getBodyPart(i));
767             }
768         } else {
769             if (part.isMimeType("text/*")) {
770                 part.setHeader("Content-Transfer-Encoding", "quoted-printable");
771                 part.addHeader("X-MIME-Autoconverted", "from 8bit to quoted-printable by "+getMailetContext().getServerInfo());
772             } else {
773                 // if the part doesn't contain text it will be base64 encoded.
774                 part.setHeader("Content-Transfer-Encoding", "base64");
775                 part.addHeader("X-MIME-Autoconverted", "from 8bit to base64 by "+getMailetContext().getServerInfo());
776             }
777         }
778     }
779     
780     /***
781      * Adds an encoding information to each text mime part. This is a workaround
782      * for a javamail 1.3.2 bug: if a message is sent without encoding
783      * information a null pointer exception is thrown during the message
784      * delivery.
785      * 
786      * @param part
787      * @throws MessagingException
788      * @throws IOException
789      */
790     private void setEncodingIfMissing(MimePart part) throws MessagingException, IOException {
791         if (part.isMimeType("text/*")) {
792             String enc = part.getEncoding();
793             if (enc == null) part.setHeader("Content-Transfer-Encoding", "7bit");
794         } else if (part.isMimeType("multipart/*")) {
795             Object content = part.getContent();
796             if (content instanceof MimeMultipart) {
797                 MimeMultipart parts = (MimeMultipart) content;
798                 int count = parts.getCount();
799                 for (int i = 0; i < count; i++) {
800                     setEncodingIfMissing((MimePart)parts.getBodyPart(i));
801                 }
802             }
803         }
804     }
805     
806     /***
807      * Insert the method's description here.
808      * Creation date: (2/25/00 1:14:18 AM)
809      * @param mail org.apache.james.core.MailImpl
810      * @param exception javax.mail.MessagingException
811      * @param boolean permanent
812      * @return boolean Whether the message failed fully and can be deleted
813      */
814     private boolean failMessage(Mail mail, MessagingException ex, boolean permanent) {
815         StringWriter sout = new StringWriter();
816         PrintWriter out = new PrintWriter(sout, true);
817         if (permanent) {
818             out.print("Permanent");
819         } else {
820             out.print("Temporary");
821         }
822         StringBuffer logBuffer =
823             new StringBuffer(64)
824                 .append(" exception delivering mail (")
825                 .append(mail.getName())
826                 .append(": ");
827         out.print(logBuffer.toString());
828         if (isDebug) ex.printStackTrace(out);
829         log(sout.toString());
830         if (!permanent) {
831             if (!mail.getState().equals(Mail.ERROR)) {
832                 mail.setState(Mail.ERROR);
833                 mail.setErrorMessage("0");
834                 mail.setLastUpdated(new Date());
835             }
836             int retries = Integer.parseInt(mail.getErrorMessage());
837             if (retries < maxRetries) {
838                 logBuffer =
839                     new StringBuffer(128)
840                             .append("Storing message ")
841                             .append(mail.getName())
842                             .append(" into outgoing after ")
843                             .append(retries)
844                             .append(" retries");
845                 log(logBuffer.toString());
846                 ++retries;
847                 mail.setErrorMessage(retries + "");
848                 mail.setLastUpdated(new Date());
849                 return false;
850             } else {
851                 logBuffer =
852                     new StringBuffer(128)
853                             .append("Bouncing message ")
854                             .append(mail.getName())
855                             .append(" after ")
856                             .append(retries)
857                             .append(" retries");
858                 log(logBuffer.toString());
859             }
860         }
861 
862         if (mail.getSender() == null) {
863             log("Null Sender: no bounce will be generated for " + mail.getName());
864             return true;
865         }
866 
867         if (bounceProcessor != null) {
868             // do the new DSN bounce
869             // setting attributes for DSN mailet
870             mail.setAttribute("delivery-error", ex);
871             mail.setState(bounceProcessor);
872             // re-insert the mail into the spool for getting it passed to the dsn-processor
873             MailetContext mc = getMailetContext();
874             try {
875                 mc.sendMail(mail);
876             } catch (MessagingException e) {
877                 // we shouldn't get an exception, because the mail was already processed
878                 log("Exception re-inserting failed mail: ", e);
879             }
880         } else {
881             // do an old style bounce
882             bounce(mail, ex);
883         }
884         return true;
885     }
886 
887     private void bounce(Mail mail, MessagingException ex) {
888         StringWriter sout = new StringWriter();
889         PrintWriter out = new PrintWriter(sout, true);
890         String machine = "[unknown]";
891         try {
892             InetAddress me = InetAddress.getLocalHost();
893             machine = me.getHostName();
894         } catch(Exception e){
895             machine = "[address unknown]";
896         }
897         StringBuffer bounceBuffer =
898             new StringBuffer(128)
899                     .append("Hi. This is the James mail server at ")
900                     .append(machine)
901                     .append(".");
902         out.println(bounceBuffer.toString());
903         out.println("I'm afraid I wasn't able to deliver your message to the following addresses.");
904         out.println("This is a permanent error; I've given up. Sorry it didn't work out.  Below");
905         out.println("I include the list of recipients and the reason why I was unable to deliver");
906         out.println("your message.");
907         out.println();
908         for (Iterator i = mail.getRecipients().iterator(); i.hasNext(); ) {
909             out.println(i.next());
910         }
911         if (ex.getNextException() == null) {
912             out.println(ex.getMessage().trim());
913         } else {
914             Exception ex1 = ex.getNextException();
915             if (ex1 instanceof SendFailedException) {
916                 out.println("Remote mail server told me: " + ex1.getMessage().trim());
917             } else if (ex1 instanceof UnknownHostException) {
918                 out.println("Unknown host: " + ex1.getMessage().trim());
919                 out.println("This could be a DNS server error, a typo, or a problem with the recipient's mail server.");
920             } else if (ex1 instanceof ConnectException) {
921                 //Already formatted as "Connection timed out: connect"
922                 out.println(ex1.getMessage().trim());
923             } else if (ex1 instanceof SocketException) {
924                 out.println("Socket exception: " + ex1.getMessage().trim());
925             } else {
926                 out.println(ex1.getMessage().trim());
927             }
928         }
929         out.println();
930 
931         log("Sending failure message " + mail.getName());
932         try {
933             getMailetContext().bounce(mail, sout.toString());
934         } catch (MessagingException me) {
935             log("Encountered unexpected messaging exception while bouncing message: " + me.getMessage());
936         } catch (Exception e) {
937             log("Encountered unexpected exception while bouncing message: " + e.getMessage());
938         }
939     }
940 
941     public String getMailetInfo() {
942         return "RemoteDelivery Mailet";
943     }
944 
945     /***
946      * For this message, we take the list of recipients, organize these into distinct
947      * servers, and duplicate the message for each of these servers, and then call
948      * the deliver (messagecontainer) method for each server-specific
949      * messagecontainer ... that will handle storing it in the outgoing queue if needed.
950      *
951      * @param mail org.apache.mailet.Mail
952      */
953     public void service(Mail mail) throws MessagingException{
954         // Do I want to give the internal key, or the message's Message ID
955         if (isDebug) {
956             log("Remotely delivering mail " + mail.getName());
957         }
958         Collection recipients = mail.getRecipients();
959 
960         if (gatewayServer == null) {
961             // Must first organize the recipients into distinct servers (name made case insensitive)
962             Hashtable targets = new Hashtable();
963             for (Iterator i = recipients.iterator(); i.hasNext();) {
964                 MailAddress target = (MailAddress)i.next();
965                 String targetServer = target.getHost().toLowerCase(Locale.US);
966                 Collection temp = (Collection)targets.get(targetServer);
967                 if (temp == null) {
968                     temp = new ArrayList();
969                     targets.put(targetServer, temp);
970                 }
971                 temp.add(target);
972             }
973 
974             //We have the recipients organized into distinct servers... put them into the
975             //delivery store organized like this... this is ultra inefficient I think...
976 
977             // Store the new message containers, organized by server, in the outgoing mail repository
978             String name = mail.getName();
979             for (Iterator i = targets.keySet().iterator(); i.hasNext(); ) {
980                 String host = (String) i.next();
981                 Collection rec = (Collection) targets.get(host);
982                 if (isDebug) {
983                     StringBuffer logMessageBuffer =
984                         new StringBuffer(128)
985                                 .append("Sending mail to ")
986                                 .append(rec)
987                                 .append(" on host ")
988                                 .append(host);
989                     log(logMessageBuffer.toString());
990                 }
991                 mail.setRecipients(rec);
992                 StringBuffer nameBuffer =
993                     new StringBuffer(128)
994                             .append(name)
995                             .append("-to-")
996                             .append(host);
997                 mail.setName(nameBuffer.toString());
998                 outgoing.store(mail);
999                 //Set it to try to deliver (in a separate thread) immediately (triggered by storage)
1000             }
1001         } else {
1002             // Store the mail unaltered for processing by the gateway server(s)
1003             if (isDebug) {
1004                 StringBuffer logMessageBuffer =
1005                     new StringBuffer(128)
1006                         .append("Sending mail to ")
1007                         .append(mail.getRecipients())
1008                         .append(" via ")
1009                         .append(gatewayServer);
1010                 log(logMessageBuffer.toString());
1011             }
1012 
1013              //Set it to try to deliver (in a separate thread) immediately (triggered by storage)
1014             outgoing.store(mail);
1015         }
1016         mail.setState(Mail.GHOST);
1017     }
1018 
1019     // Need to synchronize to get object monitor for notifyAll()
1020     public synchronized void destroy() {
1021         //Mark flag so threads from this mailet stop themselves
1022         destroyed = true;
1023         //Wake up all threads from waiting for an accept
1024         for (Iterator i = deliveryThreads.iterator(); i.hasNext(); ) {
1025             Thread t = (Thread)i.next();
1026             t.interrupt();
1027         }
1028         notifyAll();
1029     }
1030 
1031     /***
1032      * Handles checking the outgoing spool for new mail and delivering them if
1033      * there are any
1034      */
1035     public void run() {
1036 
1037         /* TODO: CHANGE ME!!! The problem is that we need to wait for James to
1038          * finish initializing.  We expect the HELLO_NAME to be put into
1039          * the MailetContext, but in the current configuration we get
1040          * started before the SMTP Server, which establishes the value.
1041          * Since there is no contractual guarantee that there will be a
1042          * HELLO_NAME value, we can't just wait for it.  As a temporary
1043          * measure, I'm inserting this philosophically unsatisfactory
1044          * fix.
1045          */
1046         long stop = System.currentTimeMillis() + 60000;
1047         while ((getMailetContext().getAttribute(Constants.HELLO_NAME) == null)
1048                && stop > System.currentTimeMillis()) {
1049             try {
1050                 Thread.sleep(1000);
1051             } catch (Exception ignored) {} // wait for James to finish initializing
1052         }
1053 
1054         //Checks the pool and delivers a mail message
1055         Properties props = new Properties();
1056         //Not needed for production environment
1057         props.put("mail.debug", "false");
1058         // Reactivated: javamail 1.3.2 should no more have problems with "250 OK"
1059         // messages (WAS "false": Prevents problems encountered with 250 OK Messages)
1060         props.put("mail.smtp.ehlo", "true");
1061         // By setting this property to true the transport is allowed to
1062         // send 8 bit data to the server (if it supports the 8bitmime extension).
1063         // 2006/03/01 reverted to false because of a javamail bug converting to 8bit
1064         // messages created by an inputstream.
1065         props.setProperty("mail.smtp.allow8bitmime", "false");
1066         //Sets timeout on going connections
1067         props.put("mail.smtp.timeout", smtpTimeout + "");
1068 
1069         props.put("mail.smtp.connectiontimeout", connectionTimeout + "");
1070         props.put("mail.smtp.sendpartial",String.valueOf(sendPartial));
1071 
1072         //Set the hostname we'll use as this server
1073         if (getMailetContext().getAttribute(Constants.HELLO_NAME) != null) {
1074             props.put("mail.smtp.localhost", getMailetContext().getAttribute(Constants.HELLO_NAME));
1075         }
1076         else {
1077             String defaultDomain = (String) getMailetContext().getAttribute(Constants.DEFAULT_DOMAIN);
1078             if (defaultDomain != null) {
1079                 props.put("mail.smtp.localhost", defaultDomain);
1080             }
1081         }
1082 
1083         if (isBindUsed) {
1084             // undocumented JavaMail 1.2 feature, smtp transport will use
1085             // our socket factory, which will also set the local address
1086             props.put("mail.smtp.socketFactory.class",
1087                       "org.apache.james.transport.mailets.RemoteDeliverySocketFactory");
1088             // Don't fallback to the standard socket factory on error, do throw an exception
1089             props.put("mail.smtp.socketFactory.fallback", "false");
1090         }
1091         
1092         if (authUser != null) {
1093             props.put("mail.smtp.auth","true");
1094         }
1095 
1096         Session session = Session.getInstance(props, null);
1097         try {
1098             while (!Thread.interrupted() && !destroyed) {
1099                 try {
1100                     Mail mail = (Mail)outgoing.accept(delayFilter);
1101                     String key = mail.getName();
1102                     try {
1103                         if (isDebug) {
1104                             StringBuffer logMessageBuffer =
1105                                 new StringBuffer(128)
1106                                         .append(Thread.currentThread().getName())
1107                                         .append(" will process mail ")
1108                                         .append(key);
1109                             log(logMessageBuffer.toString());
1110                         }
1111                         if (deliver(mail, session)) {
1112                             //Message was successfully delivered/fully failed... delete it
1113                             ContainerUtil.dispose(mail);
1114                             outgoing.remove(key);
1115                         } else {
1116                             //Something happened that will delay delivery.  Store any updates
1117                             outgoing.store(mail);
1118                             ContainerUtil.dispose(mail);
1119                             // This is an update, we have to unlock and notify or this mail
1120                             // is kept locked by this thread
1121                             outgoing.unlock(key);
1122                             // We do not notify because we updated an already existing mail
1123                             // and we are now free to handle more mails.
1124                             // Furthermore this mail should not be processed now because we
1125                             // have a retry time scheduling.
1126                         }
1127                         //Clear the object handle to make sure it recycles this object.
1128                         mail = null;
1129                     } catch (Exception e) {
1130                         // Prevent unexpected exceptions from causing looping by removing
1131                         // message from outgoing.
1132                         // DO NOT CHNANGE THIS to catch Error!  For example, if there were an OutOfMemory condition
1133                         // caused because something else in the server was abusing memory, we would not want to
1134                         // start purging the outgoing spool!
1135                         ContainerUtil.dispose(mail);
1136                         outgoing.remove(key);
1137                         throw e;
1138                     }
1139                 } catch (Throwable e) {
1140                     if (!destroyed) log("Exception caught in RemoteDelivery.run()", e);
1141                 }
1142             }
1143         } finally {
1144             // Restore the thread state to non-interrupted.
1145             Thread.interrupted();
1146         }
1147     }
1148 
1149     /***
1150      * @param list holding Delay objects
1151      * @return the total attempts for all delays
1152      **/
1153     private int calcTotalAttempts (ArrayList list) {
1154         int sum = 0;
1155         Iterator i = list.iterator();
1156         while (i.hasNext()) {
1157             Delay delay = (Delay)i.next();
1158             sum += delay.getAttempts();
1159         }
1160         return sum;
1161     }
1162     
1163     /***
1164      * This method expands an ArrayList containing Delay objects into an array holding the
1165      * only delaytime in the order.<p>
1166      * So if the list has 2 Delay objects the first having attempts=2 and delaytime 4000
1167      * the second having attempts=1 and delaytime=300000 will be expanded into this array:<p>
1168      * long[0] = 4000<p>
1169      * long[1] = 4000<p>
1170      * long[2] = 300000<p>
1171      * @param list the list to expand
1172      * @return the expanded list
1173      **/
1174     private long[] expandDelays (ArrayList list) {
1175         long[] delays = new long [calcTotalAttempts(list)];
1176         Iterator i = list.iterator();
1177         int idx = 0;
1178         while (i.hasNext()) {
1179             Delay delay = (Delay)i.next();
1180             for (int j=0; j<delay.getAttempts(); j++) {
1181                 delays[idx++]= delay.getDelayTime();
1182             }            
1183         }
1184         return delays;
1185     }
1186     
1187     /***
1188      * This method returns, given a retry-count, the next delay time to use.
1189      * @param retry_count the current retry_count.
1190      * @return the next delay time to use, given the retry count
1191      **/
1192     private long getNextDelay (int retry_count) {
1193         if (retry_count > delayTimes.length) {
1194             return DEFAULT_DELAY_TIME;
1195         } 
1196         return delayTimes[retry_count-1];
1197     }
1198 
1199     /***
1200      * This class is used to hold a delay time and its corresponding number
1201      * of retries.
1202      **/
1203     private class Delay {
1204         private int attempts = 1;
1205         private long delayTime = DEFAULT_DELAY_TIME;
1206         
1207             
1208         /***
1209          * This constructor expects Strings of the form "[attempt\*]delaytime[unit]". <p>
1210          * The optional attempt is the number of tries this delay should be used (default = 1)
1211          * The unit if present must be one of (msec,sec,minute,hour,day) (default = msec)
1212          * The constructor multiplies the delaytime by the relevant multiplier for the unit,
1213          * so the delayTime instance variable is always in msec.
1214          * @param init_string the string to initialize this Delay object from
1215          **/
1216         public Delay (String init_string) throws MessagingException
1217         {
1218             String unit = "msec"; //default unit
1219             if (delayTimeMatcher.matches (init_string, PATTERN)) {
1220                 MatchResult res = delayTimeMatcher.getMatch ();
1221                 //the capturing groups will now hold
1222                 //at 1:  attempts * (if present)
1223                 //at 2:  delaytime
1224                 //at 3:  unit (if present)
1225                 
1226                 if (res.group(1) != null && !res.group(1).equals ("")) {
1227                     //we have an attempt *
1228                     String attempt_match = res.group(1);
1229                     //strip the * and whitespace
1230                     attempt_match = attempt_match.substring (0,attempt_match.length()-1).trim();
1231                     attempts = Integer.parseInt (attempt_match);
1232                 }
1233                 
1234                 delayTime = Long.parseLong (res.group(2));
1235                 
1236                 if (!res.group(3).equals ("")) {
1237                     //we have a unit
1238                     unit = res.group(3).toLowerCase(Locale.US);
1239                 }
1240             } else {
1241                 throw new MessagingException(init_string+" does not match "+PATTERN_STRING);
1242             }
1243             if (MULTIPLIERS.get (unit)!=null) {
1244                 int multiplier = ((Integer)MULTIPLIERS.get (unit)).intValue();
1245                 delayTime *= multiplier;
1246             } else {
1247                 throw new MessagingException("Unknown unit: "+unit);
1248             }
1249         }
1250 
1251         /***
1252          * This constructor makes a default Delay object, ie. attempts=1 and delayTime=DEFAULT_DELAY_TIME
1253          **/
1254         public Delay () {
1255         }
1256 
1257         /***
1258          * @return the delayTime for this Delay
1259          **/
1260         public long getDelayTime () {
1261             return delayTime;
1262         }
1263 
1264         /***
1265          * @return the number attempts this Delay should be used.
1266          **/
1267         public int getAttempts () {
1268             return attempts;
1269         }
1270         
1271         /***
1272          * Set the number attempts this Delay should be used.
1273          **/
1274         public void setAttempts (int value) {
1275             attempts = value;
1276         }
1277         
1278         /***
1279          * Pretty prints this Delay 
1280          **/
1281         public String toString () {
1282             StringBuffer buf = new StringBuffer(15);
1283             buf.append (getAttempts ());
1284             buf.append ('*');
1285             buf.append (getDelayTime());
1286             buf.append ("msec");
1287             return buf.toString();
1288         }
1289     }
1290     
1291     /*
1292      * Returns an Iterator over org.apache.mailet.HostAddress, a
1293      * specialized subclass of javax.mail.URLName, which provides
1294      * location information for servers that are specified as mail
1295      * handlers for the given hostname.  If no host is found, the
1296      * Iterator returned will be empty and the first call to hasNext()
1297      * will return false.  The Iterator is a nested iterator: the outer
1298      * iteration is over each gateway, and the inner iteration is over
1299      * potentially multiple A records for each gateway.
1300      *
1301      * @see org.apache.james.DNSServer#getSMTPHostAddresses(String)
1302      * @since v2.2.0a16-unstable
1303      * @param gatewayServers - Collection of host[:port] Strings
1304      * @return an Iterator over HostAddress instances, sorted by priority
1305      */
1306     private Iterator getGatewaySMTPHostAddresses(final Collection gatewayServers) {
1307         return new Iterator() {
1308             private Iterator gateways = gatewayServers.iterator();
1309             private Iterator addresses = null;
1310 
1311             public boolean hasNext() {
1312                 /* Make sure that when next() is called, that we can
1313                  * provide a HostAddress.  This means that we need to
1314                  * have an inner iterator, and verify that it has
1315                  * addresses.  We could, for example, run into a
1316                  * situation where the next gateway didn't have any
1317                  * valid addresses.
1318                  */
1319                 if (!hasNextAddress() && gateways.hasNext()) {
1320                     do {
1321                         String server = (String) gateways.next();
1322                         String port = "25";
1323 
1324                         int idx = server.indexOf(':');
1325                         if ( idx > 0) {
1326                             port = server.substring(idx+1);
1327                             server = server.substring(0,idx);
1328                         }
1329 
1330                         final String nextGateway = server;
1331                         final String nextGatewayPort = port;
1332                         try {
1333                             final InetAddress[] ips = org.apache.james.dnsserver.DNSServer.getAllByName(nextGateway);
1334                             addresses = new Iterator() {
1335                                 private InetAddress[] ipAddresses = ips;
1336                                 int i = 0;
1337 
1338                                 public boolean hasNext() {
1339                                     return i < ipAddresses.length;
1340                                 }
1341 
1342                                 public Object next() {
1343                                     return new org.apache.mailet.HostAddress(nextGateway, "smtp://" + (ipAddresses[i++]).getHostAddress() + ":" + nextGatewayPort);
1344                                 }
1345 
1346                                 public void remove() {
1347                                     throw new UnsupportedOperationException ("remove not supported by this iterator");
1348                                 }
1349                             };
1350                         }
1351                         catch (java.net.UnknownHostException uhe) {
1352                             log("Unknown gateway host: " + uhe.getMessage().trim());
1353                             log("This could be a DNS server error or configuration error.");
1354                         }
1355                     } while (!hasNextAddress() && gateways.hasNext());
1356                 } 
1357 
1358                 return hasNextAddress();
1359             }
1360 
1361             private boolean hasNextAddress() {
1362                 return addresses != null && addresses.hasNext();
1363             }
1364 
1365             public Object next() {
1366                 return (addresses != null) ? addresses.next() : null;
1367             }
1368 
1369             public void remove() {
1370                 throw new UnsupportedOperationException ("remove not supported by this iterator");
1371             }
1372         };
1373     }
1374 }