View Javadoc

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 					// ignore
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())) { // to
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())) { // cc
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) { // bcc
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(); // <mails>
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 }