001: /*
002: * $Id: VelocityManager.java 491656 2007-01-01 22:10:12Z mrdon $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts2.views.velocity;
022:
023: import java.io.File;
024: import java.io.FileInputStream;
025: import java.io.IOException;
026: import java.io.InputStream;
027: import java.util.ArrayList;
028: import java.util.Collections;
029: import java.util.HashMap;
030: import java.util.Iterator;
031: import java.util.List;
032: import java.util.Map;
033: import java.util.Properties;
034: import java.util.Set;
035: import java.util.StringTokenizer;
036:
037: import javax.servlet.ServletContext;
038: import javax.servlet.http.HttpServletRequest;
039: import javax.servlet.http.HttpServletResponse;
040:
041: import org.apache.commons.logging.Log;
042: import org.apache.commons.logging.LogFactory;
043: import org.apache.struts2.ServletActionContext;
044: import org.apache.struts2.StrutsConstants;
045: import org.apache.struts2.StrutsException;
046: import org.apache.struts2.util.VelocityStrutsUtil;
047: import org.apache.struts2.views.TagLibrary;
048: import org.apache.struts2.views.jsp.ui.OgnlTool;
049: import org.apache.struts2.views.util.ContextUtil;
050: import org.apache.velocity.VelocityContext;
051: import org.apache.velocity.app.Velocity;
052: import org.apache.velocity.app.VelocityEngine;
053: import org.apache.velocity.context.Context;
054: import org.apache.velocity.tools.view.ToolboxManager;
055: import org.apache.velocity.tools.view.context.ChainedContext;
056: import org.apache.velocity.tools.view.servlet.ServletToolboxManager;
057:
058: import com.opensymphony.xwork2.ObjectFactory;
059: import com.opensymphony.xwork2.inject.Container;
060: import com.opensymphony.xwork2.inject.Inject;
061: import com.opensymphony.xwork2.util.ValueStack;
062:
063: /**
064: * Manages the environment for Velocity result types
065: *
066: */
067: public class VelocityManager {
068: private static final Log log = LogFactory
069: .getLog(VelocityManager.class);
070: public static final String STRUTS = "struts";
071: private ObjectFactory objectFactory;
072:
073: /**
074: * the parent JSP tag
075: */
076: public static final String PARENT = "parent";
077:
078: /**
079: * the current JSP tag
080: */
081: public static final String TAG = "tag";
082:
083: private VelocityEngine velocityEngine;
084:
085: /**
086: * A reference to the toolbox manager.
087: */
088: protected ToolboxManager toolboxManager = null;
089: private String toolBoxLocation;
090:
091: /**
092: * Names of contexts that will be chained on every request
093: */
094: private String[] chainedContextNames;
095:
096: private Properties velocityProperties;
097:
098: private String customConfigFile;
099:
100: private List<TagLibrary> tagLibraries;
101:
102: public VelocityManager() {
103: }
104:
105: @Inject
106: public void setObjectFactory(ObjectFactory fac) {
107: this .objectFactory = fac;
108: }
109:
110: @Inject
111: public void setContainer(Container container) {
112: List<TagLibrary> list = new ArrayList<TagLibrary>();
113: Set<String> prefixes = container
114: .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: /*public synchronized static VelocityManager getInstance() {
125: if (instance == null) {
126: String classname = VelocityManager.class.getName();
127:
128: if (Settings.isSet(StrutsConstants.STRUTS_VELOCITY_MANAGER_CLASSNAME)) {
129: classname = Settings.get(StrutsConstants.STRUTS_VELOCITY_MANAGER_CLASSNAME).trim();
130: }
131:
132: if (!classname.equals(VelocityManager.class.getName())) {
133: try {
134: log.info("Instantiating VelocityManager!, " + classname);
135: // singleton instances shouldn't be built accessing request or session-specific context data
136: instance = (VelocityManager) ObjectFactory.getObjectFactory().buildBean(classname, null);
137: } catch (Exception e) {
138: log.fatal("Fatal exception occurred while trying to instantiate a VelocityManager instance, " + classname, e);
139: instance = new VelocityManager();
140: }
141: } else {
142: instance = new VelocityManager();
143: }
144: }
145:
146: return instance;
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,
174: HttpServletRequest req, HttpServletResponse res) {
175: VelocityContext[] chainedContexts = prepareChainedContexts(req,
176: res, stack.getContext());
177: StrutsVelocityContext context = new StrutsVelocityContext(
178: chainedContexts, stack);
179: Map standardMap = ContextUtil.getStandardContext(stack, req,
180: res);
181: for (Iterator iterator = standardMap.entrySet().iterator(); iterator
182: .hasNext();) {
183: Map.Entry entry = (Map.Entry) iterator.next();
184: context.put((String) entry.getKey(), entry.getValue());
185: }
186: context.put(STRUTS, new VelocityStrutsUtil(velocityEngine,
187: context, stack, req, res));
188:
189: ServletContext ctx = null;
190: try {
191: ctx = ServletActionContext.getServletContext();
192: } catch (NullPointerException npe) {
193: // in case this was used outside the lifecycle of struts servlet
194: log.debug("internal toolbox context ignored");
195: }
196:
197: if (toolboxManager != null && ctx != null) {
198: ChainedContext chained = new ChainedContext(context, req,
199: res, ctx);
200: chained.setToolbox(toolboxManager
201: .getToolboxContext(chained));
202: return chained;
203: } else {
204: return context;
205: }
206:
207: }
208:
209: /**
210: * constructs contexts for chaining on this request. This method does not
211: * perform any initialization of the contexts. All that must be done in the
212: * context itself.
213: *
214: * @param servletRequest
215: * @param servletResponse
216: * @param extraContext
217: * @return an VelocityContext[] of contexts to chain
218: */
219: protected VelocityContext[] prepareChainedContexts(
220: HttpServletRequest servletRequest,
221: HttpServletResponse servletResponse, Map extraContext) {
222: if (this .chainedContextNames == null) {
223: return null;
224: }
225: List contextList = new ArrayList();
226: for (int i = 0; i < chainedContextNames.length; i++) {
227: String className = chainedContextNames[i];
228: try {
229: VelocityContext velocityContext = (VelocityContext) objectFactory
230: .buildBean(className, null);
231: contextList.add(velocityContext);
232: } catch (Exception e) {
233: log
234: .warn("Warning. "
235: + e.getClass().getName()
236: + " caught while attempting to instantiate a chained VelocityContext, "
237: + className + " -- skipping");
238: }
239: }
240: if (contextList.size() > 0) {
241: VelocityContext[] extraContexts = new VelocityContext[contextList
242: .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: // now apply our systemic defaults, then allow user to override
284: applyDefaultConfiguration(context, properties);
285:
286: String defaultUserDirective = properties
287: .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 (customConfigFile != null) {
300: configfile = customConfigFile;
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: // 1. relative to context path, i.e. /velocity.properties
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()
320: + " from file system";
321: in = new FileInputStream(file);
322: }
323:
324: // 2. if nothing was found relative to the context path, search relative to the WEB-INF directory
325: if (in == null) {
326: file = new File(context.getRealPath("/WEB-INF/"
327: + configfile));
328:
329: if (file.isFile()) {
330: resourceLocation = file.getCanonicalPath()
331: + " from file system";
332: in = new FileInputStream(file);
333: }
334: }
335: }
336: }
337:
338: // 3. finally, if there's no physical file, how about something in our classpath
339: if (in == null) {
340: in = VelocityManager.class.getClassLoader()
341: .getResourceAsStream(configfile);
342: if (in != null) {
343: resourceLocation = configfile + " from classloader";
344: }
345: }
346:
347: // if we've got something, load 'er up
348: if (in != null) {
349: log.info("Initializing velocity using "
350: + resourceLocation);
351: properties.load(in);
352: }
353: } catch (IOException e) {
354: log.warn("Unable to load velocity configuration "
355: + resourceLocation, e);
356: } finally {
357: if (in != null) {
358: try {
359: in.close();
360: } catch (IOException e) {
361: }
362: }
363: }
364:
365: // overide with programmatically set properties
366: if (this .velocityProperties != null) {
367: Iterator keys = this .velocityProperties.keySet().iterator();
368: while (keys.hasNext()) {
369: String key = (String) keys.next();
370: properties.setProperty(key, this .velocityProperties
371: .getProperty(key));
372: }
373: }
374:
375: String userdirective = properties.getProperty("userdirective");
376:
377: if ((userdirective == null) || userdirective.trim().equals("")) {
378: userdirective = defaultUserDirective;
379: } else {
380: userdirective = userdirective.trim() + ","
381: + defaultUserDirective;
382: }
383:
384: properties.setProperty("userdirective", userdirective);
385:
386: // for debugging purposes, allows users to dump out the properties that have been configured
387: if (log.isDebugEnabled()) {
388: log
389: .debug("Initializing Velocity with the following properties ...");
390:
391: for (Iterator iter = properties.keySet().iterator(); iter
392: .hasNext();) {
393: String key = (String) iter.next();
394: String value = properties.getProperty(key);
395:
396: if (log.isDebugEnabled()) {
397: log.debug(" '" + key + "' = '" + value + "'");
398: }
399: }
400: }
401:
402: return properties;
403: }
404:
405: @Inject(StrutsConstants.STRUTS_VELOCITY_CONFIGFILE)
406: public void setCustomConfigFile(String val) {
407: this .customConfigFile = val;
408: }
409:
410: @Inject(StrutsConstants.STRUTS_VELOCITY_TOOLBOXLOCATION)
411: public void setToolBoxLocation(String toolboxLocation) {
412: this .toolBoxLocation = toolboxLocation;
413: }
414:
415: /**
416: * allow users to specify via the struts.properties file a set of additional VelocityContexts to chain to the
417: * the StrutsVelocityContext. The intent is to allow these contexts to store helper objects that the ui
418: * developer may want access to. Examples of reasonable VelocityContexts would be an IoCVelocityContext, a
419: * SpringReferenceVelocityContext, and a ToolboxVelocityContext
420: */
421: @Inject(StrutsConstants.STRUTS_VELOCITY_CONTEXTS)
422: public void setChainedContexts(String contexts) {
423: // we expect contexts to be a comma separated list of classnames
424: StringTokenizer st = new StringTokenizer(contexts, ",");
425: List contextList = new ArrayList();
426:
427: while (st.hasMoreTokens()) {
428: String classname = st.nextToken();
429: contextList.add(classname);
430: }
431: if (contextList.size() > 0) {
432: String[] chainedContexts = new String[contextList.size()];
433: contextList.toArray(chainedContexts);
434: this .chainedContextNames = chainedContexts;
435: }
436: }
437:
438: /**
439: * Initializes the ServletToolboxManager for this servlet's
440: * toolbox (if any).
441: */
442: protected void initToolbox(ServletContext context) {
443: /* if we have a toolbox, get a manager for it */
444: if (toolBoxLocation != null) {
445: toolboxManager = ServletToolboxManager.getInstance(context,
446: toolBoxLocation);
447: } else {
448: Velocity
449: .info("VelocityViewServlet: No toolbox entry in configuration.");
450: }
451: }
452:
453: /**
454: * <p/>
455: * Instantiates a new VelocityEngine.
456: * </p>
457: * <p/>
458: * The following is the default Velocity configuration
459: * </p>
460: * <pre>
461: * resource.loader = file, class
462: * file.resource.loader.path = real path of webapp
463: * class.resource.loader.description = Velocity Classpath Resource Loader
464: * class.resource.loader.class = org.apache.struts2.views.velocity.StrutsResourceLoader
465: * </pre>
466: * <p/>
467: * this default configuration can be overridden by specifying a struts.velocity.configfile property in the
468: * struts.properties file. the specified config file will be searched for in the following order:
469: * </p>
470: * <ul>
471: * <li>relative to the servlet context path</li>
472: * <li>relative to the WEB-INF directory</li>
473: * <li>on the classpath</li>
474: * </ul>
475: *
476: * @param context the current ServletContext. may <b>not</b> be null
477: */
478: protected VelocityEngine newVelocityEngine(ServletContext context) {
479: if (context == null) {
480: String gripe = "Error attempting to create a new VelocityEngine from a null ServletContext!";
481: log.error(gripe);
482: throw new IllegalArgumentException(gripe);
483: }
484:
485: Properties p = loadConfiguration(context);
486:
487: VelocityEngine velocityEngine = new VelocityEngine();
488:
489: // Set the velocity attribute for the servlet context
490: // if this is not set the webapp loader WILL NOT WORK
491: velocityEngine.setApplicationAttribute(ServletContext.class
492: .getName(), context);
493:
494: try {
495: velocityEngine.init(p);
496: } catch (Exception e) {
497: String gripe = "Unable to instantiate VelocityEngine!";
498: throw new StrutsException(gripe, e);
499: }
500:
501: return velocityEngine;
502: }
503:
504: /**
505: * once we've loaded up the user defined configurations, we will want to apply Struts specification configurations.
506: * <ul>
507: * <li>if Velocity.RESOURCE_LOADER has not been defined, then we will use the defaults which is a joined file,
508: * class loader for unpackaed wars and a straight class loader otherwise</li>
509: * <li>we need to define the various Struts custom user directives such as #param, #tag, and #bodytag</li>
510: * </ul>
511: *
512: * @param context
513: * @param p
514: */
515: private void applyDefaultConfiguration(ServletContext context,
516: Properties p) {
517: // ensure that caching isn't overly aggressive
518:
519: /**
520: * Load a default resource loader definition if there isn't one present.
521: * Ben Hall (22/08/2003)
522: */
523: if (p.getProperty(Velocity.RESOURCE_LOADER) == null) {
524: p.setProperty(Velocity.RESOURCE_LOADER,
525: "strutsfile, strutsclass");
526: }
527:
528: /**
529: * If there's a "real" path add it for the strutsfile resource loader.
530: * If there's no real path and they haven't configured a loader then we change
531: * resource loader property to just use the strutsclass loader
532: * Ben Hall (22/08/2003)
533: */
534: if (context.getRealPath("") != null) {
535: p.setProperty("strutsfile.resource.loader.description",
536: "Velocity File Resource Loader");
537: p
538: .setProperty("strutsfile.resource.loader.class",
539: "org.apache.velocity.runtime.resource.loader.FileResourceLoader");
540: p.setProperty("strutsfile.resource.loader.path", context
541: .getRealPath(""));
542: p
543: .setProperty(
544: "strutsfile.resource.loader.modificationCheckInterval",
545: "2");
546: p.setProperty("strutsfile.resource.loader.cache", "true");
547: } else {
548: // remove strutsfile from resource loader property
549: String prop = p.getProperty(Velocity.RESOURCE_LOADER);
550: if (prop.indexOf("strutsfile,") != -1) {
551: prop = replace(prop, "strutsfile,", "");
552: } else if (prop.indexOf(", strutsfile") != -1) {
553: prop = replace(prop, ", strutsfile", "");
554: } else if (prop.indexOf("strutsfile") != -1) {
555: prop = replace(prop, "strutsfile", "");
556: }
557:
558: p.setProperty(Velocity.RESOURCE_LOADER, prop);
559: }
560:
561: /**
562: * Refactored the Velocity templates for the Struts taglib into the classpath from the web path. This will
563: * enable Struts projects to have access to the templates by simply including the Struts jar file.
564: * Unfortunately, there does not appear to be a macro for the class loader keywords
565: * Matt Ho - Mon Mar 17 00:21:46 PST 2003
566: */
567: p.setProperty("strutsclass.resource.loader.description",
568: "Velocity Classpath Resource Loader");
569: p
570: .setProperty("strutsclass.resource.loader.class",
571: "org.apache.struts2.views.velocity.StrutsResourceLoader");
572: p
573: .setProperty(
574: "strutsclass.resource.loader.modificationCheckInterval",
575: "2");
576: p.setProperty("strutsclass.resource.loader.cache", "true");
577:
578: // components
579: StringBuffer sb = new StringBuffer();
580:
581: for (TagLibrary tagLibrary : tagLibraries) {
582: List<Class> directives = tagLibrary
583: .getVelocityDirectiveClasses();
584: for (Class directive : directives) {
585: addDirective(sb, directive);
586: }
587: }
588:
589: String directives = sb.toString();
590:
591: String userdirective = p.getProperty("userdirective");
592: if ((userdirective == null) || userdirective.trim().equals("")) {
593: userdirective = directives;
594: } else {
595: userdirective = userdirective.trim() + "," + directives;
596: }
597:
598: p.setProperty("userdirective", userdirective);
599: }
600:
601: private void addDirective(StringBuffer sb, Class clazz) {
602: sb.append(clazz.getName()).append(",");
603: }
604:
605: private static final String replace(String string,
606: String oldString, String newString) {
607: if (string == null) {
608: return null;
609: }
610: // If the newString is null, just return the string since there's nothing to replace.
611: if (newString == null) {
612: return string;
613: }
614: int i = 0;
615: // Make sure that oldString appears at least once before doing any processing.
616: if ((i = string.indexOf(oldString, i)) >= 0) {
617: // Use char []'s, as they are more efficient to deal with.
618: char[] string2 = string.toCharArray();
619: char[] newString2 = newString.toCharArray();
620: int oLength = oldString.length();
621: StringBuffer buf = new StringBuffer(string2.length);
622: buf.append(string2, 0, i).append(newString2);
623: i += oLength;
624: int j = i;
625: // Replace all remaining instances of oldString with newString.
626: while ((i = string.indexOf(oldString, i)) > 0) {
627: buf.append(string2, j, i - j).append(newString2);
628: i += oLength;
629: j = i;
630: }
631: buf.append(string2, j, string2.length - j);
632: return buf.toString();
633: }
634: return string;
635: }
636:
637: /**
638: * @return the velocityProperties
639: */
640: public Properties getVelocityProperties() {
641: return velocityProperties;
642: }
643:
644: /**
645: * @param velocityProperties the velocityProperties to set
646: */
647: public void setVelocityProperties(Properties velocityProperties) {
648: this.velocityProperties = velocityProperties;
649: }
650: }
|