001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.mail;
018:
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.net.MalformedURLException;
022: import java.util.ArrayList;
023: import java.util.Date;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.Properties;
027: import javax.activation.DataHandler;
028: import javax.activation.DataSource;
029: import javax.mail.Authenticator;
030: import javax.mail.Message.RecipientType;
031: import javax.mail.MessagingException;
032: import javax.mail.Multipart;
033: import javax.mail.PasswordAuthentication;
034: import javax.mail.Session;
035: import javax.mail.Transport;
036: import javax.mail.internet.AddressException;
037: import javax.mail.internet.InternetAddress;
038: import javax.mail.internet.MimeBodyPart;
039: import javax.mail.internet.MimeMessage;
040: import javax.mail.internet.MimeMultipart;
041: import javax.mail.internet.MimePart;
042:
043: import org.apache.avalon.framework.CascadingRuntimeException;
044: import org.apache.avalon.framework.activity.Initializable;
045: import org.apache.avalon.framework.component.Component;
046: import org.apache.avalon.framework.configuration.Configurable;
047: import org.apache.avalon.framework.configuration.Configuration;
048: import org.apache.avalon.framework.configuration.ConfigurationException;
049: import org.apache.avalon.framework.logger.AbstractLogEnabled;
050: import org.apache.avalon.framework.logger.Logger;
051: import org.apache.avalon.framework.service.ServiceException;
052: import org.apache.avalon.framework.service.ServiceManager;
053: import org.apache.avalon.framework.service.Serviceable;
054: import org.apache.excalibur.source.Source;
055: import org.apache.excalibur.source.SourceResolver;
056:
057: import org.apache.cocoon.mail.datasource.AbstractDataSource;
058: import org.apache.cocoon.mail.datasource.FilePartDataSource;
059: import org.apache.cocoon.mail.datasource.InputStreamDataSource;
060: import org.apache.cocoon.mail.datasource.SourceDataSource;
061: import org.apache.cocoon.servlet.multipart.Part;
062:
063: /**
064: * A helper class used by the {@link org.apache.cocoon.acting.Sendmail}
065: * and the <code>sendmail.xsl</code> logicsheet for sending an email message.
066: *
067: * <h3>Configuration</h3>
068: * <table><tbody>
069: * <tr><th>smtp-host</th><td>SMTP server to use sending mail.</td><td>opt</td><td>String</td><td><code>localhost</code></td></tr>
070: * <tr><th>smtp-user</th><td>User name for authentication</td><td>opt</td><td>String</td></tr>
071: * <tr><th>smtp-password</th><td>Password for authentication</td><td>opt</td><td>String</td></tr>
072: * </tbody></table>
073: *
074: * @since 2.1
075: * @author <a href="mailto:frank.ridderbusch@gmx.de">Frank Ridderbusch</a>
076: * @author <a href="mailto:haul@apache.org">Christian Haul</a>
077: * @version $Id: MailMessageSender.java 588324 2007-10-25 19:54:27Z vgritsenko $
078: */
079: public class MailMessageSender extends AbstractLogEnabled implements
080: MailSender, Configurable, Serviceable, Initializable, Component {
081:
082: private ServiceManager manager;
083:
084: private Session session;
085:
086: private String smtpHost;
087: private String smtpUser;
088: private String smtpPswd;
089:
090: private String from;
091: private String to;
092: private String replyTo;
093: private String cc;
094: private String bcc;
095: private String subject;
096:
097: private Attachment body;
098: private String bodyType;
099: private String bodySrcType;
100: private List attachments;
101:
102: private Exception exception;
103:
104: /** Java 1.3 Accessor */
105: private Logger getMyLogger() {
106: return getLogger();
107: }
108:
109: /**
110: * Check string for null, empty, and "null".
111: * @param str
112: * @return true if str is null, empty string, or equals "null"
113: */
114: private boolean isNullOrEmpty(String str) {
115: return str == null || "".equals(str) || "null".equals(str);
116: }
117:
118: /**
119: * Helper class for attachment data.
120: */
121: private class Attachment {
122: private Object obj;
123: private String type;
124: private String name;
125: protected boolean isURL;
126:
127: /**
128: * Create a new attachment object encapsulating obj.
129: * @param obj attachment
130: */
131: public Attachment(Object obj) {
132: this (obj, null, null);
133: }
134:
135: /**
136: * Create a new attachment object encapsulating obj
137: * @param obj attachment
138: * @param type override mime type
139: * @param name override attachment name
140: */
141: public Attachment(Object obj, String type, String name) {
142: this (obj, type, name, false);
143: }
144:
145: /**
146: * Create a new attachment object encapsulating obj
147: * @param obj attachment
148: * @param type override mime type
149: * @param name override attachment name
150: * @param isURI obj is an instance of String and contains a URL
151: */
152: public Attachment(Object obj, String type, String name,
153: boolean isURI) {
154: this .obj = obj;
155: this .type = type;
156: this .name = name;
157: this .isURL = isURI;
158:
159: if (isNullOrEmpty(this .type)) {
160: this .type = null;
161: }
162:
163: if (isNullOrEmpty(this .name)) {
164: this .name = null;
165: }
166: }
167:
168: /**
169: * Is the encapsulated object a URL?
170: * @return true if URL
171: */
172: public boolean isURL() {
173: return this .isURL;
174: }
175:
176: /**
177: * Is the encapsulated object a text?
178: * @return true if text (String object)
179: */
180: public boolean isText() {
181: return !isURL() && this .obj instanceof String;
182: }
183:
184: /**
185: * Return attachment name.
186: */
187: public String getName() {
188: return this .name;
189: }
190:
191: /**
192: * Return attachment type.
193: */
194: public String getType() {
195: return this .type;
196: }
197:
198: /**
199: * Returns encapsulated object
200: */
201: public Object getObject() {
202: return this .obj;
203: }
204:
205: public String getText() {
206: return (String) this .obj;
207: }
208:
209: public DataSource getDataSource(SourceResolver resolver,
210: List sources) throws IOException, MessagingException {
211: AbstractDataSource ds = null;
212:
213: if (isURL) {
214: String url = (String) getObject();
215: Source src = resolver.resolveURI(url);
216: sources.add(src);
217: if (src.exists()) {
218: ds = new SourceDataSource(src, getType(), getName());
219: }
220: } else if (getObject() instanceof Part) {
221: Part part = (Part) getObject();
222: ds = new FilePartDataSource(part, getType(), getName());
223: } else if (getObject() instanceof InputStream) {
224: InputStream in = (InputStream) getObject();
225: ds = new InputStreamDataSource(in, getType(), getName());
226: } else if (getObject() instanceof byte[]) {
227: byte[] data = (byte[]) getObject();
228: ds = new InputStreamDataSource(data, getType(),
229: getName());
230: } else {
231: // TODO: other classes?
232: throw new MessagingException("Not yet supported: "
233: + getObject());
234: }
235:
236: if (ds != null) {
237: ds.enableLogging(getMyLogger());
238: }
239: return ds;
240: }
241:
242: public void setContentTo(SourceResolver resolver, List sources,
243: MimePart part) throws IOException, MessagingException {
244: if (isText()) {
245: // Set text
246: if (type != null) {
247: part.setContent(getText(), type);
248: } else {
249: // Let JavaMail decide on character encoding.
250: part.setText(getText());
251: }
252: if (name != null) {
253: part.setFileName(name);
254: }
255: } else {
256: // Set data
257: DataSource ds = getDataSource(resolver, sources);
258: part.setDataHandler(new DataHandler(ds));
259: String name = ds.getName();
260: if (name != null) {
261: part.setFileName(name);
262: }
263: }
264: }
265:
266: public MimeBodyPart getBodyPart(SourceResolver resolver,
267: List sources) throws IOException, MessagingException {
268: MimeBodyPart part = new MimeBodyPart();
269: setContentTo(resolver, sources, part);
270: return part;
271: }
272: }
273:
274: private class Body extends Attachment {
275: public Body(Object obj) {
276: super (obj);
277: }
278:
279: public Body(Object obj, String type) {
280: super (obj, type, null);
281: }
282:
283: public Body(Object obj, String type, boolean isURI) {
284: super (obj, type, null, isURI);
285: }
286:
287: // Override to set name to null: body can not have name.
288: public DataSource getDataSource(SourceResolver resolver,
289: List sources) throws IOException, MessagingException {
290: AbstractDataSource ds = (AbstractDataSource) super
291: .getDataSource(resolver, sources);
292: ds.setName(null);
293: return ds;
294: }
295: }
296:
297: public MailMessageSender() {
298: }
299:
300: /**
301: * Creates a new instance of MailMessageSender.
302: * Keep constructor for backwards compatibility.
303: *
304: * @param smtpHost The host name or ip-address of a host to accept
305: * the email for delivery.
306: * @deprecated Since 2.1.5. Please use {@link MailSender} component instead.
307: */
308: public MailMessageSender(String smtpHost) {
309: smtpHost = smtpHost.trim();
310: setSmtpHost(smtpHost);
311: initialize();
312: }
313:
314: public void service(ServiceManager manager) {
315: this .manager = manager;
316: }
317:
318: public void configure(Configuration config)
319: throws ConfigurationException {
320: this .smtpHost = config.getChild("smtp-host").getValue(null);
321: this .smtpUser = config.getChild("smtp-user").getValue(null);
322: this .smtpPswd = config.getChild("smtp-password").getValue(null);
323: }
324:
325: public void initialize() {
326: initSession();
327: this .attachments = new ArrayList();
328: }
329:
330: private void initSession() {
331: Properties properties = new Properties();
332: if (smtpHost == null || smtpHost.length() == 0
333: || smtpHost.equals("null")) {
334: properties.put("mail.smtp.host", "127.0.0.1");
335: } else {
336: properties.put("mail.smtp.host", smtpHost);
337: }
338:
339: if (smtpUser == null || smtpUser.length() == 0
340: || smtpUser.equals("null")) {
341: this .session = Session.getInstance(properties);
342: } else {
343: properties.put("mail.smtp.auth", "true");
344: this .session = Session.getInstance(properties,
345: new Authenticator() {
346: protected PasswordAuthentication getPasswordAuthentication() {
347: return new PasswordAuthentication(smtpUser,
348: smtpPswd);
349: }
350: });
351: }
352: }
353:
354: /* (non-Javadoc)
355: * @see org.apache.cocoon.mail.MailSender#setSmtpHost(java.lang.String)
356: */
357: public void setSmtpHost(String hostname) {
358: this .smtpHost = hostname;
359: initSession();
360: }
361:
362: public void setSmtpHost(String hostname, String username,
363: String password) {
364: this .smtpUser = username;
365: this .smtpPswd = password;
366: setSmtpHost(hostname);
367: }
368:
369: /**
370: * Assemble the message from the defined fields and send it.
371: *
372: * @throws AddressException when problems with email addresses are found
373: * @throws MessagingException when message could not be send.
374: */
375: public void send() throws AddressException, MessagingException {
376: SourceResolver resolver = null;
377: try {
378: resolver = (SourceResolver) this .manager
379: .lookup(SourceResolver.ROLE);
380: doSend(resolver);
381: } catch (ServiceException se) {
382: throw new CascadingRuntimeException(
383: "Cannot get Source Resolver to send mail", se);
384: } finally {
385: this .manager.release(resolver);
386: }
387: }
388:
389: /**
390: * Assemble the message from the defined fields and send it.
391: *
392: * @throws AddressException when problems with email addresses are found
393: * @throws MessagingException when message could not be send.
394: * @deprecated Since 2.1.5. Use {@link #send()} which doesn't require passing the source resolver
395: */
396: public void send(
397: org.apache.cocoon.environment.SourceResolver resolver)
398: throws AddressException, MessagingException {
399: // resolver is automatically down-casted
400: doSend(resolver);
401: }
402:
403: private void doSend(SourceResolver resolver)
404: throws AddressException, MessagingException {
405:
406: final MimeMessage message = new MimeMessage(this .session);
407:
408: if (this .from == null) {
409: throw new AddressException("No from address");
410: } else {
411: try {
412: message.setFrom(new InternetAddress(this .from));
413: } catch (AddressException e) {
414: throw new AddressException("Invalid from address: "
415: + this .from + ": " + e.getMessage());
416: }
417: }
418:
419: if (this .to == null) {
420: throw new AddressException("no to address");
421: } else {
422: try {
423: message.setRecipients(RecipientType.TO, InternetAddress
424: .parse(this .to));
425: } catch (AddressException e) {
426: throw new AddressException("Invalid to address: "
427: + this .to + ": " + e.getMessage());
428: }
429: }
430:
431: if (this .replyTo != null) {
432: try {
433: message.setReplyTo(InternetAddress.parse(this .replyTo));
434: } catch (AddressException e) {
435: throw new AddressException("Invalid replyTo address: "
436: + this .replyTo + ": " + e.getMessage());
437: }
438: }
439:
440: if (this .cc != null) {
441: try {
442: message.setRecipients(RecipientType.CC, InternetAddress
443: .parse(this .cc));
444: } catch (AddressException e) {
445: throw new AddressException("Invalid cc address: "
446: + this .cc + ": " + e.getMessage());
447: }
448: }
449:
450: if (this .bcc != null) {
451: try {
452: message.setRecipients(RecipientType.BCC,
453: InternetAddress.parse(this .bcc));
454: } catch (AddressException e) {
455: throw new AddressException("Invalid bcc address: "
456: + this .bcc + ": " + e.getMessage());
457: }
458: }
459:
460: if (this .subject != null) {
461: message.setSubject(this .subject);
462: }
463:
464: message.setSentDate(new Date());
465:
466: Attachment a = null;
467: final List sources = new ArrayList();
468: try {
469: if (this .attachments.isEmpty()) {
470: // Message consists of single part
471: if (this .body != null) {
472: a = this .body;
473: a.setContentTo(resolver, sources, message);
474: }
475: } else {
476: // Message consists of multiple parts
477: Multipart multipart = new MimeMultipart();
478: message.setContent(multipart);
479:
480: // Body part
481: if (this .body != null) {
482: a = this .body;
483: multipart.addBodyPart(a.getBodyPart(resolver,
484: sources));
485: }
486:
487: // Attachment parts
488: for (Iterator i = this .attachments.iterator(); i
489: .hasNext();) {
490: a = (Attachment) i.next();
491: multipart.addBodyPart(a.getBodyPart(resolver,
492: sources));
493: }
494: }
495:
496: message.saveChanges();
497: Transport.send(message);
498: } catch (MalformedURLException e) {
499: throw new MessagingException("Malformed attachment URL: "
500: + a.getObject() + " error " + e.getMessage());
501: } catch (IOException e) {
502: throw new MessagingException(
503: "IOException accessing attachment URL: "
504: + a.getObject() + " error "
505: + e.getMessage());
506: } finally {
507: for (Iterator j = sources.iterator(); j.hasNext();) {
508: resolver.release((Source) j.next());
509: }
510: }
511: }
512:
513: /**
514: * Invokes the {@link #send()} method but catches any exception thrown. This
515: * method is intended to be used from the sendmail logicsheet.
516: *
517: * @return true when successful
518: */
519: public boolean sendIt() {
520: this .exception = null;
521: try {
522: send();
523: } catch (Exception e) {
524: this .exception = e;
525: }
526: return exception == null;
527: }
528:
529: /**
530: * Invokes the {@link #send(org.apache.cocoon.environment.SourceResolver)}
531: * method but catches any exception thrown. This
532: * method is intended to be used from the sendmail logicsheet.
533: *
534: * @return true when successful
535: * @deprecated Since 2.1.5. Use {@link #sendIt()} which doesn't require passing the source resolver
536: */
537: public boolean sendIt(
538: org.apache.cocoon.environment.SourceResolver resolver) {
539: this .exception = null;
540: try {
541: send(resolver);
542: } catch (Exception e) {
543: this .exception = e;
544: }
545: return exception == null;
546: }
547:
548: /**
549: * Accesses any Exception caught by
550: * {@link #sendIt(org.apache.cocoon.environment.SourceResolver)}.
551: *
552: * @return AddressException or MessagingException
553: */
554: public Exception getException() {
555: return this .exception;
556: }
557:
558: /**
559: * Set the <code>from</code> address of the message.
560: *
561: * @param from The address the message appears to be from.
562: */
563: public void setFrom(String from) {
564: if (!isNullOrEmpty(from)) {
565: this .from = from.trim();
566: }
567: }
568:
569: /**
570: * Sets the destination address(es) for the message. The address
571: * is in the format, that
572: * {@link javax.mail.internet.InternetAddress#parse(String)} can handle
573: * (one or more email addresses separated by a commas).
574: *
575: * @param to the destination address(es)
576: * @see javax.mail.internet.InternetAddress#parse(String)
577: */
578: public void setTo(String to) {
579: if (!isNullOrEmpty(to)) {
580: this .to = to.trim();
581: }
582: }
583:
584: /**
585: * Sets the reply-to address(es) for the message. The address
586: * is in the format, that
587: * {@link javax.mail.internet.InternetAddress#parse(String)} can handle
588: * (one or more email addresses separated by a commas).
589: *
590: * @param replyTo the address(es) that replies should be sent to
591: * @see javax.mail.internet.InternetAddress#parse(String)
592: */
593: public void setReplyTo(String replyTo) {
594: if (!isNullOrEmpty(replyTo)) {
595: this .replyTo = replyTo.trim();
596: }
597: }
598:
599: /**
600: * Sets the address(es), which should receive a carbon copy of the
601: * message. The address is in the format, that
602: * {@link javax.mail.internet.InternetAddress#parse(String)} can handle
603: * (one or more email addresses separated by a commas).
604: *
605: * @param cc the address(es), which should receive a carbon copy.
606: * @see javax.mail.internet.InternetAddress#parse(String)
607: */
608: public void setCc(String cc) {
609: if (!isNullOrEmpty(cc)) {
610: this .cc = cc.trim();
611: }
612: }
613:
614: /**
615: * Sets the address(es), which should receive a black carbon copy of
616: * the message. The address is in the format, that
617: * {@link javax.mail.internet.InternetAddress#parse(String)} can handle
618: * (one or more email addresses separated by a commas).
619: *
620: * @param bcc the address(es), which should receive a black carbon copy.
621: * @see javax.mail.internet.InternetAddress#parse(String)
622: */
623: public void setBcc(String bcc) {
624: if (!isNullOrEmpty(bcc)) {
625: this .bcc = bcc.trim();
626: }
627: }
628:
629: /**
630: * Sets the subject line of the message.
631: *
632: * @param subject the subject line of the message
633: */
634: public void setSubject(String subject) {
635: if (!isNullOrEmpty(subject)) {
636: this .subject = subject;
637: }
638: }
639:
640: /**
641: * Sets the character set for encoding the message. This has no effect,
642: * if any attachments are send in the message.
643: *
644: * @param charset the character set to be used for enbcoding the message
645: */
646: public void setCharset(String charset) {
647: if (!isNullOrEmpty(charset)) {
648: this .bodyType = "text/plain; charset=" + charset.trim();
649: if (this .body != null && this .body.isText()
650: && this .body.type == null) {
651: this .body.type = this .bodyType;
652: }
653: }
654: }
655:
656: /**
657: * Sets the body text of the email message.
658: * If both a text body and a body read from a source are set,
659: * only the latter will be used.
660: *
661: * @param body The body text of the email message
662: * @deprecated Since 2.1.10. Use {@link #setBody(Object)}
663: */
664: public void setBody(String body) {
665: if (!isNullOrEmpty(body)) {
666: setBody(body, bodyType);
667: }
668: }
669:
670: /**
671: * Sets the body source URL of the email message.
672: * If both a text body and a body read from a source are set,
673: * only the latter will be used.
674: *
675: * @param src The body source URL of the email message
676: * @deprecated Since 2.1.10. Use {@link #setBodyURL(String)}
677: */
678: public void setBodyFromSrc(String src) {
679: if (!isNullOrEmpty(src)) {
680: setBodyURL(src, bodySrcType);
681: }
682: }
683:
684: /**
685: * Sets the optional body source Mime Type of the email message.
686: *
687: * @param srcMimeType The optional body source Mime Type of the email message
688: * @deprecated Since 2.1.10. Use {@link #setBodyURL(String, String)}
689: */
690: public void setBodyFromSrcMimeType(String srcMimeType) {
691: if (!isNullOrEmpty(srcMimeType)) {
692: this .bodySrcType = srcMimeType;
693: // Pass into this.body if it was set.
694: if (this .body != null && this .body.isURL()
695: && this .body.type == null) {
696: this .body.type = srcMimeType;
697: }
698: }
699: }
700:
701: /**
702: * Sets the body content of the email message.
703: *
704: * <p>The body can be any of: {@link org.apache.excalibur.source.Source},
705: * {@link org.apache.cocoon.servlet.multipart.Part}, {@link java.io.InputStream},
706: * <code>byte[]</code>, {@link String}, or a subclass.
707: *
708: * @param body The body text of the email message
709: */
710: public void setBody(Object body) {
711: setBody(body, null);
712: }
713:
714: /**
715: * Sets the body content of the email message.
716: *
717: * <p>The body can be any of: {@link org.apache.excalibur.source.Source},
718: * {@link org.apache.cocoon.servlet.multipart.Part}, {@link java.io.InputStream},
719: * <code>byte[]</code>, {@link String}, or a subclass.
720: *
721: * @param body The body text of the email message
722: * @param type mime type (optional)
723: */
724: public void setBody(Object body, String type) {
725: if (body != null) {
726: this .body = new Body(body, type);
727: }
728: }
729:
730: /**
731: * Sets the body content of the email message.
732: *
733: * @param url URL to use as message body
734: * @see org.apache.excalibur.source.Source
735: */
736: public void setBodyURL(String url) {
737: setBodyURL(url, null);
738: }
739:
740: /**
741: * Sets the body content of the email message.
742: *
743: * @param url URL to use as message body
744: * @param type mime type (optional)
745: * @see org.apache.excalibur.source.Source
746: */
747: public void setBodyURL(String url, String type) {
748: if (url != null) {
749: this .body = new Body(url, type, true);
750: }
751: }
752:
753: /**
754: * Add an attachement to the message to be send.
755: *
756: * <p>The attachment can be any of: {@link org.apache.excalibur.source.Source},
757: * {@link org.apache.cocoon.servlet.multipart.Part}, {@link java.io.InputStream},
758: * <code>byte[]</code>, {@link String}, or a subclass.
759: *
760: * @param attachment to be send with the message
761: */
762: public void addAttachment(Object attachment) {
763: if (attachment != null) {
764: attachments.add(new Attachment(attachment));
765: }
766: }
767:
768: /**
769: * Add an attachement to the message to be send.
770: *
771: * <p>The attachment can be any of: {@link org.apache.excalibur.source.Source},
772: * {@link org.apache.cocoon.servlet.multipart.Part}, {@link java.io.InputStream},
773: * <code>byte[]</code>, {@link String}, or a subclass.
774: *
775: * @param attachment to be send with the message
776: * @param type mime type (optional)
777: * @param name attachment name (optional)
778: */
779: public void addAttachment(Object attachment, String type,
780: String name) {
781: if (attachment != null) {
782: attachments.add(new Attachment(attachment, type, name));
783: }
784: }
785:
786: /**
787: * Add an attachement to the message to be send.
788: *
789: * @param url URL to attach to the message
790: * @see org.apache.excalibur.source.Source
791: */
792: public void addAttachmentURL(String url) {
793: if (url != null) {
794: attachments.add(new Attachment(url, null, null, true));
795: }
796: }
797:
798: /**
799: * Add an attachement to the message to be send.
800: *
801: * @param url URL to attach to the message
802: * @param type mime type (optional)
803: * @param name attachment name (optional)
804: * @see org.apache.excalibur.source.Source
805: */
806: public void addAttachmentURL(String url, String type, String name) {
807: if (url != null) {
808: attachments.add(new Attachment(url, type, name, true));
809: }
810: }
811: }
|