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