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