View Javadoc

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