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