diff --git a/plugins/config-browser/src/main/java/org/apache/struts2/config_browser/ShowBeansAction.java b/plugins/config-browser/src/main/java/org/apache/struts2/config_browser/ShowBeansAction.java index 6248b69647..53f8eeebc2 100644 --- a/plugins/config-browser/src/main/java/org/apache/struts2/config_browser/ShowBeansAction.java +++ b/plugins/config-browser/src/main/java/org/apache/struts2/config_browser/ShowBeansAction.java @@ -30,7 +30,7 @@ import org.apache.struts2.dispatcher.multipart.MultiPartRequest; import org.apache.struts2.views.freemarker.FreemarkerManager; import org.apache.struts2.views.velocity.VelocityConstants; -import org.apache.struts2.views.velocity.VelocityManagerInterface; +import org.apache.struts2.views.velocity.VelocityManager; import java.util.Map; import java.util.Set; @@ -57,7 +57,7 @@ public void setContainer(Container container) { bindings.put(ActionMapper.class.getName(), addBindings(container, ActionMapper.class, StrutsConstants.STRUTS_MAPPER_CLASS)); bindings.put(MultiPartRequest.class.getName(), addBindings(container, MultiPartRequest.class, StrutsConstants.STRUTS_MULTIPART_PARSER)); bindings.put(FreemarkerManager.class.getName(), addBindings(container, FreemarkerManager.class, StrutsConstants.STRUTS_FREEMARKER_MANAGER_CLASSNAME)); - bindings.put(VelocityManagerInterface.class.getName(), addBindings(container, VelocityManagerInterface.class, VelocityConstants.STRUTS_VELOCITY_MANAGER_CLASSNAME)); + bindings.put(VelocityManager.class.getName(), addBindings(container, VelocityManager.class, VelocityConstants.STRUTS_VELOCITY_MANAGER_CLASSNAME)); bindings.put(UrlRenderer.class.getName(), addBindings(container, UrlRenderer.class, StrutsConstants.STRUTS_URL_RENDERER)); } diff --git a/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/OldDecorator2NewStrutsVelocityDecorator.java b/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/OldDecorator2NewStrutsVelocityDecorator.java index d51745fa0e..edc2737a49 100644 --- a/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/OldDecorator2NewStrutsVelocityDecorator.java +++ b/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/OldDecorator2NewStrutsVelocityDecorator.java @@ -29,7 +29,7 @@ import jakarta.servlet.http.HttpServletResponse; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.struts2.views.velocity.VelocityManagerInterface; +import org.apache.struts2.views.velocity.VelocityManager; import org.apache.velocity.context.Context; import java.io.IOException; @@ -41,10 +41,10 @@ public class OldDecorator2NewStrutsVelocityDecorator extends OldDecorator2NewStrutsDecorator { private static final Logger LOG = LogManager.getLogger(OldDecorator2NewStrutsFreemarkerDecorator.class); - private static VelocityManagerInterface velocityManager; + private static VelocityManager velocityManager; @Inject(required = false) - public static void setVelocityManager(VelocityManagerInterface mgr) { + public static void setVelocityManager(VelocityManager mgr) { velocityManager = mgr; } diff --git a/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/VelocityDecoratorServlet.java b/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/VelocityDecoratorServlet.java index 255571aff3..b05e45da06 100644 --- a/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/VelocityDecoratorServlet.java +++ b/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/VelocityDecoratorServlet.java @@ -27,24 +27,23 @@ import com.opensymphony.module.sitemesh.RequestConstants; import com.opensymphony.module.sitemesh.util.OutputConverter; import com.opensymphony.xwork2.ActionContext; +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.ServletActionContext; import org.apache.struts2.dispatcher.Dispatcher; import org.apache.struts2.dispatcher.listener.StrutsListener; +import org.apache.struts2.views.velocity.StrutsVelocityManager; import org.apache.struts2.views.velocity.VelocityManager; -import org.apache.struts2.views.velocity.VelocityManagerInterface; import org.apache.velocity.Template; import org.apache.velocity.context.Context; import org.apache.velocity.runtime.RuntimeConstants; import org.apache.velocity.tools.view.VelocityView; import org.apache.velocity.tools.view.VelocityViewServlet; -import jakarta.servlet.ServletConfig; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - import java.io.IOException; import java.io.StringWriter; @@ -61,7 +60,7 @@ public class VelocityDecoratorServlet extends VelocityViewServlet { private static final long serialVersionUID = -6731485159371716918L; - protected VelocityManagerInterface velocityManager; + protected VelocityManager velocityManager; protected String defaultContentType; /** @@ -82,7 +81,7 @@ public void init(ServletConfig config) throws ServletException { if (dispatcher == null) { throw new IllegalStateException("Unable to find the Dispatcher in the Servlet Context. Is '" + StrutsListener.class.getName() + "' missing in web.xml?"); } - velocityManager = dispatcher.getContainer().getInstance(VelocityManagerInterface.class); + velocityManager = dispatcher.getContainer().getInstance(VelocityManager.class); velocityManager.init(config.getServletContext()); // do whatever we have to do to init Velocity @@ -164,7 +163,7 @@ private DecoratorMapper getDecoratorMapper() { * @param response servlet reponse to client */ protected Context createContext(HttpServletRequest request, HttpServletResponse response) { - Context context = (Context) request.getAttribute(VelocityManager.KEY_VELOCITY_STRUTS_CONTEXT); + Context context = (Context) request.getAttribute(StrutsVelocityManager.KEY_VELOCITY_STRUTS_CONTEXT); if (context == null) { ActionContext ctx = ServletActionContext.getActionContext(request); context = velocityManager.createContext(ctx.getValueStack(), request, response); diff --git a/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/VelocityPageFilter.java b/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/VelocityPageFilter.java index c2549801b3..c6b2f85699 100644 --- a/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/VelocityPageFilter.java +++ b/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/VelocityPageFilter.java @@ -25,7 +25,7 @@ import com.opensymphony.sitemesh.webapp.SiteMeshWebAppContext; import com.opensymphony.xwork2.inject.Inject; import jakarta.servlet.FilterConfig; -import org.apache.struts2.views.velocity.VelocityManagerInterface; +import org.apache.struts2.views.velocity.VelocityManager; /** * Core Filter for integrating SiteMesh into a Java web application. @@ -33,7 +33,7 @@ public class VelocityPageFilter extends SiteMeshFilter { @Inject(required = false) - public static void setVelocityManager(VelocityManagerInterface mgr) { + public static void setVelocityManager(VelocityManager mgr) { OldDecorator2NewStrutsVelocityDecorator.setVelocityManager(mgr); } diff --git a/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/StrutsVelocityManager.java b/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/StrutsVelocityManager.java new file mode 100644 index 0000000000..66ec2a8e38 --- /dev/null +++ b/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/StrutsVelocityManager.java @@ -0,0 +1,402 @@ +/* + * 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 jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.StrutsConstants; +import org.apache.struts2.StrutsException; +import org.apache.struts2.views.TagLibraryDirectiveProvider; +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 java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import static java.util.Collections.emptyList; +import static java.util.Objects.requireNonNullElse; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.struts2.views.util.ContextUtil.STRUTS; +import static org.apache.velocity.runtime.DeprecatedRuntimeConstants.OLD_CUSTOM_DIRECTIVES; + +/** + * Manages the environment for Velocity result types + * + * @since 7.0 + */ +public class StrutsVelocityManager implements VelocityManager { + + private static final Logger LOG = LogManager.getLogger(StrutsVelocityManager.class); + + private ObjectFactory objectFactory; + + public static final String DEFAULT_CONFIG_FILE = "velocity.properties"; + public static final String KEY_VELOCITY_STRUTS_CONTEXT = ".KEY_velocity.struts2.context"; + + private VelocityEngine velocityEngine; + + private VelocityTools velocityTools; + + /** + * Names of contexts that will be chained on every request + */ + private List chainedContextNames = emptyList(); + + private Properties velocityProperties; + + private String customConfigFile; + + private List tagLibraries; + + @Inject + public void setObjectFactory(ObjectFactory fac) { + this.objectFactory = fac; + } + + @Inject + public void setContainer(Container container) { + this.tagLibraries = container.getInstanceNames(TagLibraryDirectiveProvider.class).stream() + .map(prefix -> container.getInstance(TagLibraryDirectiveProvider.class, prefix)).toList(); + } + + /** + * @return a reference to the VelocityEngine used by all Struts Velocity results except directly + * accessed *.vm pages (unless otherwise configured) + */ + @Override + public VelocityEngine getVelocityEngine() { + return velocityEngine; + } + + /** + * This method is responsible for creating the standard VelocityContext used by all Struts Velocity views. + * + * @param stack the current {@link ValueStack} + * @param req the current HttpServletRequest + * @param res the current HttpServletResponse + * @return a new StrutsVelocityContext + */ + @Override + public Context createContext(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { + Context context = null; + if (velocityTools != null) { + context = velocityTools.createContext(); + } + if (context == null) { + context = buildContext(stack, req, res); + } + req.setAttribute(KEY_VELOCITY_STRUTS_CONTEXT, context); + return context; + } + + protected Context buildContext(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { + List chainedContexts = prepareChainedContexts(req, res, stack.getContext()); + Context context = new StrutsVelocityContext(chainedContexts, stack); + ContextUtil.getStandardContext(stack, req, res).forEach(context::put); + VelocityStrutsUtil util = new VelocityStrutsUtil(velocityEngine, context, stack, req, res); + context.put(STRUTS, util); + return context; + } + + /** + * 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 a List of contexts to chain or an empty list + */ + protected List prepareChainedContexts(HttpServletRequest servletRequest, + HttpServletResponse servletResponse, + Map extraContext) { + List contextList = new ArrayList<>(); + for (String className : chainedContextNames) { + try { + VelocityContext velocityContext = (VelocityContext) objectFactory.buildBean(className, extraContext); + contextList.add(velocityContext); + } catch (Exception e) { + LOG.warn("Unable to instantiate chained VelocityContext {}, skipping", className, e); + } + } + return contextList; + } + + /** + * initializes the StrutsVelocityManager. 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 + */ + @Override + public synchronized void init(ServletContext context) { + if (velocityEngine != null) { + return; + } + velocityEngine = newVelocityEngine(context); + if (velocityTools != null) { + velocityTools.init(context, velocityEngine); + } + } + + protected Properties loadConfiguration(ServletContext context) { + if (context == null) { + throw new IllegalArgumentException("Error attempting to create a loadConfiguration from a null ServletContext!"); + } + Properties properties = new Properties(); + applyDefaultConfiguration(context, properties); // Apply defaults before loading user overrides + String defaultUserDirective = properties.getProperty("userdirective"); + + applyUserConfiguration(context, properties); + + if (velocityProperties != null) { // Apply additional overriding properties if any + velocityProperties.stringPropertyNames().forEach(k -> properties.setProperty(k, velocityProperties.getProperty(k))); + } + + String userDirective = properties.getProperty(OLD_CUSTOM_DIRECTIVES); + String newDirective = isBlank(userDirective) ? defaultUserDirective : userDirective.strip() + "," + defaultUserDirective; + properties.setProperty(OLD_CUSTOM_DIRECTIVES, newDirective); + + if (LOG.isDebugEnabled()) { + LOG.debug("Initializing Velocity with the following properties ..."); + properties.stringPropertyNames().forEach(k -> LOG.debug(" '{}' = '{}'", k, properties.getProperty(k))); + } + return properties; + } + + /** + * Load optional velocity properties using the following loading strategy + *
    + *
  • relative to the servlet context path
  • + *
  • relative to the WEB-INF directory
  • + *
  • on the classpath
  • + *
+ */ + private void applyUserConfiguration(ServletContext context, Properties properties) { + String configFile = requireNonNullElse(customConfigFile, DEFAULT_CONFIG_FILE).trim(); + try { + if (loadFile(properties, context.getRealPath(configFile))) { + return; + } + } catch (IOException e) { + LOG.warn("Unable to load Velocity configuration from servlet context path", e); + } + try { + if (loadFile(properties, context.getRealPath("/WEB-INF/" + configFile))) { + return; + } + } catch (IOException e) { + LOG.warn("Unable to load Velocity configuration from WEB-INF path", e); + } + try { + loadClassPathFile(properties, configFile); + } catch (IOException e) { + LOG.warn("Unable to load Velocity configuration from classpath", e); + } + } + + private boolean loadClassPathFile(Properties properties, String configFile) throws IOException { + try (InputStream is = StrutsVelocityManager.class.getClassLoader().getResourceAsStream(configFile)) { + if (is == null) { + return false; + } + properties.load(is); + LOG.info("Initializing Velocity using {} from classpath", configFile); + return true; + } + } + + private boolean loadFile(Properties properties, String fileName) throws IOException { + if (fileName == null) { + return false; + } + File file = new File(fileName); + if (!file.isFile()) { + return false; + } + try (InputStream is = new FileInputStream(file)) { + properties.load(is); + LOG.info("Initializing Velocity using {}", file.getCanonicalPath() + " from file system"); + return true; + } + } + + @Inject(StrutsConstants.STRUTS_VELOCITY_CONFIGFILE) + public void setCustomConfigFile(String customConfigFile) { + this.customConfigFile = customConfigFile; + } + + @Inject(StrutsConstants.STRUTS_VELOCITY_TOOLBOXLOCATION) + public void setToolBoxLocation(String toolboxLocation) { + if (!isBlank(toolboxLocation)) { + this.velocityTools = new VelocityTools(toolboxLocation); + } + } + + public VelocityTools getVelocityTools() { + return velocityTools; + } + + /** + * Allow users to specify via the struts.properties file a set of additional VelocityContexts to chain to 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) { + this.chainedContextNames = Arrays.stream(contexts.split(",")).filter(StringUtils::isNotBlank).collect(toList()); + } + + /** + * Instantiates a new VelocityEngine. + *

+ * The following is the default Velocity configuration + *

+     *  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
+     * 
+ * 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: + *
    + *
  • relative to the servlet context path
  • + *
  • relative to the WEB-INF directory
  • + *
  • on the classpath
  • + *
+ * + * @param context the current ServletContext. may not be null + * @return the new velocity engine + */ + protected VelocityEngine newVelocityEngine(ServletContext context) { + if (context == null) { + throw new IllegalArgumentException("Error attempting to create a new VelocityEngine from a null ServletContext!"); + } + VelocityEngine velocityEngine = new VelocityEngine(); + velocityEngine.setApplicationAttribute(ServletContext.class.getName(), context); // Required for webapp loader + try { + velocityEngine.init(loadConfiguration(context)); + } 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. + *
    + *
  • 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
  • + *
  • we need to define the various Struts custom user directives such as #param, #tag, and #bodytag
  • + *
+ * + * @param context the servlet context + * @param properties velocity properties + */ + private void applyDefaultConfiguration(ServletContext context, Properties properties) { + LOG.debug("Load a default resource loader definition if there isn't one present."); + 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 + String realPath = context.getRealPath(""); + if (realPath != null) { + setStrutsFileResourceLoader(properties, realPath); + } else { + clearStrutsFileResourceLoader(properties); + } + + setStrutsClasspathResourceLoader(properties); + + String directives = tagLibraries.stream().map(TagLibraryDirectiveProvider::getDirectiveClasses) + .flatMap(Collection::stream).map(directive -> directive.getName() + ",").collect(joining()); + + String userDirective = properties.getProperty(OLD_CUSTOM_DIRECTIVES); + String newDirective = isBlank(userDirective) ? directives : userDirective.strip() + "," + directives; + properties.setProperty(OLD_CUSTOM_DIRECTIVES, newDirective); + } + + private void setStrutsFileResourceLoader(Properties properties, String realPath) { + 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", realPath); + properties.setProperty("strutsfile.resource.loader.modificationCheckInterval", "2"); + properties.setProperty("strutsfile.resource.loader.cache", "true"); + } + + private void clearStrutsFileResourceLoader(Properties properties) { + String prop = properties.getProperty(Velocity.RESOURCE_LOADER) + .replace("strutsfile,", "") + .replace(", strutsfile", "") + .replace("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 + */ + private void setStrutsClasspathResourceLoader(Properties properties) { + 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"); + } + + /** + * @return the velocityProperties + */ + public Properties getVelocityProperties() { + return velocityProperties; + } + + /** + * @param velocityProperties the velocityProperties to set + */ + public void setVelocityProperties(Properties velocityProperties) { + this.velocityProperties = velocityProperties; + } +} diff --git a/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/VelocityBeanSelectionProvider.java b/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/VelocityBeanSelectionProvider.java index d2c195b0f5..f53ec66079 100644 --- a/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/VelocityBeanSelectionProvider.java +++ b/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/VelocityBeanSelectionProvider.java @@ -49,7 +49,7 @@ public class VelocityBeanSelectionProvider extends AbstractBeanSelectionProvider @Override public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { - alias(VelocityManagerInterface.class, VelocityConstants.STRUTS_VELOCITY_MANAGER_CLASSNAME, builder, props); + alias(VelocityManager.class, VelocityConstants.STRUTS_VELOCITY_MANAGER_CLASSNAME, builder, props); } } diff --git a/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/VelocityManager.java b/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/VelocityManager.java index 25ded15ad7..16894896c5 100644 --- a/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/VelocityManager.java +++ b/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/VelocityManager.java @@ -18,383 +18,21 @@ */ 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 jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.struts2.StrutsConstants; -import org.apache.struts2.StrutsException; -import org.apache.struts2.views.TagLibraryDirectiveProvider; -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 java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import static java.util.Collections.emptyList; -import static java.util.Objects.requireNonNullElse; -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.struts2.views.util.ContextUtil.STRUTS; -import static org.apache.velocity.runtime.DeprecatedRuntimeConstants.OLD_CUSTOM_DIRECTIVES; - /** - * Manages the environment for Velocity result types + * @since 7.0 */ -public class VelocityManager implements VelocityManagerInterface { - - private static final Logger LOG = LogManager.getLogger(VelocityManager.class); - - private ObjectFactory objectFactory; - - public static final String DEFAULT_CONFIG_FILE = "velocity.properties"; - public static final String KEY_VELOCITY_STRUTS_CONTEXT = ".KEY_velocity.struts2.context"; - - private VelocityEngine velocityEngine; - - private VelocityTools velocityTools; - - /** - * Names of contexts that will be chained on every request - */ - private List chainedContextNames = emptyList(); - - private Properties velocityProperties; - - private String customConfigFile; - - private List tagLibraries; - - @Inject - public void setObjectFactory(ObjectFactory fac) { - this.objectFactory = fac; - } - - @Inject - public void setContainer(Container container) { - this.tagLibraries = container.getInstanceNames(TagLibraryDirectiveProvider.class).stream() - .map(prefix -> container.getInstance(TagLibraryDirectiveProvider.class, prefix)).toList(); - } - - /** - * @return a reference to the VelocityEngine used by all Struts Velocity results except directly - * accessed *.vm pages (unless otherwise configured) - */ - @Override - public VelocityEngine getVelocityEngine() { - return velocityEngine; - } - - /** - * This method is responsible for creating the standard VelocityContext used by all Struts Velocity views. - * - * @param stack the current {@link ValueStack} - * @param req the current HttpServletRequest - * @param res the current HttpServletResponse - * @return a new StrutsVelocityContext - */ - @Override - public Context createContext(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { - Context context = null; - if (velocityTools != null) { - context = velocityTools.createContext(); - } - if (context == null) { - context = buildContext(stack, req, res); - } - req.setAttribute(KEY_VELOCITY_STRUTS_CONTEXT, context); - return context; - } - - protected Context buildContext(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { - List chainedContexts = prepareChainedContexts(req, res, stack.getContext()); - Context context = new StrutsVelocityContext(chainedContexts, stack); - ContextUtil.getStandardContext(stack, req, res).forEach(context::put); - VelocityStrutsUtil util = new VelocityStrutsUtil(velocityEngine, context, stack, req, res); - context.put(STRUTS, util); - return context; - } - - /** - * 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 a List of contexts to chain or an empty list - */ - protected List prepareChainedContexts(HttpServletRequest servletRequest, - HttpServletResponse servletResponse, - Map extraContext) { - List contextList = new ArrayList<>(); - for (String className : chainedContextNames) { - try { - VelocityContext velocityContext = (VelocityContext) objectFactory.buildBean(className, extraContext); - contextList.add(velocityContext); - } catch (Exception e) { - LOG.warn("Unable to instantiate chained VelocityContext {}, skipping", className, e); - } - } - return contextList; - } - - /** - * 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 - */ - @Override - public synchronized void init(ServletContext context) { - if (velocityEngine != null) { - return; - } - velocityEngine = newVelocityEngine(context); - if (velocityTools != null) { - velocityTools.init(context, velocityEngine); - } - } - - protected Properties loadConfiguration(ServletContext context) { - if (context == null) { - throw new IllegalArgumentException("Error attempting to create a loadConfiguration from a null ServletContext!"); - } - Properties properties = new Properties(); - applyDefaultConfiguration(context, properties); // Apply defaults before loading user overrides - String defaultUserDirective = properties.getProperty("userdirective"); - - applyUserConfiguration(context, properties); - - if (velocityProperties != null) { // Apply additional overriding properties if any - velocityProperties.stringPropertyNames().forEach(k -> properties.setProperty(k, velocityProperties.getProperty(k))); - } - - String userDirective = properties.getProperty(OLD_CUSTOM_DIRECTIVES); - String newDirective = isBlank(userDirective) ? defaultUserDirective : userDirective.strip() + "," + defaultUserDirective; - properties.setProperty(OLD_CUSTOM_DIRECTIVES, newDirective); - - if (LOG.isDebugEnabled()) { - LOG.debug("Initializing Velocity with the following properties ..."); - properties.stringPropertyNames().forEach(k -> LOG.debug(" '{}' = '{}'", k, properties.getProperty(k))); - } - return properties; - } - - /** - * Load optional velocity properties using the following loading strategy - *
    - *
  • relative to the servlet context path
  • - *
  • relative to the WEB-INF directory
  • - *
  • on the classpath
  • - *
- */ - private void applyUserConfiguration(ServletContext context, Properties properties) { - String configFile = requireNonNullElse(customConfigFile, DEFAULT_CONFIG_FILE).trim(); - try { - if (loadFile(properties, context.getRealPath(configFile))) { - return; - } - } catch (IOException e) { - LOG.warn("Unable to load Velocity configuration from servlet context path", e); - } - try { - if (loadFile(properties, context.getRealPath("/WEB-INF/" + configFile))) { - return; - } - } catch (IOException e) { - LOG.warn("Unable to load Velocity configuration from WEB-INF path", e); - } - try { - loadClassPathFile(properties, configFile); - } catch (IOException e) { - LOG.warn("Unable to load Velocity configuration from classpath", e); - } - } - - private boolean loadClassPathFile(Properties properties, String configFile) throws IOException { - try (InputStream is = VelocityManager.class.getClassLoader().getResourceAsStream(configFile)) { - if (is == null) { - return false; - } - properties.load(is); - LOG.info("Initializing Velocity using {} from classpath", configFile); - return true; - } - } - - private boolean loadFile(Properties properties, String fileName) throws IOException { - if (fileName == null) { - return false; - } - File file = new File(fileName); - if (!file.isFile()) { - return false; - } - try (InputStream is = new FileInputStream(file)) { - properties.load(is); - LOG.info("Initializing Velocity using {}", file.getCanonicalPath() + " from file system"); - return true; - } - } - - @Inject(StrutsConstants.STRUTS_VELOCITY_CONFIGFILE) - public void setCustomConfigFile(String customConfigFile) { - this.customConfigFile = customConfigFile; - } - - @Inject(StrutsConstants.STRUTS_VELOCITY_TOOLBOXLOCATION) - public void setToolBoxLocation(String toolboxLocation) { - if (!isBlank(toolboxLocation)) { - this.velocityTools = new VelocityTools(toolboxLocation); - } - } - - public VelocityTools getVelocityTools() { - return velocityTools; - } - - /** - * Allow users to specify via the struts.properties file a set of additional VelocityContexts to chain to 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) { - this.chainedContextNames = Arrays.stream(contexts.split(",")).filter(StringUtils::isNotBlank).collect(toList()); - } - - /** - * Instantiates a new VelocityEngine. - *

- * The following is the default Velocity configuration - *

-     *  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
-     * 
- * 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: - *
    - *
  • relative to the servlet context path
  • - *
  • relative to the WEB-INF directory
  • - *
  • on the classpath
  • - *
- * - * @param context the current ServletContext. may not be null - * @return the new velocity engine - */ - protected VelocityEngine newVelocityEngine(ServletContext context) { - if (context == null) { - throw new IllegalArgumentException("Error attempting to create a new VelocityEngine from a null ServletContext!"); - } - VelocityEngine velocityEngine = new VelocityEngine(); - velocityEngine.setApplicationAttribute(ServletContext.class.getName(), context); // Required for webapp loader - try { - velocityEngine.init(loadConfiguration(context)); - } 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. - *
    - *
  • 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
  • - *
  • we need to define the various Struts custom user directives such as #param, #tag, and #bodytag
  • - *
- * - * @param context the servlet context - * @param properties velocity properties - */ - private void applyDefaultConfiguration(ServletContext context, Properties properties) { - LOG.debug("Load a default resource loader definition if there isn't one present."); - 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 - String realPath = context.getRealPath(""); - if (realPath != null) { - setStrutsFileResourceLoader(properties, realPath); - } else { - clearStrutsFileResourceLoader(properties); - } - - setStrutsClasspathResourceLoader(properties); - - String directives = tagLibraries.stream().map(TagLibraryDirectiveProvider::getDirectiveClasses) - .flatMap(Collection::stream).map(directive -> directive.getName() + ",").collect(joining()); - - String userDirective = properties.getProperty(OLD_CUSTOM_DIRECTIVES); - String newDirective = isBlank(userDirective) ? directives : userDirective.strip() + "," + directives; - properties.setProperty(OLD_CUSTOM_DIRECTIVES, newDirective); - } - - private void setStrutsFileResourceLoader(Properties properties, String realPath) { - 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", realPath); - properties.setProperty("strutsfile.resource.loader.modificationCheckInterval", "2"); - properties.setProperty("strutsfile.resource.loader.cache", "true"); - } - - private void clearStrutsFileResourceLoader(Properties properties) { - String prop = properties.getProperty(Velocity.RESOURCE_LOADER) - .replace("strutsfile,", "") - .replace(", strutsfile", "") - .replace("strutsfile", ""); - properties.setProperty(Velocity.RESOURCE_LOADER, prop); - } +public interface VelocityManager { - /** - * 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 - */ - private void setStrutsClasspathResourceLoader(Properties properties) { - 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"); - } + Context createContext(ValueStack stack, HttpServletRequest req, HttpServletResponse res); - /** - * @return the velocityProperties - */ - public Properties getVelocityProperties() { - return velocityProperties; - } + VelocityEngine getVelocityEngine(); - /** - * @param velocityProperties the velocityProperties to set - */ - public void setVelocityProperties(Properties velocityProperties) { - this.velocityProperties = velocityProperties; - } + void init(ServletContext context); } diff --git a/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/VelocityManagerInterface.java b/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/VelocityManagerInterface.java deleted file mode 100644 index 4e6b7b69aa..0000000000 --- a/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/VelocityManagerInterface.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.util.ValueStack; -import jakarta.servlet.ServletContext; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.apache.velocity.app.VelocityEngine; -import org.apache.velocity.context.Context; - -/** - * @since 6.4.0 - */ -public interface VelocityManagerInterface { - - Context createContext(ValueStack stack, HttpServletRequest req, HttpServletResponse res); - - VelocityEngine getVelocityEngine(); - - void init(ServletContext context); -} diff --git a/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/result/VelocityResult.java b/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/result/VelocityResult.java index a89ad6ce31..07a3d97589 100644 --- a/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/result/VelocityResult.java +++ b/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/result/VelocityResult.java @@ -34,7 +34,7 @@ import org.apache.struts2.StrutsConstants; import org.apache.struts2.result.StrutsResultSupport; import org.apache.struts2.views.JspSupportServlet; -import org.apache.struts2.views.velocity.VelocityManagerInterface; +import org.apache.struts2.views.velocity.VelocityManager; import org.apache.velocity.Template; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.context.Context; @@ -87,7 +87,7 @@ public class VelocityResult extends StrutsResultSupport { private static final Logger LOG = LogManager.getLogger(VelocityResult.class); private String defaultEncoding; - private transient VelocityManagerInterface velocityManager; + private transient VelocityManager velocityManager; private JspFactory jspFactory = JspFactory.getDefaultFactory(); public VelocityResult() { @@ -104,7 +104,7 @@ public void setDefaultEncoding(String val) { } @Inject - public void setVelocityManager(VelocityManagerInterface mgr) { + public void setVelocityManager(VelocityManager mgr) { this.velocityManager = mgr; } @@ -232,7 +232,7 @@ protected Template getTemplate(ValueStack stack, VelocityEngine velocity, Action * @param location the name of the template that is being used * @return the a minted Velocity context. */ - protected Context createContext(VelocityManagerInterface velocityManager, + protected Context createContext(VelocityManager velocityManager, ValueStack stack, HttpServletRequest request, HttpServletResponse response, diff --git a/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/template/VelocityTemplateEngine.java b/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/template/VelocityTemplateEngine.java index bc0bb4268c..9c62b1c805 100644 --- a/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/template/VelocityTemplateEngine.java +++ b/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/template/VelocityTemplateEngine.java @@ -28,7 +28,7 @@ import org.apache.struts2.components.template.BaseTemplateEngine; import org.apache.struts2.components.template.Template; import org.apache.struts2.components.template.TemplateRenderingContext; -import org.apache.struts2.views.velocity.VelocityManagerInterface; +import org.apache.struts2.views.velocity.VelocityManager; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.context.Context; @@ -42,10 +42,10 @@ public class VelocityTemplateEngine extends BaseTemplateEngine { private static final Logger LOG = LogManager.getLogger(VelocityTemplateEngine.class); - private VelocityManagerInterface velocityManager; + private VelocityManager velocityManager; @Inject - public void setVelocityManager(VelocityManagerInterface mgr) { + public void setVelocityManager(VelocityManager mgr) { this.velocityManager = mgr; } diff --git a/plugins/velocity/src/main/resources/struts-plugin.xml b/plugins/velocity/src/main/resources/struts-plugin.xml index b19aec9f2a..3c21b2761d 100644 --- a/plugins/velocity/src/main/resources/struts-plugin.xml +++ b/plugins/velocity/src/main/resources/struts-plugin.xml @@ -25,11 +25,8 @@ - - - - + diff --git a/plugins/velocity/src/test/java/org/apache/struts2/views/velocity/VelocityManagerTest.java b/plugins/velocity/src/test/java/org/apache/struts2/views/velocity/StrutsVelocityManagerTest.java similarity index 59% rename from plugins/velocity/src/test/java/org/apache/struts2/views/velocity/VelocityManagerTest.java rename to plugins/velocity/src/test/java/org/apache/struts2/views/velocity/StrutsVelocityManagerTest.java index d611cb5692..098fe67ca2 100644 --- a/plugins/velocity/src/test/java/org/apache/struts2/views/velocity/VelocityManagerTest.java +++ b/plugins/velocity/src/test/java/org/apache/struts2/views/velocity/StrutsVelocityManagerTest.java @@ -33,7 +33,7 @@ import java.util.Properties; -import static org.apache.struts2.views.velocity.VelocityManager.KEY_VELOCITY_STRUTS_CONTEXT; +import static org.apache.struts2.views.velocity.StrutsVelocityManager.KEY_VELOCITY_STRUTS_CONTEXT; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -42,13 +42,13 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -public class VelocityManagerTest extends StrutsJUnit4TestCase { +public class StrutsVelocityManagerTest extends StrutsJUnit4TestCase { - VelocityManager velocityManager = new VelocityManager(); + StrutsVelocityManager strutsVelocityManager = new StrutsVelocityManager(); @Before public void inject() { - container.inject(velocityManager); + container.inject(strutsVelocityManager); ServletActionContext.setServletContext(servletContext); } @@ -61,80 +61,80 @@ public void reset() { public void overridingPropertiesLoaded() { var props = new Properties(); props.setProperty("test", "value"); - velocityManager.setVelocityProperties(props); + strutsVelocityManager.setVelocityProperties(props); - velocityManager.init(servletContext); + strutsVelocityManager.init(servletContext); - assertEquals("value", velocityManager.getVelocityEngine().getProperty("test")); - assertEquals(props, velocityManager.getVelocityProperties()); + assertEquals("value", strutsVelocityManager.getVelocityEngine().getProperty("test")); + assertEquals(props, strutsVelocityManager.getVelocityProperties()); } @Test public void initSuccessful() { - velocityManager.init(servletContext); + strutsVelocityManager.init(servletContext); - assertNotNull(velocityManager.getVelocityEngine()); + assertNotNull(strutsVelocityManager.getVelocityEngine()); } @Test public void exceptionThrownOnNoServletContext() { - assertThrows(IllegalArgumentException.class, () -> velocityManager.init(null)); + assertThrows(IllegalArgumentException.class, () -> strutsVelocityManager.init(null)); } @Test public void initMethodIdempotent() { - velocityManager.init(servletContext); + strutsVelocityManager.init(servletContext); - var engine = velocityManager.getVelocityEngine(); + var engine = strutsVelocityManager.getVelocityEngine(); - velocityManager.init(servletContext); + strutsVelocityManager.init(servletContext); - assertEquals(engine, velocityManager.getVelocityEngine()); + assertEquals(engine, strutsVelocityManager.getVelocityEngine()); } @Test public void loadsConfigFromWebInfPath() { - velocityManager.setCustomConfigFile("webinf-velocity.properties"); + strutsVelocityManager.setCustomConfigFile("webinf-velocity.properties"); - velocityManager.init(servletContext); + strutsVelocityManager.init(servletContext); - assertEquals("webinf", velocityManager.getVelocityEngine().getProperty("test")); + assertEquals("webinf", strutsVelocityManager.getVelocityEngine().getProperty("test")); } @Test public void loadsConfigFromClassPath() { var servletContext = mock(ServletContext.class); doReturn(null).when(servletContext).getRealPath(anyString()); - velocityManager.setCustomConfigFile("test-velocity.properties"); + strutsVelocityManager.setCustomConfigFile("test-velocity.properties"); - velocityManager.init(servletContext); + strutsVelocityManager.init(servletContext); - assertEquals("value", velocityManager.getVelocityEngine().getProperty("test")); + assertEquals("value", strutsVelocityManager.getVelocityEngine().getProperty("test")); } @Test public void initWithToolboxLocation() { - velocityManager.setToolBoxLocation("tools.xml"); + strutsVelocityManager.setToolBoxLocation("tools.xml"); - velocityManager.init(servletContext); + strutsVelocityManager.init(servletContext); - assertNotNull(velocityManager.getVelocityEngine()); - assertNotNull(velocityManager.getVelocityTools()); + assertNotNull(strutsVelocityManager.getVelocityEngine()); + assertNotNull(strutsVelocityManager.getVelocityTools()); } @Test public void initFailsWithInvalidToolBoxLocation() { - velocityManager.setToolBoxLocation("invalid.xml"); + strutsVelocityManager.setToolBoxLocation("invalid.xml"); - Exception e = assertThrows(Exception.class, () -> velocityManager.init(servletContext)); + Exception e = assertThrows(Exception.class, () -> strutsVelocityManager.init(servletContext)); assertThat(e).hasMessageContaining("Could not find any configuration at invalid.xml"); } @Test public void createContext() { - velocityManager.init(servletContext); + strutsVelocityManager.init(servletContext); - Context context = velocityManager.createContext(ActionContext.getContext().getValueStack(), request, response); + Context context = strutsVelocityManager.createContext(ActionContext.getContext().getValueStack(), request, response); assertNotNull(context); assertThat(context.get("struts")).isInstanceOf(VelocityStrutsUtil.class); @@ -146,10 +146,10 @@ public void createContext() { @Test public void createToolboxContext() { - velocityManager.setToolBoxLocation("tools.xml"); - velocityManager.init(servletContext); + strutsVelocityManager.setToolBoxLocation("tools.xml"); + strutsVelocityManager.init(servletContext); - Context context = velocityManager.createContext(ActionContext.getContext().getValueStack(), request, response); + Context context = strutsVelocityManager.createContext(ActionContext.getContext().getValueStack(), request, response); assertNotNull(context); assertThat(context).isInstanceOf(ToolContext.class);