001 /*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License"). You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 *
026 * Copyright 2006-2008 Sun Microsystems, Inc.
027 */
028 package org.opends.server.util;
029
030
031
032 import java.io.BufferedReader;
033 import java.io.File;
034 import java.io.FileReader;
035 import java.util.ArrayList;
036 import java.util.Date;
037 import java.util.LinkedList;
038 import java.util.List;
039 import java.util.Properties;
040 import javax.activation.DataHandler;
041 import javax.activation.FileDataSource;
042 import javax.mail.MessagingException;
043 import javax.mail.SendFailedException;
044 import javax.mail.Session;
045 import javax.mail.Transport;
046 import javax.mail.internet.InternetAddress;
047 import javax.mail.internet.MimeBodyPart;
048 import javax.mail.internet.MimeMessage;
049 import javax.mail.internet.MimeMultipart;
050
051 import org.opends.messages.Message;
052 import org.opends.messages.MessageBuilder;
053 import org.opends.server.core.DirectoryServer;
054 import org.opends.server.loggers.debug.DebugTracer;
055 import org.opends.server.types.DebugLogLevel;
056 import org.opends.server.util.args.ArgumentException;
057 import org.opends.server.util.args.ArgumentParser;
058 import org.opends.server.util.args.BooleanArgument;
059 import org.opends.server.util.args.StringArgument;
060
061 import static org.opends.messages.ToolMessages.*;
062 import static org.opends.messages.UtilityMessages.*;
063 import static org.opends.server.loggers.debug.DebugLogger.*;
064 import static org.opends.server.util.ServerConstants.*;
065 import static org.opends.server.util.StaticUtils.*;
066
067
068
069 /**
070 * This class defines an e-mail message that may be sent to one or more
071 * recipients via SMTP. This is a wrapper around JavaMail to make this process
072 * more convenient and fit better into the Directory Server framework.
073 */
074 @org.opends.server.types.PublicAPI(
075 stability=org.opends.server.types.StabilityLevel.VOLATILE,
076 mayInstantiate=true,
077 mayExtend=false,
078 mayInvoke=true)
079 public final class EMailMessage
080 {
081 /**
082 * The tracer object for the debug logger.
083 */
084 private static final DebugTracer TRACER = getTracer();
085
086
087 // The addresses of the recipients to whom this message should be sent.
088 private List<String> recipients;
089
090 // The set of attachments to include in this message.
091 private LinkedList<MimeBodyPart> attachments;
092
093 // The MIME type for the message body.
094 private String bodyMIMEType;
095
096 // The address of the sender for this message.
097 private String sender;
098
099 // The subject for the mail message.
100 private String subject;
101
102 // The body for the mail message.
103 private MessageBuilder body;
104
105
106
107 /**
108 * Creates a new e-mail message with the provided information.
109 *
110 * @param sender The address of the sender for the message.
111 * @param recipient The address of the recipient for the message.
112 * @param subject The subject to use for the message.
113 */
114 public EMailMessage(String sender, String recipient, String subject)
115 {
116 this.sender = sender;
117 this.subject = subject;
118
119 recipients = new ArrayList<String>();
120 recipients.add(recipient);
121
122 body = new MessageBuilder();
123 attachments = new LinkedList<MimeBodyPart>();
124 bodyMIMEType = "text/plain";
125 }
126
127
128
129 /**
130 * Creates a new e-mail message with the provided information.
131 *
132 * @param sender The address of the sender for the message.
133 * @param recipients The addresses of the recipients for the message.
134 * @param subject The subject to use for the message.
135 */
136 public EMailMessage(String sender, List<String> recipients,
137 String subject)
138 {
139 this.sender = sender;
140 this.recipients = recipients;
141 this.subject = subject;
142
143 body = new MessageBuilder();
144 attachments = new LinkedList<MimeBodyPart>();
145 bodyMIMEType = "text/plain";
146 }
147
148
149
150 /**
151 * Retrieves the sender for this message.
152 *
153 * @return The sender for this message.
154 */
155 public String getSender()
156 {
157 return sender;
158 }
159
160
161
162 /**
163 * Specifies the sender for this message.
164 *
165 * @param sender The sender for this message.
166 */
167 public void setSender(String sender)
168 {
169 this.sender = sender;
170 }
171
172
173
174 /**
175 * Retrieves the set of recipients for this message. This list may be
176 * directly manipulated by the caller.
177 *
178 * @return The set of recipients for this message.
179 */
180 public List<String> getRecipients()
181 {
182 return recipients;
183 }
184
185
186
187 /**
188 * Specifies the set of recipients for this message.
189 *
190 * @param recipients The set of recipients for this message.
191 */
192 public void setRecipients(ArrayList<String> recipients)
193 {
194 this.recipients = recipients;
195 }
196
197
198
199 /**
200 * Adds the specified recipient to this message.
201 *
202 * @param recipient The recipient to add to this message.
203 */
204 public void addRecipient(String recipient)
205 {
206 recipients.add(recipient);
207 }
208
209
210
211 /**
212 * Retrieves the subject for this message.
213 *
214 * @return The subject for this message.
215 */
216 public String getSubject()
217 {
218 return subject;
219 }
220
221
222
223 /**
224 * Specifies the subject for this message.
225 *
226 * @param subject The subject for this message.
227 */
228 public void setSubject(String subject)
229 {
230 this.subject = subject;
231 }
232
233
234
235 /**
236 * Retrieves the body for this message. It may be directly manipulated by the
237 * caller.
238 *
239 * @return The body for this message.
240 */
241 public MessageBuilder getBody()
242 {
243 return body;
244 }
245
246
247
248 /**
249 * Specifies the body for this message.
250 *
251 * @param body The body for this message.
252 */
253 public void setBody(MessageBuilder body)
254 {
255 this.body = body;
256 }
257
258
259
260 /**
261 * Specifies the body for this message.
262 *
263 * @param body The body for this message.
264 */
265 public void setBody(Message body)
266 {
267 this.body = new MessageBuilder(body);
268 }
269
270
271
272 /**
273 * Appends the provided text to the body of this message.
274 *
275 * @param text The text to append to the body of the message.
276 */
277 public void appendToBody(String text)
278 {
279 body.append(text);
280 }
281
282
283
284 /**
285 * Retrieves the set of attachments for this message. This list may be
286 * directly modified by the caller if desired.
287 *
288 * @return The set of attachments for this message.
289 */
290 public LinkedList<MimeBodyPart> getAttachments()
291 {
292 return attachments;
293 }
294
295
296
297 /**
298 * Adds the provided attachment to this mail message.
299 *
300 * @param attachment The attachment to add to this mail message.
301 */
302 public void addAttachment(MimeBodyPart attachment)
303 {
304 attachments.add(attachment);
305 }
306
307
308
309 /**
310 * Adds an attachment to this mail message with the provided text.
311 *
312 * @param attachmentText The text to include in the attachment.
313 *
314 * @throws MessagingException If there is a problem of some type with the
315 * attachment.
316 */
317 public void addAttachment(String attachmentText)
318 throws MessagingException
319 {
320 MimeBodyPart attachment = new MimeBodyPart();
321 attachment.setText(attachmentText);
322 attachments.add(attachment);
323 }
324
325
326
327 /**
328 * Adds the provided attachment to this mail message.
329 *
330 * @param attachmentFile The file containing the attachment data.
331 *
332 * @throws MessagingException If there is a problem of some type with the
333 * attachment.
334 */
335 public void addAttachment(File attachmentFile)
336 throws MessagingException
337 {
338 MimeBodyPart attachment = new MimeBodyPart();
339
340 FileDataSource dataSource = new FileDataSource(attachmentFile);
341 attachment.setDataHandler(new DataHandler(dataSource));
342 attachment.setFileName(attachmentFile.getName());
343
344 attachments.add(attachment);
345 }
346
347
348
349 /**
350 * Attempts to send this message to the intended recipient(s). This will use
351 * the mail server(s) defined in the Directory Server mail handler
352 * configuration. If multiple servers are specified and the first is
353 * unavailable, then the other server(s) will be tried before returning a
354 * failure to the caller.
355 *
356 * @throws MessagingException If a problem occurred while attempting to send
357 * the message.
358 */
359 public void send()
360 throws MessagingException
361 {
362 send(DirectoryServer.getMailServerPropertySets());
363 }
364
365
366
367 /**
368 * Attempts to send this message to the intended recipient(s). If multiple
369 * servers are specified and the first is unavailable, then the other
370 * server(s) will be tried before returning a failure to the caller.
371 *
372 * @param mailServerPropertySets A list of property sets providing
373 * information about the mail servers to use
374 * when sending the message.
375 *
376 * @throws MessagingException If a problem occurred while attempting to send
377 * the message.
378 */
379 public void send(List<Properties> mailServerPropertySets)
380 throws MessagingException
381 {
382 // Get information about the available mail servers that we can use.
383 MessagingException sendException = null;
384 for (Properties props : mailServerPropertySets)
385 {
386 // Get a session and use it to create a new message.
387 Session session = Session.getInstance(props);
388 MimeMessage message = new MimeMessage(session);
389 message.setSubject(subject);
390 message.setSentDate(new Date());
391
392
393 // Add the sender address. If this fails, then it's a fatal problem we'll
394 // propagate to the caller.
395 try
396 {
397 message.setFrom(new InternetAddress(sender));
398 }
399 catch (MessagingException me)
400 {
401 if (debugEnabled())
402 {
403 TRACER.debugCaught(DebugLogLevel.ERROR, me);
404 }
405
406 Message msg = ERR_EMAILMSG_INVALID_SENDER_ADDRESS.get(
407 String.valueOf(sender), me.getMessage());
408 throw new MessagingException(msg.toString(), me);
409 }
410
411
412 // Add the recipient addresses. If any of them fail, then that's a fatal
413 // problem we'll propagate to the caller.
414 InternetAddress[] recipientAddresses =
415 new InternetAddress[recipients.size()];
416 for (int i=0; i < recipientAddresses.length; i++)
417 {
418 String recipient = recipients.get(i);
419
420 try
421 {
422 recipientAddresses[i] = new InternetAddress(recipient);
423 }
424 catch (MessagingException me)
425 {
426 if (debugEnabled())
427 {
428 TRACER.debugCaught(DebugLogLevel.ERROR, me);
429 }
430
431 Message msg = ERR_EMAILMSG_INVALID_RECIPIENT_ADDRESS.get(
432 String.valueOf(recipient), me.getMessage());
433 throw new MessagingException(msg.toString(), me);
434 }
435 }
436 message.setRecipients(
437 javax.mail.Message.RecipientType.TO,
438 recipientAddresses);
439
440
441 // If we have any attachments, then the whole thing needs to be
442 // multipart. Otherwise, just set the text of the message.
443 if (attachments.isEmpty())
444 {
445 message.setText(body.toString());
446 }
447 else
448 {
449 MimeMultipart multiPart = new MimeMultipart();
450
451 MimeBodyPart bodyPart = new MimeBodyPart();
452 bodyPart.setText(body.toString());
453 multiPart.addBodyPart(bodyPart);
454
455 for (MimeBodyPart attachment : attachments)
456 {
457 multiPart.addBodyPart(attachment);
458 }
459
460 message.setContent(multiPart);
461 }
462
463
464 // Try to send the message. If this fails, it can be a complete failure
465 // or a partial one. If it's a complete failure then try rolling over to
466 // the next server. If it's a partial one, then that likely means that
467 // the message was sent but one or more recipients was rejected, so we'll
468 // propagate that back to the caller.
469 try
470 {
471 Transport.send(message);
472 return;
473 }
474 catch (SendFailedException sfe)
475 {
476 if (debugEnabled())
477 {
478 TRACER.debugCaught(DebugLogLevel.ERROR, sfe);
479 }
480
481 // We'll ignore this and hope that another server is available. If not,
482 // then at least save the exception so that we can throw it if all else
483 // fails.
484 if (sendException == null)
485 {
486 sendException = sfe;
487 }
488 }
489 // FIXME -- Are there any other types of MessagingException that we might
490 // want to catch so we could try again on another server?
491 }
492
493
494 // If we've gotten here, then we've tried all of the servers in the list and
495 // still failed. If we captured an earlier exception, then throw it.
496 // Otherwise, throw a generic exception.
497 if (sendException == null)
498 {
499 Message message = ERR_EMAILMSG_CANNOT_SEND.get();
500 throw new MessagingException(message.toString());
501 }
502 else
503 {
504 throw sendException;
505 }
506 }
507
508
509
510 /**
511 * Provide a command-line mechanism for sending an e-mail message via SMTP.
512 *
513 * @param args The command-line arguments provided to this program.
514 */
515 public static void main(String[] args)
516 {
517 Message description = INFO_EMAIL_TOOL_DESCRIPTION.get();
518 ArgumentParser argParser = new ArgumentParser(EMailMessage.class.getName(),
519 description, false);
520
521 BooleanArgument showUsage = null;
522 StringArgument attachFile = null;
523 StringArgument bodyFile = null;
524 StringArgument host = null;
525 StringArgument from = null;
526 StringArgument subject = null;
527 StringArgument to = null;
528
529 try
530 {
531 host = new StringArgument("host", 'h', "host", true, true, true,
532 INFO_HOST_PLACEHOLDER.get(), "127.0.0.1", null,
533 INFO_EMAIL_HOST_DESCRIPTION.get());
534 argParser.addArgument(host);
535
536
537 from = new StringArgument("from", 'f', "from", true, false, true,
538 INFO_ADDRESS_PLACEHOLDER.get(), null, null,
539 INFO_EMAIL_FROM_DESCRIPTION.get());
540 argParser.addArgument(from);
541
542
543 to = new StringArgument("to", 't', "to", true, true, true,
544 INFO_ADDRESS_PLACEHOLDER.get(),
545 null, null, INFO_EMAIL_TO_DESCRIPTION.get());
546 argParser.addArgument(to);
547
548
549 subject = new StringArgument("subject", 's', "subject", true, false, true,
550 INFO_SUBJECT_PLACEHOLDER.get(), null, null,
551 INFO_EMAIL_SUBJECT_DESCRIPTION.get());
552 argParser.addArgument(subject);
553
554
555 bodyFile = new StringArgument("bodyfile", 'b', "body", true, true, true,
556 INFO_PATH_PLACEHOLDER.get(), null, null,
557 INFO_EMAIL_BODY_DESCRIPTION.get());
558 argParser.addArgument(bodyFile);
559
560
561 attachFile = new StringArgument("attachfile", 'a', "attach", false, true,
562 true, INFO_PATH_PLACEHOLDER.get(), null,
563 null,
564 INFO_EMAIL_ATTACH_DESCRIPTION.get());
565 argParser.addArgument(attachFile);
566
567
568 showUsage = new BooleanArgument("help", 'H', "help",
569 INFO_EMAIL_HELP_DESCRIPTION.get());
570 argParser.addArgument(showUsage);
571 argParser.setUsageArgument(showUsage);
572 }
573 catch (ArgumentException ae)
574 {
575 System.err.println(
576 ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()).toString());
577 System.exit(1);
578 }
579
580 try
581 {
582 argParser.parseArguments(args);
583 }
584 catch (ArgumentException ae)
585 {
586 System.err.println(
587 ERR_ERROR_PARSING_ARGS.get(ae.getMessage()).toString());
588 System.exit(1);
589 }
590
591 if (showUsage.isPresent())
592 {
593 return;
594 }
595
596 LinkedList<Properties> mailServerProperties = new LinkedList<Properties>();
597 for (String s : host.getValues())
598 {
599 Properties p = new Properties();
600 p.setProperty(SMTP_PROPERTY_HOST, s);
601 mailServerProperties.add(p);
602 }
603
604 EMailMessage message = new EMailMessage(from.getValue(), to.getValues(),
605 subject.getValue());
606
607 for (String s : bodyFile.getValues())
608 {
609 try
610 {
611 File f = new File(s);
612 if (! f.exists())
613 {
614 System.err.println(ERR_EMAIL_NO_SUCH_BODY_FILE.get(s));
615 System.exit(1);
616 }
617
618 BufferedReader reader = new BufferedReader(new FileReader(f));
619 while (true)
620 {
621 String line = reader.readLine();
622 if (line == null)
623 {
624 break;
625 }
626
627 message.appendToBody(line);
628 message.appendToBody("\r\n"); // SMTP says we should use CRLF.
629 }
630
631 reader.close();
632 }
633 catch (Exception e)
634 {
635 System.err.println(ERR_EMAIL_CANNOT_PROCESS_BODY_FILE.get(s,
636 getExceptionMessage(e)));
637 System.exit(1);
638 }
639 }
640
641 if (attachFile.isPresent())
642 {
643 for (String s : attachFile.getValues())
644 {
645 File f = new File(s);
646 if (! f.exists())
647 {
648 System.err.println(ERR_EMAIL_NO_SUCH_ATTACHMENT_FILE.get(s));
649 System.exit(1);
650 }
651
652 try
653 {
654 message.addAttachment(f);
655 }
656 catch (Exception e)
657 {
658 System.err.println(ERR_EMAIL_CANNOT_ATTACH_FILE.get(s,
659 getExceptionMessage(e)));
660 }
661 }
662 }
663
664 try
665 {
666 message.send(mailServerProperties);
667 }
668 catch (Exception e)
669 {
670 System.err.println(ERR_EMAIL_CANNOT_SEND_MESSAGE.get(
671 getExceptionMessage(e)));
672 System.exit(1);
673 }
674 }
675 }
676