/*
* Copyright (c) 2002-2012 Alibaba Group Holding Limited.
* All rights reserved.
*
* Licensed 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 com.alibaba.citrus.service.mail.builder;
import static com.alibaba.citrus.service.mail.MailConstant.*;
import static com.alibaba.citrus.service.mail.builder.MailAddressType.*;
import static com.alibaba.citrus.util.ArrayUtil.*;
import static com.alibaba.citrus.util.Assert.*;
import static com.alibaba.citrus.util.BasicConstant.*;
import static com.alibaba.citrus.util.CollectionUtil.*;
import static com.alibaba.citrus.util.ObjectUtil.*;
import static com.alibaba.citrus.util.StringUtil.*;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;
import com.alibaba.citrus.service.mail.MailService;
import com.alibaba.citrus.service.mail.util.MailUtil;
import com.alibaba.citrus.util.ToStringBuilder;
import com.alibaba.citrus.util.ToStringBuilder.MapBuilder;
/**
* 创建一个javamail对象的工具类。
* <p>
* <code>MailBuilder</code>对象是有状态的,不能被多个线程同时使用。
* </p>
*
* @author Michael Zhou
*/
public class MailBuilder implements Cloneable {
private MailService mailService;
private final Set<InternetAddress>[] addresses;
private final Map<String, Object> attributes;
private String id;
private String charset;
private String subject;
private Date sentDate;
private MailContent content;
private transient Session session;
@SuppressWarnings("unchecked")
public MailBuilder() {
this.addresses = (Set<InternetAddress>[]) new Set<?>[MailAddressType.values().length];
this.attributes = createHashMap();
}
/** 深度复制一个mail builder。 */
@Override
public MailBuilder clone() {
MailBuilder copy = new MailBuilder();
copy.mailService = mailService;
copy.id = id;
copy.charset = charset;
for (int i = 0; i < addresses.length; i++) {
Set<InternetAddress> addrSet = addresses[i];
if (addrSet != null && !addrSet.isEmpty()) {
copy.addresses[i] = createLinkedHashSet(addrSet);
}
}
copy.attributes.putAll(attributes);
copy.subject = subject;
copy.sentDate = sentDate;
if (content != null) {
copy.setContent(content.clone());
}
return copy;
}
/** 取得此mail builder所属的service。 */
public MailService getMailService() {
return mailService;
}
/** 设置此mail builder所属的service。 */
public void setMailService(MailService mailService) {
this.mailService = mailService;
}
/** 取得mail builder的ID。 */
public String getId() {
return id;
}
/** 设置mail builder的ID。 */
public void setId(String id) {
this.id = trimToNull(id);
}
/**
* 取得当前mail builder的session。
* <p>
* 只有在build阶段方可得到此值。
* </p>
*/
public Session getSession() {
return assertNotNull(session, ExceptionType.ILLEGAL_STATE, "Not in build time");
}
/** 取得邮件的主题。 */
public String getSubject() {
return subject;
}
/** 设置邮件的主题。 */
public void setSubject(String subject) {
this.subject = trimToNull(subject);
}
/** 取得生成邮件时使用的编码字符集。如果未指定,则返回默认字符集<code>UTF-8</code>。 */
public String getCharacterEncoding() {
return getDefaultCharsetIfNull(charset);
}
/** 设置生成邮件时使用的编码字符集。 */
public void setCharacterEncoding(String javaCharset) {
javaCharset = trimToNull(javaCharset);
String oldCharset = getCharacterEncoding();
String newCharset = getDefaultCharsetIfNull(javaCharset);
if (!oldCharset.equals(newCharset)) {
this.charset = javaCharset;
updateAddressCharset(newCharset);
}
}
private void updateAddressCharset(String newCharset) {
String mimeCharset = MimeUtility.mimeCharset(newCharset);
for (Set<InternetAddress> addrSet : addresses) {
if (addrSet != null) {
for (InternetAddress addr : addrSet) {
try {
addr.setPersonal(addr.getPersonal(), mimeCharset);
} catch (UnsupportedEncodingException e) {
invalidCharset(newCharset, e);
}
}
}
}
}
private static String getDefaultCharsetIfNull(String charset) {
return defaultIfNull(charset, DEFAULT_CHARSET);
}
/** 取得指定类型的所有地址。如果未设置该类型的地址,则返回空数组。 */
public InternetAddress[] getAddresses(MailAddressType addrType) {
Set<InternetAddress> addrSet = getAddressSet(addrType, false);
if (addrSet == null) {
return new InternetAddress[0];
} else {
return addrSet.toArray(new InternetAddress[addrSet.size()]);
}
}
/** 添加邮件地址。 */
public void addAddress(MailAddressType addrType, String addrList) throws InvalidAddressException {
if (isEmpty(addrList)) {
return;
}
InternetAddress[] addrs;
String javaCharset = getCharacterEncoding();
try {
addrs = MailUtil.parse(addrList, javaCharset);
} catch (AddressException e) {
throw new InvalidAddressException("Invalid mail address: " + addrList, e);
} catch (UnsupportedEncodingException e) {
invalidCharset(javaCharset, e);
return;
}
Set<InternetAddress> addrSet = getAddressSet(addrType, true);
for (InternetAddress addr : addrs) {
addrSet.add(addr);
}
}
/**
* 设置邮件地址。
* <p>
* 和<code>addAddress</code>方法不同,该方法清除原有的地址。
* </p>
*/
public void setAddress(MailAddressType addrType, String addr) throws InvalidAddressException {
getAddressSet(addrType, true).clear();
addAddress(addrType, addr);
}
/** 取得邮件内容。 */
public MailContent getContent() {
return content;
}
/**
* 取得指定ID的content。
* <p>
* 如果content未指定ID,则无法找到。
* </p>
* <p>
* 如果和ID对应的content实例未找到,则返回<code>null</code>。
* </p>
*/
public MailContent getContent(String id) {
return findContent(trimToNull(id), content);
}
/** 设置邮件内容。 */
public void setContent(MailContent content) {
MailContent oldContent = this.content;
this.content = content;
this.content.setMailBuilder(this);
if (oldContent != null) {
oldContent.setMailBuilder(null);
}
}
/** 取得发信日期,如果未设置,则取得当前时间。 */
public Date getSentDate() {
if (sentDate == null) {
sentDate = new Date();
}
return sentDate;
}
/** 设置发信日期。 */
public void setSentDate(Date sentDate) {
this.sentDate = sentDate;
}
/** 取得绑定的对象。 */
public Object getAttribute(String key) {
return attributes.get(key);
}
/** 取得attributes的key集合。 */
public Set<String> getAttributeKeys() {
return attributes.keySet();
}
/** 绑定指定的对象。 */
public void setAttribute(String key, Object object) {
if (object == null) {
attributes.remove(key);
} else {
attributes.put(key, object);
}
}
/** 批量绑定对象。 */
public void setAttributes(Map<String, Object> attrs) {
attributes.putAll(attrs);
}
/** 转换成javamail邮件对象。 */
public MimeMessage getMessage(Session session) throws MailBuilderException {
this.session = assertNotNull(session, "session");
MimeMessage message = new MimeMessage(session);
try {
if (content != null) {
content.render(message);
} else {
message.setContent(EMPTY_STRING, "text/plain");
}
} catch (MessagingException e) {
throw new MailBuilderException("Failed to render content", e);
}
try {
// from addresses
message.addFrom(getAddresses(FROM));
// recipients addresses
message.setRecipients(Message.RecipientType.TO, getAddresses(TO));
message.setRecipients(Message.RecipientType.CC, getAddresses(CC));
message.setRecipients(Message.RecipientType.BCC, getAddresses(BCC));
// reply to addresses
message.setReplyTo(getAddresses(REPLY_TO));
// subject
message.setSubject(MailUtil.encodeHeader(getSubject(), getCharacterEncoding()));
// sent date
message.setSentDate(getSentDate());
} catch (MessagingException e) {
throw new MailBuilderException("Failed to create javamail message", e);
} catch (UnsupportedEncodingException e) {
invalidCharset(getCharacterEncoding(), e);
return null;
}
return message;
}
/** 将javamail邮件对象转换成文本形式,其格式为标准的<code>.eml</code>格式。 */
public String getMessageAsString(Session session) throws MailBuilderException {
Message message = getMessage(session);
try {
return MailUtil.toString(message, getCharacterEncoding());
} catch (UnsupportedEncodingException e) {
invalidCharset(getCharacterEncoding(), e);
return null;
} catch (MessagingException e) {
throw new MailBuilderException(e);
}
}
/** 将javamail邮件对象输出到指定流中。 */
public void writeTo(OutputStream ostream, Session session) throws MailBuilderException, IOException {
Message message = getMessage(session);
try {
message.writeTo(ostream);
} catch (MessagingException e) {
throw new MailBuilderException(e);
}
}
/**
* 取得指定类型的邮件地址集合。
* <p>
* 如果该类型地址不存在,返回<code>null</code>。假如<code>create==true</code>,则会自动创建集合。
* </p>
*/
private Set<InternetAddress> getAddressSet(MailAddressType addrType, boolean create) {
assertNotNull(addrType, "addressType");
int index = addrType.ordinal();
assertTrue(index < addresses.length, "internal state inconsistent");
Set<InternetAddress> addrSet = addresses[index];
if (addrSet == null && create) {
addrSet = createLinkedHashSet();
addresses[index] = addrSet;
}
return addrSet;
}
/** 递归查找指定ID的content。 */
private MailContent findContent(String id, MailContent content) {
MailContent result = null;
if (id != null && content != null) {
String contentId = content.getId();
if (id.equals(contentId)) {
result = content;
} else if (content instanceof Multipart) {
for (MailContent subcontent : ((Multipart) content).getContents()) {
result = findContent(id, subcontent);
if (result != null) {
break;
}
}
}
}
return result;
}
private void invalidCharset(String charset, UnsupportedEncodingException e) {
StringBuilder message = new StringBuilder();
String id = getId();
message.append("Invalid charset \"").append(charset).append("\"");
if (!isEmpty(id)) {
message.append(" specified at mail (id=\"").append(id).append("\")");
}
throw new MailBuilderException(message.toString(), e);
}
@Override
public String toString() {
MapBuilder mb = new MapBuilder();
if (getId() != null) {
mb.append("id", getId());
}
mb.append("subject", getSubject());
mb.append("charset", getCharacterEncoding());
mb.append("sentDate", sentDate); // don't use getSentDate()
for (MailAddressType addrType : MailAddressType.values()) {
InternetAddress[] addrs = getAddresses(addrType);
if (isEmptyArray(addrs)) {
mb.append(addrType.name(), EMPTY_STRING);
} else if (addrs.length == 1) {
mb.append(addrType.name(), addrs[0]);
} else {
mb.append(addrType.name(), addrs);
}
}
mb.append("attributes", attributes);
mb.append("content", content);
return new ToStringBuilder().append(getClass().getSimpleName()).append(mb).toString();
}
}