1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.struts2.views.velocity;
19
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.util.ArrayList;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Properties;
29 import java.util.StringTokenizer;
30
31 import javax.servlet.ServletContext;
32 import javax.servlet.http.HttpServletRequest;
33 import javax.servlet.http.HttpServletResponse;
34
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37 import org.apache.struts2.ServletActionContext;
38 import org.apache.struts2.StrutsConstants;
39 import org.apache.struts2.StrutsException;
40 import org.apache.struts2.config.Settings;
41 import org.apache.struts2.util.VelocityStrutsUtil;
42 import org.apache.struts2.views.jsp.ui.OgnlTool;
43 import org.apache.struts2.views.util.ContextUtil;
44 import org.apache.struts2.views.velocity.components.ActionDirective;
45 import org.apache.struts2.views.velocity.components.ActionErrorDirective;
46 import org.apache.struts2.views.velocity.components.ActionMessageDirective;
47 import org.apache.struts2.views.velocity.components.AnchorDirective;
48 import org.apache.struts2.views.velocity.components.BeanDirective;
49 import org.apache.struts2.views.velocity.components.CheckBoxDirective;
50 import org.apache.struts2.views.velocity.components.CheckBoxListDirective;
51 import org.apache.struts2.views.velocity.components.ComboBoxDirective;
52 import org.apache.struts2.views.velocity.components.ComponentDirective;
53 import org.apache.struts2.views.velocity.components.DateDirective;
54 import org.apache.struts2.views.velocity.components.DatePickerDirective;
55 import org.apache.struts2.views.velocity.components.DivDirective;
56 import org.apache.struts2.views.velocity.components.DoubleSelectDirective;
57 import org.apache.struts2.views.velocity.components.FieldErrorDirective;
58 import org.apache.struts2.views.velocity.components.FileDirective;
59 import org.apache.struts2.views.velocity.components.FormDirective;
60 import org.apache.struts2.views.velocity.components.HeadDirective;
61 import org.apache.struts2.views.velocity.components.HiddenDirective;
62 import org.apache.struts2.views.velocity.components.I18nDirective;
63 import org.apache.struts2.views.velocity.components.IncludeDirective;
64 import org.apache.struts2.views.velocity.components.LabelDirective;
65 import org.apache.struts2.views.velocity.components.OptionTransferSelectDirective;
66 import org.apache.struts2.views.velocity.components.PanelDirective;
67 import org.apache.struts2.views.velocity.components.ParamDirective;
68 import org.apache.struts2.views.velocity.components.PasswordDirective;
69 import org.apache.struts2.views.velocity.components.PropertyDirective;
70 import org.apache.struts2.views.velocity.components.PushDirective;
71 import org.apache.struts2.views.velocity.components.RadioDirective;
72 import org.apache.struts2.views.velocity.components.ResetDirective;
73 import org.apache.struts2.views.velocity.components.SelectDirective;
74 import org.apache.struts2.views.velocity.components.SetDirective;
75 import org.apache.struts2.views.velocity.components.SubmitDirective;
76 import org.apache.struts2.views.velocity.components.TabbedPanelDirective;
77 import org.apache.struts2.views.velocity.components.TextAreaDirective;
78 import org.apache.struts2.views.velocity.components.TextDirective;
79 import org.apache.struts2.views.velocity.components.TextFieldDirective;
80 import org.apache.struts2.views.velocity.components.TokenDirective;
81 import org.apache.struts2.views.velocity.components.TreeDirective;
82 import org.apache.struts2.views.velocity.components.TreeNodeDirective;
83 import org.apache.struts2.views.velocity.components.URLDirective;
84 import org.apache.struts2.views.velocity.components.UpDownSelectDirective;
85 import org.apache.struts2.views.velocity.components.WebTableDirective;
86 import org.apache.velocity.VelocityContext;
87 import org.apache.velocity.app.Velocity;
88 import org.apache.velocity.app.VelocityEngine;
89 import org.apache.velocity.context.Context;
90 import org.apache.velocity.tools.view.ToolboxManager;
91 import org.apache.velocity.tools.view.context.ChainedContext;
92 import org.apache.velocity.tools.view.servlet.ServletToolboxManager;
93
94 import com.opensymphony.xwork2.ObjectFactory;
95 import com.opensymphony.xwork2.util.ValueStack;
96
97
98 /***
99 * Manages the environment for Velocity result types
100 *
101 */
102 public class VelocityManager {
103 private static final Log log = LogFactory.getLog(VelocityManager.class);
104 private static VelocityManager instance;
105 public static final String STRUTS = "struts";
106
107 /***
108 * the parent JSP tag
109 */
110 public static final String PARENT = "parent";
111
112 /***
113 * the current JSP tag
114 */
115 public static final String TAG = "tag";
116
117 private VelocityEngine velocityEngine;
118
119 /***
120 * A reference to the toolbox manager.
121 */
122 protected ToolboxManager toolboxManager = null;
123 private String toolBoxLocation;
124
125
126 /***
127 * Names of contexts that will be chained on every request
128 */
129 private String[] chainedContextNames;
130
131 private Properties velocityProperties;
132
133 protected VelocityManager() {
134 init();
135 }
136
137 /***
138 * retrieve an instance to the current VelocityManager
139 */
140 public synchronized static VelocityManager getInstance() {
141 if (instance == null) {
142 String classname = VelocityManager.class.getName();
143
144 if (Settings.isSet(StrutsConstants.STRUTS_VELOCITY_MANAGER_CLASSNAME)) {
145 classname = Settings.get(StrutsConstants.STRUTS_VELOCITY_MANAGER_CLASSNAME).trim();
146 }
147
148 if (!classname.equals(VelocityManager.class.getName())) {
149 try {
150 log.info("Instantiating VelocityManager!, " + classname);
151
152 instance = (VelocityManager) ObjectFactory.getObjectFactory().buildBean(classname, null);
153 } catch (Exception e) {
154 log.fatal("Fatal exception occurred while trying to instantiate a VelocityManager instance, " + classname, e);
155 instance = new VelocityManager();
156 }
157 } else {
158 instance = new VelocityManager();
159 }
160 }
161
162 return instance;
163 }
164
165 /***
166 * @return a reference to the VelocityEngine used by <b>all</b> struts velocity thingies with the exception of
167 * directly accessed *.vm pages
168 */
169 public VelocityEngine getVelocityEngine() {
170 return velocityEngine;
171 }
172
173 /***
174 * This method is responsible for creating the standard VelocityContext used by all WW2 velocity views. The
175 * following context parameters are defined:
176 * <p/>
177 * <ul>
178 * <li><strong>request</strong> - the current HttpServletRequest</li>
179 * <li><strong>response</strong> - the current HttpServletResponse</li>
180 * <li><strong>stack</strong> - the current {@link ValueStack}</li>
181 * <li><strong>ognl</strong> - an {@link OgnlTool}</li>
182 * <li><strong>struts</strong> - an instance of {@link org.apache.struts2.util.StrutsUtil}</li>
183 * <li><strong>action</strong> - the current Struts action</li>
184 * </ul>
185 *
186 * @return a new StrutsVelocityContext
187 */
188 public Context createContext(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
189 VelocityContext[] chainedContexts = prepareChainedContexts(req, res, stack.getContext());
190 StrutsVelocityContext context = new StrutsVelocityContext(chainedContexts, stack);
191 Map standardMap = ContextUtil.getStandardContext(stack, req, res);
192 for (Iterator iterator = standardMap.entrySet().iterator(); iterator.hasNext();) {
193 Map.Entry entry = (Map.Entry) iterator.next();
194 context.put((String) entry.getKey(), entry.getValue());
195 }
196 context.put(STRUTS, new VelocityStrutsUtil(context, stack, req, res));
197
198
199 ServletContext ctx = null;
200 try {
201 ctx = ServletActionContext.getServletContext();
202 } catch (NullPointerException npe) {
203
204 log.debug("internal toolbox context ignored");
205 }
206
207 if (toolboxManager != null && ctx != null) {
208 ChainedContext chained = new ChainedContext(context, req, res, ctx);
209 chained.setToolbox(toolboxManager.getToolboxContext(chained));
210 return chained;
211 } else {
212 return context;
213 }
214
215 }
216
217 /***
218 * constructs contexts for chaining on this request. This method does not
219 * perform any initialization of the contexts. All that must be done in the
220 * context itself.
221 *
222 * @param servletRequest
223 * @param servletResponse
224 * @param extraContext
225 * @return an VelocityContext[] of contexts to chain
226 */
227 protected VelocityContext[] prepareChainedContexts(HttpServletRequest servletRequest, HttpServletResponse servletResponse, Map extraContext) {
228 if (this.chainedContextNames == null) {
229 return null;
230 }
231 List contextList = new ArrayList();
232 for (int i = 0; i < chainedContextNames.length; i++) {
233 String className = chainedContextNames[i];
234 try {
235 VelocityContext velocityContext = (VelocityContext) ObjectFactory.getObjectFactory().buildBean(className, null);
236 contextList.add(velocityContext);
237 } catch (Exception e) {
238 log.warn("Warning. " + e.getClass().getName() + " caught while attempting to instantiate a chained VelocityContext, " + className + " -- skipping");
239 }
240 }
241 if (contextList.size() > 0) {
242 VelocityContext[] extraContexts = new VelocityContext[contextList.size()];
243 contextList.toArray(extraContexts);
244 return extraContexts;
245 } else {
246 return null;
247 }
248 }
249
250 /***
251 * initializes the VelocityManager. this should be called during the initialization process, say by
252 * ServletDispatcher. this may be called multiple times safely although calls beyond the first won't do anything
253 *
254 * @param context the current servlet context
255 */
256 public synchronized void init(ServletContext context) {
257 if (velocityEngine == null) {
258 velocityEngine = newVelocityEngine(context);
259 }
260 this.initToolbox(context);
261 }
262
263 /***
264 * load optional velocity properties using the following loading strategy
265 * <ul>
266 * <li>relative to the servlet context path</li>
267 * <li>relative to the WEB-INF directory</li>
268 * <li>on the classpath</li>
269 * </ul>
270 *
271 * @param context the current ServletContext. may <b>not</b> be null
272 * @return the optional properties if struts.velocity.configfile was specified, an empty Properties file otherwise
273 */
274 public Properties loadConfiguration(ServletContext context) {
275 if (context == null) {
276 String gripe = "Error attempting to create a loadConfiguration from a null ServletContext!";
277 log.error(gripe);
278 throw new IllegalArgumentException(gripe);
279 }
280
281 Properties properties = new Properties();
282
283
284 applyDefaultConfiguration(context, properties);
285
286
287 String defaultUserDirective = properties.getProperty("userdirective");
288
289 /***
290 * if the user has specified an external velocity configuration file, we'll want to search for it in the
291 * following order
292 *
293 * 1. relative to the context path
294 * 2. relative to /WEB-INF
295 * 3. in the class path
296 */
297 String configfile;
298
299 if (Settings.isSet(StrutsConstants.STRUTS_VELOCITY_CONFIGFILE)) {
300 configfile = Settings.get(StrutsConstants.STRUTS_VELOCITY_CONFIGFILE);
301 } else {
302 configfile = "velocity.properties";
303 }
304
305 configfile = configfile.trim();
306
307 InputStream in = null;
308 String resourceLocation = null;
309
310 try {
311 if (context.getRealPath(configfile) != null) {
312
313 String filename = context.getRealPath(configfile);
314
315 if (filename != null) {
316 File file = new File(filename);
317
318 if (file.isFile()) {
319 resourceLocation = file.getCanonicalPath() + " from file system";
320 in = new FileInputStream(file);
321 }
322
323
324 if (in == null) {
325 file = new File(context.getRealPath("/WEB-INF/" + configfile));
326
327 if (file.isFile()) {
328 resourceLocation = file.getCanonicalPath() + " from file system";
329 in = new FileInputStream(file);
330 }
331 }
332 }
333 }
334
335
336 if (in == null) {
337 in = VelocityManager.class.getClassLoader().getResourceAsStream(configfile);
338 if (in != null) {
339 resourceLocation = configfile + " from classloader";
340 }
341 }
342
343
344 if (in != null) {
345 log.info("Initializing velocity using " + resourceLocation);
346 properties.load(in);
347 }
348 } catch (IOException e) {
349 log.warn("Unable to load velocity configuration " + resourceLocation, e);
350 } finally {
351 if (in != null) {
352 try {
353 in.close();
354 } catch (IOException e) {
355 }
356 }
357 }
358
359
360 if (this.velocityProperties != null) {
361 Iterator keys = this.velocityProperties.keySet().iterator();
362 while (keys.hasNext()) {
363 String key = (String) keys.next();
364 properties.setProperty(key, this.velocityProperties.getProperty(key));
365 }
366 }
367
368 String userdirective = properties.getProperty("userdirective");
369
370 if ((userdirective == null) || userdirective.trim().equals("")) {
371 userdirective = defaultUserDirective;
372 } else {
373 userdirective = userdirective.trim() + "," + defaultUserDirective;
374 }
375
376 properties.setProperty("userdirective", userdirective);
377
378
379
380 if (log.isDebugEnabled()) {
381 log.debug("Initializing Velocity with the following properties ...");
382
383 for (Iterator iter = properties.keySet().iterator();
384 iter.hasNext();) {
385 String key = (String) iter.next();
386 String value = properties.getProperty(key);
387
388 if (log.isDebugEnabled()) {
389 log.debug(" '" + key + "' = '" + value + "'");
390 }
391 }
392 }
393
394 return properties;
395 }
396
397 /***
398 * performs one-time initializations
399 */
400 protected void init() {
401
402
403 initChainedContexts();
404
405
406 if (Settings.isSet(StrutsConstants.STRUTS_VELOCITY_TOOLBOXLOCATION)) {
407 toolBoxLocation = Settings.get(StrutsConstants.STRUTS_VELOCITY_TOOLBOXLOCATION).toString();
408 }
409
410 }
411
412
413 /***
414 * Initializes the ServletToolboxManager for this servlet's
415 * toolbox (if any).
416 */
417 protected void initToolbox(ServletContext context) {
418
419 if (toolBoxLocation != null) {
420 toolboxManager = ServletToolboxManager.getInstance(context, toolBoxLocation);
421 } else {
422 Velocity.info("VelocityViewServlet: No toolbox entry in configuration.");
423 }
424 }
425
426
427 /***
428 * allow users to specify via the struts.properties file a set of additional VelocityContexts to chain to the
429 * the StrutsVelocityContext. The intent is to allow these contexts to store helper objects that the ui
430 * developer may want access to. Examples of reasonable VelocityContexts would be an IoCVelocityContext, a
431 * SpringReferenceVelocityContext, and a ToolboxVelocityContext
432 */
433 protected void initChainedContexts() {
434
435 if (Settings.isSet(StrutsConstants.STRUTS_VELOCITY_CONTEXTS)) {
436
437 String contexts = Settings.get(StrutsConstants.STRUTS_VELOCITY_CONTEXTS).toString();
438 StringTokenizer st = new StringTokenizer(contexts, ",");
439 List contextList = new ArrayList();
440
441 while (st.hasMoreTokens()) {
442 String classname = st.nextToken();
443 contextList.add(classname);
444 }
445 if (contextList.size() > 0) {
446 String[] chainedContexts = new String[contextList.size()];
447 contextList.toArray(chainedContexts);
448 this.chainedContextNames = chainedContexts;
449 }
450
451
452 }
453
454 }
455
456 /***
457 * <p/>
458 * Instantiates a new VelocityEngine.
459 * </p>
460 * <p/>
461 * The following is the default Velocity configuration
462 * </p>
463 * <pre>
464 * resource.loader = file, class
465 * file.resource.loader.path = real path of webapp
466 * class.resource.loader.description = Velocity Classpath Resource Loader
467 * class.resource.loader.class = org.apache.struts2.views.velocity.StrutsResourceLoader
468 * </pre>
469 * <p/>
470 * this default configuration can be overridden by specifying a struts.velocity.configfile property in the
471 * struts.properties file. the specified config file will be searched for in the following order:
472 * </p>
473 * <ul>
474 * <li>relative to the servlet context path</li>
475 * <li>relative to the WEB-INF directory</li>
476 * <li>on the classpath</li>
477 * </ul>
478 *
479 * @param context the current ServletContext. may <b>not</b> be null
480 */
481 protected VelocityEngine newVelocityEngine(ServletContext context) {
482 if (context == null) {
483 String gripe = "Error attempting to create a new VelocityEngine from a null ServletContext!";
484 log.error(gripe);
485 throw new IllegalArgumentException(gripe);
486 }
487
488 Properties p = loadConfiguration(context);
489
490 VelocityEngine velocityEngine = new VelocityEngine();
491
492
493
494 velocityEngine.setApplicationAttribute(ServletContext.class.getName(),
495 context);
496
497 try {
498 velocityEngine.init(p);
499 } catch (Exception e) {
500 String gripe = "Unable to instantiate VelocityEngine!";
501 throw new StrutsException(gripe, e);
502 }
503
504 return velocityEngine;
505 }
506
507 /***
508 * once we've loaded up the user defined configurations, we will want to apply Struts specification configurations.
509 * <ul>
510 * <li>if Velocity.RESOURCE_LOADER has not been defined, then we will use the defaults which is a joined file,
511 * class loader for unpackaed wars and a straight class loader otherwise</li>
512 * <li>we need to define the various Struts custom user directives such as #param, #tag, and #bodytag</li>
513 * </ul>
514 *
515 * @param context
516 * @param p
517 */
518 private void applyDefaultConfiguration(ServletContext context, Properties p) {
519
520
521 /***
522 * Load a default resource loader definition if there isn't one present.
523 * Ben Hall (22/08/2003)
524 */
525 if (p.getProperty(Velocity.RESOURCE_LOADER) == null) {
526 p.setProperty(Velocity.RESOURCE_LOADER, "strutsfile, strutsclass");
527 }
528
529 /***
530 * If there's a "real" path add it for the strutsfile resource loader.
531 * If there's no real path and they haven't configured a loader then we change
532 * resource loader property to just use the strutsclass loader
533 * Ben Hall (22/08/2003)
534 */
535 if (context.getRealPath("") != null) {
536 p.setProperty("strutsfile.resource.loader.description", "Velocity File Resource Loader");
537 p.setProperty("strutsfile.resource.loader.class", "org.apache.velocity.runtime.resource.loader.FileResourceLoader");
538 p.setProperty("strutsfile.resource.loader.path", context.getRealPath(""));
539 p.setProperty("strutsfile.resource.loader.modificationCheckInterval", "2");
540 p.setProperty("strutsfile.resource.loader.cache", "true");
541 } else {
542
543 String prop = p.getProperty(Velocity.RESOURCE_LOADER);
544 if (prop.indexOf("strutsfile,") != -1) {
545 prop = replace(prop, "strutsfile,", "");
546 } else if (prop.indexOf(", strutsfile") != -1) {
547 prop = replace(prop, ", strutsfile", "");
548 } else if (prop.indexOf("strutsfile") != -1) {
549 prop = replace(prop, "strutsfile", "");
550 }
551
552 p.setProperty(Velocity.RESOURCE_LOADER, prop);
553 }
554
555 /***
556 * Refactored the Velocity templates for the Struts taglib into the classpath from the web path. This will
557 * enable Struts projects to have access to the templates by simply including the Struts jar file.
558 * Unfortunately, there does not appear to be a macro for the class loader keywords
559 * Matt Ho - Mon Mar 17 00:21:46 PST 2003
560 */
561 p.setProperty("strutsclass.resource.loader.description", "Velocity Classpath Resource Loader");
562 p.setProperty("strutsclass.resource.loader.class", "org.apache.struts2.views.velocity.StrutsResourceLoader");
563 p.setProperty("strutsclass.resource.loader.modificationCheckInterval", "2");
564 p.setProperty("strutsclass.resource.loader.cache", "true");
565
566
567 StringBuffer sb = new StringBuffer();
568
569 addDirective(sb, ActionDirective.class);
570 addDirective(sb, BeanDirective.class);
571 addDirective(sb, CheckBoxDirective.class);
572 addDirective(sb, CheckBoxListDirective.class);
573 addDirective(sb, ComboBoxDirective.class);
574 addDirective(sb, ComponentDirective.class);
575 addDirective(sb, DateDirective.class);
576 addDirective(sb, DatePickerDirective.class);
577 addDirective(sb, DivDirective.class);
578 addDirective(sb, DoubleSelectDirective.class);
579 addDirective(sb, FileDirective.class);
580 addDirective(sb, FormDirective.class);
581 addDirective(sb, HeadDirective.class);
582 addDirective(sb, HiddenDirective.class);
583 addDirective(sb, AnchorDirective.class);
584 addDirective(sb, I18nDirective.class);
585 addDirective(sb, IncludeDirective.class);
586 addDirective(sb, LabelDirective.class);
587 addDirective(sb, PanelDirective.class);
588 addDirective(sb, ParamDirective.class);
589 addDirective(sb, PasswordDirective.class);
590 addDirective(sb, PushDirective.class);
591 addDirective(sb, PropertyDirective.class);
592 addDirective(sb, RadioDirective.class);
593 addDirective(sb, SelectDirective.class);
594 addDirective(sb, SetDirective.class);
595 addDirective(sb, SubmitDirective.class);
596 addDirective(sb, ResetDirective.class);
597 addDirective(sb, TabbedPanelDirective.class);
598 addDirective(sb, TextAreaDirective.class);
599 addDirective(sb, TextDirective.class);
600 addDirective(sb, TextFieldDirective.class);
601 addDirective(sb, TokenDirective.class);
602 addDirective(sb, TreeDirective.class);
603 addDirective(sb, TreeNodeDirective.class);
604 addDirective(sb, URLDirective.class);
605 addDirective(sb, WebTableDirective.class);
606 addDirective(sb, ActionErrorDirective.class);
607 addDirective(sb, ActionMessageDirective.class);
608 addDirective(sb, FieldErrorDirective.class);
609 addDirective(sb, OptionTransferSelectDirective.class);
610 addDirective(sb, UpDownSelectDirective.class);
611
612 String directives = sb.toString();
613
614 String userdirective = p.getProperty("userdirective");
615 if ((userdirective == null) || userdirective.trim().equals("")) {
616 userdirective = directives;
617 } else {
618 userdirective = userdirective.trim() + "," + directives;
619 }
620
621 p.setProperty("userdirective", userdirective);
622 }
623
624 private void addDirective(StringBuffer sb, Class clazz) {
625 sb.append(clazz.getName()).append(",");
626 }
627
628 private static final String replace(String string, String oldString, String newString) {
629 if (string == null) {
630 return null;
631 }
632
633 if (newString == null) {
634 return string;
635 }
636 int i = 0;
637
638 if ((i = string.indexOf(oldString, i)) >= 0) {
639
640 char[] string2 = string.toCharArray();
641 char[] newString2 = newString.toCharArray();
642 int oLength = oldString.length();
643 StringBuffer buf = new StringBuffer(string2.length);
644 buf.append(string2, 0, i).append(newString2);
645 i += oLength;
646 int j = i;
647
648 while ((i = string.indexOf(oldString, i)) > 0) {
649 buf.append(string2, j, i - j).append(newString2);
650 i += oLength;
651 j = i;
652 }
653 buf.append(string2, j, string2.length - j);
654 return buf.toString();
655 }
656 return string;
657 }
658
659 /***
660 * @return the velocityProperties
661 */
662 public Properties getVelocityProperties() {
663 return velocityProperties;
664 }
665
666 /***
667 * @param velocityProperties the velocityProperties to set
668 */
669 public void setVelocityProperties(Properties velocityProperties) {
670 this.velocityProperties = velocityProperties;
671 }
672 }