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 }