001: /*
002: * Copyright (c) 2002-2003 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.webwork.views.velocity;
006:
007: import com.opensymphony.webwork.ServletActionContext;
008: import com.opensymphony.webwork.WebWorkConstants;
009: import com.opensymphony.webwork.WebWorkException;
010: import com.opensymphony.webwork.config.Configuration;
011: import com.opensymphony.webwork.util.VelocityWebWorkUtil;
012: import com.opensymphony.webwork.views.jsp.ui.OgnlTool;
013: import com.opensymphony.webwork.views.util.ContextUtil;
014: import com.opensymphony.webwork.views.velocity.components.*;
015: import com.opensymphony.xwork.ObjectFactory;
016: import com.opensymphony.xwork.util.OgnlValueStack;
017: import org.apache.commons.logging.Log;
018: import org.apache.commons.logging.LogFactory;
019: import org.apache.velocity.VelocityContext;
020: import org.apache.velocity.app.Velocity;
021: import org.apache.velocity.app.VelocityEngine;
022: import org.apache.velocity.context.Context;
023: import org.apache.velocity.tools.view.ToolboxManager;
024: import org.apache.velocity.tools.view.context.ChainedContext;
025: import org.apache.velocity.tools.view.servlet.ServletToolboxManager;
026:
027: import javax.servlet.ServletContext;
028: import javax.servlet.http.HttpServletRequest;
029: import javax.servlet.http.HttpServletResponse;
030: import java.io.File;
031: import java.io.FileInputStream;
032: import java.io.IOException;
033: import java.io.InputStream;
034: import java.util.*;
035:
036: /**
037: * Manages the environment for Velocity result types
038: *
039: * @author Matt Ho <matt@indigoegg.com>
040: */
041: public class VelocityManager {
042: private static final Log log = LogFactory
043: .getLog(VelocityManager.class);
044: private static VelocityManager instance;
045: public static final String WEBWORK = "webwork";
046:
047: /**
048: * the parent JSP tag
049: */
050: public static final String PARENT = "parent";
051:
052: /**
053: * the current JSP tag
054: */
055: public static final String TAG = "tag";
056:
057: private VelocityEngine velocityEngine;
058:
059: /**
060: * A reference to the toolbox manager.
061: */
062: protected ToolboxManager toolboxManager = null;
063: private String toolBoxLocation;
064:
065: /**
066: * Names of contexts that will be chained on every request
067: */
068: private String[] chainedContextNames;
069:
070: private Properties velocityProperties;
071:
072: protected VelocityManager() {
073: init();
074: }
075:
076: /**
077: * retrieve an instance to the current VelocityManager
078: */
079: public synchronized static VelocityManager getInstance() {
080: if (instance == null) {
081: String classname = VelocityManager.class.getName();
082:
083: if (Configuration
084: .isSet(WebWorkConstants.WEBWORK_VELOCITY_MANAGER_CLASSNAME)) {
085: classname = Configuration
086: .getString(
087: WebWorkConstants.WEBWORK_VELOCITY_MANAGER_CLASSNAME)
088: .trim();
089: }
090:
091: if (!classname.equals(VelocityManager.class.getName())) {
092: try {
093: log.info("Instantiating VelocityManager!, "
094: + classname);
095: // singleton instances shouldn't be built accessing request or session-specific context data
096: instance = (VelocityManager) ObjectFactory
097: .getObjectFactory().buildBean(classname,
098: null);
099: } catch (Exception e) {
100: log
101: .fatal(
102: "Fatal exception occurred while trying to instantiate a VelocityManager instance, "
103: + classname, e);
104: instance = new VelocityManager();
105: }
106: } else {
107: instance = new VelocityManager();
108: }
109: }
110:
111: return instance;
112: }
113:
114: /**
115: * @return a reference to the VelocityEngine used by <b>all</b> webwork velocity thingies with the exception of
116: * directly accessed *.vm pages
117: */
118: public VelocityEngine getVelocityEngine() {
119: return velocityEngine;
120: }
121:
122: /**
123: * This method is responsible for creating the standard VelocityContext used by all WW2 velocity views. The
124: * following context parameters are defined:
125: * <p/>
126: * <ul>
127: * <li><strong>req</strong> - the current HttpServletRequest</li>
128: * <li><strong>res</strong> - the current HttpServletResponse</li>
129: * <li><strong>stack</strong> - the current {@link OgnlValueStack}</li>
130: * <li><strong>ognl</strong> - an {@link OgnlTool}</li>
131: * <li><strong>webwork</strong> - an instance of {@link com.opensymphony.webwork.util.WebWorkUtil}</li>
132: * <li><strong>action</strong> - the current WebWork action</li>
133: * </ul>
134: *
135: * @return a new WebWorkVelocityContext
136: */
137: public Context createContext(OgnlValueStack stack,
138: HttpServletRequest req, HttpServletResponse res) {
139: VelocityContext[] chainedContexts = prepareChainedContexts(req,
140: res, stack.getContext());
141: WebWorkVelocityContext context = new WebWorkVelocityContext(
142: chainedContexts, stack);
143: Map standardMap = ContextUtil.getStandardContext(stack, req,
144: res);
145: for (Iterator iterator = standardMap.entrySet().iterator(); iterator
146: .hasNext();) {
147: Map.Entry entry = (Map.Entry) iterator.next();
148: context.put((String) entry.getKey(), entry.getValue());
149: }
150: context.put(WEBWORK, new VelocityWebWorkUtil(context, stack,
151: req, res));
152:
153: ServletContext ctx = null;
154: try {
155: ctx = ServletActionContext.getServletContext();
156: } catch (NullPointerException npe) {
157: // in case this was used outside the lifecycle of webwork servlet
158: log.debug("internal toolbox context ignored");
159: }
160:
161: if (toolboxManager != null && ctx != null) {
162: ChainedContext chained = new ChainedContext(context, req,
163: res, ctx);
164: chained.setToolbox(toolboxManager
165: .getToolboxContext(chained));
166: return chained;
167: } else {
168: return context;
169: }
170:
171: }
172:
173: /**
174: * constructs contexts for chaining on this request. This method does not
175: * perform any initialization of the contexts. All that must be done in the
176: * context itself.
177: *
178: * @param servletRequest
179: * @param servletResponse
180: * @param extraContext
181: * @return an VelocityContext[] of contexts to chain
182: */
183: protected VelocityContext[] prepareChainedContexts(
184: HttpServletRequest servletRequest,
185: HttpServletResponse servletResponse, Map extraContext) {
186: if (this .chainedContextNames == null) {
187: return null;
188: }
189: List contextList = new ArrayList();
190: for (int i = 0; i < chainedContextNames.length; i++) {
191: String className = chainedContextNames[i];
192: try {
193: VelocityContext velocityContext = (VelocityContext) ObjectFactory
194: .getObjectFactory().buildBean(className, null);
195: contextList.add(velocityContext);
196: } catch (Exception e) {
197: log
198: .warn("Warning. "
199: + e.getClass().getName()
200: + " caught while attempting to instantiate a chained VelocityContext, "
201: + className + " -- skipping");
202: }
203: }
204: if (contextList.size() > 0) {
205: VelocityContext[] extraContexts = new VelocityContext[contextList
206: .size()];
207: contextList.toArray(extraContexts);
208: return extraContexts;
209: } else {
210: return null;
211: }
212: }
213:
214: /**
215: * initializes the VelocityManager. this should be called during the initialization process, say by
216: * ServletDispatcher. this may be called multiple times safely although calls beyond the first won't do anything
217: *
218: * @param context the current servlet context
219: */
220: public synchronized void init(ServletContext context) {
221: if (velocityEngine == null) {
222: velocityEngine = newVelocityEngine(context);
223: }
224: this .initToolbox(context);
225: }
226:
227: /**
228: * load optional velocity properties using the following loading strategy
229: * <ul>
230: * <li>relative to the servlet context path</li>
231: * <li>relative to the WEB-INF directory</li>
232: * <li>on the classpath</li>
233: * </ul>
234: *
235: * @param context the current ServletContext. may <b>not</b> be null
236: * @return the optional properties if webwork.velocity.configfile was specified, an empty Properties file otherwise
237: */
238: public Properties loadConfiguration(ServletContext context) {
239: if (context == null) {
240: String gripe = "Error attempting to create a loadConfiguration from a null ServletContext!";
241: log.error(gripe);
242: throw new IllegalArgumentException(gripe);
243: }
244:
245: Properties properties = new Properties();
246:
247: // now apply our systemic defaults, then allow user to override
248: applyDefaultConfiguration(context, properties);
249:
250: String defaultUserDirective = properties
251: .getProperty("userdirective");
252:
253: /**
254: * if the user has specified an external velocity configuration file, we'll want to search for it in the
255: * following order
256: *
257: * 1. relative to the context path
258: * 2. relative to /WEB-INF
259: * 3. in the class path
260: */
261: String configfile;
262:
263: if (Configuration
264: .isSet(WebWorkConstants.WEBWORK_VELOCITY_CONFIGFILE)) {
265: configfile = Configuration
266: .getString(WebWorkConstants.WEBWORK_VELOCITY_CONFIGFILE);
267: } else {
268: configfile = "velocity.properties";
269: }
270:
271: configfile = configfile.trim();
272:
273: InputStream in = null;
274: String resourceLocation = null;
275:
276: try {
277: if (context.getRealPath(configfile) != null) {
278: // 1. relative to context path, i.e. /velocity.properties
279: String filename = context.getRealPath(configfile);
280:
281: if (filename != null) {
282: File file = new File(filename);
283:
284: if (file.isFile()) {
285: resourceLocation = file.getCanonicalPath()
286: + " from file system";
287: in = new FileInputStream(file);
288: }
289:
290: // 2. if nothing was found relative to the context path, search relative to the WEB-INF directory
291: if (in == null) {
292: file = new File(context.getRealPath("/WEB-INF/"
293: + configfile));
294:
295: if (file.isFile()) {
296: resourceLocation = file.getCanonicalPath()
297: + " from file system";
298: in = new FileInputStream(file);
299: }
300: }
301: }
302: }
303:
304: // 3. finally, if there's no physical file, how about something in our classpath
305: if (in == null) {
306: in = VelocityManager.class.getClassLoader()
307: .getResourceAsStream(configfile);
308: if (in != null) {
309: resourceLocation = configfile + " from classloader";
310: }
311: }
312:
313: // if we've got something, load 'er up
314: if (in != null) {
315: log.info("Initializing velocity using "
316: + resourceLocation);
317: properties.load(in);
318: }
319: } catch (IOException e) {
320: log.warn("Unable to load velocity configuration "
321: + resourceLocation, e);
322: } finally {
323: if (in != null) {
324: try {
325: in.close();
326: } catch (IOException e) {
327: }
328: }
329: }
330:
331: // overide with programmatically set properties
332: if (this .velocityProperties != null) {
333: Iterator keys = this .velocityProperties.keySet().iterator();
334: while (keys.hasNext()) {
335: String key = (String) keys.next();
336: properties.setProperty(key, this .velocityProperties
337: .getProperty(key));
338: }
339: }
340:
341: String userdirective = properties.getProperty("userdirective");
342:
343: if ((userdirective == null) || userdirective.trim().equals("")) {
344: userdirective = defaultUserDirective;
345: } else {
346: userdirective = userdirective.trim() + ","
347: + defaultUserDirective;
348: }
349:
350: properties.setProperty("userdirective", userdirective);
351:
352: // for debugging purposes, allows users to dump out the properties that have been configured
353: if (log.isDebugEnabled()) {
354: log
355: .debug("Initializing Velocity with the following properties ...");
356:
357: for (Iterator iter = properties.keySet().iterator(); iter
358: .hasNext();) {
359: String key = (String) iter.next();
360: String value = properties.getProperty(key);
361:
362: if (log.isDebugEnabled()) {
363: log.debug(" '" + key + "' = '" + value + "'");
364: }
365: }
366: }
367:
368: return properties;
369: }
370:
371: /**
372: * performs one-time initializations
373: */
374: protected void init() {
375:
376: // read in the names of contexts to add to each request
377: initChainedContexts();
378:
379: if (Configuration
380: .isSet(WebWorkConstants.WEBWORK_VELOCITY_TOOLBOXLOCATION)) {
381: toolBoxLocation = Configuration.get(
382: WebWorkConstants.WEBWORK_VELOCITY_TOOLBOXLOCATION)
383: .toString();
384: }
385:
386: }
387:
388: /**
389: * Initializes the ServletToolboxManager for this servlet's
390: * toolbox (if any).
391: */
392: protected void initToolbox(ServletContext context) {
393: /* if we have a toolbox, get a manager for it */
394: if (toolBoxLocation != null) {
395: toolboxManager = ServletToolboxManager.getInstance(context,
396: toolBoxLocation);
397: } else {
398: Velocity
399: .info("VelocityViewServlet: No toolbox entry in configuration.");
400: }
401: }
402:
403: /**
404: * allow users to specify via the webwork.properties file a set of additional VelocityContexts to chain to the
405: * the WebWorkVelocityContext. The intent is to allow these contexts to store helper objects that the ui
406: * developer may want access to. Examples of reasonable VelocityContexts would be an IoCVelocityContext, a
407: * SpringReferenceVelocityContext, and a ToolboxVelocityContext
408: */
409: protected void initChainedContexts() {
410:
411: if (Configuration
412: .isSet(WebWorkConstants.WEBWORK_VELOCITY_CONTEXTS)) {
413: // we expect contexts to be a comma separated list of classnames
414: String contexts = Configuration.get(
415: WebWorkConstants.WEBWORK_VELOCITY_CONTEXTS)
416: .toString();
417: StringTokenizer st = new StringTokenizer(contexts, ",");
418: List contextList = new ArrayList();
419:
420: while (st.hasMoreTokens()) {
421: String classname = st.nextToken();
422: contextList.add(classname);
423: }
424: if (contextList.size() > 0) {
425: String[] chainedContexts = new String[contextList
426: .size()];
427: contextList.toArray(chainedContexts);
428: this .chainedContextNames = chainedContexts;
429: }
430:
431: }
432:
433: }
434:
435: /**
436: * <p/>
437: * Instantiates a new VelocityEngine.
438: * </p>
439: * <p/>
440: * The following is the default Velocity configuration
441: * </p>
442: * <pre>
443: * resource.loader = file, class
444: * file.resource.loader.path = real path of webapp
445: * class.resource.loader.description = Velocity Classpath Resource Loader
446: * class.resource.loader.class = com.opensymphony.webwork.views.velocity.WebWorkResourceLoader
447: * </pre>
448: * <p/>
449: * this default configuration can be overridden by specifying a webwork.velocity.configfile property in the
450: * webwork.properties file. the specified config file will be searched for in the following order:
451: * </p>
452: * <ul>
453: * <li>relative to the servlet context path</li>
454: * <li>relative to the WEB-INF directory</li>
455: * <li>on the classpath</li>
456: * </ul>
457: *
458: * @param context the current ServletContext. may <b>not</b> be null
459: */
460: protected VelocityEngine newVelocityEngine(ServletContext context) {
461: if (context == null) {
462: String gripe = "Error attempting to create a new VelocityEngine from a null ServletContext!";
463: log.error(gripe);
464: throw new IllegalArgumentException(gripe);
465: }
466:
467: Properties p = loadConfiguration(context);
468:
469: VelocityEngine velocityEngine = new VelocityEngine();
470:
471: // Set the velocity attribute for the servlet context
472: // if this is not set the webapp loader WILL NOT WORK
473: velocityEngine.setApplicationAttribute(ServletContext.class
474: .getName(), context);
475:
476: try {
477: velocityEngine.init(p);
478: } catch (Exception e) {
479: String gripe = "Unable to instantiate VelocityEngine!";
480: log.error(gripe, e);
481: throw new WebWorkException(gripe);
482: }
483:
484: return velocityEngine;
485: }
486:
487: /**
488: * once we've loaded up the user defined configurations, we will want to apply WebWork specification configurations.
489: * <ul>
490: * <li>if Velocity.RESOURCE_LOADER has not been defined, then we will use the defaults which is a joined file,
491: * class loader for unpackaed wars and a straight class loader otherwise</li>
492: * <li>we need to define the various WebWork custom user directives such as #param, #tag, and #bodytag</li>
493: * </ul>
494: *
495: * @param context
496: * @param p
497: */
498: private void applyDefaultConfiguration(ServletContext context,
499: Properties p) {
500: // ensure that caching isn't overly aggressive
501:
502: /**
503: * Load a default resource loader definition if there isn't one present.
504: * Ben Hall (22/08/2003)
505: */
506: if (p.getProperty(Velocity.RESOURCE_LOADER) == null) {
507: p.setProperty(Velocity.RESOURCE_LOADER, "wwfile, wwclass");
508: }
509:
510: /**
511: * If there's a "real" path add it for the wwfile resource loader.
512: * If there's no real path and they haven't configured a loader then we change
513: * resource loader property to just use the wwclass loader
514: * Ben Hall (22/08/2003)
515: */
516: if (context.getRealPath("") != null) {
517: p.setProperty("wwfile.resource.loader.description",
518: "Velocity File Resource Loader");
519: p
520: .setProperty("wwfile.resource.loader.class",
521: "org.apache.velocity.runtime.resource.loader.FileResourceLoader");
522: p.setProperty("wwfile.resource.loader.path", context
523: .getRealPath(""));
524: p.setProperty(
525: "wwfile.resource.loader.modificationCheckInterval",
526: "2");
527: p.setProperty("wwfile.resource.loader.cache", "true");
528: } else {
529: // remove wwfile from resource loader property
530: String prop = p.getProperty(Velocity.RESOURCE_LOADER);
531: if (prop.indexOf("wwfile,") != -1) {
532: prop = replace(prop, "wwfile,", "");
533: } else if (prop.indexOf(", wwfile") != -1) {
534: prop = replace(prop, ", wwfile", "");
535: } else if (prop.indexOf("wwfile") != -1) {
536: prop = replace(prop, "wwfile", "");
537: }
538:
539: p.setProperty(Velocity.RESOURCE_LOADER, prop);
540: }
541:
542: /**
543: * Refactored the Velocity templates for the WebWork taglib into the classpath from the web path. This will
544: * enable WebWork projects to have access to the templates by simply including the WebWork jar file.
545: * Unfortunately, there does not appear to be a macro for the class loader keywords
546: * Matt Ho - Mon Mar 17 00:21:46 PST 2003
547: */
548: p.setProperty("wwclass.resource.loader.description",
549: "Velocity Classpath Resource Loader");
550: p
551: .setProperty("wwclass.resource.loader.class",
552: "com.opensymphony.webwork.views.velocity.WebWorkResourceLoader");
553: p.setProperty(
554: "wwclass.resource.loader.modificationCheckInterval",
555: "2");
556: p.setProperty("wwclass.resource.loader.cache", "true");
557:
558: // components
559: StringBuffer sb = new StringBuffer();
560:
561: addDirective(sb, ActionDirective.class);
562: addDirective(sb, BeanDirective.class);
563: addDirective(sb, CheckBoxDirective.class);
564: addDirective(sb, CheckBoxListDirective.class);
565: addDirective(sb, ComboBoxDirective.class);
566: addDirective(sb, ComponentDirective.class);
567: addDirective(sb, DateDirective.class);
568: addDirective(sb, DatePickerDirective.class);
569: addDirective(sb, DebugDirective.class);
570: addDirective(sb, DivDirective.class);
571: addDirective(sb, DoubleSelectDirective.class);
572: addDirective(sb, FileDirective.class);
573: addDirective(sb, FormDirective.class);
574: addDirective(sb, HeadDirective.class);
575: addDirective(sb, HiddenDirective.class);
576: addDirective(sb, AnchorDirective.class);
577: addDirective(sb, I18nDirective.class);
578: addDirective(sb, IncludeDirective.class);
579: addDirective(sb, LabelDirective.class);
580: addDirective(sb, PanelDirective.class);
581: addDirective(sb, ParamDirective.class);
582: addDirective(sb, PasswordDirective.class);
583: addDirective(sb, PushDirective.class);
584: addDirective(sb, PropertyDirective.class);
585: addDirective(sb, RadioDirective.class);
586: addDirective(sb, SelectDirective.class);
587: addDirective(sb, SetDirective.class);
588: addDirective(sb, SubmitDirective.class);
589: addDirective(sb, ResetDirective.class);
590: addDirective(sb, TabbedPanelDirective.class);
591: addDirective(sb, TextAreaDirective.class);
592: addDirective(sb, TextDirective.class);
593: addDirective(sb, TextFieldDirective.class);
594: addDirective(sb, TokenDirective.class);
595: addDirective(sb, TreeDirective.class);
596: addDirective(sb, TreeNodeDirective.class);
597: addDirective(sb, URLDirective.class);
598: addDirective(sb, WebTableDirective.class);
599: addDirective(sb, ActionErrorDirective.class);
600: addDirective(sb, ActionMessageDirective.class);
601: addDirective(sb, FieldErrorDirective.class);
602: addDirective(sb, OptionTransferSelectDirective.class);
603: addDirective(sb, UpDownSelectDirective.class);
604:
605: String directives = sb.toString();
606:
607: String userdirective = p.getProperty("userdirective");
608: if ((userdirective == null) || userdirective.trim().equals("")) {
609: userdirective = directives;
610: } else {
611: userdirective = userdirective.trim() + "," + directives;
612: }
613:
614: p.setProperty("userdirective", userdirective);
615: }
616:
617: private void addDirective(StringBuffer sb, Class clazz) {
618: sb.append(clazz.getName()).append(",");
619: }
620:
621: private static final String replace(String string,
622: String oldString, String newString) {
623: if (string == null) {
624: return null;
625: }
626: // If the newString is null, just return the string since there's nothing to replace.
627: if (newString == null) {
628: return string;
629: }
630: int i = 0;
631: // Make sure that oldString appears at least once before doing any processing.
632: if ((i = string.indexOf(oldString, i)) >= 0) {
633: // Use char []'s, as they are more efficient to deal with.
634: char[] string2 = string.toCharArray();
635: char[] newString2 = newString.toCharArray();
636: int oLength = oldString.length();
637: StringBuffer buf = new StringBuffer(string2.length);
638: buf.append(string2, 0, i).append(newString2);
639: i += oLength;
640: int j = i;
641: // Replace all remaining instances of oldString with newString.
642: while ((i = string.indexOf(oldString, i)) > 0) {
643: buf.append(string2, j, i - j).append(newString2);
644: i += oLength;
645: j = i;
646: }
647: buf.append(string2, j, string2.length - j);
648: return buf.toString();
649: }
650: return string;
651: }
652:
653: /**
654: * @return the velocityProperties
655: */
656: public Properties getVelocityProperties() {
657: return velocityProperties;
658: }
659:
660: /**
661: * @param velocityProperties the velocityProperties to set
662: */
663: public void setVelocityProperties(Properties velocityProperties) {
664: this.velocityProperties = velocityProperties;
665: }
666: }
|