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 }