1 package com.ozacc.mail.impl;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.StringReader;
6 import java.io.StringWriter;
7 import java.util.HashMap;
8 import java.util.Map;
9 import java.util.Properties;
10
11 import javax.xml.parsers.DocumentBuilder;
12 import javax.xml.transform.OutputKeys;
13 import javax.xml.transform.Transformer;
14 import javax.xml.transform.TransformerConfigurationException;
15 import javax.xml.transform.TransformerException;
16 import javax.xml.transform.TransformerFactory;
17 import javax.xml.transform.TransformerFactoryConfigurationError;
18 import javax.xml.transform.dom.DOMSource;
19 import javax.xml.transform.stream.StreamResult;
20
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23 import org.apache.velocity.VelocityContext;
24 import org.apache.velocity.app.Velocity;
25 import org.apache.velocity.exception.MethodInvocationException;
26 import org.apache.velocity.exception.ParseErrorException;
27 import org.apache.velocity.exception.ResourceNotFoundException;
28 import org.w3c.dom.Document;
29 import org.w3c.dom.Element;
30 import org.xml.sax.InputSource;
31 import org.xml.sax.SAXException;
32
33 import com.ozacc.mail.Mail;
34 import com.ozacc.mail.MailBuildException;
35 import com.ozacc.mail.VelocityMultipleMailBuilder;
36
37 /***
38 * XMLファイルを読み込み、Velocityと連携して動的にメールデータを生成し、そのデータからMailインスタンスを生成するクラス。
39 *
40 * @since 1.0.1
41 * @author Tomohiro Otsuka
42 * @version $Id: XMLVelocityMailBuilderImpl.java,v 1.9 2006/03/03 06:00:29 otsuka Exp $
43 */
44 public class XMLVelocityMailBuilderImpl extends XMLMailBuilderImpl implements
45 VelocityMultipleMailBuilder {
46
47 private static Log log = LogFactory.getLog(XMLVelocityMailBuilderImpl.class);
48
49 private static String CACHE_KEY_SEPARATOR = "#";
50
51 private static String DEFAULT_MAIL_ID = "DEFAULT";
52
53 protected String charset = "UTF-8";
54
55 static {
56 Velocity.setProperty(Velocity.RUNTIME_LOG_LOGSYSTEM, new VelocityLogSystem());
57 try {
58 Velocity.init();
59 } catch (Exception e) {
60 throw new MailBuildException("Velocityの初期化に失敗しました。", e);
61 }
62 }
63
64 protected Map templateCache = new HashMap();
65
66 private boolean cacheEnabled = false;
67
68 protected boolean hasTemplateCache(String key) {
69 if (cacheEnabled) {
70 return templateCache.containsKey(key);
71 }
72 return false;
73 }
74
75 protected void putTemplateCache(String key, String templateXmlText) {
76 if (cacheEnabled) {
77 log.debug("テンプレートをキャッシュします。[key='" + key + "']");
78 templateCache.put(key, templateXmlText);
79 }
80 }
81
82 protected String getTemplateCache(String key) {
83 if (hasTemplateCache(key)) {
84 log.debug("テンプレートキャッシュを返します。[key='" + key + "']");
85 return (String)templateCache.get(key);
86 }
87 return null;
88 }
89
90 /***
91 * @see com.ozacc.mail.VelocityMailBuilder#clearCache()
92 */
93 public synchronized void clearCache() {
94 log.debug("テンプレートキャッシュをクリアします。");
95 templateCache.clear();
96 }
97
98 /***
99 * @see com.ozacc.mail.VelocityMailBuilder#isCacheEnabled()
100 */
101 public boolean isCacheEnabled() {
102 return cacheEnabled;
103 }
104
105 /***
106 * @see com.ozacc.mail.VelocityMailBuilder#setCacheEnabled(boolean)
107 */
108 public void setCacheEnabled(boolean cacheEnabled) {
109 if (!cacheEnabled) {
110 clearCache();
111 }
112 this.cacheEnabled = cacheEnabled;
113 }
114
115 /***
116 * @see com.ozacc.mail.VelocityMailBuilder#buildMail(java.lang.String, org.apache.velocity.VelocityContext)
117 */
118 public Mail buildMail(String classPath, VelocityContext context) throws MailBuildException {
119 String cacheKey = classPath + CACHE_KEY_SEPARATOR + DEFAULT_MAIL_ID;
120
121 String templateXmlText;
122 if (!hasTemplateCache(cacheKey)) {
123 Document doc;
124 try {
125
126 doc = getDocumentFromClassPath(classPath, false);
127 } catch (SAXException e) {
128 throw new MailBuildException("XMLのパースに失敗しました。" + e.getMessage(), e);
129 } catch (IOException e) {
130 throw new MailBuildException("XMLファイルの読み込みに失敗しました。", e);
131 }
132 templateXmlText = convertDocumentIntoString(doc.getDocumentElement());
133 putTemplateCache(cacheKey, templateXmlText);
134 } else {
135 templateXmlText = getTemplateCache(cacheKey);
136 }
137
138 try {
139 return build(templateXmlText, context);
140 } catch (Exception e) {
141 throw new MailBuildException("メールの生成に失敗しました。", e);
142 }
143 }
144
145 /***
146 * @see com.ozacc.mail.VelocityMailBuilder#buildMail(java.io.File, org.apache.velocity.VelocityContext)
147 */
148 public Mail buildMail(File file, VelocityContext context) throws MailBuildException {
149 String cacheKey = file.getAbsolutePath() + CACHE_KEY_SEPARATOR + DEFAULT_MAIL_ID;
150
151 String templateXmlText;
152 if (!hasTemplateCache(cacheKey)) {
153 Document doc;
154 try {
155
156 doc = getDocumentFromFile(file, false);
157 } catch (SAXException e) {
158 throw new MailBuildException("XMLのパースに失敗しました。" + e.getMessage(), e);
159 } catch (IOException e) {
160 throw new MailBuildException("XMLファイルの読み込みに失敗しました。", e);
161 }
162 templateXmlText = convertDocumentIntoString(doc.getDocumentElement());
163 putTemplateCache(cacheKey, templateXmlText);
164 } else {
165 templateXmlText = getTemplateCache(cacheKey);
166 }
167
168 try {
169 return build(templateXmlText, context);
170 } catch (Exception e) {
171 throw new MailBuildException("メールの生成に失敗しました。", e);
172 }
173 }
174
175 /***
176 * メールデータをVelocityContextとマージして生成されたXMLからMailインスタンスを生成します。
177 *
178 * @param templateXmlText メールデータのテンプレート
179 * @param context テンプレートにマージする内容を格納したVelocityContext
180 * @return VelocityContextをテンプレートにマージして生成されたXMLから生成されたMailインスタンス
181 * @throws TransformerFactoryConfigurationError
182 * @throws Exception
183 * @throws ParseErrorException
184 * @throws MethodInvocationException
185 * @throws ResourceNotFoundException
186 * @throws IOException
187 */
188 protected Mail build(String templateXmlText, VelocityContext context)
189 throws TransformerFactoryConfigurationError,
190 Exception,
191 ParseErrorException,
192 MethodInvocationException,
193 ResourceNotFoundException,
194 IOException {
195 if (log.isDebugEnabled()) {
196 log.debug("Source XML Mail Data\n" + templateXmlText);
197 }
198
199 StringWriter w = new StringWriter();
200 Velocity.evaluate(context, w, "XML Mail Data", templateXmlText);
201 StringReader reader = new StringReader(w.toString());
202
203 DocumentBuilder db = createDocumentBuilder();
204 InputSource source = new InputSource(reader);
205 Document newDoc = db.parse(source);
206
207 if (log.isDebugEnabled()) {
208 String newXmlContent = convertDocumentIntoString(newDoc.getDocumentElement());
209 log.debug("VelocityContext-merged XML Mail Data\n" + newXmlContent);
210 }
211
212 return buildMail(newDoc.getDocumentElement());
213 }
214
215 /***
216 * 指定されたDOM Documentを文字列に変換します。
217 *
218 * @param mailElement
219 * @return XMLドキュメントの文字列
220 * @throws TransformerFactoryConfigurationError
221 */
222 protected String convertDocumentIntoString(Element mailElement)
223 throws TransformerFactoryConfigurationError {
224 TransformerFactory tf = TransformerFactory.newInstance();
225 Transformer t;
226 try {
227 t = tf.newTransformer();
228 } catch (TransformerConfigurationException e) {
229 throw new MailBuildException(e.getMessage(), e);
230 }
231 t.setOutputProperties(getOutputProperties());
232
233 DOMSource source = new DOMSource(mailElement);
234 StringWriter w = new StringWriter();
235 StreamResult result = new StreamResult(w);
236 try {
237 t.transform(source, result);
238 } catch (TransformerException e) {
239 throw new MailBuildException(e.getMessage(), e);
240 }
241
242 return w.toString();
243 }
244
245 /***
246 * 出力プロパティを生成。
247 * @return 出力プロパティを設定したPropertiesインスタンス
248 */
249 protected Properties getOutputProperties() {
250 Properties p = new Properties();
251 p.put(OutputKeys.ENCODING, charset);
252 p.put(OutputKeys.DOCTYPE_PUBLIC, Mail.DOCTYPE_PUBLIC);
253 p.put(OutputKeys.DOCTYPE_SYSTEM, Mail.DOCTYPE_SYSTEM);
254 return p;
255 }
256
257 /***
258 * @see com.ozacc.mail.VelocityMultipleMailBuilder#buildMail(java.lang.String, org.apache.velocity.VelocityContext, java.lang.String)
259 */
260 public Mail buildMail(String classPath, VelocityContext context, String mailId)
261 throws MailBuildException {
262 if (mailId == null || "".equals(mailId)) {
263 throw new IllegalArgumentException("メールIDが指定されていません。");
264 }
265
266 String cacheKey = classPath + CACHE_KEY_SEPARATOR + mailId;
267
268 String templateXmlText;
269 if (!hasTemplateCache(cacheKey)) {
270 Document doc;
271 try {
272
273 doc = getDocumentFromClassPath(classPath, false);
274 } catch (SAXException e) {
275 throw new MailBuildException("XMLのパースに失敗しました。" + e.getMessage(), e);
276 } catch (IOException e) {
277 throw new MailBuildException("XMLファイルの読み込みに失敗しました。", e);
278 }
279 if (Mail.DOCTYPE_PUBLIC.equals(doc.getDoctype().getPublicId())) {
280 throw new MailBuildException("指定されたクラスパスのXMLはシングルメールテンプレートです。[classPath='"
281 + classPath + "']");
282 }
283 templateXmlText = getAndCacheTemplateText(doc, mailId, cacheKey);
284 } else {
285 templateXmlText = getTemplateCache(cacheKey);
286 }
287
288 try {
289 return build(templateXmlText, context);
290 } catch (Exception e) {
291 throw new MailBuildException("メールの生成に失敗しました。", e);
292 }
293 }
294
295 private String getAndCacheTemplateText(Document doc, String mailId, String cacheKey)
296 throws TransformerFactoryConfigurationError {
297 Element mailElem = doc.getElementById(mailId);
298 if (mailElem == null) {
299 throw new MailBuildException("指定されたID[" + mailId + "]のメールデータは見つかりませんでした。");
300 }
301 String templateXmlText = templateXmlText = convertDocumentIntoString(mailElem);
302 putTemplateCache(cacheKey, templateXmlText);
303 return templateXmlText;
304 }
305
306 /***
307 * @see com.ozacc.mail.VelocityMultipleMailBuilder#buildMail(java.io.File, org.apache.velocity.VelocityContext, java.lang.String)
308 */
309 public Mail buildMail(File file, VelocityContext context, String mailId)
310 throws MailBuildException {
311 if (mailId == null || "".equals(mailId)) {
312 throw new IllegalArgumentException("メールIDが指定されていません。");
313 }
314
315 String cacheKey = file.getAbsolutePath() + CACHE_KEY_SEPARATOR + mailId;
316
317 String templateXmlText;
318 if (!hasTemplateCache(cacheKey)) {
319 Document doc;
320 try {
321
322 doc = getDocumentFromFile(file, false);
323 } catch (SAXException e) {
324 throw new MailBuildException("XMLのパースに失敗しました。" + e.getMessage(), e);
325 } catch (IOException e) {
326 throw new MailBuildException("XMLファイルの読み込みに失敗しました。", e);
327 }
328 if (Mail.DOCTYPE_PUBLIC.equals(doc.getDoctype().getPublicId())) {
329 throw new MailBuildException("指定されたファイルのXMLはシングルメールテンプレートです。[filePath='"
330 + file.getAbsolutePath() + "']");
331 }
332 templateXmlText = getAndCacheTemplateText(doc, mailId, cacheKey);
333 } else {
334 templateXmlText = getTemplateCache(cacheKey);
335 }
336
337 try {
338 return build(templateXmlText, context);
339 } catch (Exception e) {
340 throw new MailBuildException("メールの生成に失敗しました。", e);
341 }
342 }
343
344 }