package com.opensymphony.webwork.portlet.velocity;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import com.opensymphony.webwork.config.Configuration;
import com.opensymphony.webwork.util.VelocityWebWorkUtil;
import com.opensymphony.webwork.util.WebWorkUtil;
import com.opensymphony.webwork.views.jsp.ui.OgnlTool;
import com.opensymphony.webwork.views.velocity.WebWorkVelocityContext;
import com.opensymphony.xwork.ActionContext;
import com.opensymphony.xwork.ActionInvocation;
import com.opensymphony.xwork.ObjectFactory;
import com.opensymphony.xwork.util.OgnlValueStack;
/**
* * Revise the original VelocityManager for WebWork to
* <li>add the actionURL implement in VelocityManager for Portlet
* <li>init the Velocity Engine from ServletContext.
*
* @author Henry Hu -- hu_pengfei@yahoo.com.cn
* @since 2005-7-6
*
*/
public class VelocityManager {
//~ Static fields/initializers
// /////////////////////////////////////////////
private static final Log log = LogFactory.getLog(VelocityManager.class);
private static VelocityManager instance;
public static final String REQUEST = "req";
public static final String RESPONSE = "res";
public static final String STACK = "stack";
public static final String OGNL = "ognl";
public static final String WEBWORK = "webwork";
public static final String ACTION = "action";
//In order to get the Portlet actionURL in VM result, add this attribute in Context.
public static final String ACTION_URL = "actionURL";
public static final String ACTION_XURL = "actionXURL";
/**
* the parent JSP tag
*/
public static final String PARENT = "parent";
/**
* the current JSP tag
*/
public static final String TAG = "tag";
//~ Instance fields
// ////////////////////////////////////////////////////////
private OgnlTool ognlTool = OgnlTool.getInstance();
private VelocityEngine velocityEngine;
private VelocityContext[] chainedContexts;
//~ Constructors
// ///////////////////////////////////////////////////////////
protected VelocityManager() {
init();
}
//~ Methods
// ////////////////////////////////////////////////////////////////
/**
* retrieve an instance to the current VelocityManager
*/
public synchronized static VelocityManager getInstance() {
if (instance == null) {
instance = new VelocityManager();
}
return instance;
}
/**
* @return a reference to the VelocityEngine used by <b>all </b> webwork
* velocity thingies with the exception of directly accessed *.vm
* pages
*/
public VelocityEngine getVelocityEngine() {
return velocityEngine;
}
/**
* This method is responsible for creating the standard VelocityContext used
* by all WW2 velocity views. The following context parameters are defined:
* <p/>
* <ul>
* <li><strong>req </strong>- the current HttpServletRequest</li>
* <li><strong>res </strong>- the current HttpServletResponse</li>
* <li><strong>stack </strong>- the current {@link OgnlValueStack}</li>
* <li><strong>ognl </strong>- an {@link OgnlTool}</li>
* <li><strong>webwork </strong>- an instance of {@link WebWorkUtil}</li>
* <li><strong>action </strong>- the current WebWork action</li>
* </ul>
*
* @return a new WebWorkVelocityContext
*/
public Context createContext(OgnlValueStack stack, HttpServletRequest request, HttpServletResponse response) {
WebWorkVelocityContext context = new WebWorkVelocityContext(chainedContexts, stack);
context.put(REQUEST, request);
context.put(RESPONSE, response);
context.put(STACK, stack);
context.put(OGNL, ognlTool);
context.put(WEBWORK, new VelocityWebWorkUtil(context, stack, (HttpServletRequest) request, (HttpServletResponse) response));
/*
* Add actiontURl/actionXURL in the OGNL context for VM result to retrieve.
*/
String actionURL = com.opensymphony.webwork.portlet.context.PortletContext.getContext().getActionURL();
String actionXURL = actionURL+"?wwXAction=";
context.put(ACTION_URL, actionURL);
context.put(ACTION_XURL, actionXURL);
ActionInvocation invocation = (ActionInvocation) stack.getContext().get(ActionContext.ACTION_INVOCATION);
if (invocation != null) {
context.put(ACTION, invocation.getAction());
}
return context;
}
/**
* initializes the VelocityEngine from ServletContext, this should be called during the
* initialization process, say by WebWorkPortlet init(). this may be called
* multiple times safely although calls beyond the first won't do anything
*
* @param context
* the current Portlet context
*/
public synchronized void init(ServletContext context) {
if (velocityEngine == null) {
velocityEngine = newVelocityEngine(context);
}
}
/**
* load optional velocity properties using the following loading strategy
* <ul>
* <li>relative to the Portlet context path</li>
* <li>relative to the WEB-INF directory</li>
* <li>on the classpath</li>
* </ul>
*
* @param context
* the current ServletContext. may <b>not </b> be null
* @return the optional properties if webwork.velocity.configfile was
* specified, an empty Properties file otherwise
*/
public Properties loadConfiguration(ServletContext context) {
if (context == null) {
String gripe = "Error attempting to create a loadConfiguration from a null ServletContext!";
log.error(gripe);
throw new IllegalArgumentException(gripe);
}
Properties properties = new Properties();
// now apply our systemic defaults, then allow user to override
applyDefaultConfiguration(context, properties);
/**
* if the user has specified an external velocity configuration file,
* we'll want to search for it in the following order
*
* 1. relative to the context path 2. relative to /WEB-INF 3. in the
* class path
*/
String configfile;
if (Configuration.isSet("webwork.velocity.configfile")) {
configfile = Configuration.getString("webwork.velocity.configfile");
} else {
configfile = "velocity.properties";
}
configfile = configfile.trim();
InputStream in = null;
try {
if (context.getRealPath(configfile) != null) {
// 1. relative to context path, i.e. /velocity.properties
String filename = context.getRealPath(configfile);
if (filename != null) {
File file = new File(filename);
if (file.isFile()) {
in = new FileInputStream(file);
}
// 2. if nothing was found relative to the context path,
// search relative to the WEB-INF directory
if (in == null) {
file = new File(context.getRealPath("/WEB-INF/" + configfile));
if (file.isFile()) {
in = new FileInputStream(file);
}
}
}
}
// 3. finally, if there's no physical file, how about something in
// our classpath
if (in == null) {
in = VelocityManager.class.getClassLoader().getResourceAsStream(configfile);
}
// if we've got something, load 'er up
if (in != null) {
log.info("Initializing velocity using '" + configfile + "'");
properties.load(in);
}
} catch (IOException e) {
log.warn("Unable to load velocity configuration file '" + configfile + "'", e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
}
// for debugging purposes, allows users to dump out the properties that
// have been configured
if (log.isDebugEnabled()) {
log.debug("Initializing Velocity with the following properties ...");
for (Iterator iter = properties.keySet().iterator(); iter.hasNext();) {
String key = (String) iter.next();
String value = properties.getProperty(key);
if (log.isDebugEnabled()) {
log.debug(" '" + key + "' = '" + value + "'");
}
}
}
return properties;
}
/**
* performs one-time initializations
*/
protected void init() {
/**
* allow users to specify via the webwork.properties file a set of
* additional VelocityContexts to chain to the the
* WebWorkVelocityContext. The intent is to allow these contexts to
* store helper objects that the ui developer may want access to.
* Examples of reasonable VelocityContexts would be an
* IoCVelocityContext, a SpringReferenceVelocityContext, and a
* ToolboxVelocityContext
*/
if (Configuration.isSet("webwork.velocity.contexts")) {
// we expect contexts to be a comma separated list of classnames
String contexts = Configuration.get("webwork.velocity.contexts").toString();
StringTokenizer st = new StringTokenizer(contexts, ",");
List contextList = new ArrayList();
while (st.hasMoreTokens()) {
String classname = st.nextToken();
try {
VelocityContext velocityContext = (VelocityContext) ObjectFactory.getObjectFactory()
.buildBean(Class.forName(classname));
contextList.add(velocityContext);
} catch (Exception e) {
log.warn("Warning. " + e.getClass().getName() + " caught while attempting to instantiate a chained VelocityContext, "
+ classname + " -- skipping");
}
}
if (contextList.size() > 0) {
VelocityContext[] chainedContexts = new VelocityContext[contextList.size()];
contextList.toArray(chainedContexts);
this.chainedContexts = chainedContexts;
} else {
this.chainedContexts = null;
}
}
}
/**
* <p/>Instantiates a new VelocityEngine.
* </p>
* <p/>The following is the default Velocity configuration
* </p>
*
* <pre>
*
* resource.loader = file, class
* file.resource.loader.path = real path of webapp
* class.resource.loader.description = Velocity Classpath Resource Loader
* class.resource.loader.class = com.opensymphony.webwork.views.velocity.WebWorkResourceLoader
*
* </pre>
*
* <p/>this default configuration can be overridden by specifying a
* webwork.velocity.configfile property in the webwork.properties file. the
* specified config file will be searched for in the following order:
* </p>
* <ul>
* <li>relative to the servlet context path</li>
* <li>relative to the WEB-INF directory</li>
* <li>on the classpath</li>
* </ul>
*
* @param context
* the current ServletContext. may <b>not </b> be null
*/
protected VelocityEngine newVelocityEngine(ServletContext context) {
if (context == null) {
String gripe = "Error attempting to create a new VelocityEngine from a null ServletContext!";
log.error(gripe);
throw new IllegalArgumentException(gripe);
}
Properties p = loadConfiguration(context);
VelocityEngine velocityEngine = new VelocityEngine();
try {
velocityEngine.init(p);
} catch (Exception e) {
String gripe = "Unable to instantiate VelocityEngine!";
log.error(gripe, e);
throw new RuntimeException(gripe);
}
return velocityEngine;
}
/**
* once we've loaded up the user defined configurations, we will want to
* apply WebWork specification configurations.
* <ul>
* <li>if Velocity.RESOURCE_LOADER has not been defined, then we will use
* the defaults which is a joined file, class loader for unpackaed wars and
* a straight class loader otherwise</li>
* <li>we need to define the various WebWork custom user directives such as
* #param, #tag, and #bodytag</li>
* </ul>
*
* @param context
* @param p
*/
private void applyDefaultConfiguration(ServletContext context, Properties p) {
// ensure that caching isn't overly aggressive
/**
* Load a default resource loader definition if there isn't one present.
* Ben Hall (22/08/2003)
*/
if (p.getProperty(Velocity.RESOURCE_LOADER) == null) {
p.setProperty(Velocity.RESOURCE_LOADER, "wwfile, wwclass");
}
/**
* If there's a "real" path add it for the wwfile resource loader. If
* there's no real path and they haven't configured a loader then we
* change resource loader property to just use the wwclass loader Ben
* Hall (22/08/2003)
*/
if (context.getRealPath("") != null) {
p.setProperty("wwfile.resource.loader.description", "Velocity File Resource Loader");
p.setProperty("wwfile.resource.loader.class", "org.apache.velocity.runtime.resource.loader.FileResourceLoader");
p.setProperty("wwfile.resource.loader.path", context.getRealPath(""));
p.setProperty("wwfile.resource.loader.modificationCheckInterval", "2");
p.setProperty("wwfile.resource.loader.cache", "true");
} else {
// remove wwfile from resource loader property
String prop = p.getProperty(Velocity.RESOURCE_LOADER);
if (prop.indexOf("wwfile,") != -1) {
prop = replace(prop, "wwfile,", "");
} else if (prop.indexOf(", wwfile") != -1) {
prop = replace(prop, ", wwfile", "");
} else if (prop.indexOf("wwfile") != -1) {
prop = replace(prop, "wwfile", "");
}
p.setProperty(Velocity.RESOURCE_LOADER, prop);
}
/**
* Refactored the Velocity templates for the WebWork taglib into the
* classpath from the web path. This will enable WebWork projects to
* have access to the templates by simply including the WebWork jar
* file. Unfortunately, there does not appear to be a macro for the
* class loader keywords Matt Ho - Mon Mar 17 00:21:46 PST 2003
*/
p.setProperty("wwclass.resource.loader.description", "Velocity Classpath Resource Loader");
p.setProperty("wwclass.resource.loader.class", "com.opensymphony.webwork.views.velocity.WebWorkResourceLoader");
p.setProperty("wwclass.resource.loader.modificationCheckInterval", "2");
p.setProperty("wwclass.resource.loader.cache", "true");
/**
* the TagDirective and BodyTagDirective must be added to the
* userdirective TODO: there must be a better place for this... LIKE A
* CONFIG FILE IN THE JAR TODO: ... also, people must be allowed to
* define their own config that overrides this
*/
String userdirective = p.getProperty("userdirective");
String directives = "com.opensymphony.webwork.views.velocity.ParamDirective" + ","
+ "com.opensymphony.webwork.views.velocity.TagDirective" + "," + "com.opensymphony.webwork.views.velocity.BodyTagDirective";
if ((userdirective == null) || userdirective.trim().equals("")) {
userdirective = directives;
} else {
userdirective = userdirective.trim() + "," + directives;
}
p.setProperty("userdirective", userdirective);
}
private static final String replace(String string, String oldString, String newString) {
if (string == null) {
return null;
}
// If the newString is null, just return the string since there's
// nothing to replace.
if (newString == null) {
return string;
}
int i = 0;
// Make sure that oldString appears at least once before doing any
// processing.
if ((i = string.indexOf(oldString, i)) >= 0) {
// Use char []'s, as they are more efficient to deal with.
char[] string2 = string.toCharArray();
char[] newString2 = newString.toCharArray();
int oLength = oldString.length();
StringBuffer buf = new StringBuffer(string2.length);
buf.append(string2, 0, i).append(newString2);
i += oLength;
int j = i;
// Replace all remaining instances of oldString with newString.
while ((i = string.indexOf(oldString, i)) > 0) {
buf.append(string2, j, i - j).append(newString2);
i += oLength;
j = i;
}
buf.append(string2, j, string2.length - j);
return buf.toString();
}
return string;
}
}