| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.struts2.views.velocity; |
| |
| import com.opensymphony.xwork2.ObjectFactory; |
| import com.opensymphony.xwork2.inject.Container; |
| import com.opensymphony.xwork2.inject.Inject; |
| import com.opensymphony.xwork2.util.ValueStack; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.logging.log4j.LogManager; |
| import org.apache.logging.log4j.Logger; |
| import org.apache.struts2.ServletActionContext; |
| import org.apache.struts2.StrutsConstants; |
| import org.apache.struts2.StrutsException; |
| import org.apache.struts2.util.VelocityStrutsUtil; |
| import org.apache.struts2.views.TagLibraryDirectiveProvider; |
| import org.apache.struts2.views.jsp.ui.OgnlTool; |
| import org.apache.struts2.views.util.ContextUtil; |
| import org.apache.velocity.VelocityContext; |
| import org.apache.velocity.app.Velocity; |
| import org.apache.velocity.app.VelocityEngine; |
| import org.apache.velocity.context.Context; |
| import org.apache.velocity.tools.view.ToolboxManager; |
| import org.apache.velocity.tools.view.context.ChainedContext; |
| import org.apache.velocity.tools.view.servlet.ServletToolboxManager; |
| |
| import javax.servlet.ServletContext; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.*; |
| |
| /** |
| * Manages the environment for Velocity result types |
| */ |
| public class VelocityManager { |
| |
| private static final Logger LOG = LogManager.getLogger(VelocityManager.class); |
| |
| public static final String STRUTS = "struts"; |
| |
| private ObjectFactory objectFactory; |
| |
| public static final String KEY_VELOCITY_STRUTS_CONTEXT = ".KEY_velocity.struts2.context"; |
| |
| private VelocityEngine velocityEngine; |
| |
| /** |
| * A reference to the toolbox manager. |
| */ |
| protected ToolboxManager toolboxManager = null; |
| private String toolBoxLocation; |
| |
| /** |
| * Names of contexts that will be chained on every request |
| */ |
| private String[] chainedContextNames; |
| |
| private Properties velocityProperties; |
| |
| private String customConfigFile; |
| |
| private List<TagLibraryDirectiveProvider> tagLibraries; |
| |
| @Inject |
| public void setObjectFactory(ObjectFactory fac) { |
| this.objectFactory = fac; |
| } |
| |
| @Inject |
| public void setContainer(Container container) { |
| List<TagLibraryDirectiveProvider> list = new ArrayList<>(); |
| Set<String> prefixes = container.getInstanceNames(TagLibraryDirectiveProvider.class); |
| for (String prefix : prefixes) { |
| list.add(container.getInstance(TagLibraryDirectiveProvider.class, prefix)); |
| } |
| this.tagLibraries = Collections.unmodifiableList(list); |
| } |
| |
| /** |
| * @return a reference to the VelocityEngine used by <b>all</b> struts velocity thingies with the exception of |
| * directly accessed *.vm pages |
| */ |
| public VelocityEngine getVelocityEngine() { |
| return velocityEngine; |
| } |
| |
| /** |
| * <p> |
| * 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>request</strong> - the current HttpServletRequest</li> |
| * <li><strong>response</strong> - the current HttpServletResponse</li> |
| * <li><strong>stack</strong> - the current {@link ValueStack}</li> |
| * <li><strong>ognl</strong> - an {@link OgnlTool}</li> |
| * <li><strong>struts</strong> - an instance of {@link org.apache.struts2.util.StrutsUtil}</li> |
| * <li><strong>action</strong> - the current Struts action</li> |
| * </ul> |
| * |
| * @param stack the current {@link ValueStack} |
| * @param req the current HttpServletRequest |
| * @param res the current HttpServletResponse |
| * @return a new StrutsVelocityContext |
| */ |
| public Context createContext(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { |
| Context result = null; |
| VelocityContext[] chainedContexts = prepareChainedContexts(req, res, stack.getContext()); |
| StrutsVelocityContext context = new StrutsVelocityContext(chainedContexts, stack); |
| Map standardMap = ContextUtil.getStandardContext(stack, req, res); |
| for (Object o : standardMap.entrySet()) { |
| Map.Entry entry = (Map.Entry) o; |
| context.put((String) entry.getKey(), entry.getValue()); |
| } |
| context.put(STRUTS, new VelocityStrutsUtil(velocityEngine, context, stack, req, res)); |
| |
| |
| ServletContext ctx = null; |
| try { |
| ctx = ServletActionContext.getServletContext(); |
| } catch (NullPointerException npe) { |
| // in case this was used outside the lifecycle of struts servlet |
| LOG.debug("internal toolbox context ignored"); |
| } |
| |
| if (toolboxManager != null && ctx != null) { |
| ChainedContext chained = new ChainedContext(context, velocityEngine, req, res, ctx); |
| chained.setToolbox(toolboxManager.getToolbox(chained)); |
| result = chained; |
| } else { |
| result = context; |
| } |
| |
| req.setAttribute(KEY_VELOCITY_STRUTS_CONTEXT, result); |
| return result; |
| } |
| |
| /** |
| * constructs contexts for chaining on this request. This method does not |
| * perform any initialization of the contexts. All that must be done in the |
| * context itself. |
| * |
| * @param servletRequest the servlet request object |
| * @param servletResponse the servlet response object |
| * @param extraContext map with extra context |
| * @return an VelocityContext[] of contexts to chain |
| */ |
| protected VelocityContext[] prepareChainedContexts(HttpServletRequest servletRequest, HttpServletResponse servletResponse, Map extraContext) { |
| if (this.chainedContextNames == null) { |
| return null; |
| } |
| List contextList = new ArrayList(); |
| for (int i = 0; i < chainedContextNames.length; i++) { |
| String className = chainedContextNames[i]; |
| try { |
| VelocityContext velocityContext = (VelocityContext) objectFactory.buildBean(className, null); |
| contextList.add(velocityContext); |
| } catch (Exception e) { |
| LOG.warn("Warning. {} caught while attempting to instantiate a chained VelocityContext, {} -- skipping", e.getClass().getName(), className); |
| } |
| } |
| if (contextList.size() > 0) { |
| VelocityContext[] extraContexts = new VelocityContext[contextList.size()]; |
| contextList.toArray(extraContexts); |
| return extraContexts; |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * initializes the VelocityManager. this should be called during the initialization process, say by |
| * ServletDispatcher. this may be called multiple times safely although calls beyond the first won't do anything |
| * |
| * @param context the current servlet context |
| */ |
| public synchronized void init(ServletContext context) { |
| if (velocityEngine == null) { |
| velocityEngine = newVelocityEngine(context); |
| } |
| this.initToolbox(context); |
| } |
| |
| /** |
| * load optional velocity properties using the following loading strategy |
| * <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 |
| * @return the optional properties if struts.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); |
| |
| |
| String defaultUserDirective = properties.getProperty("userdirective"); |
| |
| /* |
| 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 (customConfigFile != null) { |
| configfile = customConfigFile; |
| } else { |
| configfile = "velocity.properties"; |
| } |
| |
| configfile = configfile.trim(); |
| |
| InputStream in = null; |
| String resourceLocation = 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()) { |
| resourceLocation = file.getCanonicalPath() + " from file system"; |
| 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()) { |
| resourceLocation = file.getCanonicalPath() + " from file system"; |
| 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 (in != null) { |
| resourceLocation = configfile + " from classloader"; |
| } |
| } |
| |
| // if we've got something, load 'er up |
| if (in != null) { |
| LOG.info("Initializing velocity using {}", resourceLocation); |
| properties.load(in); |
| } |
| } catch (IOException e) { |
| LOG.warn("Unable to load velocity configuration {}", resourceLocation, e); |
| } finally { |
| if (in != null) { |
| try { |
| in.close(); |
| } catch (IOException ignore) { |
| } |
| } |
| } |
| |
| // overide with programmatically set properties |
| if (this.velocityProperties != null) { |
| for (Object o : this.velocityProperties.keySet()) { |
| String key = (String) o; |
| properties.setProperty(key, this.velocityProperties.getProperty(key)); |
| } |
| } |
| |
| String userdirective = properties.getProperty("userdirective"); |
| |
| if ((userdirective == null) || userdirective.trim().equals("")) { |
| userdirective = defaultUserDirective; |
| } else { |
| userdirective = userdirective.trim() + "," + defaultUserDirective; |
| } |
| |
| properties.setProperty("userdirective", userdirective); |
| |
| |
| // 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 (Object o : properties.keySet()) { |
| String key = (String) o; |
| String value = properties.getProperty(key); |
| LOG.debug(" '{}' = '{}'", key, value); |
| } |
| } |
| |
| return properties; |
| } |
| |
| @Inject(StrutsConstants.STRUTS_VELOCITY_CONFIGFILE) |
| public void setCustomConfigFile(String val) { |
| this.customConfigFile = val; |
| } |
| |
| @Inject(StrutsConstants.STRUTS_VELOCITY_TOOLBOXLOCATION) |
| public void setToolBoxLocation(String toolboxLocation) { |
| this.toolBoxLocation = toolboxLocation; |
| } |
| |
| public ToolboxManager getToolboxManager() { |
| return toolboxManager; |
| } |
| |
| /** |
| * allow users to specify via the struts.properties file a set of additional VelocityContexts to chain to the |
| * the StrutsVelocityContext. 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 |
| * |
| * @param contexts comma separated velocity context's |
| */ |
| @Inject(StrutsConstants.STRUTS_VELOCITY_CONTEXTS) |
| public void setChainedContexts(String contexts) { |
| // we expect contexts to be a comma separated list of classnames |
| StringTokenizer st = new StringTokenizer(contexts, ","); |
| List<String> contextList = new ArrayList<>(); |
| |
| while (st.hasMoreTokens()) { |
| String classname = st.nextToken(); |
| contextList.add(classname); |
| } |
| if (contextList.size() > 0) { |
| String[] chainedContexts = new String[contextList.size()]; |
| contextList.toArray(chainedContexts); |
| this.chainedContextNames = chainedContexts; |
| } |
| } |
| |
| /** |
| * Initializes the ServletToolboxManager for this servlet's |
| * toolbox (if any). |
| * |
| * @param context the servlet context |
| */ |
| protected void initToolbox(ServletContext context) { |
| /* if we have a toolbox, get a manager for it */ |
| if (StringUtils.isNotBlank(toolBoxLocation)) { |
| toolboxManager = ServletToolboxManager.getInstance(context, toolBoxLocation); |
| } else { |
| Velocity.info("VelocityViewServlet: No toolbox entry in configuration."); |
| } |
| } |
| |
| /** |
| * <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 = org.apache.struts2.views.velocity.StrutsResourceLoader |
| * </pre> |
| * <p> |
| * this default configuration can be overridden by specifying a struts.velocity.configfile property in the |
| * struts.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 |
| * |
| * @return the new velocity engine |
| */ |
| 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(); |
| |
| // Set the velocity attribute for the servlet context |
| // if this is not set the webapp loader WILL NOT WORK |
| velocityEngine.setApplicationAttribute(ServletContext.class.getName(), |
| context); |
| |
| try { |
| velocityEngine.init(p); |
| } catch (Exception e) { |
| throw new StrutsException("Unable to instantiate VelocityEngine!", e); |
| } |
| |
| return velocityEngine; |
| } |
| |
| /** |
| * once we've loaded up the user defined configurations, we will want to apply Struts 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 Struts custom user directives such as #param, #tag, and #bodytag</li> |
| * </ul> |
| * |
| * @param context the servlet context |
| * @param properties velocity properties |
| */ |
| private void applyDefaultConfiguration(ServletContext context, Properties properties) { |
| // 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 (properties.getProperty(Velocity.RESOURCE_LOADER) == null) { |
| properties.setProperty(Velocity.RESOURCE_LOADER, "strutsfile, strutsclass"); |
| } |
| |
| /** |
| * If there's a "real" path add it for the strutsfile 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 strutsclass loader |
| * Ben Hall (22/08/2003) |
| */ |
| if (context.getRealPath("") != null) { |
| properties.setProperty("strutsfile.resource.loader.description", "Velocity File Resource Loader"); |
| properties.setProperty("strutsfile.resource.loader.class", "org.apache.velocity.runtime.resource.loader.FileResourceLoader"); |
| properties.setProperty("strutsfile.resource.loader.path", context.getRealPath("")); |
| properties.setProperty("strutsfile.resource.loader.modificationCheckInterval", "2"); |
| properties.setProperty("strutsfile.resource.loader.cache", "true"); |
| } else { |
| // remove strutsfile from resource loader property |
| String prop = properties.getProperty(Velocity.RESOURCE_LOADER); |
| if (prop.contains("strutsfile,")) { |
| prop = replace(prop, "strutsfile,", ""); |
| } else if (prop.contains(", strutsfile")) { |
| prop = replace(prop, ", strutsfile", ""); |
| } else if (prop.contains("strutsfile")) { |
| prop = replace(prop, "strutsfile", ""); |
| } |
| |
| properties.setProperty(Velocity.RESOURCE_LOADER, prop); |
| } |
| |
| /** |
| * Refactored the Velocity templates for the Struts taglib into the classpath from the web path. This will |
| * enable Struts projects to have access to the templates by simply including the Struts 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 |
| */ |
| properties.setProperty("strutsclass.resource.loader.description", "Velocity Classpath Resource Loader"); |
| properties.setProperty("strutsclass.resource.loader.class", "org.apache.struts2.views.velocity.StrutsResourceLoader"); |
| properties.setProperty("strutsclass.resource.loader.modificationCheckInterval", "2"); |
| properties.setProperty("strutsclass.resource.loader.cache", "true"); |
| |
| // components |
| StringBuilder sb = new StringBuilder(); |
| |
| for (TagLibraryDirectiveProvider tagLibrary : tagLibraries) { |
| List<Class> directives = tagLibrary.getDirectiveClasses(); |
| for (Class directive : directives) { |
| addDirective(sb, directive); |
| } |
| } |
| |
| String directives = sb.toString(); |
| |
| String userdirective = properties.getProperty("userdirective"); |
| if ((userdirective == null) || userdirective.trim().equals("")) { |
| userdirective = directives; |
| } else { |
| userdirective = userdirective.trim() + "," + directives; |
| } |
| |
| properties.setProperty("userdirective", userdirective); |
| } |
| |
| private void addDirective(StringBuilder sb, Class clazz) { |
| sb.append(clazz.getName()).append(","); |
| } |
| |
| 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(); |
| StringBuilder buf = new StringBuilder(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; |
| } |
| |
| /** |
| * @return the velocityProperties |
| */ |
| public Properties getVelocityProperties() { |
| return velocityProperties; |
| } |
| |
| /** |
| * @param velocityProperties the velocityProperties to set |
| */ |
| public void setVelocityProperties(Properties velocityProperties) { |
| this.velocityProperties = velocityProperties; |
| } |
| } |