| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.cocoon.mail; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.MalformedURLException; |
| import java.util.ArrayList; |
| import java.util.Date; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Properties; |
| import javax.activation.DataHandler; |
| import javax.activation.DataSource; |
| import javax.mail.Authenticator; |
| import javax.mail.Message.RecipientType; |
| import javax.mail.MessagingException; |
| import javax.mail.Multipart; |
| import javax.mail.PasswordAuthentication; |
| import javax.mail.Session; |
| import javax.mail.Transport; |
| import javax.mail.internet.AddressException; |
| import javax.mail.internet.InternetAddress; |
| import javax.mail.internet.MimeBodyPart; |
| import javax.mail.internet.MimeMessage; |
| import javax.mail.internet.MimeMultipart; |
| import javax.mail.internet.MimePart; |
| |
| import org.apache.avalon.framework.CascadingRuntimeException; |
| import org.apache.avalon.framework.activity.Initializable; |
| import org.apache.avalon.framework.component.Component; |
| import org.apache.avalon.framework.configuration.Configurable; |
| import org.apache.avalon.framework.configuration.Configuration; |
| import org.apache.avalon.framework.configuration.ConfigurationException; |
| import org.apache.avalon.framework.logger.AbstractLogEnabled; |
| import org.apache.avalon.framework.logger.Logger; |
| import org.apache.avalon.framework.service.ServiceException; |
| import org.apache.avalon.framework.service.ServiceManager; |
| import org.apache.avalon.framework.service.Serviceable; |
| import org.apache.excalibur.source.Source; |
| import org.apache.excalibur.source.SourceResolver; |
| |
| import org.apache.cocoon.mail.datasource.AbstractDataSource; |
| import org.apache.cocoon.mail.datasource.FilePartDataSource; |
| import org.apache.cocoon.mail.datasource.InputStreamDataSource; |
| import org.apache.cocoon.mail.datasource.SourceDataSource; |
| import org.apache.cocoon.servlet.multipart.Part; |
| |
| /** |
| * A helper class used by the {@link org.apache.cocoon.acting.Sendmail} |
| * and the <code>sendmail.xsl</code> logicsheet for sending an email message. |
| * |
| * <h3>Configuration</h3> |
| * <table><tbody> |
| * <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> |
| * <tr><th>smtp-user</th><td>User name for authentication</td><td>opt</td><td>String</td></tr> |
| * <tr><th>smtp-password</th><td>Password for authentication</td><td>opt</td><td>String</td></tr> |
| * </tbody></table> |
| * |
| * @since 2.1 |
| * @author <a href="mailto:frank.ridderbusch@gmx.de">Frank Ridderbusch</a> |
| * @author <a href="mailto:haul@apache.org">Christian Haul</a> |
| * @version $Id$ |
| */ |
| public class MailMessageSender extends AbstractLogEnabled |
| implements MailSender, Configurable, Serviceable, |
| Initializable, Component { |
| |
| private ServiceManager manager; |
| |
| private Session session; |
| |
| private String smtpHost; |
| private String smtpUser; |
| private String smtpPswd; |
| |
| private String from; |
| private String to; |
| private String replyTo; |
| private String cc; |
| private String bcc; |
| private String subject; |
| |
| private Attachment body; |
| private String bodyType; |
| private String bodySrcType; |
| private List attachments; |
| |
| private Exception exception; |
| |
| |
| /** Java 1.3 Accessor */ |
| private Logger getMyLogger() { |
| return getLogger(); |
| } |
| |
| /** |
| * Check string for null, empty, and "null". |
| * @param str |
| * @return true if str is null, empty string, or equals "null" |
| */ |
| private boolean isNullOrEmpty(String str) { |
| return str == null || "".equals(str) || "null".equals(str); |
| } |
| |
| /** |
| * Helper class for attachment data. |
| */ |
| private class Attachment { |
| private Object obj; |
| private String type; |
| private String name; |
| protected boolean isURL; |
| |
| /** |
| * Create a new attachment object encapsulating obj. |
| * @param obj attachment |
| */ |
| public Attachment(Object obj) { |
| this(obj, null, null); |
| } |
| |
| /** |
| * Create a new attachment object encapsulating obj |
| * @param obj attachment |
| * @param type override mime type |
| * @param name override attachment name |
| */ |
| public Attachment(Object obj, String type, String name) { |
| this(obj, type, name, false); |
| } |
| |
| /** |
| * Create a new attachment object encapsulating obj |
| * @param obj attachment |
| * @param type override mime type |
| * @param name override attachment name |
| * @param isURI obj is an instance of String and contains a URL |
| */ |
| public Attachment(Object obj, String type, String name, boolean isURI) { |
| this.obj = obj; |
| this.type = type; |
| this.name = name; |
| this.isURL = isURI; |
| |
| if (isNullOrEmpty(this.type)) { |
| this.type = null; |
| } |
| |
| if (isNullOrEmpty(this.name)) { |
| this.name = null; |
| } |
| } |
| |
| /** |
| * Is the encapsulated object a URL? |
| * @return true if URL |
| */ |
| public boolean isURL() { |
| return this.isURL; |
| } |
| |
| /** |
| * Is the encapsulated object a text? |
| * @return true if text (String object) |
| */ |
| public boolean isText() { |
| return !isURL() && this.obj instanceof String; |
| } |
| |
| /** |
| * Return attachment name. |
| */ |
| public String getName() { |
| return this.name; |
| } |
| |
| /** |
| * Return attachment type. |
| */ |
| public String getType() { |
| return this.type; |
| } |
| |
| /** |
| * Returns encapsulated object |
| */ |
| public Object getObject() { |
| return this.obj; |
| } |
| |
| public String getText() { |
| return (String) this.obj; |
| } |
| |
| public DataSource getDataSource(SourceResolver resolver, List sources) |
| throws IOException, MessagingException { |
| AbstractDataSource ds = null; |
| |
| if (isURL) { |
| String url = (String) getObject(); |
| Source src = resolver.resolveURI(url); |
| sources.add(src); |
| if (src.exists()) { |
| ds = new SourceDataSource(src, getType(), getName()); |
| } |
| } else if (getObject() instanceof Part) { |
| Part part = (Part) getObject(); |
| ds = new FilePartDataSource(part, getType(), getName()); |
| } else if (getObject() instanceof InputStream) { |
| InputStream in = (InputStream) getObject(); |
| ds = new InputStreamDataSource(in, getType(), getName()); |
| } else if (getObject() instanceof byte[]) { |
| byte[] data = (byte[]) getObject(); |
| ds = new InputStreamDataSource(data, getType(), getName()); |
| } else { |
| // TODO: other classes? |
| throw new MessagingException("Not yet supported: " + getObject()); |
| } |
| |
| if (ds != null) { |
| ds.enableLogging(getMyLogger()); |
| } |
| return ds; |
| } |
| |
| public void setContentTo(SourceResolver resolver, List sources, MimePart part) |
| throws IOException, MessagingException { |
| if (isText()) { |
| // Set text |
| if (type != null) { |
| part.setContent(getText(), type); |
| } else { |
| // Let JavaMail decide on character encoding. |
| part.setText(getText()); |
| } |
| if (name != null) { |
| part.setFileName(name); |
| } |
| } else { |
| // Set data |
| DataSource ds = getDataSource(resolver, sources); |
| part.setDataHandler(new DataHandler(ds)); |
| String name = ds.getName(); |
| if (name != null) { |
| part.setFileName(name); |
| } |
| } |
| } |
| |
| public MimeBodyPart getBodyPart(SourceResolver resolver, List sources) |
| throws IOException, MessagingException { |
| MimeBodyPart part = new MimeBodyPart(); |
| setContentTo(resolver, sources, part); |
| return part; |
| } |
| } |
| |
| private class Body extends Attachment { |
| public Body(Object obj) { |
| super(obj); |
| } |
| |
| public Body(Object obj, String type) { |
| super(obj, type, null); |
| } |
| |
| public Body(Object obj, String type, boolean isURI) { |
| super(obj, type, null, isURI); |
| } |
| |
| // Override to set name to null: body can not have name. |
| public DataSource getDataSource(SourceResolver resolver, List sources) |
| throws IOException, MessagingException { |
| AbstractDataSource ds = (AbstractDataSource) super.getDataSource(resolver, sources); |
| ds.setName(null); |
| return ds; |
| } |
| } |
| |
| |
| public MailMessageSender() { |
| } |
| |
| /** |
| * Creates a new instance of MailMessageSender. |
| * Keep constructor for backwards compatibility. |
| * |
| * @param smtpHost The host name or ip-address of a host to accept |
| * the email for delivery. |
| * @deprecated Since 2.1.5. Please use {@link MailSender} component instead. |
| */ |
| public MailMessageSender(String smtpHost) { |
| smtpHost = smtpHost.trim(); |
| setSmtpHost(smtpHost); |
| initialize(); |
| } |
| |
| public void service(ServiceManager manager) { |
| this.manager = manager; |
| } |
| |
| public void configure(Configuration config) throws ConfigurationException { |
| this.smtpHost = config.getChild("smtp-host").getValue(null); |
| this.smtpUser = config.getChild("smtp-user").getValue(null); |
| this.smtpPswd = config.getChild("smtp-password").getValue(null); |
| } |
| |
| public void initialize() { |
| initSession(); |
| this.attachments = new ArrayList(); |
| } |
| |
| private void initSession() { |
| Properties properties = new Properties(); |
| if (smtpHost == null || smtpHost.length() == 0 || smtpHost.equals("null")) { |
| properties.put("mail.smtp.host", "127.0.0.1"); |
| } else { |
| properties.put("mail.smtp.host", smtpHost); |
| } |
| |
| if (smtpUser == null || smtpUser.length() == 0 || smtpUser.equals("null")) { |
| this.session = Session.getInstance(properties); |
| } else { |
| properties.put("mail.smtp.auth", "true"); |
| this.session = Session.getInstance(properties, new Authenticator() { |
| protected PasswordAuthentication getPasswordAuthentication() { |
| return new PasswordAuthentication(smtpUser, smtpPswd); |
| } |
| }); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.cocoon.mail.MailSender#setSmtpHost(java.lang.String) |
| */ |
| public void setSmtpHost(String hostname) { |
| this.smtpHost = hostname; |
| initSession(); |
| } |
| |
| public void setSmtpHost(String hostname, String username, String password) { |
| this.smtpUser = username; |
| this.smtpPswd = password; |
| setSmtpHost(hostname); |
| } |
| |
| /** |
| * Assemble the message from the defined fields and send it. |
| * |
| * @throws AddressException when problems with email addresses are found |
| * @throws MessagingException when message could not be send. |
| */ |
| public void send() throws AddressException, MessagingException { |
| SourceResolver resolver = null; |
| try { |
| resolver = (SourceResolver) this.manager.lookup(SourceResolver.ROLE); |
| doSend(resolver); |
| } catch (ServiceException se) { |
| throw new CascadingRuntimeException("Cannot get Source Resolver to send mail", se); |
| } finally { |
| this.manager.release(resolver); |
| } |
| } |
| |
| /** |
| * Assemble the message from the defined fields and send it. |
| * |
| * @throws AddressException when problems with email addresses are found |
| * @throws MessagingException when message could not be send. |
| * @deprecated Since 2.1.5. Use {@link #send()} which doesn't require passing the source resolver |
| */ |
| public void send(org.apache.cocoon.environment.SourceResolver resolver) |
| throws AddressException, MessagingException { |
| // resolver is automatically down-casted |
| doSend(resolver); |
| } |
| |
| private void doSend(SourceResolver resolver) |
| throws AddressException, MessagingException { |
| |
| final MimeMessage message = new MimeMessage(this.session); |
| |
| if (this.from == null) { |
| throw new AddressException("No from address"); |
| } else { |
| try { |
| message.setFrom(new InternetAddress(this.from)); |
| } catch (AddressException e) { |
| throw new AddressException("Invalid from address: " + this.from + ": " + |
| e.getMessage()); |
| } |
| } |
| |
| if (this.to == null) { |
| throw new AddressException("no to address"); |
| } else { |
| try { |
| message.setRecipients(RecipientType.TO, |
| InternetAddress.parse(this.to)); |
| } catch (AddressException e) { |
| throw new AddressException("Invalid to address: " + this.to + ": " + |
| e.getMessage()); |
| } |
| } |
| |
| if (this.replyTo != null) { |
| try { |
| message.setReplyTo(InternetAddress.parse(this.replyTo)); |
| } catch (AddressException e) { |
| throw new AddressException("Invalid replyTo address: " + this.replyTo + ": " + |
| e.getMessage()); |
| } |
| } |
| |
| if (this.cc != null) { |
| try { |
| message.setRecipients(RecipientType.CC, |
| InternetAddress.parse(this.cc)); |
| } catch (AddressException e) { |
| throw new AddressException("Invalid cc address: " + this.cc + ": " + |
| e.getMessage()); |
| } |
| } |
| |
| if (this.bcc != null) { |
| try { |
| message.setRecipients(RecipientType.BCC, |
| InternetAddress.parse(this.bcc)); |
| } catch (AddressException e) { |
| throw new AddressException("Invalid bcc address: " + this.bcc + ": " + |
| e.getMessage()); |
| } |
| } |
| |
| if (this.subject != null) { |
| message.setSubject(this.subject); |
| } |
| |
| message.setSentDate(new Date()); |
| |
| Attachment a = null; |
| final List sources = new ArrayList(); |
| try { |
| if (this.attachments.isEmpty()) { |
| // Message consists of single part |
| if (this.body != null) { |
| a = this.body; |
| a.setContentTo(resolver, sources, message); |
| } |
| } else { |
| // Message consists of multiple parts |
| Multipart multipart = new MimeMultipart(); |
| message.setContent(multipart); |
| |
| // Body part |
| if (this.body != null) { |
| a = this.body; |
| multipart.addBodyPart(a.getBodyPart(resolver, sources)); |
| } |
| |
| // Attachment parts |
| for (Iterator i = this.attachments.iterator(); i.hasNext();) { |
| a = (Attachment) i.next(); |
| multipart.addBodyPart(a.getBodyPart(resolver, sources)); |
| } |
| } |
| |
| message.saveChanges(); |
| Transport.send(message); |
| } catch (MalformedURLException e) { |
| throw new MessagingException("Malformed attachment URL: " + |
| a.getObject() + " error " + e.getMessage()); |
| } catch (IOException e) { |
| throw new MessagingException("IOException accessing attachment URL: " + |
| a.getObject() + " error " + e.getMessage()); |
| } finally { |
| for (Iterator j = sources.iterator(); j.hasNext();) { |
| resolver.release((Source) j.next()); |
| } |
| } |
| } |
| |
| /** |
| * Invokes the {@link #send()} method but catches any exception thrown. This |
| * method is intended to be used from the sendmail logicsheet. |
| * |
| * @return true when successful |
| */ |
| public boolean sendIt() { |
| this.exception = null; |
| try { |
| send(); |
| } catch (Exception e) { |
| this.exception = e; |
| } |
| return exception == null; |
| } |
| |
| /** |
| * Invokes the {@link #send(org.apache.cocoon.environment.SourceResolver)} |
| * method but catches any exception thrown. This |
| * method is intended to be used from the sendmail logicsheet. |
| * |
| * @return true when successful |
| * @deprecated Since 2.1.5. Use {@link #sendIt()} which doesn't require passing the source resolver |
| */ |
| public boolean sendIt(org.apache.cocoon.environment.SourceResolver resolver) { |
| this.exception = null; |
| try { |
| send(resolver); |
| } catch (Exception e) { |
| this.exception = e; |
| } |
| return exception == null; |
| } |
| |
| /** |
| * Accesses any Exception caught by |
| * {@link #sendIt(org.apache.cocoon.environment.SourceResolver)}. |
| * |
| * @return AddressException or MessagingException |
| */ |
| public Exception getException() { |
| return this.exception; |
| } |
| |
| |
| /** |
| * Set the <code>from</code> address of the message. |
| * |
| * @param from The address the message appears to be from. |
| */ |
| public void setFrom(String from) { |
| if (!isNullOrEmpty(from)) { |
| this.from = from.trim(); |
| } |
| } |
| |
| /** |
| * Sets the destination address(es) for the message. The address |
| * is in the format, that |
| * {@link javax.mail.internet.InternetAddress#parse(String)} can handle |
| * (one or more email addresses separated by a commas). |
| * |
| * @param to the destination address(es) |
| * @see javax.mail.internet.InternetAddress#parse(String) |
| */ |
| public void setTo(String to) { |
| if (!isNullOrEmpty(to)) { |
| this.to = to.trim(); |
| } |
| } |
| |
| /** |
| * Sets the reply-to address(es) for the message. The address |
| * is in the format, that |
| * {@link javax.mail.internet.InternetAddress#parse(String)} can handle |
| * (one or more email addresses separated by a commas). |
| * |
| * @param replyTo the address(es) that replies should be sent to |
| * @see javax.mail.internet.InternetAddress#parse(String) |
| */ |
| public void setReplyTo(String replyTo) { |
| if (!isNullOrEmpty(replyTo)) { |
| this.replyTo = replyTo.trim(); |
| } |
| } |
| |
| /** |
| * Sets the address(es), which should receive a carbon copy of the |
| * message. The address is in the format, that |
| * {@link javax.mail.internet.InternetAddress#parse(String)} can handle |
| * (one or more email addresses separated by a commas). |
| * |
| * @param cc the address(es), which should receive a carbon copy. |
| * @see javax.mail.internet.InternetAddress#parse(String) |
| */ |
| public void setCc(String cc) { |
| if (!isNullOrEmpty(cc)) { |
| this.cc = cc.trim(); |
| } |
| } |
| |
| /** |
| * Sets the address(es), which should receive a black carbon copy of |
| * the message. The address is in the format, that |
| * {@link javax.mail.internet.InternetAddress#parse(String)} can handle |
| * (one or more email addresses separated by a commas). |
| * |
| * @param bcc the address(es), which should receive a black carbon copy. |
| * @see javax.mail.internet.InternetAddress#parse(String) |
| */ |
| public void setBcc(String bcc) { |
| if (!isNullOrEmpty(bcc)) { |
| this.bcc = bcc.trim(); |
| } |
| } |
| |
| /** |
| * Sets the subject line of the message. |
| * |
| * @param subject the subject line of the message |
| */ |
| public void setSubject(String subject) { |
| if (!isNullOrEmpty(subject)) { |
| this.subject = subject; |
| } |
| } |
| |
| /** |
| * Sets the character set for encoding the message. This has no effect, |
| * if any attachments are send in the message. |
| * |
| * @param charset the character set to be used for enbcoding the message |
| */ |
| public void setCharset(String charset) { |
| if (!isNullOrEmpty(charset)) { |
| this.bodyType = "text/plain; charset=" + charset.trim(); |
| if (this.body != null && this.body.isText() && this.body.type == null) { |
| this.body.type = this.bodyType; |
| } |
| } |
| } |
| |
| /** |
| * Sets the body text of the email message. |
| * If both a text body and a body read from a source are set, |
| * only the latter will be used. |
| * |
| * @param body The body text of the email message |
| * @deprecated Since 2.1.10. Use {@link #setBody(Object)} |
| */ |
| public void setBody(String body) { |
| if (!isNullOrEmpty(body)) { |
| setBody(body, bodyType); |
| } |
| } |
| |
| /** |
| * Sets the body source URL of the email message. |
| * If both a text body and a body read from a source are set, |
| * only the latter will be used. |
| * |
| * @param src The body source URL of the email message |
| * @deprecated Since 2.1.10. Use {@link #setBodyURL(String)} |
| */ |
| public void setBodyFromSrc(String src) { |
| if (!isNullOrEmpty(src)) { |
| setBodyURL(src, bodySrcType); |
| } |
| } |
| |
| /** |
| * Sets the optional body source Mime Type of the email message. |
| * |
| * @param srcMimeType The optional body source Mime Type of the email message |
| * @deprecated Since 2.1.10. Use {@link #setBodyURL(String, String)} |
| */ |
| public void setBodyFromSrcMimeType(String srcMimeType) { |
| if (!isNullOrEmpty(srcMimeType)) { |
| this.bodySrcType = srcMimeType; |
| // Pass into this.body if it was set. |
| if (this.body != null && this.body.isURL() && this.body.type == null) { |
| this.body.type = srcMimeType; |
| } |
| } |
| } |
| |
| /** |
| * Sets the body content of the email message. |
| * |
| * <p>The body can be any of: {@link org.apache.excalibur.source.Source}, |
| * {@link org.apache.cocoon.servlet.multipart.Part}, {@link java.io.InputStream}, |
| * <code>byte[]</code>, {@link String}, or a subclass. |
| * |
| * @param body The body text of the email message |
| */ |
| public void setBody(Object body) { |
| setBody(body, null); |
| } |
| |
| /** |
| * Sets the body content of the email message. |
| * |
| * <p>The body can be any of: {@link org.apache.excalibur.source.Source}, |
| * {@link org.apache.cocoon.servlet.multipart.Part}, {@link java.io.InputStream}, |
| * <code>byte[]</code>, {@link String}, or a subclass. |
| * |
| * @param body The body text of the email message |
| * @param type mime type (optional) |
| */ |
| public void setBody(Object body, String type) { |
| if (body != null) { |
| this.body = new Body(body, type); |
| } |
| } |
| |
| /** |
| * Sets the body content of the email message. |
| * |
| * @param url URL to use as message body |
| * @see org.apache.excalibur.source.Source |
| */ |
| public void setBodyURL(String url) { |
| setBodyURL(url, null); |
| } |
| |
| /** |
| * Sets the body content of the email message. |
| * |
| * @param url URL to use as message body |
| * @param type mime type (optional) |
| * @see org.apache.excalibur.source.Source |
| */ |
| public void setBodyURL(String url, String type) { |
| if (url != null) { |
| this.body = new Body(url, type, true); |
| } |
| } |
| |
| /** |
| * Add an attachement to the message to be send. |
| * |
| * <p>The attachment can be any of: {@link org.apache.excalibur.source.Source}, |
| * {@link org.apache.cocoon.servlet.multipart.Part}, {@link java.io.InputStream}, |
| * <code>byte[]</code>, {@link String}, or a subclass. |
| * |
| * @param attachment to be send with the message |
| */ |
| public void addAttachment(Object attachment) { |
| if (attachment != null) { |
| attachments.add(new Attachment(attachment)); |
| } |
| } |
| |
| /** |
| * Add an attachement to the message to be send. |
| * |
| * <p>The attachment can be any of: {@link org.apache.excalibur.source.Source}, |
| * {@link org.apache.cocoon.servlet.multipart.Part}, {@link java.io.InputStream}, |
| * <code>byte[]</code>, {@link String}, or a subclass. |
| * |
| * @param attachment to be send with the message |
| * @param type mime type (optional) |
| * @param name attachment name (optional) |
| */ |
| public void addAttachment(Object attachment, String type, String name) { |
| if (attachment != null) { |
| attachments.add(new Attachment(attachment, type, name)); |
| } |
| } |
| |
| /** |
| * Add an attachement to the message to be send. |
| * |
| * @param url URL to attach to the message |
| * @see org.apache.excalibur.source.Source |
| */ |
| public void addAttachmentURL(String url) { |
| if (url != null) { |
| attachments.add(new Attachment(url, null, null, true)); |
| } |
| } |
| |
| /** |
| * Add an attachement to the message to be send. |
| * |
| * @param url URL to attach to the message |
| * @param type mime type (optional) |
| * @param name attachment name (optional) |
| * @see org.apache.excalibur.source.Source |
| */ |
| public void addAttachmentURL(String url, String type, String name) { |
| if (url != null) { |
| attachments.add(new Attachment(url, type, name, true)); |
| } |
| } |
| } |