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: 767519 $ $Date: 2009-04-22 14:37:53 +0100 (Wed, 22 Apr 2009) $
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                             try
566                             {
567                               // James-899: transport.close() sends QUIT to the server; if that fails
568                               // (e.g. because the server has already closed the connection) the message
569                               // should be considered to be delivered because the error happened outside
570                               // of the mail transaction (MAIL, RCPT, DATA). 
571                               transport.close();
572                             }
573                             catch (MessagingException e)
574                             {
575                               log("Warning: could not close the SMTP transport after sending mail (" + mail.getName()
576                                   + ") to " + outgoingMailServer.getHostName() + " at " + outgoingMailServer.getHost()
577                                   + " for " + mail.getRecipients() + "; probably the server has already closed the "
578                                   + "connection. Message is considered to be delivered. Exception: " + e.getMessage());
579                             }
580                             transport = null;
581                         }
582                     }
583                     logMessageBuffer =
584                                       new StringBuffer(256)
585                                       .append("Mail (")
586                                       .append(mail.getName())
587                                       .append(") sent successfully to ")
588                                       .append(outgoingMailServer.getHostName())
589                                       .append(" at ")
590                                       .append(outgoingMailServer.getHost())
591                                       .append(" for ")
592                                       .append(mail.getRecipients());
593                     log(logMessageBuffer.toString());
594                     return true;
595                 } catch (SendFailedException sfe) {
596                     logSendFailedException(sfe);
597 
598                     if (sfe.getValidSentAddresses() != null) {
599                         Address[] validSent = sfe.getValidSentAddresses();
600                         if (validSent.length > 0) {
601                             StringBuffer logMessageBuffer =
602                                 new StringBuffer(256)
603                                 .append("Mail (")
604                                 .append(mail.getName())
605                                 .append(") sent successfully for ")
606                                 .append(Arrays.asList(validSent));
607                             log(logMessageBuffer.toString());
608                         }
609                     }
610 
611                     /* SMTPSendFailedException introduced in JavaMail 1.3.2, and provides detailed protocol reply code for the operation */
612                     if (sfe instanceof SMTPSendFailedException) {
613                         SMTPSendFailedException ssfe = (SMTPSendFailedException) sfe;
614                         // if 5xx, terminate this delivery attempt by re-throwing the exception.
615                         if (ssfe.getReturnCode() >= 500 && ssfe.getReturnCode() <= 599) throw sfe;
616                     }
617 
618                     if (sfe.getValidUnsentAddresses() != null
619                         && sfe.getValidUnsentAddresses().length > 0) {
620                         if (isDebug) log("Send failed, " + sfe.getValidUnsentAddresses().length + " valid addresses remain, continuing with any other servers");
621                         lastError = sfe;
622                         continue;
623                     } else {
624                         // There are no valid addresses left to send, so rethrow
625                         throw sfe;
626                     }
627                 } catch (MessagingException me) {
628                     //MessagingException are horribly difficult to figure out what actually happened.
629                     StringBuffer exceptionBuffer =
630                         new StringBuffer(256)
631                         .append("Exception delivering message (")
632                         .append(mail.getName())
633                         .append(") - ")
634                         .append(me.getMessage());
635                     log(exceptionBuffer.toString());
636                     if ((me.getNextException() != null) &&
637                           (me.getNextException() instanceof java.io.IOException)) {
638                         //This is more than likely a temporary failure
639 
640                         // If it's an IO exception with no nested exception, it's probably
641                         // some socket or weird I/O related problem.
642                         lastError = me;
643                         continue;
644                     }
645                     // This was not a connection or I/O error particular to one
646                     // SMTP server of an MX set.  Instead, it is almost certainly
647                     // a protocol level error.  In this case we assume that this
648                     // is an error we'd encounter with any of the SMTP servers
649                     // associated with this MX record, and we pass the exception
650                     // to the code in the outer block that determines its severity.
651                     throw me;
652                 }
653             } // end while
654             //If we encountered an exception while looping through,
655             //throw the last MessagingException we caught.  We only
656             //do this if we were unable to send the message to any
657             //server.  If sending eventually succeeded, we exit
658             //deliver() though the return at the end of the try
659             //block.
660             if (lastError != null) {
661                 throw lastError;
662             }
663         } catch (SendFailedException sfe) {
664         logSendFailedException(sfe);
665 
666             Collection recipients = mail.getRecipients();
667 
668             boolean deleteMessage = false;
669 
670             /*
671              * If you send a message that has multiple invalid
672              * addresses, you'll get a top-level SendFailedException
673              * that that has the valid, valid-unsent, and invalid
674              * address lists, with all of the server response messages
675              * will be contained within the nested exceptions.  [Note:
676              * the content of the nested exceptions is implementation
677              * dependent.]
678              *
679              * sfe.getInvalidAddresses() should be considered permanent.
680              * sfe.getValidUnsentAddresses() should be considered temporary.
681              *
682              * JavaMail v1.3 properly populates those collections based
683              * upon the 4xx and 5xx response codes to RCPT TO.  Some
684              * servers, such as Yahoo! don't respond to the RCPT TO,
685              * and provide a 5xx reply after DATA.  In that case, we
686              * will pick up the failure from SMTPSendFailedException.
687              *
688              */
689 
690             /* SMTPSendFailedException introduced in JavaMail 1.3.2, and provides detailed protocol reply code for the operation */
691             if (sfe instanceof SMTPSendFailedException) {
692                 // If we got an SMTPSendFailedException, use its RetCode to determine default permanent/temporary failure
693                 SMTPSendFailedException ssfe = (SMTPSendFailedException) sfe;
694                 deleteMessage = (ssfe.getReturnCode() >= 500 && ssfe.getReturnCode() <= 599);
695             } else {
696                 // Sometimes we'll get a normal SendFailedException with nested SMTPAddressFailedException, so use the latter RetCode
697                 MessagingException me = sfe;
698                 Exception ne;
699                 while ((ne = me.getNextException()) != null && ne instanceof MessagingException) {
700                     me = (MessagingException)ne;
701                     if (me instanceof SMTPAddressFailedException) {
702                         SMTPAddressFailedException ssfe = (SMTPAddressFailedException)me;
703                         deleteMessage = (ssfe.getReturnCode() >= 500 && ssfe.getReturnCode() <= 599);
704                     }
705                 }
706             }
707 
708             // log the original set of intended recipients
709             if (isDebug) log("Recipients: " + recipients);
710 
711             if (sfe.getInvalidAddresses() != null) {
712                 Address[] address = sfe.getInvalidAddresses();
713                 if (address.length > 0) {
714                     recipients.clear();
715                     for (int i = 0; i < address.length; i++) {
716                         try {
717                             recipients.add(new MailAddress(address[i].toString()));
718                         } catch (ParseException pe) {
719                             // this should never happen ... we should have
720                             // caught malformed addresses long before we
721                             // got to this code.
722                             log("Can't parse invalid address: " + pe.getMessage());
723                         }
724                     }
725                     if (isDebug) log("Invalid recipients: " + recipients);
726                     deleteMessage = failMessage(mail, sfe, true);
727                 }
728             }
729 
730             if (sfe.getValidUnsentAddresses() != null) {
731                 Address[] address = sfe.getValidUnsentAddresses();
732                 if (address.length > 0) {
733                     recipients.clear();
734                     for (int i = 0; i < address.length; i++) {
735                         try {
736                             recipients.add(new MailAddress(address[i].toString()));
737                         } catch (ParseException pe) {
738                             // this should never happen ... we should have
739                             // caught malformed addresses long before we
740                             // got to this code.
741                             log("Can't parse unsent address: " + pe.getMessage());
742                         }
743                     }
744                     if (isDebug) log("Unsent recipients: " + recipients);
745                     if (sfe instanceof SMTPSendFailedException) {
746                         SMTPSendFailedException ssfe = (SMTPSendFailedException) sfe;
747                         deleteMessage = failMessage(mail, sfe, ssfe.getReturnCode() >= 500 && ssfe.getReturnCode() <= 599);
748                     } else {
749                         deleteMessage = failMessage(mail, sfe, false);
750                     }
751                 }
752             }
753 
754             return deleteMessage;
755         } catch (MessagingException ex) {
756             // We should do a better job checking this... if the failure is a general
757             // connect exception, this is less descriptive than more specific SMTP command
758             // failure... have to lookup and see what are the various Exception
759             // possibilities
760 
761             // Unable to deliver message after numerous tries... fail accordingly
762 
763             // We check whether this is a 5xx error message, which
764             // indicates a permanent failure (like account doesn't exist
765             // or mailbox is full or domain is setup wrong).
766             // We fail permanently if this was a 5xx error
767             return failMessage(mail, ex, ('5' == ex.getMessage().charAt(0)));
768         }
769 
770         /* If we get here, we've exhausted the loop of servers without
771          * sending the message or throwing an exception.  One case
772          * where this might happen is if we get a MessagingException on
773          * each transport.connect(), e.g., if there is only one server
774          * and we get a connect exception.
775          */
776         return failMessage(mail, new MessagingException("No mail server(s) available at this time."), false);
777     }
778 
779     /***
780      * Converts a message to 7 bit.
781      * 
782      * @param message
783      * @return
784      */
785     private void convertTo7Bit(MimePart part) throws MessagingException, IOException {
786         if (part.isMimeType("multipart/*")) {
787             MimeMultipart parts = (MimeMultipart) part.getContent();
788             int count = parts.getCount();
789             for (int i = 0; i < count; i++) {
790                 convertTo7Bit((MimePart)parts.getBodyPart(i));
791             }
792         } else if ("8bit".equals(part.getEncoding())) {
793             // The content may already be in encoded the form (likely with mail created from a
794             // stream).  In that case, just changing the encoding to quoted-printable will mangle 
795             // the result when this is transmitted.  We must first convert the content into its 
796             // native format, set it back, and only THEN set the transfer encoding to force the 
797             // content to be encoded appropriately.
798             
799             // if the part doesn't contain text it will be base64 encoded.
800             String contentTransferEncoding = part.isMimeType("text/*") ? "quoted-printable" : "base64";
801             part.setContent(part.getContent(), part.getContentType()); 
802             part.setHeader("Content-Transfer-Encoding", contentTransferEncoding);
803             part.addHeader("X-MIME-Autoconverted", "from 8bit to "+contentTransferEncoding+" by "+getMailetContext().getServerInfo());
804         }
805     }
806     
807     /***
808      * Adds an encoding information to each text mime part. This is a workaround
809      * for a javamail 1.3.2 bug: if a message is sent without encoding
810      * information a null pointer exception is thrown during the message
811      * delivery.
812      * 
813      * @param part
814      * @throws MessagingException
815      * @throws IOException
816      */
817     private void setEncodingIfMissing(MimePart part) throws MessagingException, IOException {
818         if (part.isMimeType("text/*")) {
819             String enc = part.getEncoding();
820             if (enc == null) part.setHeader("Content-Transfer-Encoding", "7bit");
821         } else if (part.isMimeType("multipart/*")) {
822             Object content = part.getContent();
823             if (content instanceof MimeMultipart) {
824                 MimeMultipart parts = (MimeMultipart) content;
825                 int count = parts.getCount();
826                 for (int i = 0; i < count; i++) {
827                     setEncodingIfMissing((MimePart)parts.getBodyPart(i));
828                 }
829             }
830         }
831     }
832     
833     /***
834      * Insert the method's description here.
835      * Creation date: (2/25/00 1:14:18 AM)
836      * @param mail org.apache.james.core.MailImpl
837      * @param exception javax.mail.MessagingException
838      * @param boolean permanent
839      * @return boolean Whether the message failed fully and can be deleted
840      */
841     private boolean failMessage(Mail mail, MessagingException ex, boolean permanent) {
842         StringWriter sout = new StringWriter();
843         PrintWriter out = new PrintWriter(sout, true);
844         if (permanent) {
845             out.print("Permanent");
846         } else {
847             out.print("Temporary");
848         }
849         StringBuffer logBuffer =
850             new StringBuffer(64)
851                 .append(" exception delivering mail (")
852                 .append(mail.getName())
853                 .append(": ");
854         out.print(logBuffer.toString());
855         if (isDebug) ex.printStackTrace(out);
856         log(sout.toString());
857         if (!permanent) {
858             if (!mail.getState().equals(Mail.ERROR)) {
859                 mail.setState(Mail.ERROR);
860                 mail.setErrorMessage("0");
861                 mail.setLastUpdated(new Date());
862             }
863             int retries = Integer.parseInt(mail.getErrorMessage());
864             if (retries < maxRetries) {
865                 logBuffer =
866                     new StringBuffer(128)
867                             .append("Storing message ")
868                             .append(mail.getName())
869                             .append(" into outgoing after ")
870                             .append(retries)
871                             .append(" retries");
872                 log(logBuffer.toString());
873                 ++retries;
874                 mail.setErrorMessage(retries + "");
875                 mail.setLastUpdated(new Date());
876                 return false;
877             } else {
878                 logBuffer =
879                     new StringBuffer(128)
880                             .append("Bouncing message ")
881                             .append(mail.getName())
882                             .append(" after ")
883                             .append(retries)
884                             .append(" retries");
885                 log(logBuffer.toString());
886             }
887         }
888 
889         if (mail.getSender() == null) {
890             log("Null Sender: no bounce will be generated for " + mail.getName());
891             return true;
892         }
893 
894         if (bounceProcessor != null) {
895             // do the new DSN bounce
896             // setting attributes for DSN mailet
897             mail.setAttribute("delivery-error", ex);
898             mail.setState(bounceProcessor);
899             // re-insert the mail into the spool for getting it passed to the dsn-processor
900             MailetContext mc = getMailetContext();
901             try {
902                 mc.sendMail(mail);
903             } catch (MessagingException e) {
904                 // we shouldn't get an exception, because the mail was already processed
905                 log("Exception re-inserting failed mail: ", e);
906             }
907         } else {
908             // do an old style bounce
909             bounce(mail, ex);
910         }
911         return true;
912     }
913 
914     private void bounce(Mail mail, MessagingException ex) {
915         StringWriter sout = new StringWriter();
916         PrintWriter out = new PrintWriter(sout, true);
917         String machine = "[unknown]";
918         try {
919             InetAddress me = InetAddress.getLocalHost();
920             machine = me.getHostName();
921         } catch(Exception e){
922             machine = "[address unknown]";
923         }
924         StringBuffer bounceBuffer =
925             new StringBuffer(128)
926                     .append("Hi. This is the James mail server at ")
927                     .append(machine)
928                     .append(".");
929         out.println(bounceBuffer.toString());
930         out.println("I'm afraid I wasn't able to deliver your message to the following addresses.");
931         out.println("This is a permanent error; I've given up. Sorry it didn't work out.  Below");
932         out.println("I include the list of recipients and the reason why I was unable to deliver");
933         out.println("your message.");
934         out.println();
935         for (Iterator i = mail.getRecipients().iterator(); i.hasNext(); ) {
936             out.println(i.next());
937         }
938         if (ex.getNextException() == null) {
939             out.println(ex.getMessage().trim());
940         } else {
941             Exception ex1 = ex.getNextException();
942             if (ex1 instanceof SendFailedException) {
943                 out.println("Remote mail server told me: " + ex1.getMessage().trim());
944             } else if (ex1 instanceof UnknownHostException) {
945                 out.println("Unknown host: " + ex1.getMessage().trim());
946                 out.println("This could be a DNS server error, a typo, or a problem with the recipient's mail server.");
947             } else if (ex1 instanceof ConnectException) {
948                 //Already formatted as "Connection timed out: connect"
949                 out.println(ex1.getMessage().trim());
950             } else if (ex1 instanceof SocketException) {
951                 out.println("Socket exception: " + ex1.getMessage().trim());
952             } else {
953                 out.println(ex1.getMessage().trim());
954             }
955         }
956         out.println();
957 
958         log("Sending failure message " + mail.getName());
959         try {
960             getMailetContext().bounce(mail, sout.toString());
961         } catch (MessagingException me) {
962             log("Encountered unexpected messaging exception while bouncing message: " + me.getMessage());
963         } catch (Exception e) {
964             log("Encountered unexpected exception while bouncing message: " + e.getMessage());
965         }
966     }
967 
968     public String getMailetInfo() {
969         return "RemoteDelivery Mailet";
970     }
971 
972     /***
973      * For this message, we take the list of recipients, organize these into distinct
974      * servers, and duplicate the message for each of these servers, and then call
975      * the deliver (messagecontainer) method for each server-specific
976      * messagecontainer ... that will handle storing it in the outgoing queue if needed.
977      *
978      * @param mail org.apache.mailet.Mail
979      */
980     public void service(Mail mail) throws MessagingException{
981         // Do I want to give the internal key, or the message's Message ID
982         if (isDebug) {
983             log("Remotely delivering mail " + mail.getName());
984         }
985         Collection recipients = mail.getRecipients();
986 
987         if (gatewayServer == null) {
988             // Must first organize the recipients into distinct servers (name made case insensitive)
989             Hashtable targets = new Hashtable();
990             for (Iterator i = recipients.iterator(); i.hasNext();) {
991                 MailAddress target = (MailAddress)i.next();
992                 String targetServer = target.getHost().toLowerCase(Locale.US);
993                 Collection temp = (Collection)targets.get(targetServer);
994                 if (temp == null) {
995                     temp = new ArrayList();
996                     targets.put(targetServer, temp);
997                 }
998                 temp.add(target);
999             }
1000 
1001             //We have the recipients organized into distinct servers... put them into the
1002             //delivery store organized like this... this is ultra inefficient I think...
1003 
1004             // Store the new message containers, organized by server, in the outgoing mail repository
1005             String name = mail.getName();
1006             for (Iterator i = targets.keySet().iterator(); i.hasNext(); ) {
1007                 String host = (String) i.next();
1008                 Collection rec = (Collection) targets.get(host);
1009                 if (isDebug) {
1010                     StringBuffer logMessageBuffer =
1011                         new StringBuffer(128)
1012                                 .append("Sending mail to ")
1013                                 .append(rec)
1014                                 .append(" on host ")
1015                                 .append(host);
1016                     log(logMessageBuffer.toString());
1017                 }
1018                 mail.setRecipients(rec);
1019                 StringBuffer nameBuffer =
1020                     new StringBuffer(128)
1021                             .append(name)
1022                             .append("-to-")
1023                             .append(host);
1024                 mail.setName(nameBuffer.toString());
1025                 outgoing.store(mail);
1026                 //Set it to try to deliver (in a separate thread) immediately (triggered by storage)
1027             }
1028         } else {
1029             // Store the mail unaltered for processing by the gateway server(s)
1030             if (isDebug) {
1031                 StringBuffer logMessageBuffer =
1032                     new StringBuffer(128)
1033                         .append("Sending mail to ")
1034                         .append(mail.getRecipients())
1035                         .append(" via ")
1036                         .append(gatewayServer);
1037                 log(logMessageBuffer.toString());
1038             }
1039 
1040              //Set it to try to deliver (in a separate thread) immediately (triggered by storage)
1041             outgoing.store(mail);
1042         }
1043         mail.setState(Mail.GHOST);
1044     }
1045 
1046     // Need to synchronize to get object monitor for notifyAll()
1047     public synchronized void destroy() {
1048         //Mark flag so threads from this mailet stop themselves
1049         destroyed = true;
1050         //Wake up all threads from waiting for an accept
1051         for (Iterator i = deliveryThreads.iterator(); i.hasNext(); ) {
1052             Thread t = (Thread)i.next();
1053             t.interrupt();
1054         }
1055         notifyAll();
1056     }
1057 
1058     /***
1059      * Handles checking the outgoing spool for new mail and delivering them if
1060      * there are any
1061      */
1062     public void run() {
1063         /* TODO: CHANGE ME!!! The problem is that we need to wait for James to
1064          * finish initializing.  We expect the HELLO_NAME to be put into
1065          * the MailetContext, but in the current configuration we get
1066          * started before the SMTP Server, which establishes the value.
1067          * Since there is no contractual guarantee that there will be a
1068          * HELLO_NAME value, we can't just wait for it.  As a temporary
1069          * measure, I'm inserting this philosophically unsatisfactory
1070          * fix.
1071          */
1072         long stop = System.currentTimeMillis() + 60000;
1073         while ((getMailetContext().getAttribute(Constants.HELLO_NAME) == null)
1074             && stop > System.currentTimeMillis()) {
1075             
1076             try {
1077                Thread.sleep(1000);
1078             } catch (Exception ignored) {} // wait for James to finish initializing
1079         }
1080 
1081         //Checks the pool and delivers a mail message
1082         Properties props = new Properties();
1083         //Not needed for production environment
1084         props.put("mail.debug", "false");
1085         // Reactivated: javamail 1.3.2 should no more have problems with "250 OK"
1086         // messages (WAS "false": Prevents problems encountered with 250 OK Messages)
1087         props.put("mail.smtp.ehlo", "true");
1088         // By setting this property to true the transport is allowed to
1089         // send 8 bit data to the server (if it supports the 8bitmime extension).
1090         // 2006/03/01 reverted to false because of a javamail bug converting to 8bit
1091         // messages created by an inputstream.
1092         props.setProperty("mail.smtp.allow8bitmime", "false");
1093         //Sets timeout on going connections
1094         props.put("mail.smtp.timeout", smtpTimeout + "");
1095 
1096         props.put("mail.smtp.connectiontimeout", connectionTimeout + "");
1097         props.put("mail.smtp.sendpartial",String.valueOf(sendPartial));
1098 
1099         //Set the hostname we'll use as this server
1100         if (getMailetContext().getAttribute(Constants.HELLO_NAME) != null) {
1101            props.put("mail.smtp.localhost", getMailetContext().getAttribute(Constants.HELLO_NAME));
1102         } else {
1103             String defaultDomain = (String) getMailetContext().getAttribute(Constants.DEFAULT_DOMAIN);
1104             if (defaultDomain != null) {
1105                 props.put("mail.smtp.localhost", defaultDomain);
1106             }
1107         }
1108 
1109         if (isBindUsed) {
1110             // undocumented JavaMail 1.2 feature, smtp transport will use
1111             // our socket factory, which will also set the local address
1112             props.put("mail.smtp.socketFactory.class",
1113                       "org.apache.james.transport.mailets.RemoteDeliverySocketFactory");
1114             // Don't fallback to the standard socket factory on error, do throw an exception
1115             props.put("mail.smtp.socketFactory.fallback", "false");
1116         }
1117         
1118         if (authUser != null) {
1119             props.put("mail.smtp.auth","true");
1120         }
1121 
1122         props.putAll(defprops);
1123         
1124         Session session = Session.getInstance(props, null);
1125         try {
1126             while (!Thread.interrupted() && !destroyed) {
1127                 try {
1128                     Mail mail = (Mail)outgoing.accept(delayFilter);
1129                     String key = mail.getName();
1130                     try {
1131                         if (isDebug) {
1132                             StringBuffer logMessageBuffer =
1133                                 new StringBuffer(128)
1134                                         .append(Thread.currentThread().getName())
1135                                         .append(" will process mail ")
1136                                         .append(key);
1137                             log(logMessageBuffer.toString());
1138                         }
1139                         if (deliver(mail, session)) {
1140                             //Message was successfully delivered/fully failed... delete it
1141                             ContainerUtil.dispose(mail);
1142                             outgoing.remove(key);
1143                         } else {
1144                             //Something happened that will delay delivery.  Store any updates
1145                             outgoing.store(mail);
1146                             ContainerUtil.dispose(mail);
1147                             // This is an update, we have to unlock and notify or this mail
1148                             // is kept locked by this thread
1149                             outgoing.unlock(key);
1150                             // We do not notify because we updated an already existing mail
1151                             // and we are now free to handle more mails.
1152                             // Furthermore this mail should not be processed now because we
1153                             // have a retry time scheduling.
1154                         }
1155                         //Clear the object handle to make sure it recycles this object.
1156                         mail = null;
1157                     } catch (Exception e) {
1158                         // Prevent unexpected exceptions from causing looping by removing
1159                         // message from outgoing.
1160                         // DO NOT CHNANGE THIS to catch Error!  For example, if there were an OutOfMemory condition
1161                         // caused because something else in the server was abusing memory, we would not want to
1162                         // start purging the outgoing spool!
1163                         ContainerUtil.dispose(mail);
1164                         outgoing.remove(key);
1165                         throw e;
1166                     }
1167                 } catch (Throwable e) {
1168                     if (!destroyed) log("Exception caught in RemoteDelivery.run()", e);
1169                 }
1170             }
1171         } finally {
1172             // Restore the thread state to non-interrupted.
1173             Thread.interrupted();
1174         }
1175     }
1176 
1177     /***
1178      * @param list holding Delay objects
1179      * @return the total attempts for all delays
1180      **/
1181     private int calcTotalAttempts (ArrayList list) {
1182         int sum = 0;
1183         Iterator i = list.iterator();
1184         while (i.hasNext()) {
1185             Delay delay = (Delay)i.next();
1186             sum += delay.getAttempts();
1187         }
1188         return sum;
1189     }
1190     
1191     /***
1192      * This method expands an ArrayList containing Delay objects into an array holding the
1193      * only delaytime in the order.<p>
1194      * So if the list has 2 Delay objects the first having attempts=2 and delaytime 4000
1195      * the second having attempts=1 and delaytime=300000 will be expanded into this array:<p>
1196      * long[0] = 4000<p>
1197      * long[1] = 4000<p>
1198      * long[2] = 300000<p>
1199      * @param list the list to expand
1200      * @return the expanded list
1201      **/
1202     private long[] expandDelays (ArrayList list) {
1203         long[] delays = new long [calcTotalAttempts(list)];
1204         Iterator i = list.iterator();
1205         int idx = 0;
1206         while (i.hasNext()) {
1207             Delay delay = (Delay)i.next();
1208             for (int j=0; j<delay.getAttempts(); j++) {
1209                 delays[idx++]= delay.getDelayTime();
1210             }            
1211         }
1212         return delays;
1213     }
1214     
1215     /***
1216      * This method returns, given a retry-count, the next delay time to use.
1217      * @param retry_count the current retry_count.
1218      * @return the next delay time to use, given the retry count
1219      **/
1220     private long getNextDelay (int retry_count) {
1221         if (retry_count > delayTimes.length) {
1222             return DEFAULT_DELAY_TIME;
1223         } 
1224         return delayTimes[retry_count-1];
1225     }
1226 
1227     /***
1228      * This class is used to hold a delay time and its corresponding number
1229      * of retries.
1230      **/
1231     private class Delay {
1232         private int attempts = 1;
1233         private long delayTime = DEFAULT_DELAY_TIME;
1234         
1235             
1236         /***
1237          * This constructor expects Strings of the form "[attempt\*]delaytime[unit]". <p>
1238          * The optional attempt is the number of tries this delay should be used (default = 1)
1239          * The unit if present must be one of (msec,sec,minute,hour,day) (default = msec)
1240          * The constructor multiplies the delaytime by the relevant multiplier for the unit,
1241          * so the delayTime instance variable is always in msec.
1242          * @param init_string the string to initialize this Delay object from
1243          **/
1244         public Delay (String init_string) throws MessagingException
1245         {
1246             String unit = "msec"; //default unit
1247             if (delayTimeMatcher.matches (init_string, PATTERN)) {
1248                 MatchResult res = delayTimeMatcher.getMatch ();
1249                 //the capturing groups will now hold
1250                 //at 1:  attempts * (if present)
1251                 //at 2:  delaytime
1252                 //at 3:  unit (if present)
1253                 
1254                 if (res.group(1) != null && !res.group(1).equals ("")) {
1255                     //we have an attempt *
1256                     String attempt_match = res.group(1);
1257                     //strip the * and whitespace
1258                     attempt_match = attempt_match.substring (0,attempt_match.length()-1).trim();
1259                     attempts = Integer.parseInt (attempt_match);
1260                 }
1261                 
1262                 delayTime = Long.parseLong (res.group(2));
1263                 
1264                 if (!res.group(3).equals ("")) {
1265                     //we have a unit
1266                     unit = res.group(3).toLowerCase(Locale.US);
1267                 }
1268             } else {
1269                 throw new MessagingException(init_string+" does not match "+PATTERN_STRING);
1270             }
1271             if (MULTIPLIERS.get (unit)!=null) {
1272                 int multiplier = ((Integer)MULTIPLIERS.get (unit)).intValue();
1273                 delayTime *= multiplier;
1274             } else {
1275                 throw new MessagingException("Unknown unit: "+unit);
1276             }
1277         }
1278 
1279         /***
1280          * This constructor makes a default Delay object, ie. attempts=1 and delayTime=DEFAULT_DELAY_TIME
1281          **/
1282         public Delay () {
1283         }
1284 
1285         /***
1286          * @return the delayTime for this Delay
1287          **/
1288         public long getDelayTime () {
1289             return delayTime;
1290         }
1291 
1292         /***
1293          * @return the number attempts this Delay should be used.
1294          **/
1295         public int getAttempts () {
1296             return attempts;
1297         }
1298         
1299         /***
1300          * Set the number attempts this Delay should be used.
1301          **/
1302         public void setAttempts (int value) {
1303             attempts = value;
1304         }
1305         
1306         /***
1307          * Pretty prints this Delay 
1308          **/
1309         public String toString () {
1310             StringBuffer buf = new StringBuffer(15);
1311             buf.append (getAttempts ());
1312             buf.append ('*');
1313             buf.append (getDelayTime());
1314             buf.append ("msec");
1315             return buf.toString();
1316         }
1317     }
1318     
1319     /*
1320      * Returns an Iterator over org.apache.mailet.HostAddress, a
1321      * specialized subclass of javax.mail.URLName, which provides
1322      * location information for servers that are specified as mail
1323      * handlers for the given hostname.  If no host is found, the
1324      * Iterator returned will be empty and the first call to hasNext()
1325      * will return false.  The Iterator is a nested iterator: the outer
1326      * iteration is over each gateway, and the inner iteration is over
1327      * potentially multiple A records for each gateway.
1328      *
1329      * @see org.apache.james.DNSServer#getSMTPHostAddresses(String)
1330      * @since v2.2.0a16-unstable
1331      * @param gatewayServers - Collection of host[:port] Strings
1332      * @return an Iterator over HostAddress instances, sorted by priority
1333      */
1334     private Iterator getGatewaySMTPHostAddresses(final Collection gatewayServers) {
1335         return new Iterator() {
1336             private Iterator gateways = gatewayServers.iterator();
1337             private Iterator addresses = null;
1338 
1339             public boolean hasNext() {
1340                 /* Make sure that when next() is called, that we can
1341                  * provide a HostAddress.  This means that we need to
1342                  * have an inner iterator, and verify that it has
1343                  * addresses.  We could, for example, run into a
1344                  * situation where the next gateway didn't have any
1345                  * valid addresses.
1346                  */
1347                 if (!hasNextAddress() && gateways.hasNext()) {
1348                     do {
1349                         String server = (String) gateways.next();
1350                         String port = "25";
1351 
1352                         int idx = server.indexOf(':');
1353                         if ( idx > 0) {
1354                             port = server.substring(idx+1);
1355                             server = server.substring(0,idx);
1356                         }
1357 
1358                         final String nextGateway = server;
1359                         final String nextGatewayPort = port;
1360                         try {
1361                             final InetAddress[] ips = org.apache.james.dnsserver.DNSServer.getAllByName(nextGateway);
1362                             addresses = new Iterator() {
1363                                 private InetAddress[] ipAddresses = ips;
1364                                 int i = 0;
1365 
1366                                 public boolean hasNext() {
1367                                     return i < ipAddresses.length;
1368                                 }
1369 
1370                                 public Object next() {
1371                                     return new org.apache.mailet.HostAddress(nextGateway, "smtp://" + (ipAddresses[i++]).getHostAddress() + ":" + nextGatewayPort);
1372                                 }
1373 
1374                                 public void remove() {
1375                                     throw new UnsupportedOperationException ("remove not supported by this iterator");
1376                                 }
1377                             };
1378                         }
1379                         catch (java.net.UnknownHostException uhe) {
1380                             log("Unknown gateway host: " + uhe.getMessage().trim());
1381                             log("This could be a DNS server error or configuration error.");
1382                         }
1383                     } while (!hasNextAddress() && gateways.hasNext());
1384                 } 
1385 
1386                 return hasNextAddress();
1387             }
1388 
1389             private boolean hasNextAddress() {
1390                 return addresses != null && addresses.hasNext();
1391             }
1392 
1393             public Object next() {
1394                 return (addresses != null) ? addresses.next() : null;
1395             }
1396 
1397             public void remove() {
1398                 throw new UnsupportedOperationException ("remove not supported by this iterator");
1399             }
1400         };
1401     }
1402 }