View Javadoc

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.11 2006/05/06 05:50:57 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 				// Velocityマージ前のXMLではコメントを許可する
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 				// Velocityマージ前のXMLではコメントを許可する
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 				// Velocityマージ前のXMLではコメントを許可する
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 				// Velocityマージ前のXMLではコメントを許可する
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 }