1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You 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 implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18 package org.apache.tools.ant.taskdefs.email;
19
20 import java.io.File;
21 import java.util.Iterator;
22 import java.util.StringTokenizer;
23 import java.util.Vector;
24
25 import org.apache.tools.ant.BuildException;
26 import org.apache.tools.ant.Project;
27 import org.apache.tools.ant.Task;
28 import org.apache.tools.ant.types.EnumeratedAttribute;
29 import org.apache.tools.ant.types.FileSet;
30 import org.apache.tools.ant.types.Path;
31 import org.apache.tools.ant.types.Resource;
32 import org.apache.tools.ant.types.resources.FileProvider;
33 import org.apache.tools.ant.types.resources.FileResource;
34 import org.apache.tools.ant.util.ClasspathUtils;
35
36 /**
37 * A task to send SMTP email. This is a refactoring of the SendMail and
38 * MimeMail tasks such that both are within a single task.
39 *
40 * @since Ant 1.5
41 * @ant.task name="mail" category="network"
42 */
43 public class EmailTask extends Task {
44 private static final int SMTP_PORT = 25;
45
46 /** Constant to show that the best available mailer should be used. */
47 public static final String AUTO = "auto";
48 /** Constant to allow the Mime mailer to be requested */
49 public static final String MIME = "mime";
50 /** Constant to allow the UU mailer to be requested */
51 public static final String UU = "uu";
52 /** Constant to allow the plaintext mailer to be requested */
53 public static final String PLAIN = "plain";
54
55 /**
56 * Enumerates the encoding constants.
57 */
58 public static class Encoding extends EnumeratedAttribute {
59 /**
60 * finds the valid encoding values
61 *
62 * @return a list of valid entries
63 */
64 public String[] getValues() {
65 return new String[] {AUTO, MIME, UU, PLAIN};
66 }
67 }
68
69 private String encoding = AUTO;
70 /** host running SMTP */
71 private String host = "localhost";
72 private int port = SMTP_PORT;
73 /** subject field */
74 private String subject = null;
75 /** any text */
76 private Message message = null;
77 /** failure flag */
78 private boolean failOnError = true;
79 private boolean includeFileNames = false;
80 private String messageMimeType = null;
81 /* special headers */
82 /** sender */
83 private EmailAddress from = null;
84 /** replyto */
85 private Vector replyToList = new Vector();
86 /** TO recipients */
87 private Vector toList = new Vector();
88 /** CC (Carbon Copy) recipients */
89 private Vector ccList = new Vector();
90 /** BCC (Blind Carbon Copy) recipients */
91 private Vector bccList = new Vector();
92
93 /** generic headers */
94 private Vector headers = new Vector();
95
96 /** file list */
97 private Path attachments = null;
98 /** Character set for MimeMailer*/
99 private String charset = null;
100 /** User for SMTP auth */
101 private String user = null;
102 /** Password for SMTP auth */
103 private String password = null;
104 /** indicate if the user wishes SSL-TLS */
105 private boolean ssl = false;
106 /** indicate if the user wishes support for STARTTLS */
107 private boolean starttls = false;
108
109 /** ignore invalid recipients? */
110 private boolean ignoreInvalidRecipients = false;
111
112 /**
113 * Set the user for SMTP auth; this requires JavaMail.
114 * @param user the String username.
115 * @since Ant 1.6
116 */
117 public void setUser(String user) {
118 this.user = user;
119 }
120
121 /**
122 * Set the password for SMTP auth; this requires JavaMail.
123 * @param password the String password.
124 * @since Ant 1.6
125 */
126 public void setPassword(String password) {
127 this.password = password;
128 }
129
130 /**
131 * Set whether to send data over SSL.
132 * @param ssl boolean; if true SSL will be used.
133 * @since Ant 1.6
134 */
135 public void setSSL(boolean ssl) {
136 this.ssl = ssl;
137 }
138
139 /**
140 * Set whether to allow authentication to switch to a TLS
141 * connection via STARTTLS.
142 * @param b boolean; if true STARTTLS will be supported.
143 * @since Ant 1.8.0
144 */
145 public void setEnableStartTLS(boolean b) {
146 this.starttls = b;
147 }
148
149 /**
150 * Set the preferred encoding method.
151 *
152 * @param encoding The encoding (one of AUTO, MIME, UU, PLAIN).
153 */
154 public void setEncoding(Encoding encoding) {
155 this.encoding = encoding.getValue();
156 }
157
158 /**
159 * Set the mail server port.
160 *
161 * @param port The port to use.
162 */
163 public void setMailport(int port) {
164 this.port = port;
165 }
166
167 /**
168 * Set the host.
169 *
170 * @param host The host to connect to.
171 */
172 public void setMailhost(String host) {
173 this.host = host;
174 }
175
176 /**
177 * Set the subject line of the email.
178 *
179 * @param subject Subject of this email.
180 */
181 public void setSubject(String subject) {
182 this.subject = subject;
183 }
184
185 /**
186 * Shorthand method to set the message.
187 *
188 * @param message Message body of this email.
189 */
190 public void setMessage(String message) {
191 if (this.message != null) {
192 throw new BuildException("Only one message can be sent in an "
193 + "email");
194 }
195 this.message = new Message(message);
196 this.message.setProject(getProject());
197 }
198
199 /**
200 * Shorthand method to set the message from a file.
201 *
202 * @param file The file from which to take the message.
203 */
204 public void setMessageFile(File file) {
205 if (this.message != null) {
206 throw new BuildException("Only one message can be sent in an "
207 + "email");
208 }
209 this.message = new Message(file);
210 this.message.setProject(getProject());
211 }
212
213 /**
214 * Shorthand method to set type of the text message, text/plain by default
215 * but text/html or text/xml is quite feasible.
216 *
217 * @param type The new MessageMimeType value.
218 */
219 public void setMessageMimeType(String type) {
220 this.messageMimeType = type;
221 }
222
223 /**
224 * Add a message element.
225 *
226 * @param message The message object.
227 * @throws BuildException if a message has already been added.
228 */
229 public void addMessage(Message message) throws BuildException {
230 if (this.message != null) {
231 throw new BuildException(
232 "Only one message can be sent in an email");
233 }
234 this.message = message;
235 }
236
237 /**
238 * Add a from address element.
239 *
240 * @param address The address to send from.
241 */
242 public void addFrom(EmailAddress address) {
243 if (this.from != null) {
244 throw new BuildException("Emails can only be from one address");
245 }
246 this.from = address;
247 }
248
249 /**
250 * Shorthand to set the from address element.
251 *
252 * @param address The address to send mail from.
253 */
254 public void setFrom(String address) {
255 if (this.from != null) {
256 throw new BuildException("Emails can only be from one address");
257 }
258 this.from = new EmailAddress(address);
259 }
260
261 /**
262 * Add a replyto address element.
263 *
264 * @param address The address to reply to.
265 * @since Ant 1.6
266 */
267 public void addReplyTo(EmailAddress address) {
268 this.replyToList.add(address);
269 }
270
271 /**
272 * Shorthand to set the replyto address element.
273 *
274 * @param address The address to which replies should be directed.
275 * @since Ant 1.6
276 */
277 public void setReplyTo(String address) {
278 this.replyToList.add(new EmailAddress(address));
279 }
280
281 /**
282 * Add a to address element.
283 *
284 * @param address An email address.
285 */
286 public void addTo(EmailAddress address) {
287 toList.addElement(address);
288 }
289
290 /**
291 * Shorthand to set the "to" address element.
292 *
293 * @param list Comma-separated list of addresses.
294 */
295 public void setToList(String list) {
296 StringTokenizer tokens = new StringTokenizer(list, ",");
297
298 while (tokens.hasMoreTokens()) {
299 toList.addElement(new EmailAddress(tokens.nextToken()));
300 }
301 }
302
303 /**
304 * Add a "cc" address element.
305 *
306 * @param address The email address.
307 */
308 public void addCc(EmailAddress address) {
309 ccList.addElement(address);
310 }
311
312 /**
313 * Shorthand to set the "cc" address element.
314 *
315 * @param list Comma separated list of addresses.
316 */
317 public void setCcList(String list) {
318 StringTokenizer tokens = new StringTokenizer(list, ",");
319
320 while (tokens.hasMoreTokens()) {
321 ccList.addElement(new EmailAddress(tokens.nextToken()));
322 }
323 }
324
325 /**
326 * Add a "bcc" address element.
327 *
328 * @param address The email address.
329 */
330 public void addBcc(EmailAddress address) {
331 bccList.addElement(address);
332 }
333
334 /**
335 * Shorthand to set the "bcc" address element.
336 *
337 * @param list comma separated list of addresses.
338 */
339 public void setBccList(String list) {
340 StringTokenizer tokens = new StringTokenizer(list, ",");
341
342 while (tokens.hasMoreTokens()) {
343 bccList.addElement(new EmailAddress(tokens.nextToken()));
344 }
345 }
346
347 /**
348 * Set whether BuildExceptions should be passed back to the core.
349 *
350 * @param failOnError The new FailOnError value.
351 */
352 public void setFailOnError(boolean failOnError) {
353 this.failOnError = failOnError;
354 }
355
356 /**
357 * Set the list of files to be attached.
358 *
359 * @param filenames Comma-separated list of files.
360 */
361 public void setFiles(String filenames) {
362 StringTokenizer t = new StringTokenizer(filenames, ", ");
363
364 while (t.hasMoreTokens()) {
365 createAttachments()
366 .add(new FileResource(getProject().resolveFile(t.nextToken())));
367 }
368 }
369
370 /**
371 * Add a set of files (nested fileset attribute).
372 *
373 * @param fs The fileset.
374 */
375 public void addFileset(FileSet fs) {
376 createAttachments().add(fs);
377 }
378
379 /**
380 * Creates a Path as container for attachments. Supports any
381 * filesystem resource-collections that way.
382 * @return the path to be configured.
383 * @since Ant 1.7
384 */
385 public Path createAttachments() {
386 if (attachments == null) {
387 attachments = new Path(getProject());
388 }
389 return attachments.createPath();
390 }
391
392 /**
393 * Create a nested header element.
394 * @return a Header instance.
395 */
396 public Header createHeader() {
397 Header h = new Header();
398 headers.add(h);
399 return h;
400 }
401
402 /**
403 * Set whether to include filenames.
404 *
405 * @param includeFileNames Whether to include filenames in the text of the
406 * message.
407 */
408 public void setIncludefilenames(boolean includeFileNames) {
409 this.includeFileNames = includeFileNames;
410 }
411
412 /**
413 * Get whether file names should be included.
414 *
415 * @return Identifies whether file names should be included.
416 */
417 public boolean getIncludeFileNames() {
418 return includeFileNames;
419 }
420
421 /**
422 * Whether invalid recipients should be ignored (but a warning
423 * will be logged) instead of making the task fail.
424 *
425 * <p>Even with this property set to true the task will still fail
426 * if the mail couldn't be sent to any recipient at all.</p>
427 *
428 * @since Ant 1.8.0
429 */
430 public void setIgnoreInvalidRecipients(boolean b) {
431 ignoreInvalidRecipients = b;
432 }
433
434 /**
435 * Send an email.
436 */
437 public void execute() {
438 Message savedMessage = message;
439
440 try {
441 Mailer mailer = null;
442
443 // prepare for the auto select mechanism
444 boolean autoFound = false;
445 // try MIME format
446 if (encoding.equals(MIME)
447 || (encoding.equals(AUTO) && !autoFound)) {
448 try {
449 //check to make sure that activation.jar
450 //and mail.jar are available - see bug 31969
451 Class.forName("javax.activation.DataHandler");
452 Class.forName("javax.mail.internet.MimeMessage");
453
454 mailer = (Mailer) ClasspathUtils.newInstance(
455 "org.apache.tools.ant.taskdefs.email.MimeMailer",
456 EmailTask.class.getClassLoader(), Mailer.class);
457 autoFound = true;
458
459 log("Using MIME mail", Project.MSG_VERBOSE);
460 } catch (BuildException e) {
461 logBuildException("Failed to initialise MIME mail: ", e);
462 }
463 }
464 // SMTP auth only allowed with MIME mail
465 if (!autoFound && ((user != null) || (password != null))
466 && (encoding.equals(UU) || encoding.equals(PLAIN))) {
467 throw new BuildException("SMTP auth only possible with MIME mail");
468 }
469 // SSL only allowed with MIME mail
470 if (!autoFound && (ssl || starttls)
471 && (encoding.equals(UU) || encoding.equals(PLAIN))) {
472 throw new BuildException("SSL and STARTTLS only possible with"
473 + " MIME mail");
474 }
475 // try UU format
476 if (encoding.equals(UU)
477 || (encoding.equals(AUTO) && !autoFound)) {
478 try {
479 mailer = (Mailer) ClasspathUtils.newInstance(
480 "org.apache.tools.ant.taskdefs.email.UUMailer",
481 EmailTask.class.getClassLoader(), Mailer.class);
482 autoFound = true;
483 log("Using UU mail", Project.MSG_VERBOSE);
484 } catch (BuildException e) {
485 logBuildException("Failed to initialise UU mail: ", e);
486 }
487 }
488 // try plain format
489 if (encoding.equals(PLAIN)
490 || (encoding.equals(AUTO) && !autoFound)) {
491 mailer = new PlainMailer();
492 autoFound = true;
493 log("Using plain mail", Project.MSG_VERBOSE);
494 }
495 // a valid mailer must be present by now
496 if (mailer == null) {
497 throw new BuildException("Failed to initialise encoding: "
498 + encoding);
499 }
500 // a valid message is required
501 if (message == null) {
502 message = new Message();
503 message.setProject(getProject());
504 }
505 // an address to send from is required
506 if (from == null || from.getAddress() == null) {
507 throw new BuildException("A from element is required");
508 }
509 // at least one address to send to/cc/bcc is required
510 if (toList.isEmpty() && ccList.isEmpty() && bccList.isEmpty()) {
511 throw new BuildException("At least one of to, cc or bcc must "
512 + "be supplied");
513 }
514 // set the mimetype if not done already (and required)
515 if (messageMimeType != null) {
516 if (message.isMimeTypeSpecified()) {
517 throw new BuildException("The mime type can only be "
518 + "specified in one location");
519 }
520 message.setMimeType(messageMimeType);
521 }
522 // set the character set if not done already (and required)
523 if (charset != null) {
524 if (message.getCharset() != null) {
525 throw new BuildException("The charset can only be "
526 + "specified in one location");
527 }
528 message.setCharset(charset);
529 }
530
531 // identify which files should be attached
532 Vector files = new Vector();
533 if (attachments != null) {
534 Iterator iter = attachments.iterator();
535
536 while (iter.hasNext()) {
537 Resource r = (Resource) iter.next();
538 files.addElement(((FileProvider) r.as(FileProvider.class))
539 .getFile());
540 }
541 }
542 // let the user know what's going to happen
543 log("Sending email: " + subject, Project.MSG_INFO);
544 log("From " + from, Project.MSG_VERBOSE);
545 log("ReplyTo " + replyToList, Project.MSG_VERBOSE);
546 log("To " + toList, Project.MSG_VERBOSE);
547 log("Cc " + ccList, Project.MSG_VERBOSE);
548 log("Bcc " + bccList, Project.MSG_VERBOSE);
549
550 // pass the params to the mailer
551 mailer.setHost(host);
552 mailer.setPort(port);
553 mailer.setUser(user);
554 mailer.setPassword(password);
555 mailer.setSSL(ssl);
556 mailer.setEnableStartTLS(starttls);
557 mailer.setMessage(message);
558 mailer.setFrom(from);
559 mailer.setReplyToList(replyToList);
560 mailer.setToList(toList);
561 mailer.setCcList(ccList);
562 mailer.setBccList(bccList);
563 mailer.setFiles(files);
564 mailer.setSubject(subject);
565 mailer.setTask(this);
566 mailer.setIncludeFileNames(includeFileNames);
567 mailer.setHeaders(headers);
568 mailer.setIgnoreInvalidRecipients(ignoreInvalidRecipients);
569
570 // send the email
571 mailer.send();
572
573 // let the user know what happened
574 int count = files.size();
575
576 log("Sent email with " + count + " attachment"
577 + (count == 1 ? "" : "s"), Project.MSG_INFO);
578 } catch (BuildException e) {
579 logBuildException("Failed to send email: ", e);
580 if (failOnError) {
581 throw e;
582 }
583 } catch (Exception e) {
584 log("Failed to send email: " + e.getMessage(), Project.MSG_WARN);
585 if (failOnError) {
586 throw new BuildException(e);
587 }
588 } finally {
589 message = savedMessage;
590 }
591 }
592
593 private void logBuildException(String reason, BuildException e) {
594 Throwable t = e.getCause() == null ? e : e.getCause();
595 log(reason + t.getMessage(), Project.MSG_WARN);
596 }
597
598 /**
599 * Sets the character set of mail message.
600 * Will be ignored if mimeType contains ....; Charset=... substring or
601 * encoding is not a <code>mime</code>.
602 * @param charset the character encoding to use.
603 * @since Ant 1.6
604 */
605 public void setCharset(String charset) {
606 this.charset = charset;
607 }
608
609 /**
610 * Returns the character set of mail message.
611 *
612 * @return Charset of mail message.
613 * @since Ant 1.6
614 */
615 public String getCharset() {
616 return charset;
617 }
618
619 }
620