KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > james > transport > mailets > RemoteDelivery


1 /***********************************************************************
2  * Copyright (c) 2000-2004 The Apache Software Foundation. *
3  * All rights reserved. *
4  * ------------------------------------------------------------------- *
5  * Licensed under the Apache License, Version 2.0 (the "License"); you *
6  * may not use this file except in compliance with the License. You *
7  * may obtain a copy of the License at: *
8  * *
9  * http://www.apache.org/licenses/LICENSE-2.0 *
10  * *
11  * Unless required by applicable law or agreed to in writing, software *
12  * distributed under the License is distributed on an "AS IS" BASIS, *
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or *
14  * implied. See the License for the specific language governing *
15  * permissions and limitations under the License. *
16  ***********************************************************************/

17
18 package org.apache.james.transport.mailets;
19
20 import java.io.PrintWriter JavaDoc;
21 import java.io.StringWriter JavaDoc;
22 import java.net.ConnectException JavaDoc;
23 import java.net.InetAddress JavaDoc;
24 import java.net.SocketException JavaDoc;
25 import java.net.UnknownHostException JavaDoc;
26 import java.util.Arrays JavaDoc;
27 import java.util.Collection JavaDoc;
28 import java.util.Date JavaDoc;
29 import java.util.Hashtable JavaDoc;
30 import java.util.HashMap JavaDoc;
31 import java.util.Iterator JavaDoc;
32 import java.util.Locale JavaDoc;
33 import java.util.Properties JavaDoc;
34 import java.util.StringTokenizer JavaDoc;
35 import java.util.Vector JavaDoc;
36 import java.util.ArrayList JavaDoc;
37
38 import javax.mail.Address JavaDoc;
39 import javax.mail.MessagingException JavaDoc;
40 import javax.mail.SendFailedException JavaDoc;
41 import javax.mail.Session JavaDoc;
42 import javax.mail.Transport JavaDoc;
43 import javax.mail.URLName JavaDoc;
44 import javax.mail.internet.AddressException JavaDoc;
45 import javax.mail.internet.InternetAddress JavaDoc;
46 import javax.mail.internet.MimeMessage JavaDoc;
47 import javax.mail.internet.ParseException JavaDoc;
48
49 import org.apache.avalon.framework.component.ComponentException;
50 import org.apache.avalon.framework.component.ComponentManager;
51 import org.apache.avalon.framework.configuration.DefaultConfiguration;
52 import org.apache.james.Constants;
53 import org.apache.james.core.MailImpl;
54 import org.apache.james.services.MailServer;
55 import org.apache.james.services.MailStore;
56 import org.apache.james.services.SpoolRepository;
57 import org.apache.mailet.MailetContext;
58 import org.apache.mailet.GenericMailet;
59 import org.apache.mailet.HostAddress;
60 import org.apache.mailet.Mail;
61 import org.apache.mailet.MailAddress;
62
63 import org.apache.oro.text.regex.MalformedPatternException;
64 import org.apache.oro.text.regex.Pattern;
65 import org.apache.oro.text.regex.Perl5Compiler;
66 import org.apache.oro.text.regex.Perl5Matcher;
67 import org.apache.oro.text.regex.MatchResult;
68
69
70 /**
71  * Receives a MessageContainer from JamesSpoolManager and takes care of delivery
72  * the message to remote hosts. If for some reason mail can't be delivered
73  * store it in the "outgoing" Repository and set an Alarm. After the next "delayTime" the
74  * Alarm will wake the servlet that will try to send it again. After "maxRetries"
75  * the mail will be considered undeliverable and will be returned to sender.
76  *
77  * TO DO (in priority):
78  * 1. Support a gateway (a single server where all mail will be delivered) (DONE)
79  * 2. Provide better failure messages (DONE)
80  * 3. More efficiently handle numerous recipients
81  * 4. Migrate to use Phoenix for the delivery threads
82  *
83  * You really want to read the JavaMail documentation if you are
84  * working in here, and you will want to view the list of JavaMail
85  * attributes, which are documented here:
86  *
87  * http://java.sun.com/products/javamail/1.3/docs/javadocs/com/sun/mail/smtp/package-summary.html
88  *
89  * as well as other places.
90  *
91  * @version CVS $Revision: 1.33.4.21 $ $Date: 2004/05/02 06:08:37 $
92  */

