/* * 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.content; import static com.alibaba.citrus.service.mail.MailConstant.*; import static com.alibaba.citrus.util.Assert.*; import static com.alibaba.citrus.util.CollectionUtil.*; import static com.alibaba.citrus.util.StringUtil.*; import java.io.IOException; import java.text.MessageFormat; import java.util.Date; import java.util.Map; import java.util.Set; import javax.activation.DataHandler; import javax.activation.DataSource; import javax.activation.URLDataSource; import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.Part; import javax.mail.internet.ContentType; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMultipart; import com.alibaba.citrus.service.mail.builder.MailBuilderException; import com.alibaba.citrus.service.mail.support.ResourceDataSource; import com.alibaba.citrus.service.mail.util.MailUtil; import com.alibaba.citrus.service.template.TemplateContext; import com.alibaba.citrus.util.FileUtil; import com.alibaba.citrus.util.SystemUtil; import com.alibaba.citrus.util.ToStringBuilder.MapBuilder; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; /** * 用模板生成的HTML的内容。 * * @author Michael Zhou */ public class HTMLTemplateContent extends TemplateContent implements ResourceLoaderAware { private ResourceLoader resourceLoader; private Map<String, String> inlineResourceMap = createHashMap(); private Map<String, InlineResource> inlineResources = createHashMap(); /** 创建一个<code>HTMLTemplateContent</code>。 */ public HTMLTemplateContent() { } /** 创建一个<code>HTMLTemplateContent</code>。 */ public HTMLTemplateContent(String templateName) { setTemplate(templateName); } /** 创建一个<code>HTMLTemplateContent</code>。 */ public HTMLTemplateContent(String templateName, String contentType) { setTemplate(templateName); setContentType(contentType); } /** 取得用来装载资源的<code>ResourceLoader</code>。 */ public ResourceLoader getResourceLoader() { return resourceLoader; } /** 设置用来装载资源的<code>ResourceLoader</code>。 */ public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } /** 设置一组内置资源的tool。 */ public void setInlineResources(Map<String, String> resourceMap) { if (resourceMap != null) { inlineResourceMap.clear(); for (Map.Entry<String, String> entry : resourceMap.entrySet()) { addInlineResource(entry.getKey(), entry.getValue()); } } } /** * 添加一个在模板中代表内置资源的tool。其中,<code>id</code>为在模板中引用该tool的key, * <code>prefix</code>为该tool在查找资源时,自动加上指定前缀。 */ public void addInlineResource(String id, String prefix) { id = assertNotNull(trimToNull(id), "The ID of inline resource was not specified"); prefix = assertNotNull(trimToNull(prefix), "The prefix of inline resource was not specified"); assertTrue(!inlineResourceMap.containsKey(id), "Duplicated ID \"%s\" of inline resource", id); inlineResourceMap.put(id, prefix); } /** 渲染邮件内容。 */ public void render(Part mailPart) throws MessagingException { // 设置内联资源的helper对象, 以便模板产生resource的引用. inlineResources.clear(); // 渲染模板. String text = renderTemplate(); // 设置message part. // 如果包括一个以上的内嵌资源, 则使用multipart/related类型, 否则使用text/html类型. try { if (inlineResources.isEmpty()) { renderHTMLContent(mailPart, text); } else { MimeMultipart multipartRelated = new MimeMultipart(CONTENT_TYPE_MULTIPART_SUBTYPE_RELATED); MimeBodyPart bodyPart = new MimeBodyPart(); renderHTMLContent(bodyPart, text); multipartRelated.addBodyPart(bodyPart); // 取得所有内嵌的资源 Set<String> fileNames = createHashSet(); for (InlineResource inlineResource : inlineResources.values()) { renderInlineResource(multipartRelated, inlineResource, fileNames); } mailPart.setContent(multipartRelated); } } finally { inlineResources.clear(); } } /** 渲染HTML内容。 */ private void renderHTMLContent(Part mailPart, String text) throws MessagingException { String contentType = getContentType(); ContentType contentTypeObject = MailUtil.getContentType(contentType, getMailBuilder().getCharacterEncoding()); mailPart.setContent(text, contentTypeObject.toString()); mailPart.setHeader(CONTENT_TRANSFER_ENCODING, DEFAULT_TRANSFER_ENCODING); } private void renderInlineResource(Multipart multipart, InlineResource inlineResource, Set<String> fileNames) throws MessagingException { assertNotNull(resourceLoader, "no resourceLoader"); String resourceName = inlineResource.getResourceName(); Resource resource = resourceLoader.getResource(resourceName); if (!resource.exists()) { throw new MailBuilderException("Could not find resource \"" + resourceName + "\""); } DataSource ds; try { ds = new URLDataSource(resource.getURL()); } catch (IOException e) { ds = new ResourceDataSource(resource); } MimeBodyPart bodyPart = new MimeBodyPart(); bodyPart.setDataHandler(new DataHandler(ds)); bodyPart.setHeader(CONTENT_ID, "<" + inlineResource.getContentId() + ">"); bodyPart.setFileName(inlineResource.getUniqueFilename(fileNames)); bodyPart.setDisposition("inline"); multipart.addBodyPart(bodyPart); } /** 组装templateContext中的内容。 */ @Override protected void populateTemplateContext(TemplateContext templateContext) { for (Map.Entry<String, String> entry : inlineResourceMap.entrySet()) { String key = entry.getKey(); String prefix = entry.getValue(); templateContext.put(key, new InlineResourceHelper(prefix)); } } /** 深度复制一个content。 */ @Override protected void copyTo(AbstractContent copy) { super.copyTo(copy); HTMLTemplateContent copyContent = (HTMLTemplateContent) copy; copyContent.resourceLoader = resourceLoader; copyContent.inlineResourceMap.clear(); copyContent.inlineResourceMap.putAll(inlineResourceMap); copyContent.inlineResources.clear(); } @Override protected HTMLTemplateContent newInstance() { return new HTMLTemplateContent(); } @Override protected String getDefaultContentType() { return CONTENT_TYPE_TEXT_HTML; } @Override protected void toString(MapBuilder mb) { super.toString(mb); mb.append("inlineResources", inlineResourceMap); } /** 记录在模板中使用到的所有内联资源的信息。 */ private static class InlineResource { private static MessageFormat formatter = new MessageFormat("{0,time,yyyyMMdd.HHmmss}.{1}@{2}"); private static int count = 0; private static String hostname = SystemUtil.getHostInfo().getName(); private String resourceName; private String contentId; private String filename; public InlineResource(String resourceName) { this.resourceName = resourceName; synchronized (getClass()) { count = (count + 1) % (2 << 20); this.contentId = formatter.format(new Object[] { new Date(), String.valueOf(count), hostname }); } this.filename = getFileName(resourceName); } private static String getFileName(String name) { if (name.endsWith("/")) { name = name.substring(0, name.length() - 1); } return name.substring(name.lastIndexOf("/") + 1); } public String getResourceName() { return resourceName; } public String getContentId() { return contentId; } /** 取得唯一的文件名。 */ public String getUniqueFilename(Set<String> fileNames) { String name = filename; int dotIndex = filename.lastIndexOf("."); for (int i = 1; fileNames.contains(name); i++) { if (dotIndex >= 0) { name = filename.substring(0, dotIndex) + i + filename.substring(dotIndex); } else { name = filename + i; } } fileNames.add(name); return name; } } /** 在模板中嵌入内置的资源的辅助类。 */ public class InlineResourceHelper { private String prefix; public InlineResourceHelper(String prefix) { this.prefix = FileUtil.normalizeAbsolutePath(prefix + "/"); } public String getURI(String path) { String resourceName = FileUtil.normalizeAbsolutePath(prefix + path); InlineResource inlineResource = inlineResources.get(resourceName); if (inlineResource == null) { inlineResource = new InlineResource(resourceName); inlineResources.put(resourceName, inlineResource); } return "cid:" + inlineResource.getContentId(); } } }