1 package com.ozacc.mail.impl;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.io.StringReader;
7 import java.io.StringWriter;
8 import java.util.HashMap;
9 import java.util.Iterator;
10 import java.util.List;
11 import java.util.Map;
12
13 import org.apache.commons.logging.Log;
14 import org.apache.commons.logging.LogFactory;
15 import org.apache.velocity.VelocityContext;
16 import org.apache.velocity.app.Velocity;
17 import org.apache.velocity.exception.MethodInvocationException;
18 import org.apache.velocity.exception.ParseErrorException;
19 import org.apache.velocity.exception.ResourceNotFoundException;
20 import org.apache.velocity.runtime.log.LogSystem;
21 import org.jdom.Document;
22 import org.jdom.Element;
23 import org.jdom.JDOMException;
24 import org.jdom.input.SAXBuilder;
25 import org.jdom.output.XMLOutputter;
26
27 import com.ozacc.mail.Mail;
28 import com.ozacc.mail.MailBuildException;
29 import com.ozacc.mail.MultipleMailBuilder;
30 import com.ozacc.mail.VelocityMultipleMailBuilder;
31
32 /***
33 * <a href="http://www.jdom.org/">JDOM</a>を利用してXMLファイルからMailインスタンスを生成するクラス。
34 * <p>
35 * ソースXMLを読み込む際に、DTDバリデーションが実行されますので妥当なXMLデータ(Valid XML Document)でなければいけません。
36 *
37 * @since 1.0
38 *
39 * @author Tomohiro Otsuka
40 * @version $Id: JDomXMLMailBuilder.java,v 1.10.2.5 2005/02/01 20:37:49 otsuka Exp $
41 */
42 public class JDomXMLMailBuilder implements MultipleMailBuilder, VelocityMultipleMailBuilder {
43
44 private static Log log = LogFactory.getLog(JDomXMLMailBuilder.class);
45
46 private static String CACHE_KEY_SEPARATOR = "#";
47
48 private static String DEFAULT_MAIL_ID = "DEFAULT";
49
50 protected LogSystem velocityLogSystem = new VelocityLogSystem();
51
52 private boolean cacheEnabled = false;
53
54 protected Map templateCache = new HashMap();
55
56 /***
57 * コンストラクタ。
58 */
59 public JDomXMLMailBuilder() {}
60
61 /***
62 * 指定されたクラスパス上のXMLファイルからMailインスタンスを生成します。
63 *
64 * @param classPath メール内容を記述したXMLファイルのパス
65 * @return 生成されたMailインスタンス
66 * @throws MailBuildException Mailインスタンスの生成に失敗した場合
67 */
68 public Mail buildMail(String classPath) throws MailBuildException {
69 Document doc = getDocumentFromClassPath(classPath);
70 return build(doc.getRootElement());
71 }
72
73 /***
74 * 指定されたクラスパス上のXMLファイルからMailインスタンスを生成します。
75 * 指定されたVelocityContextを使って、XMLファイルの内容を動的に生成できます。
76 *
77 * @param classPath メール内容を記述したXMLファイルのパス
78 * @param context VelocityContext
79 * @return 生成されたMailインスタンス
80 * @throws MailBuildException Mailインスタンスの生成に失敗した場合
81 */
82 public Mail buildMail(String classPath, VelocityContext context) throws MailBuildException {
83 String cacheKey = classPath + CACHE_KEY_SEPARATOR + DEFAULT_MAIL_ID;
84 String templateXmlText;
85 if (!hasTemplateCache(cacheKey)) {
86 Document doc = getDocumentFromClassPath(classPath);
87 templateXmlText = cacheTemplateText(doc, cacheKey);
88 } else {
89 templateXmlText = getTemplateCache(cacheKey);
90 }
91 try {
92 return build(templateXmlText, context);
93 } catch (Exception e) {
94 throw new MailBuildException("メールの生成に失敗しました。", e);
95 }
96 }
97
98 /***
99 * 指定されたXMLファイルからMailインスタンスを生成します。
100 *
101 * @param file メール内容を記述したXMLファイル
102 * @return 生成されたMailインスタンス
103 * @throws MailBuildException Mailインスタンスの生成に失敗した場合
104 */
105 public Mail buildMail(File file) throws MailBuildException {
106 Document doc = getDocumentFromFile(file);
107 return build(doc.getRootElement());
108 }
109
110 /***
111 * 指定されたXMLファイルからMailインスタンスを生成します。
112 * 指定されたVelocityContextを使って、XMLファイルの内容を動的に生成できます。
113 *
114 * @param file メール内容を記述したXMLファイル
115 * @param context VelocityContext
116 * @return 生成されたMailインスタンス
117 * @throws MailBuildException Mailインスタンスの生成に失敗した場合
118 */
119 public Mail buildMail(File file, VelocityContext context) throws MailBuildException {
120 String cacheKey = file.getAbsolutePath() + CACHE_KEY_SEPARATOR + DEFAULT_MAIL_ID;
121 String templateXmlText;
122 if (!hasTemplateCache(cacheKey)) {
123 Document doc = getDocumentFromFile(file);
124 templateXmlText = cacheTemplateText(doc, cacheKey);
125 } else {
126 templateXmlText = getTemplateCache(cacheKey);
127 }
128 try {
129 return build(templateXmlText, context);
130 } catch (Exception e) {
131 throw new MailBuildException("メールの生成に失敗しました。", e);
132 }
133 }
134
135 private String cacheTemplateText(Document doc, String cacheKey) {
136 XMLOutputter output = new XMLOutputter();
137 String templateXmlText = "<!DOCTYPE mail PUBLIC \"" + Mail.DOCTYPE_PUBLIC + "\" \""
138 + Mail.DOCTYPE_SYSTEM + "\">\n" + output.outputString(doc.getRootElement());
139 log.debug("以下のXMLデータをキャッシュします。\n" + templateXmlText);
140 putTemplateCache(cacheKey, templateXmlText);
141 return templateXmlText;
142 }
143
144 /***
145 * 指定されたクラスパス上のファイルを読み込んで、XMLドキュメントを生成します。
146 *
147 * @param classPath
148 * @return JDOM Document
149 */
150 protected Document getDocumentFromClassPath(String classPath) throws MailBuildException {
151 InputStream is = getClass().getResourceAsStream(classPath);
152 SAXBuilder builder = new SAXBuilder(true);
153 builder.setEntityResolver(new DTDEntityResolver());
154 Document doc;
155 try {
156 doc = builder.build(is);
157 } catch (JDOMException e) {
158 throw new MailBuildException("XMLのパースに失敗しました。" + e.getMessage(), e);
159 } catch (IOException e) {
160 throw new MailBuildException("XMLファイルの読み込みに失敗しました。", e);
161 } finally {
162 if (is != null) {
163 try {
164 is.close();
165 } catch (IOException e) {
166
167 }
168 }
169 }
170 return doc;
171 }
172
173 /***
174 * 指定されたファイルを読み込んで、XMLドキュメントを生成します。
175 *
176 * @param file
177 * @return JDOM Document
178 */
179 protected Document getDocumentFromFile(File file) {
180 SAXBuilder builder = new SAXBuilder(true);
181 builder.setEntityResolver(new DTDEntityResolver());
182 Document doc;
183 try {
184 doc = builder.build(file);
185 } catch (JDOMException e) {
186 throw new MailBuildException("XMLのパースに失敗しました。" + e.getMessage(), e);
187 } catch (IOException e) {
188 throw new MailBuildException("XMLファイルの読み込みに失敗しました。", e);
189 }
190 return doc;
191 }
192
193 /***
194 * XMLのmailルートエレメントからMailインスタンスを生成します。
195 *
196 * @param mailElement mail要素を示すElementインスタンス
197 * @return Mail 生成されたMail
198 */
199 protected Mail build(Element mailElement) {
200 Mail mail = new Mail();
201 setFrom(mailElement, mail);
202 setRecipients(mailElement, mail);
203 setSubject(mailElement, mail);
204 setBody(mailElement, mail);
205 setReplyTo(mailElement, mail);
206 setReturnPath(mailElement, mail);
207
208 setHtml(mailElement, mail);
209
210 return mail;
211 }
212
213 /***
214 * VelocityContextとXMLテンプレートをマージさせ、Mailインスタンスを生成します。
215 *
216 * @param templateText マージするXMLテンプレートの文字列
217 * @param context マージするVelocityContext
218 * @return Mail
219 *
220 * @throws Exception
221 * @throws ParseErrorException
222 * @throws MethodInvocationException
223 * @throws ResourceNotFoundException
224 * @throws IOException
225 * @throws JDOMException
226 */
227 protected Mail build(String templateText, VelocityContext context) throws Exception,
228 ParseErrorException,
229 MethodInvocationException,
230 ResourceNotFoundException,
231 IOException, JDOMException {
232 if (log.isDebugEnabled()) {
233 log.debug("ソースXMLデータ\n" + templateText);
234 }
235
236 Velocity.setProperty(Velocity.RUNTIME_LOG_LOGSYSTEM, velocityLogSystem);
237 Velocity.init();
238 StringWriter w = new StringWriter();
239 Velocity.evaluate(context, w, "XML Mail Data", templateText);
240
241 if (log.isDebugEnabled()) {
242 log.debug("VelocityContextとマージ後のXMLデータ\n" + w.toString());
243 }
244
245 StringReader reader = new StringReader(w.toString());
246 SAXBuilder builder = new SAXBuilder(true);
247 builder.setEntityResolver(new DTDEntityResolver());
248 Document mergedDoc = builder.build(reader);
249
250 return build(mergedDoc.getRootElement());
251 }
252
253 /***
254 * @param root
255 * @param mail
256 */
257 protected void setReturnPath(Element root, Mail mail) {
258 Element returnPathElem = root.getChild("returnPath");
259 if (returnPathElem != null && returnPathElem.getAttributeValue("email") != null) {
260 mail.setReturnPath(returnPathElem.getAttributeValue("email"));
261 }
262 }
263
264 /***
265 * @param root
266 * @param mail
267 */
268 protected void setReplyTo(Element root, Mail mail) {
269 Element replyToElem = root.getChild("replyTo");
270 if (replyToElem != null && replyToElem.getAttributeValue("email") != null) {
271 mail.setReplyTo(replyToElem.getAttributeValue("email"));
272 }
273 }
274
275 /***
276 * @param root
277 * @param mail
278 */
279 protected void setBody(Element root, Mail mail) {
280 Element bodyElem = root.getChild("body");
281 if (bodyElem != null) {
282 mail.setText(bodyElem.getTextTrim());
283 }
284 }
285
286 /***
287 * @param root
288 * @param mail
289 */
290 protected void setHtml(Element root, Mail mail) {
291 Element htmlElem = root.getChild("html");
292 if (htmlElem != null) {
293 mail.setHtmlText(htmlElem.getTextTrim());
294 }
295 }
296
297 /***
298 * @param root
299 * @param mail
300 */
301 protected void setSubject(Element root, Mail mail) {
302 Element subjectElem = root.getChild("subject");
303 if (subjectElem != null) {
304 mail.setSubject(subjectElem.getTextTrim());
305 }
306 }
307
308 /***
309 * @param root
310 * @param mail
311 */
312 protected void setRecipients(Element root, Mail mail) {
313 Element recipientsElem = root.getChild("recipients");
314 if (recipientsElem == null) {
315 return;
316 }
317
318 List recipientElemList = recipientsElem.getChildren();
319 for (int i = 0, max = recipientElemList.size(); i < max; i++) {
320 Element e = (Element)recipientElemList.get(i);
321 if ("to".equals(e.getName())) {
322 if (e.getAttributeValue("email") != null) {
323 if (e.getAttributeValue("name") != null) {
324 mail.addTo(e.getAttributeValue("email"), e.getAttributeValue("name"));
325 } else {
326 mail.addTo(e.getAttributeValue("email"));
327 }
328 }
329 } else if ("cc".equals(e.getName())) {
330 if (e.getAttributeValue("email") != null) {
331 if (e.getAttributeValue("name") != null) {
332 mail.addCc(e.getAttributeValue("email"), e.getAttributeValue("name"));
333 } else {
334 mail.addCc(e.getAttributeValue("email"));
335 }
336 }
337 } else {
338 if (e.getAttributeValue("email") != null) {
339 mail.addBcc(e.getAttributeValue("email"));
340 }
341 }
342 }
343 }
344
345 /***
346 * @param root
347 * @param mail
348 */
349 protected void setFrom(Element root, Mail mail) {
350 Element fromElem = root.getChild("from");
351 if (fromElem != null && fromElem.getAttributeValue("email") != null) {
352 if (fromElem.getAttributeValue("name") != null) {
353 mail.setFrom(fromElem.getAttributeValue("email"), fromElem
354 .getAttributeValue("name"));
355 } else {
356 mail.setFrom(fromElem.getAttributeValue("email"));
357 }
358 }
359 }
360
361 /***
362 * @see com.ozacc.mail.VelocityMailBuilder#clearCache()
363 */
364 public synchronized void clearCache() {
365 log.debug("テンプレートキャッシュをクリアします。");
366 templateCache.clear();
367 }
368
369 /***
370 * @see com.ozacc.mail.VelocityMailBuilder#isCacheEnabled()
371 */
372 public boolean isCacheEnabled() {
373 return cacheEnabled;
374 }
375
376 /***
377 * @see com.ozacc.mail.VelocityMailBuilder#setCacheEnabled(boolean)
378 */
379 public void setCacheEnabled(boolean cacheEnabled) {
380 if (!cacheEnabled) {
381 clearCache();
382 }
383 this.cacheEnabled = cacheEnabled;
384 }
385
386 protected boolean hasTemplateCache(String key) {
387 if (cacheEnabled) {
388 return templateCache.containsKey(key);
389 }
390 return false;
391 }
392
393 protected void putTemplateCache(String key, String templateXmlText) {
394 if (cacheEnabled) {
395 log.debug("テンプレートをキャッシュします。[key='" + key + "']");
396 templateCache.put(key, templateXmlText);
397 }
398 }
399
400 protected String getTemplateCache(String key) {
401 if (hasTemplateCache(key)) {
402 log.debug("テンプレートキャッシュを返します。[key='" + key + "']");
403 return (String)templateCache.get(key);
404 }
405 return null;
406 }
407
408 /***
409 * @see com.ozacc.mail.VelocityMultipleMailBuilder#buildMail(java.lang.String, org.apache.velocity.VelocityContext, java.lang.String)
410 */
411 public Mail buildMail(String classPath, VelocityContext context, String mailId)
412 throws MailBuildException {
413 if (mailId == null || "".equals(mailId)) {
414 throw new IllegalArgumentException("メールIDが指定されていません。");
415 }
416
417 String cacheKey = classPath + CACHE_KEY_SEPARATOR + mailId;
418 String templateXmlText;
419 if (!hasTemplateCache(cacheKey)) {
420 Document doc = getDocumentFromClassPath(classPath);
421 templateXmlText = getAndCacheTemplateText(doc, mailId, cacheKey);
422 } else {
423 templateXmlText = getTemplateCache(cacheKey);
424 }
425 try {
426 return build(templateXmlText, context);
427 } catch (Exception e) {
428 throw new MailBuildException("メールの生成に失敗しました。", e);
429 }
430 }
431
432 private String getAndCacheTemplateText(Document doc, String mailId, String cacheKey)
433 throws MailBuildException {
434 Element mailElem = getElementById(doc, mailId);
435 XMLOutputter output = new XMLOutputter();
436 String templateXmlText = output.outputString(mailElem);
437
438 putTemplateCache(cacheKey, templateXmlText);
439 return templateXmlText;
440 }
441
442 /***
443 * @see com.ozacc.mail.VelocityMultipleMailBuilder#buildMail(java.io.File, org.apache.velocity.VelocityContext, java.lang.String)
444 */
445 public Mail buildMail(File file, VelocityContext context, String mailId)
446 throws MailBuildException {
447 if (mailId == null || "".equals(mailId)) {
448 throw new IllegalArgumentException("メールIDが指定されていません。");
449 }
450
451 String cacheKey = file.getAbsolutePath() + CACHE_KEY_SEPARATOR + mailId;
452 String templateXmlText;
453 if (!hasTemplateCache(cacheKey)) {
454 Document doc = getDocumentFromFile(file);
455 templateXmlText = getAndCacheTemplateText(doc, mailId, cacheKey);
456 } else {
457 templateXmlText = getTemplateCache(cacheKey);
458 }
459 try {
460 return build(templateXmlText, context);
461 } catch (Exception e) {
462 throw new MailBuildException("メールの生成に失敗しました。", e);
463 }
464 }
465
466 /***
467 * @see com.ozacc.mail.MultipleMailBuilder#buildMail(java.lang.String, java.lang.String)
468 */
469 public Mail buildMail(String classPath, String mailId) throws MailBuildException {
470 Document doc = getDocumentFromClassPath(classPath);
471 Element mailElem = getElementById(doc, mailId);
472 return build(mailElem);
473 }
474
475 /***
476 * @see com.ozacc.mail.MultipleMailBuilder#buildMail(java.io.File, java.lang.String)
477 */
478 public Mail buildMail(File file, String mailId) throws MailBuildException {
479 Document doc = getDocumentFromFile(file);
480 Element mailElem = getElementById(doc, mailId);
481 return build(mailElem);
482 }
483
484 /***
485 * 指定されたXMLドキュメントの中から、指定されたid属性がセットされている要素を取得します。
486 *
487 * @param doc XMLドキュメント
488 * @param id 抽出する要素のid属性値
489 * @return XMLドキュメントで見つかったid属性を持つ要素
490 */
491 private Element getElementById(Document doc, String id) {
492 Element mailsElem = doc.getRootElement();
493 List mailElemList = mailsElem.getChildren("mail");
494 for (Iterator itr = mailElemList.iterator(); itr.hasNext();) {
495 Element mailElem = (Element)itr.next();
496 String mailId = mailElem.getAttributeValue("id");
497 if (mailId.equals(id)) {
498 return mailElem;
499 }
500 }
501 throw new MailBuildException("指定されたID[" + id + "]のメールデータは見つかりませんでした。");
502 }
503
504 }