93 public class RemoteDelivery extends GenericMailet implements Runnable JavaDoc {
94
95     private static final long DEFAULT_DELAY_TIME = 21600000; // default is 6*60*60*1000 millis (6 hours)
96
private static final String JavaDoc PATTERN_STRING =
97         "\\s*([0-9]*\\s*[\\*])?\\s*([0-9]+)\\s*([a-z,A-Z]*)\\s*";//pattern to match
98
//[attempts*]delay[units]
99

100     private static Pattern PATTERN = null; //the compiled pattern of the above String
101
private static final HashMap JavaDoc MULTIPLIERS = new HashMap JavaDoc (10); //holds allowed units for delaytime together with
102
//the factor to turn it into the equivalent time in msec
103

104     /*
105      * Static initializer.<p>
106      * Compiles pattern for processing delaytime entries.<p>
107      * Initializes MULTIPLIERS with the supported unit quantifiers
108      */

109     static {
110         try {
111             Perl5Compiler compiler = new Perl5Compiler();
112             PATTERN = compiler.compile(PATTERN_STRING, Perl5Compiler.READ_ONLY_MASK);
113         } catch(MalformedPatternException mpe) {
114             //this should not happen as the pattern string is hardcoded.
115
System.err.println ("Malformed pattern: " + PATTERN_STRING);
116             mpe.printStackTrace (System.err);
117         }
118         //add allowed units and their respective multiplier
119
MULTIPLIERS.put ("msec", new Integer JavaDoc (1));
120         MULTIPLIERS.put ("msecs", new Integer JavaDoc (1));
121         MULTIPLIERS.put ("sec", new Integer JavaDoc (1000));
122         MULTIPLIERS.put ("secs", new Integer JavaDoc (1000));
123         MULTIPLIERS.put ("minute", new Integer JavaDoc (1000*60));
124         MULTIPLIERS.put ("minutes", new Integer JavaDoc (1000*60));
125         MULTIPLIERS.put ("hour", new Integer JavaDoc (1000*60*60));
126         MULTIPLIERS.put ("hours", new Integer JavaDoc (1000*60*60));
127         MULTIPLIERS.put ("day", new Integer JavaDoc (1000*60*60*24));
128         MULTIPLIERS.put ("days", new Integer JavaDoc (1000*60*60*24));
129     }
130     
131     /**
132      * This filter is used in the accept call to the spool.
133      * It will select the next mail ready for processing according to the mails
134      * retrycount and lastUpdated time
135      **/

136     private class MultipleDelayFilter implements SpoolRepository.AcceptFilter
137     {
138         /**
139          * holds the time to wait for the youngest mail to get ready for processing
140          **/

141         long youngest = 0;
142
143         /**
144          * Uses the getNextDelay to determine if a mail is ready for processing based on the delivered parameters
145          * errorMessage (which holds the retrycount), lastUpdated and state
146          * @param key the name/key of the message
147          * @param state the mails state
148          * @param lastUpdated the mail was last written to the spool at this time.
149          * @param errorMessage actually holds the retrycount as a string (see failMessage below)
150          **/

151         public boolean accept (String JavaDoc key, String JavaDoc state, long lastUpdated, String JavaDoc errorMessage) {
152             if (state.equals(Mail.ERROR)) {
153                 //Test the time...
154
int retries = Integer.parseInt(errorMessage);
155                 long delay = getNextDelay (retries);
156                 long timeToProcess = delay + lastUpdated;
157
158                 
159                 if (System.currentTimeMillis() > timeToProcess) {
160                     //We're ready to process this again
161
return true;
162                 } else {
163                     //We're not ready to process this.
164
if (youngest == 0 || youngest > timeToProcess) {
165                         //Mark this as the next most likely possible mail to process
166
youngest = timeToProcess;
167                     }
168                     return false;
169                 }
170             } else {
171                 //This mail is good to go... return the key
172
return true;
173             }
174         }
175
176         /**
177          * @return the optimal time the SpoolRepository.accept(AcceptFilter) method should wait before
178          * trying to find a mail ready for processing again.
179          **/

180         public long getWaitTime () {
181             if (youngest == 0) {
182                 return 0;
183             } else {
184                 long duration = youngest - System.currentTimeMillis();
185                 youngest = 0; //get ready for next run
186
return duration <= 0 ? 1 : duration;
187             }
188         }
189     }
190
191     /**
192      * Controls certain log messages
193      */

194     private boolean isDebug = false;
195
196     private SpoolRepository outgoing; // The spool of outgoing mail
197
private long[] delayTimes; //holds expanded delayTimes
198
private int maxRetries = 5; // default number of retries
199
private long smtpTimeout = 600000; //default number of ms to timeout on smtp delivery
200
private boolean sendPartial = false; // If false then ANY address errors will cause the transmission to fail
201
private int connectionTimeout = 60000; // The amount of time JavaMail will wait before giving up on a socket connect()
202
private int deliveryThreadCount = 1; // default number of delivery threads
203
private Collection JavaDoc gatewayServer = null; // the server(s) to send all email to
204
private String JavaDoc bindAddress = null; // JavaMail delivery socket binds to this local address. If null the JavaMail default will be used.
205
private boolean isBindUsed = false; // true, if the bind configuration
206
// parameter is supplied, RemoteDeliverySocketFactory
207
// will be used in this case
208
private Collection JavaDoc deliveryThreads = new Vector JavaDoc();
209     private MailServer mailServer;
210     private volatile boolean destroyed = false; //Flag that the run method will check and end itself if set to true
211
private String JavaDoc bounceProcessor = null; // the processor for creating Bounces
212

213     private Perl5Matcher delayTimeMatcher; //matcher use at init time to parse delaytime parameters
214
private MultipleDelayFilter delayFilter = new MultipleDelayFilter ();//used by accept to selcet the next mail ready for processing
215

216     /**
217      * Initialize the mailet
218      */

219     public void init() throws MessagingException JavaDoc {
220         isDebug = (getInitParameter("debug") == null) ? false : new Boolean JavaDoc(getInitParameter("debug")).booleanValue();
221         ArrayList JavaDoc delay_times_list = new ArrayList JavaDoc();
222         try {
223             if (getInitParameter("delayTime") != null) {
224                 delayTimeMatcher = new Perl5Matcher();
225                 String JavaDoc delay_times = getInitParameter("delayTime");
226                 //split on comma's
227
StringTokenizer JavaDoc st = new StringTokenizer JavaDoc (delay_times,",");
228                 while (st.hasMoreTokens()) {
229                     String JavaDoc delay_time = st.nextToken();
230                     delay_times_list.add (new Delay(delay_time));
231                 }
232             } else {
233                 //use default delayTime.
234
delay_times_list.add (new Delay());
235             }
236         } catch (Exception JavaDoc e) {
237             log("Invalid delayTime setting: " + getInitParameter("delayTime"));
238         }
239         try {
240             if (getInitParameter("maxRetries") != null) {
241                 maxRetries = Integer.parseInt(getInitParameter("maxRetries"));
242             }
243             //check consistency with delay_times_list attempts
244
int total_attempts = calcTotalAttempts (delay_times_list);
245             if (total_attempts > maxRetries) {
246                 log("Total number of delayTime attempts exceeds maxRetries specified. Increasing maxRetries from "+maxRetries+" to "+total_attempts);
247                 maxRetries = total_attempts;
248             } else {
249                 int extra = maxRetries - total_attempts;
250                 if (extra != 0) {
251                     log("maxRetries is larger than total number of attempts specified. Increasing last delayTime with "+extra+" attempts ");
252
253                     if (delay_times_list.size() != 0) {
254                         Delay delay = (Delay)delay_times_list.get (delay_times_list.size()-1); //last Delay
255
delay.setAttempts (delay.getAttempts()+extra);
256                         log("Delay of "+delay.getDelayTime()+" msecs is now attempted: "+delay.getAttempts()+" times");
257                     } else {
258                         log ("NO, delaytimes cannot continue");
259                     }
260                 }
261             }
262             delayTimes = expandDelays (delay_times_list);
263             
264         } catch (Exception JavaDoc e) {
265             log("Invalid maxRetries setting: " + getInitParameter("maxRetries"));
266         }
267         try {
268             if (getInitParameter("timeout") != null) {
269                 smtpTimeout = Integer.parseInt(getInitParameter("timeout"));
270             }
271         } catch (Exception JavaDoc e) {
272             log("Invalid timeout setting: " + getInitParameter("timeout"));
273         }
274
275         try {
276             if (getInitParameter("connectiontimeout") != null) {
277                 connectionTimeout = Integer.parseInt(getInitParameter("connectiontimeout"));
278             }
279         } catch (Exception JavaDoc e) {
280             log("Invalid timeout setting: " + getInitParameter("timeout"));
281         }
282         sendPartial = (getInitParameter("sendpartial") == null) ? false : new Boolean JavaDoc(getInitParameter("sendpartial")).booleanValue();
283
284         bounceProcessor = getInitParameter("bounceProcessor");
285
286         String JavaDoc gateway = getInitParameter("gateway");
287         String JavaDoc gatewayPort = getInitParameter("gatewayPort");
288
289         if (gateway != null) {
290             gatewayServer = new ArrayList JavaDoc();
291             StringTokenizer JavaDoc st = new StringTokenizer JavaDoc(gateway, ",") ;
292             while (st.hasMoreTokens()) {
293                 String JavaDoc server = st.nextToken().trim() ;
294                 if (server.indexOf(':') < 0 && gatewayPort != null) {
295                     server += ":";
296                     server += gatewayPort;
297                 }
298
299                 if (isDebug) log("Adding SMTP gateway: " + server) ;
300                 gatewayServer.add(server);
301             }
302         }
303
304         ComponentManager compMgr = (ComponentManager)getMailetContext().getAttribute(Constants.AVALON_COMPONENT_MANAGER);
305         String JavaDoc outgoingPath = getInitParameter("outgoing");
306         if (outgoingPath == null) {
307             outgoingPath = "file:///../var/mail/outgoing";
308         }
309
310         try {
311             // Instantiate the a MailRepository for outgoing mails
312
MailStore mailstore = (MailStore) compMgr.lookup("org.apache.james.services.MailStore");
313
314             DefaultConfiguration spoolConf
315                 = new DefaultConfiguration("repository", "generated:RemoteDelivery.java");
316             spoolConf.setAttribute("destinationURL", outgoingPath);
317             spoolConf.setAttribute("type", "SPOOL");
318             outgoing = (SpoolRepository) mailstore.select(spoolConf);
319         } catch (ComponentException cnfe) {
320             log("Failed to retrieve Store component:" + cnfe.getMessage());
321         } catch (Exception JavaDoc e) {
322             log("Failed to retrieve Store component:" + e.getMessage());
323         }
324
325         //Start up a number of threads
326
try {
327             deliveryThreadCount = Integer.parseInt(getInitParameter("deliveryThreads"));
328         } catch (Exception JavaDoc e) {
329         }
330         for (int i = 0; i < deliveryThreadCount; i++) {
331             StringBuffer JavaDoc nameBuffer =
332                 new StringBuffer JavaDoc(32)
333                         .append("Remote delivery thread (")
334                         .append(i)
335                         .append(")");
336             Thread JavaDoc t = new Thread JavaDoc(this, nameBuffer.toString());
337             t.start();
338             deliveryThreads.add(t);
339         }
340
341         bindAddress = getInitParameter("bind");
342         isBindUsed = bindAddress != null;
343         try {
344             if (isBindUsed) RemoteDeliverySocketFactory.setBindAdress(bindAddress);
345         } catch (UnknownHostException JavaDoc e) {
346             log("Invalid bind setting (" + bindAddress + "): " + e.toString());
347         }
348     }
349
350     /**
351      * We can assume that the recipients of this message are all going to the same
352      * mail server. We will now rely on the DNS server to do DNS MX record lookup
353      * and try to deliver to the multiple mail servers. If it fails, it should
354      * throw an exception.
355      *
356      * Creation date: (2/24/00 11:25:00 PM)
357      * @param mail org.apache.james.core.MailImpl
358      * @param session javax.mail.Session
359      * @return boolean Whether the delivery was successful and the message can be deleted
360      */

361     private boolean deliver(MailImpl mail, Session JavaDoc session) {
362         try {
363             if (isDebug) {
364                 log("Attempting to deliver " + mail.getName());
365             }
366             MimeMessage JavaDoc message = mail.getMessage();
367
368             //Create an array of the recipients as InternetAddress objects
369
Collection JavaDoc recipients = mail.getRecipients();
370             InternetAddress JavaDoc addr[] = new InternetAddress JavaDoc[recipients.size()];
371             int j = 0;
372             for (Iterator JavaDoc i = recipients.iterator(); i.hasNext(); j++) {
373                 MailAddress rcpt = (MailAddress)i.next();
374                 addr[j] = rcpt.toInternetAddress();
375             }
376
377             if (addr.length <= 0) {
378                 log("No recipients specified... not sure how this could have happened.");
379                 return true;
380             }
381
382             //Figure out which servers to try to send to. This collection
383
// will hold all the possible target servers
384
Iterator JavaDoc targetServers = null;
385             if (gatewayServer == null) {
386                 MailAddress rcpt = (MailAddress) recipients.iterator().next();
387                 String JavaDoc host = rcpt.getHost();
388
389                 //Lookup the possible targets
390
targetServers = getMailetContext().getSMTPHostAddresses(host);
391                 if (!targetServers.hasNext()) {
392                     log("No mail server found for: " + host);
393                     StringBuffer JavaDoc exceptionBuffer =
394                         new StringBuffer JavaDoc(128)
395                         .append("There are no DNS entries for the hostname ")
396                         .append(host)
397                         .append(". I cannot determine where to send this message.");
398                     return failMessage(mail, new MessagingException JavaDoc(exceptionBuffer.toString()), false);
399                 }
400             } else {
401                 targetServers = getGatewaySMTPHostAddresses(gatewayServer);
402             }
403
404             MessagingException JavaDoc lastError = null;
405
406             while ( targetServers.hasNext()) {
407                 try {
408                     HostAddress outgoingMailServer = (HostAddress) targetServers.next();
409                     StringBuffer JavaDoc logMessageBuffer =
410                         new StringBuffer JavaDoc(256)
411                         .append("Attempting delivery of ")
412                         .append(mail.getName())
413                         .append(" to host ")
414                         .append(outgoingMailServer.getHostName())
415                         .append(" at ")
416                         .append(outgoingMailServer.getHost())
417                         .append(" to addresses ")
418                         .append(Arrays.asList(addr));
419                     log(logMessageBuffer.toString());
420
421                     Properties JavaDoc props = session.getProperties();
422                     if (mail.getSender() == null) {
423                         props.put("mail.smtp.from", "<>");
424                     } else {
425                         String JavaDoc sender = mail.getSender().toString();
426                         props.put("mail.smtp.from", sender);
427                     }
428
429                     //Many of these properties are only in later JavaMail versions
430
//"mail.smtp.ehlo" //default true
431
//"mail.smtp.auth" //default false
432
//"mail.smtp.dsn.ret" //default to nothing... appended as RET= after MAIL FROM line.
433
//"mail.smtp.dsn.notify" //default to nothing...appended as NOTIFY= after RCPT TO line.
434

435                     Transport JavaDoc transport = null;
436                     try {
437                         transport = session.getTransport(outgoingMailServer);
438                         try {
439                             transport.connect();
440                         } catch (MessagingException JavaDoc me) {
441                             // Any error on connect should cause the mailet to attempt
442
// to connect to the next SMTP server associated with this
443
// MX record. Just log the exception. We'll worry about
444
// failing the message at the end of the loop.
445
log(me.getMessage());
446                             continue;
447                         }
448                         transport.sendMessage(message, addr);
449                     } finally {
450                         if (transport != null) {
451                             transport.close();
452                             transport = null;
453                         }
454                     }
455                     logMessageBuffer =
456                                       new StringBuffer JavaDoc(256)
457                                       .append("Mail (")
458                                       .append(mail.getName())
459                                       .append(") sent successfully to ")
460                                       .append(outgoingMailServer.getHostName())
461                                       .append(" at ")
462                                       .append(outgoingMailServer.getHost());
463                     log(logMessageBuffer.toString());
464                     return true;
465                 } catch (SendFailedException JavaDoc sfe) {
466                     if (sfe.getValidSentAddresses() == null
467                           || sfe.getValidSentAddresses().length < 1) {
468                         if (isDebug) log("Send failed, continuing with any other servers");
469                         lastError = sfe;
470                         continue;
471                     } else {
472                         // If any mail was sent then the outgoing
473
// server config must be ok, therefore rethrow
474
throw sfe;
475                     }
476                 } catch (MessagingException JavaDoc me) {
477                     //MessagingException are horribly difficult to figure out what actually happened.
478
StringBuffer JavaDoc exceptionBuffer =
479                         new StringBuffer JavaDoc(256)
480                         .append("Exception delivering message (")
481                         .append(mail.getName())
482                         .append(") - ")
483                         .append(me.getMessage());
484                     log(exceptionBuffer.toString());
485                     if ((me.getNextException() != null) &&
486                           (me.getNextException() instanceof java.io.IOException JavaDoc)) {
487                         //This is more than likely a temporary failure
488

489                         // If it's an IO exception with no nested exception, it's probably
490
// some socket or weird I/O related problem.
491
lastError = me;
492                         continue;
493                     }
494                     // This was not a connection or I/O error particular to one
495
// SMTP server of an MX set. Instead, it is almost certainly
496
// a protocol level error. In this case we assume that this
497
// is an error we'd encounter with any of the SMTP servers
498
// associated with this MX record, and we pass the exception
499
// to the code in the outer block that determines its severity.
500
throw me;
501                 }
502             } // end while
503
//If we encountered an exception while looping through,
504
//throw the last MessagingException we caught. We only
505
//do this if we were unable to send the message to any
506
//server. If sending eventually succeeded, we exit
507
//deliver() though the return at the end of the try
508
//block.
509
if (lastError != null) {
510                 throw lastError;
511             }
512         } catch (SendFailedException JavaDoc sfe) {
513             boolean deleteMessage = false;
514             Collection JavaDoc recipients = mail.getRecipients();
515
516             //Would like to log all the types of email addresses
517
if (isDebug) log("Recipients: " + recipients);
518
519             /*
520             if (sfe.getValidSentAddresses() != null) {
521                 Address[] validSent = sfe.getValidSentAddresses();
522                 Collection recipients = mail.getRecipients();
523                 //Remove these addresses for the recipients
524                 for (int i = 0; i < validSent.length; i++) {
525                     try {
526                         MailAddress addr = new MailAddress(validSent[i].toString());
527                         recipients.remove(addr);
528                     } catch (ParseException pe) {
529                         //ignore once debugging done
530                         pe.printStackTrace();
531                     }
532                 }
533             }
534             */

535
536             /*
537              * The rest of the recipients failed for one reason or
538              * another.
539              *
540              * SendFailedException actually handles this for us. For
541              * example, if you send a message that has multiple invalid
542              * addresses, you'll get a top-level SendFailedException
543              * that that has the valid, valid-unsent, and invalid
544              * address lists, with all of the server response messages
545              * will be contained within the nested exceptions. [Note:
546              * the content of the nested exceptions is implementation
547              * dependent.]
548              *
549              * sfe.getInvalidAddresses() should be considered permanent.
550              * sfe.getValidUnsentAddresses() should be considered temporary.
551              *
552              * JavaMail v1.3 properly populates those collections based
553              * upon the 4xx and 5xx response codes.
554              *
555              */

556
557             if (sfe.getInvalidAddresses() != null) {
558                 Address JavaDoc[] address = sfe.getInvalidAddresses();
559                 if (address.length > 0) {
560                     recipients.clear();
561                     for (int i = 0; i < address.length; i++) {
562                         try {
563                             recipients.add(new MailAddress(address[i].toString()));
564                         } catch (ParseException JavaDoc pe) {
565                             // this should never happen ... we should have
566
// caught malformed addresses long before we
567
// got to this code.
568
log("Can't parse invalid address: " + pe.getMessage());
569                         }
570                     }
571                     if (isDebug) log("Invalid recipients: " + recipients);
572                     deleteMessage = failMessage(mail, sfe, true);
573                 }
574             }
575
576             if (sfe.getValidUnsentAddresses() != null) {
577                 Address JavaDoc[] address = sfe.getValidUnsentAddresses();
578                 if (address.length > 0) {
579                     recipients.clear();
580                     for (int i = 0; i < address.length; i++) {
581                         try {
582                             recipients.add(new MailAddress(address[i].toString()));
583                         } catch (ParseException JavaDoc pe) {
584                             // this should never happen ... we should have
585
// caught malformed addresses long before we
586
// got to this code.
587
log("Can't parse unsent address: " + pe.getMessage());
588                         }
589                     }
590                     if (isDebug) log("Unsent recipients: " + recipients);
591                     deleteMessage = failMessage(mail, sfe, false);
592                 }
593             }
594
595             return deleteMessage;
596         } catch (MessagingException JavaDoc ex) {
597             // We should do a better job checking this... if the failure is a general
598
// connect exception, this is less descriptive than more specific SMTP command
599
// failure... have to lookup and see what are the various Exception
600
// possibilities
601

602             // Unable to deliver message after numerous tries... fail accordingly
603

604             // We check whether this is a 5xx error message, which
605
// indicates a permanent failure (like account doesn't exist
606
// or mailbox is full or domain is setup wrong).
607
// We fail permanently if this was a 5xx error
608
return failMessage(mail, ex, ('5' == ex.getMessage().charAt(0)));
609         }
610
611         /* If we get here, we've exhausted the loop of servers without
612          * sending the message or throwing an exception. One case
613          * where this might happen is if we get a MessagingException on
614          * each transport.connect(), e.g., if there is only one server
615          * and we get a connect exception.
616          */

617         return failMessage(mail, new MessagingException JavaDoc("No mail server(s) available at this time."), false);
618     }
619
620     /**
621      * Insert the method's description here.
622      * Creation date: (2/25/00 1:14:18 AM)
623      * @param mail org.apache.james.core.MailImpl
624      * @param exception javax.mail.MessagingException
625      * @param boolean permanent
626      * @return boolean Whether the message failed fully and can be deleted
627      */

628     private boolean failMessage(MailImpl mail, MessagingException JavaDoc ex, boolean permanent) {
629         StringWriter JavaDoc sout = new StringWriter JavaDoc();
630         PrintWriter JavaDoc out = new PrintWriter JavaDoc(sout, true);
631         if (permanent) {
632             out.print("Permanent");
633         } else {
634             out.print("Temporary");
635         }
636         StringBuffer JavaDoc logBuffer =
637             new StringBuffer JavaDoc(64)
638                 .append(" exception delivering mail (")
639                 .append(mail.getName())
640                 .append(": ");
641         out.print(logBuffer.toString());
642         ex.printStackTrace(out);
643         log(sout.toString());
644         if (!permanent) {
645             if (!mail.getState().equals(Mail.ERROR)) {
646                 mail.setState(Mail.ERROR);
647                 mail.setErrorMessage("0");
648                 mail.setLastUpdated(new Date JavaDoc());
649             }
650             int retries = Integer.parseInt(mail.getErrorMessage());
651             if (retries < maxRetries) {
652                 logBuffer =
653                     new StringBuffer JavaDoc(128)
654                             .append("Storing message ")
655                             .append(mail.getName())
656                             .append(" into outgoing after ")
657                             .append(retries)
658                             .append(" retries");
659                 log(logBuffer.toString());
660                 ++retries;
661                 mail.setErrorMessage(retries + "");
662                 mail.setLastUpdated(new Date JavaDoc());
663                 return false;
664             } else {
665                 logBuffer =
666                     new StringBuffer JavaDoc(128)
667                             .append("Bouncing message ")
668                             .append(mail.getName())
669                             .append(" after ")
670                             .append(retries)
671                             .append(" retries");
672                 log(logBuffer.toString());
673             }
674         }
675         if (bounceProcessor != null) {
676             // do the new DSN bounce
677
// setting attributes for DSN mailet
678
mail.setAttribute("delivery-error", ex);
679             mail.setState(bounceProcessor);
680             // re-insert the mail into the spool for getting it passed to the dsn-processor
681
MailetContext mc = getMailetContext();
682             try {
683                 mc.sendMail(mail);
684             } catch (MessagingException JavaDoc e) {
685                 // we shouldn't get an exception, because the mail was already processed
686
log("Exception re-inserting failed mail: ", e);
687             }
688         } else {
689             // do an old style bounce
690
bounce(mail, ex);
691         }
692         return true;
693     }
694
695     private void bounce(MailImpl mail, MessagingException JavaDoc ex) {
696         StringWriter JavaDoc sout = new StringWriter JavaDoc();
697         PrintWriter JavaDoc out = new PrintWriter JavaDoc(sout, true);
698         String JavaDoc machine = "[unknown]";
699         try {
700             InetAddress JavaDoc me = InetAddress.getLocalHost();
701             machine = me.getHostName();
702         } catch(Exception JavaDoc e){
703             machine = "[address unknown]";
704         }
705         StringBuffer JavaDoc bounceBuffer =
706             new StringBuffer JavaDoc(128)
707                     .append("Hi. This is the James mail server at ")
708                     .append(machine)
709                     .append(".");
710         out.println(bounceBuffer.toString());
711         out.println("I'm afraid I wasn't able to deliver your message to the following addresses.");
712         out.println("This is a permanent error; I've given up. Sorry it didn't work out. Below");
713         out.println("I include the list of recipients and the reason why I was unable to deliver");
714         out.println("your message.");
715         out.println();
716         for (Iterator JavaDoc i = mail.getRecipients().iterator(); i.hasNext(); ) {
717             out.println(i.next());
718         }
719         if (ex.getNextException() == null) {
720             out.println(ex.getMessage().trim());
721         } else {
722             Exception JavaDoc ex1 = ex.getNextException();
723             if (ex1 instanceof SendFailedException JavaDoc) {
724                 out.println("Remote mail server told me: " + ex1.getMessage().trim());
725             } else if (ex1 instanceof UnknownHostException JavaDoc) {
726                 out.println("Unknown host: " + ex1.getMessage().trim());
727                 out.println("This could be a DNS server error, a typo, or a problem with the recipient's mail server.");
728             } else if (ex1 instanceof ConnectException JavaDoc) {
729                 //Already formatted as "Connection timed out: connect"
730
out.println(ex1.getMessage().trim());
731             } else if (ex1 instanceof SocketException JavaDoc) {
732                 out.println("Socket exception: " + ex1.getMessage().trim());
733             } else {
734                 out.println(ex1.getMessage().trim());
735             }
736         }
737         out.println();
738         out.println("The original message is attached.");
739
740         log("Sending failure message " + mail.getName());
741         try {
742             getMailetContext().bounce(mail, sout.toString());
743         } catch (MessagingException JavaDoc me) {
744             log("Encountered unexpected messaging exception while bouncing message: " + me.getMessage());
745         } catch (Exception JavaDoc e) {
746             log("Encountered unexpected exception while bouncing message: " + e.getMessage());
747         }
748     }
749
750     public String JavaDoc getMailetInfo() {
751         return "RemoteDelivery Mailet";
752     }
753
754     /**
755      * For this message, we take the list of recipients, organize these into distinct
756      * servers, and duplicate the message for each of these servers, and then call
757      * the deliver (messagecontainer) method for each server-specific
758      * messagecontainer ... that will handle storing it in the outgoing queue if needed.
759      *
760      * @param mail org.apache.mailet.Mail
761      */

762     public void service(Mail genericmail) throws MessagingException JavaDoc{
763         MailImpl mail = (MailImpl)genericmail;
764
765         // Do I want to give the internal key, or the message's Message ID
766
if (isDebug) {
767             log("Remotely delivering mail " + mail.getName());
768         }
769         Collection JavaDoc recipients = mail.getRecipients();
770
771         if (gatewayServer == null) {
772             // Must first organize the recipients into distinct servers (name made case insensitive)
773
Hashtable JavaDoc targets = new Hashtable JavaDoc();
774             for (Iterator JavaDoc i = recipients.iterator(); i.hasNext();) {
775                 MailAddress target = (MailAddress)i.next();
776                 String JavaDoc targetServer = target.getHost().toLowerCase(Locale.US);
777                 Collection JavaDoc temp = (Collection JavaDoc)targets.get(targetServer);
778                 if (temp == null) {
779                     temp = new ArrayList JavaDoc();
780                     targets.put(targetServer, temp);
781                 }
782                 temp.add(target);
783             }
784
785             //We have the recipients organized into distinct servers... put them into the
786
//delivery store organized like this... this is ultra inefficient I think...
787

788             // Store the new message containers, organized by server, in the outgoing mail repository
789
String JavaDoc name = mail.getName();
790             for (Iterator JavaDoc i = targets.keySet().iterator(); i.hasNext(); ) {
791                 String JavaDoc host = (String JavaDoc) i.next();
792                 Collection JavaDoc rec = (Collection JavaDoc) targets.get(host);
793                 if (isDebug) {
794                     StringBuffer JavaDoc logMessageBuffer =
795                         new StringBuffer JavaDoc(128)
796                                 .append("Sending mail to ")
797                                 .append(rec)
798                                 .append(" on host ")
799                                 .append(host);
800                     log(logMessageBuffer.toString());
801                 }
802                 mail.setRecipients(rec);
803                 StringBuffer JavaDoc nameBuffer =
804                     new StringBuffer JavaDoc(128)
805                             .append(name)
806                             .append("-to-")
807                             .append(host);
808                 mail.setName(nameBuffer.toString());
809                 outgoing.store(mail);
810                 //Set it to try to deliver (in a separate thread) immediately (triggered by storage)
811
}
812         } else {
813             // Store the mail unaltered for processing by the gateway server(s)
814
if (isDebug) {
815                 StringBuffer JavaDoc logMessageBuffer =
816                     new StringBuffer JavaDoc(128)
817                         .append("Sending mail to ")
818                         .append(mail.getRecipients())
819                         .append(" via ")
820                         .append(gatewayServer);
821                 log(logMessageBuffer.toString());
822             }
823
824              //Set it to try to deliver (in a separate thread) immediately (triggered by storage)
825
outgoing.store(mail);
826         }
827         mail.setState(Mail.GHOST);
828     }
829
830     // Need to synchronize to get object monitor for notifyAll()
831
public synchronized void destroy() {
832         //Mark flag so threads from this mailet stop themselves
833
destroyed = true;
834         //Wake up all threads from waiting for an accept
835
for (Iterator JavaDoc i = deliveryThreads.iterator(); i.hasNext(); ) {
836             Thread JavaDoc t = (Thread JavaDoc)i.next();
837             t.interrupt();
838         }
839         notifyAll();
840     }
841
842     /**
843      * Handles checking the outgoing spool for new mail and delivering them if
844      * there are any
845      */

846     public void run() {
847
848         /* TODO: CHANGE ME!!! The problem is that we need to wait for James to
849          * finish initializing. We expect the HELLO_NAME to be put into
850          * the MailetContext, but in the current configuration we get
851          * started before the SMTP Server, which establishes the value.
852          * Since there is no contractual guarantee that there will be a
853          * HELLO_NAME value, we can't just wait for it. As a temporary
854          * measure, I'm inserting this philosophically unsatisfactory
855          * fix.
856          */

857         long stop = System.currentTimeMillis() + 60000;
858         while ((getMailetContext().getAttribute(Constants.HELLO_NAME) == null)
859                && stop > System.currentTimeMillis()) {
860             try {
861                 Thread.sleep(1000);
862             } catch (Exception JavaDoc ignored) {} // wait for James to finish initializing
863
}
864
865         //Checks the pool and delivers a mail message
866
Properties JavaDoc props = new Properties JavaDoc();
867         //Not needed for production environment
868
props.put("mail.debug", "false");
869         //Prevents problems encountered with 250 OK Messages
870
props.put("mail.smtp.ehlo", "false");
871         //Sets timeout on going connections
872
props.put("mail.smtp.timeout", smtpTimeout + "");
873
874         props.put("mail.smtp.connectiontimeout", connectionTimeout + "");
875         props.put("mail.smtp.sendpartial",String.valueOf(sendPartial));
876
877         //Set the hostname we'll use as this server
878
if (getMailetContext().getAttribute(Constants.HELLO_NAME) != null) {
879             props.put("mail.smtp.localhost", (String JavaDoc) getMailetContext().getAttribute(Constants.HELLO_NAME));
880         }
881         else {
882             Collection JavaDoc servernames = (Collection JavaDoc) getMailetContext().getAttribute(Constants.SERVER_NAMES);
883             if ((servernames != null) && (servernames.size() > 0)) {
884                 props.put("mail.smtp.localhost", (String JavaDoc) servernames.iterator().next());
885             }
886         }
887
888         if (isBindUsed) {
889             // undocumented JavaMail 1.2 feature, smtp transport will use
890
// our socket factory, which will also set the local address
891
props.put("mail.smtp.socketFactory.class",
892                       "org.apache.james.transport.mailets.RemoteDeliverySocketFactory");
893             // Don't fallback to the standard socket factory on error, do throw an exception
894
props.put("mail.smtp.socketFactory.fallback", "false");
895         }
896
897         Session JavaDoc session = Session.getInstance(props, null);
898         try {
899             while (!Thread.currentThread().interrupted() && !destroyed) {
900                 try {
901                     MailImpl mail = (MailImpl)outgoing.accept(delayFilter);
902                     String JavaDoc key = mail.getName();
903                     try {
904                         if (isDebug) {
905                             StringBuffer JavaDoc logMessageBuffer =
906                                 new StringBuffer JavaDoc(128)
907                                         .append(Thread.currentThread().getName())
908                                         .append(" will process mail ")
909                                         .append(key);
910                             log(logMessageBuffer.toString());
911                         }
912                         if (deliver(mail, session)) {
913                             //Message was successfully delivered/fully failed... delete it
914
outgoing.remove(key);
915                         } else {
916                             //Something happened that will delay delivery. Store any updates
917
outgoing.store(mail);
918                         }
919                         //Clear the object handle to make sure it recycles this object.
920
mail = null;
921                     } catch (Exception JavaDoc e) {
922                         // Prevent unexpected exceptions from causing looping by removing
923
// message from outgoing.
924
outgoing.remove(key);
925                         throw e;
926                     }
927                 } catch (Throwable JavaDoc e) {
928                     if (!destroyed) log("Exception caught in RemoteDelivery.run()", e);
929                 }
930             }
931         } finally {
932             // Restore the thread state to non-interrupted.
933
Thread.currentThread().interrupted();
934         }
935     }
936
937     /**
938      * @param list holding Delay objects
939      * @return the total attempts for all delays
940      **/

941     private int calcTotalAttempts (ArrayList JavaDoc list) {
942         int sum = 0;
943         Iterator JavaDoc i = list.iterator();
944         while (i.hasNext()) {
945             Delay delay = (Delay)i.next();
946             sum += delay.getAttempts();
947         }
948         return sum;
949     }
950     
951     /**
952      * This method expands an ArrayList containing Delay objects into an array holding the
953      * only delaytime in the order.<p>
954      * So if the list has 2 Delay objects the first having attempts=2 and delaytime 4000
955      * the second having attempts=1 and delaytime=300000 will be expanded into this array:<p>
956      * long[0] = 4000<p>
957      * long[1] = 4000<p>
958      * long[2] = 300000<p>
959      * @param list the list to expand
960      * @return the expanded list
961      **/

962     private long[] expandDelays (ArrayList JavaDoc list) {
963         long[] delays = new long [calcTotalAttempts(list)];
964         Iterator JavaDoc i = list.iterator();
965         int idx = 0;
966         while (i.hasNext()) {
967             Delay delay = (Delay)i.next();
968             for (int j=0; j<delay.getAttempts(); j++) {
969                 delays[idx++]= delay.getDelayTime();
970             }
971         }
972         return delays;
973     }
974     
975     /**
976      * This method returns, given a retry-count, the next delay time to use.
977      * @param retry_count the current retry_count.
978      * @return the next delay time to use, given the retry count
979      **/

980     private long getNextDelay (int retry_count) {
981         return delayTimes[retry_count-1];
982     }
983
984     /**
985      * This class is used to hold a delay time and its corresponding number
986      * of retries.
987      **/

988     private class Delay {
989         private int attempts = 1;
990         private long delayTime = DEFAULT_DELAY_TIME;
991         
992             
993         /**
994          * This constructor expects Strings of the form "[attempt\*]delaytime[unit]". <p>
995          * The optional attempt is the number of tries this delay should be used (default = 1)
996          * The unit if present must be one of (msec,sec,minute,hour,day) (default = msec)
997          * The constructor multiplies the delaytime by the relevant multiplier for the unit,
998          * so the delayTime instance variable is always in msec.
999          * @param init_string the string to initialize this Delay object from
1000         **/

1001        public Delay (String JavaDoc init_string) throws MessagingException JavaDoc
1002        {
1003            String JavaDoc unit = "msec"; //default unit
1004
if (delayTimeMatcher.matches (init_string, PATTERN)) {
1005                MatchResult res = delayTimeMatcher.getMatch ();
1006                //the capturing groups will now hold
1007
//at 1: attempts * (if present)
1008
//at 2: delaytime
1009
//at 3: unit (if present)
1010

1011                if (res.group(1) != null && !res.group(1).equals ("")) {
1012                    //we have an attempt *
1013
String JavaDoc attempt_match = res.group(1);
1014                    //strip the * and whitespace
1015
attempt_match = attempt_match.substring (0,attempt_match.length()-1).trim();
1016                    attempts = Integer.parseInt (attempt_match);
1017                }
1018                
1019                delayTime = Long.parseLong (res.group(2));
1020                
1021                if (!res.group(3).equals ("")) {
1022                    //we have a unit
1023
unit = res.group(3).toLowerCase();
1024                }
1025            } else {
1026                throw new MessagingException JavaDoc(init_string+" does not match "+PATTERN_STRING);
1027            }
1028            if (MULTIPLIERS.get (unit)!=null) {
1029                int multiplier = ((Integer JavaDoc)MULTIPLIERS.get (unit)).intValue();
1030                delayTime *= multiplier;
1031            } else {
1032                throw new MessagingException JavaDoc("Unknown unit: "+unit);
1033            }
1034        }
1035
1036        /**
1037         * This constructor makes a default Delay object, ie. attempts=1 and delayTime=DEFAULT_DELAY_TIME
1038         **/

1039        public Delay () {
1040        }
1041
1042        /**
1043         * @return the delayTime for this Delay
1044         **/

1045        public long getDelayTime () {
1046            return delayTime;
1047        }
1048
1049        /**
1050         * @return the number attempts this Delay should be used.
1051         **/

1052        public int getAttempts () {
1053            return attempts;
1054        }
1055        
1056        /**
1057         * Set the number attempts this Delay should be used.
1058         **/

1059        public void setAttempts (int value) {
1060            attempts = value;
1061        }
1062        
1063        /**
1064         * Pretty prints this Delay
1065         **/

1066        public String JavaDoc toString () {
1067            StringBuffer JavaDoc buf = new StringBuffer JavaDoc(15);
1068            buf.append (getAttempts ());
1069            buf.append ('*');
1070            buf.append (getDelayTime());
1071            buf.append ("msec");
1072            return buf.toString();
1073        }
1074    }
1075    
1076    /*
1077     * Returns an Iterator over org.apache.mailet.HostAddress, a
1078     * specialized subclass of javax.mail.URLName, which provides
1079     * location information for servers that are specified as mail
1080     * handlers for the given hostname. If no host is found, the
1081     * Iterator returned will be empty and the first call to hasNext()
1082     * will return false. The Iterator is a nested iterator: the outer
1083     * iteration is over each gateway, and the inner iteration is over
1084     * potentially multiple A records for each gateway.
1085     *
1086     * @see org.apache.james.DNSServer#getSMTPHostAddresses(String)
1087     * @since v2.2.0a16-unstable
1088     * @param gatewayServers - Collection of host[:port] Strings
1089     * @return an Iterator over HostAddress instances, sorted by priority
1090     */

1091    private Iterator JavaDoc getGatewaySMTPHostAddresses(final Collection JavaDoc gatewayServers) {
1092        return new Iterator JavaDoc() {
1093            private Iterator JavaDoc gateways = gatewayServers.iterator();
1094            private Iterator JavaDoc addresses = null;
1095
1096            public boolean hasNext() {
1097                return gateways.hasNext();
1098            }
1099
1100            public Object JavaDoc next() {
1101                if (addresses == null || !addresses.hasNext())
1102                {
1103                    String JavaDoc server = (String JavaDoc) gateways.next();
1104                    String JavaDoc port = "25";
1105
1106                    int idx = server.indexOf(':');
1107                    if ( idx > 0) {
1108                        port = server.substring(idx+1);
1109                        server = server.substring(0,idx);
1110                    }
1111
1112                    final String JavaDoc nextGateway = server;
1113                    final String JavaDoc nextGatewayPort = port;
1114                    try {
1115                        final InetAddress JavaDoc[] ips = org.apache.james.dnsserver.DNSServer.getAllByName(nextGateway);
1116                        addresses = new Iterator JavaDoc() {
1117                            private InetAddress JavaDoc[] ipAddresses = ips;
1118                            int i = 0;
1119
1120                            public boolean hasNext() {
1121                                return i < ipAddresses.length;
1122                            }
1123
1124                            public Object JavaDoc next() {
1125                                return new org.apache.mailet.HostAddress(nextGateway, "smtp://" + (ipAddresses[i++]).getHostAddress() + ":" + nextGatewayPort);
1126                            }
1127
1128                            public void remove() {
1129                                throw new UnsupportedOperationException JavaDoc ("remove not supported by this iterator");
1130                            }
1131                        };
1132                    }
1133                    catch (java.net.UnknownHostException JavaDoc uhe) {
1134                        log("Unknown gateway host: " + uhe.getMessage().trim());
1135                        log("This could be a DNS server error or configuration error.");
1136                    }
1137                }
1138                return (addresses != null) ? addresses.next() : null;
1139            }
1140
1141            public void remove() {
1142                throw new UnsupportedOperationException JavaDoc ("remove not supported by this iterator");
1143            }
1144        };
1145    }
1146}
1147
Popular Tags