001: package org.tigris.scarab.services.email;
002:
003: /* ====================================================================
004: * The Apache Software License, Version 1.1
005: *
006: * Copyright (c) 2001 The Apache Software Foundation. All rights
007: * reserved.
008: *
009: * Redistribution and use in source and binary forms, with or without
010: * modification, are permitted provided that the following conditions
011: * are met:
012: *
013: * 1. Redistributions of source code must retain the above copyright
014: * notice, this list of conditions and the following disclaimer.
015: *
016: * 2. Redistributions in binary form must reproduce the above copyright
017: * notice, this list of conditions and the following disclaimer in
018: * the documentation and/or other materials provided with the
019: * distribution.
020: *
021: * 3. The end-user documentation included with the redistribution,
022: * if any, must include the following acknowledgment:
023: * "This product includes software developed by the
024: * Apache Software Foundation (http://www.apache.org/)."
025: * Alternately, this acknowledgment may appear in the software itself,
026: * if and wherever such third-party acknowledgments normally appear.
027: *
028: * 4. The names "Apache" and "Apache Software Foundation" and
029: * "Apache Turbine" must not be used to endorse or promote products
030: * derived from this software without prior written permission. For
031: * written permission, please contact apache@apache.org.
032: *
033: * 5. Products derived from this software may not be called "Apache",
034: * "Apache Turbine", nor may "Apache" appear in their name, without
035: * prior written permission of the Apache Software Foundation.
036: *
037: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
038: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
039: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
040: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
041: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
042: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
043: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
044: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
045: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
046: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
047: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
048: * SUCH DAMAGE.
049: * ====================================================================
050: *
051: * This software consists of voluntary contributions made by many
052: * individuals on behalf of the Apache Software Foundation. For more
053: * information on the Apache Software Foundation, please see
054: * <http://www.apache.org/>.
055: */
056:
057: import java.io.ByteArrayOutputStream;
058: import java.io.IOException;
059: import java.io.OutputStream;
060: import java.io.OutputStreamWriter;
061: import java.io.StringWriter;
062: import java.io.Writer;
063: import java.util.ArrayList;
064: import java.util.Iterator;
065: import java.util.List;
066:
067: import org.apache.commons.configuration.ConfigurationConverter;
068: import org.apache.fulcrum.InitializationException;
069: import org.apache.fulcrum.ServiceException;
070: import org.apache.fulcrum.template.BaseTemplateEngineService;
071: import org.apache.fulcrum.template.TemplateContext;
072: import org.apache.fulcrum.velocity.ContextAdapter;
073: import org.apache.velocity.VelocityContext;
074: import org.apache.velocity.app.VelocityEngine;
075: import org.apache.velocity.context.Context;
076: import org.apache.velocity.context.InternalEventContext;
077: import org.apache.velocity.exception.MethodInvocationException;
078: import org.tigris.scarab.tools.ScarabLocalizationTool;
079: import org.tigris.scarab.util.ScarabConstants;
080:
081: /**
082: * This is a Service that can process Velocity templates from within a
083: * Turbine Screen. Here's an example of how you might use it from a
084: * screen:<br>
085: *
086: * <code><pre>
087: * Context context = new VelocityContext();
088: * context.put("message", "Hello from Turbine!");
089: * String results = TurbineVelocity.handleRequest(context,"HelloWorld.vm");
090: * </pre></code>
091: *
092: * Character sets map codes to glyphs, while encodings map between
093: * chars/bytes and codes.
094: * <i>bytes -> [encoding] -> charset -> [rendering] -> glyphs</i>
095: *
096: * <p>This copy of TurbineVelocityService has been slightly modified
097: * from its original form to support toggling of Scarab's cross-site
098: * scripting filter.</p>
099: *
100: * @author <a href="mailto:mbryson@mont.mindspring.com">Dave Bryson</a>
101: * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
102: * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
103: * @author <a href="mailto:sean@informage.ent">Sean Legassick</a>
104: * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
105: * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
106: * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
107: * @author <a href="mailto:james@jamestaylor.org">James Taylor</a>
108: * @version $Id: VelocityEmailService.java 9104 2004-05-10 21:04:51Z dabbous $
109: */
110: public class VelocityEmailService extends BaseTemplateEngineService
111: implements EmailService {
112: /**
113: * The generic resource loader path property in velocity.
114: */
115: private static final String RESOURCE_LOADER_PATH = ".resource.loader.path";
116:
117: /**
118: * Default character set to use if not specified by caller.
119: */
120: private static final String DEFAULT_CHAR_SET = "ISO-8859-1";
121:
122: /**
123: * The prefix used for URIs which are of type <code>jar</code>.
124: */
125: private static final String JAR_PREFIX = "jar:";
126:
127: /**
128: * The prefix used for URIs which are of type <code>absolute</code>.
129: */
130: private static final String ABSOLUTE_PREFIX = "file://";
131:
132: /**
133: * The VelocityEngine used by the service to merge templates
134: */
135: private VelocityEngine velocityEngine = new VelocityEngine();
136:
137: /**
138: * Performs early initialization of this Turbine service.
139: */
140: public void init() throws InitializationException {
141: try {
142: initVelocity();
143: // Register with the template service.
144: registerConfiguration("ve");
145: setInit(true);
146: } catch (Exception e) {
147: e.printStackTrace();
148: throw new InitializationException(
149: "Failed to initialize VelocityEmailService", e); //EXCEPTION
150: }
151: }
152:
153: /**
154: * @see org.apache.fulcrum.velocity.VelocityService
155: */
156: public String handleRequest(TemplateContext context, String template)
157: throws ServiceException {
158: return handleRequest(new ContextAdapter(context), template);
159: }
160:
161: /**
162: * @see org.apache.fulcrum.velocity.VelocityService
163: */
164: public String handleRequest(Context context, String filename)
165: throws ServiceException {
166: return handleRequest(context, filename, (String) null, null);
167: }
168:
169: /**
170: * @see org.apache.fulcrum.velocity.VelocityService
171: */
172: public String handleRequest(Context context, String filename,
173: String charset, String encoding) throws ServiceException {
174: String results = null;
175: if (charset == null) {
176: StringWriter writer = null;
177: try {
178: writer = new StringWriter();
179: handleRequest(context, filename, writer, encoding);
180: results = writer.toString();
181: } catch (Exception e) {
182: renderingError(filename, e);
183: } finally {
184: try {
185: if (writer != null) {
186: writer.close();
187: }
188: } catch (IOException ignored) {
189: }
190: }
191: } else {
192: ByteArrayOutputStream bytes = null;
193: try {
194: bytes = new ByteArrayOutputStream();
195: charset = decodeRequest(context, filename, bytes,
196: charset, encoding);
197: results = bytes.toString(charset);
198: } catch (Exception e) {
199: renderingError(filename, e);
200: } finally {
201: try {
202: if (bytes != null) {
203: bytes.close();
204: }
205: } catch (IOException ignored) {
206: }
207: }
208: }
209:
210: return results;
211: }
212:
213: /**
214: * @see org.apache.fulcrum.template.TemplateEngineService
215: */
216: public void handleRequest(TemplateContext context, String template,
217: OutputStream outputStream) throws ServiceException {
218: handleRequest(new ContextAdapter(context), template,
219: outputStream);
220: }
221:
222: /**
223: * @see org.apache.fulcrum.velocity.VelocityService
224: */
225: public void handleRequest(Context context, String filename,
226: OutputStream output) throws ServiceException {
227: handleRequest(context, filename, output, null, null);
228: }
229:
230: /**
231: * @see org.apache.fulcrum.velocity.VelocityService
232: */
233: public void handleRequest(Context context, String filename,
234: OutputStream output, String charset, String encoding)
235: throws ServiceException {
236: decodeRequest(context, filename, output, charset, encoding);
237: }
238:
239: /**
240: * @see BaseTemplateEngineService#handleRequest(TemplateContext, String, Writer)
241: */
242: public void handleRequest(TemplateContext context, String template,
243: Writer writer) throws ServiceException {
244: handleRequest(new ContextAdapter(context), template, writer);
245: }
246:
247: /**
248: * @see VelocityEmailService#handleRequest(Context, String, Writer)
249: */
250: public void handleRequest(Context context, String filename,
251: Writer writer) throws ServiceException {
252: handleRequest(context, filename, writer, null);
253: }
254:
255: /**
256: * @see VelocityEmailService#handleRequest(Context, String, Writer, String)
257: */
258: public void handleRequest(Context context, String filename,
259: Writer writer, String encoding) throws ServiceException {
260: ScarabLocalizationTool l10n = null;
261: try {
262: // If the context is not already an instance of
263: // InternalEventContext, wrap it in a VeclocityContext so that
264: // event cartridges will work. Unfortunately there is no interface
265: // that extends both Context and InternalEventContext, so this
266: // is not as clear as it could be.
267:
268: Context eventContext;
269:
270: if (context instanceof InternalEventContext) {
271: eventContext = context;
272: } else {
273: eventContext = new VelocityContext(context);
274: }
275:
276: if (encoding == null) {
277: encoding = DEFAULT_CHAR_SET;
278: }
279:
280: // Assumption: Emails are always plain text.
281: l10n = (ScarabLocalizationTool) context
282: .get(ScarabConstants.LOCALIZATION_TOOL);
283: if (l10n != null) {
284: l10n.setFilterEnabled(false);
285: }
286: // Request scoped encoding first supported by Velocity 1.1.
287: velocityEngine.mergeTemplate(filename, encoding,
288: eventContext, writer);
289: } catch (Exception e) {
290: renderingError(filename, e);
291: } finally {
292: if (l10n != null) {
293: l10n.setFilterEnabled(true);
294: }
295: }
296: }
297:
298: /**
299: * Processes the request and fill in the template with the values
300: * you set in the the supplied Context. Applies the specified
301: * character and template encodings.
302: *
303: * @param context A context to use when evaluating the specified
304: * template.
305: * @param filename The file name of the template.
306: * @param output The stream to which we will write the processed
307: * template as a String.
308: * @return The character set applied to the resulting text.
309: *
310: * @throws ServiceException Any exception trown while processing
311: * will be wrapped into a ServiceException and rethrown.
312: */
313: private String decodeRequest(Context context, String filename,
314: OutputStream output, String charset, String encoding)
315: throws ServiceException {
316: if (charset == null) {
317: charset = DEFAULT_CHAR_SET;
318: }
319:
320: OutputStreamWriter writer = null;
321: try {
322: try {
323: writer = new OutputStreamWriter(output, charset);
324: } catch (Exception e) {
325: renderingError(filename, e);
326: }
327: handleRequest(context, filename, writer, encoding);
328: } finally {
329: try {
330: if (writer != null) {
331: writer.flush();
332: }
333: } catch (Exception ignored) {
334: }
335: }
336: return charset;
337: }
338:
339: /**
340: * Macro to handle rendering errors.
341: *
342: * @param filename The file name of the unrenderable template.
343: * @param e The error.
344: *
345: * @exception ServiceException Thrown every time. Adds additional
346: * information to <code>e</code>.
347: */
348: private final void renderingError(String filename, Throwable e)
349: throws ServiceException {
350: String err = "Error rendering Velocity template: " + filename;
351: getCategory().error(err + ": " + e.getMessage());
352: // if the Exception is a MethodInvocationException, the underlying
353: // Exception is likely to be more informative, so rewrap that one.
354: if (e instanceof MethodInvocationException) {
355: e = ((MethodInvocationException) e).getWrappedThrowable();
356: }
357:
358: throw new ServiceException(err, e); //EXCEPTION
359: }
360:
361: /**
362: * Setup the velocity runtime by using a subset of the
363: * Turbine configuration which relates to velocity.
364: *
365: * @exception InitializationException For any errors during initialization.
366: */
367: private void initVelocity() throws InitializationException {
368: // Now we have to perform a couple of path translations
369: // for our log file and template paths.
370: String path = getRealPath(getConfiguration().getString(
371: VelocityEngine.RUNTIME_LOG, null));
372:
373: if (path != null && path.length() > 0) {
374: getConfiguration().setProperty(VelocityEngine.RUNTIME_LOG,
375: path);
376: } else {
377: String msg = EmailService.SERVICE_NAME
378: + " runtime log file " + "is misconfigured: '"
379: + path + "' is not a valid log file";
380:
381: throw new Error(msg); //EXCEPTION
382: }
383:
384: // Get all the template paths where the velocity
385: // runtime should search for templates and
386: // collect them into a separate vector
387: // to avoid concurrent modification exceptions.
388: String key;
389: List keys = new ArrayList();
390: for (Iterator i = getConfiguration().getKeys(); i.hasNext();) {
391: key = (String) i.next();
392: if (key.endsWith(RESOURCE_LOADER_PATH)) {
393: keys.add(key);
394: }
395: }
396:
397: // Loop through all template paths, clear the corresponding
398: // velocity properties and translate them all to the webapp space.
399: int ind;
400: List paths;
401: String entry;
402: for (Iterator i = keys.iterator(); i.hasNext();) {
403: key = (String) i.next();
404: paths = getConfiguration().getList(key, null);
405: if (paths != null) {
406: velocityEngine.clearProperty(key);
407: getConfiguration().clearProperty(key);
408:
409: for (Iterator j = paths.iterator(); j.hasNext();) {
410: path = (String) j.next();
411: if (path.startsWith(JAR_PREFIX + "file")) {
412: // A local jar resource URL path is a bit more
413: // complicated, but we can translate it as well.
414: ind = path.indexOf("!/");
415: if (ind >= 0) {
416: entry = path.substring(ind);
417: path = path.substring(9, ind);
418: } else {
419: entry = "!/";
420: path = path.substring(9);
421: }
422: path = JAR_PREFIX + "file:" + getRealPath(path)
423: + entry;
424: } else if (path.startsWith(ABSOLUTE_PREFIX)) {
425: path = path.substring(ABSOLUTE_PREFIX.length(),
426: path.length());
427: } else if (!path.startsWith(JAR_PREFIX)) {
428: // But we don't translate remote jar URLs.
429: path = getRealPath(path);
430: }
431: // Put the translated paths back to the configuration.
432: getConfiguration().addProperty(key, path);
433: }
434: }
435: }
436:
437: try {
438: velocityEngine.setExtendedProperties(ConfigurationConverter
439: .getExtendedProperties(getConfiguration()));
440:
441: velocityEngine.init();
442: } catch (Exception e) {
443: // This will be caught and rethrown by the init() method.
444: // Oh well, that will protect us from RuntimeException folk showing
445: // up somewhere above this try/catch
446: throw new InitializationException(
447: "Failed to set up VelocityEmailService", e); //EXCEPTION
448: }
449: }
450:
451: /**
452: * Find out if a given template exists. Velocity
453: * will do its own searching to determine whether
454: * a template exists or not.
455: *
456: * @param String template to search for
457: * @return boolean
458: */
459: public boolean templateExists(String template) {
460: return velocityEngine.templateExists(template);
461: }
462: }
|