This is an automated email from the ASF dual-hosted git repository.

kusal pushed a commit to branch 7.0.x/WW-5449-remove-vel-tools
in repository https://gitbox.apache.org/repos/asf/struts.git

commit db7dd726129b27a381e4089aba83d7911b3ef996
Author: Kusal Kithul-Godage <g...@kusal.io>
AuthorDate: Fri Jul 26 19:17:00 2024 +1000

    WW-5449 Make Velocity Tools dependency optional for Velocity plugin
---
 plugins/sitemesh/pom.xml                           |  10 +
 plugins/velocity/pom.xml                           |   3 +-
 .../struts2/views/velocity/VelocityManager.java    | 360 ++++++++-------------
 .../struts2/views/velocity/VelocityTools.java      |  41 +++
 .../views/velocity/VelocityManagerTest.java        |   6 +-
 5 files changed, 193 insertions(+), 227 deletions(-)

diff --git a/plugins/sitemesh/pom.xml b/plugins/sitemesh/pom.xml
index c2e1546a3..4ab5e0702 100644
--- a/plugins/sitemesh/pom.xml
+++ b/plugins/sitemesh/pom.xml
@@ -44,6 +44,16 @@
             <artifactId>struts2-velocity-plugin</artifactId>
             <optional>true</optional>
         </dependency>
+        <dependency>
+            <groupId>org.apache.struts</groupId>
+            <artifactId>struts2-velocity-tools-view-jakarta</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.struts</groupId>
+            <artifactId>struts2-velocity-tools-jsp-jakarta</artifactId>
+            <optional>true</optional>
+        </dependency>
 
    </dependencies>
     <properties>
diff --git a/plugins/velocity/pom.xml b/plugins/velocity/pom.xml
index b467fa18a..02c7546e8 100644
--- a/plugins/velocity/pom.xml
+++ b/plugins/velocity/pom.xml
@@ -42,11 +42,12 @@
         <dependency>
             <groupId>org.apache.struts</groupId>
             <artifactId>struts2-velocity-tools-view-jakarta</artifactId>
+            <optional>true</optional>
         </dependency>
-
         <dependency>
             <groupId>org.apache.struts</groupId>
             <artifactId>struts2-velocity-tools-jsp-jakarta</artifactId>
+            <optional>true</optional>
         </dependency>
 
         <dependency>
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 cc4a20057..2b06e84bc 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
@@ -22,10 +22,12 @@ 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.ServletActionContext;
 import org.apache.struts2.StrutsConstants;
 import org.apache.struts2.StrutsException;
 import org.apache.struts2.views.TagLibraryDirectiveProvider;
@@ -34,13 +36,7 @@ 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.ToolContext;
-import org.apache.velocity.tools.ToolManager;
-import org.apache.velocity.tools.ToolboxFactory;
 
-import jakarta.servlet.ServletContext;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -48,16 +44,17 @@ import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 
-import static java.lang.String.format;
 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
@@ -72,11 +69,7 @@ public class VelocityManager implements 
VelocityManagerInterface {
 
     private VelocityEngine velocityEngine;
 
-    /**
-     * A reference to the toolbox manager.
-     */
-    protected ToolManager toolboxManager = null;
-    private String toolBoxLocation;
+    private VelocityTools velocityTools;
 
     /**
      * Names of contexts that will be chained on every request
@@ -96,9 +89,8 @@ public class VelocityManager implements 
VelocityManagerInterface {
 
     @Inject
     public void setContainer(Container container) {
-        List<TagLibraryDirectiveProvider> list = 
container.getInstanceNames(TagLibraryDirectiveProvider.class).stream()
-                .map(prefix -> 
container.getInstance(TagLibraryDirectiveProvider.class, 
prefix)).collect(toList());
-        this.tagLibraries = Collections.unmodifiableList(list);
+        this.tagLibraries = 
container.getInstanceNames(TagLibraryDirectiveProvider.class).stream()
+                .map(prefix -> 
container.getInstance(TagLibraryDirectiveProvider.class, prefix)).toList();
     }
 
     /**
@@ -120,7 +112,10 @@ public class VelocityManager implements 
VelocityManagerInterface {
      */
     @Override
     public Context createContext(ValueStack stack, HttpServletRequest req, 
HttpServletResponse res) {
-        Context context = buildToolContext();
+        Context context = null;
+        if (velocityTools != null) {
+            context = velocityTools.createContext();
+        }
         if (context == null) {
             context = buildContext(stack, req, res);
         }
@@ -137,42 +132,25 @@ public class VelocityManager implements 
VelocityManagerInterface {
         return context;
     }
 
-    protected Context buildToolContext() {
-        if (toolboxManager == null) {
-            return null;
-        }
-        ServletContext ctx;
-        try {
-            ctx = ServletActionContext.getServletContext();
-        } catch (NullPointerException e) {
-            return null;
-        }
-        if (ctx == null) {
-            return null;
-        }
-        ToolContext toolContext = new ToolContext(velocityEngine);
-        
toolContext.addToolbox(toolboxManager.getToolboxFactory().createToolbox(ToolboxFactory.DEFAULT_SCOPE));
-        return toolContext;
-    }
-
     /**
-     * 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.
+     * 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<VelocityContext> prepareChainedContexts(HttpServletRequest 
servletRequest, HttpServletResponse servletResponse, Map<String, Object> 
extraContext) {
+    protected List<VelocityContext> prepareChainedContexts(HttpServletRequest 
servletRequest,
+                                                           HttpServletResponse 
servletResponse,
+                                                           Map<String, Object> 
extraContext) {
         List<VelocityContext> contextList = new ArrayList<>();
         for (String className : chainedContextNames) {
             try {
                 VelocityContext velocityContext = (VelocityContext) 
objectFactory.buildBean(className, extraContext);
                 contextList.add(velocityContext);
             } catch (Exception e) {
-                LOG.warn(format("Unable to instantiate chained VelocityContext 
%s, skipping", className), e);
+                LOG.warn("Unable to instantiate chained VelocityContext {}, 
skipping", className, e);
             }
         }
         return contextList;
@@ -186,154 +164,118 @@ public class VelocityManager implements 
VelocityManagerInterface {
      */
     @Override
     public synchronized void init(ServletContext context) {
-        if (velocityEngine == null) {
-            velocityEngine = newVelocityEngine(context);
+        if (velocityEngine != null) {
+            return;
+        }
+        velocityEngine = newVelocityEngine(context);
+        if (velocityTools != null) {
+            velocityTools.init(context, velocityEngine);
         }
-        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);
+            throw new IllegalArgumentException("Error attempting to create a 
loadConfiguration from a null ServletContext!");
         }
-
         Properties properties = new Properties();
-
-        // now apply our systemic defaults, then allow user to override
-        applyDefaultConfiguration(context, properties);
-
+        applyDefaultConfiguration(context, properties); // Apply defaults 
before loading user overrides
         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;
+        applyUserConfiguration(context, properties);
 
-        if (customConfigFile != null) {
-            configfile = customConfigFile;
-        } else {
-            configfile = "velocity.properties";
+        if (velocityProperties != null) { // Apply additional overriding 
properties if any
+            velocityProperties.stringPropertyNames().forEach(k -> 
properties.setProperty(k, velocityProperties.getProperty(k)));
         }
-        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);
-                        }
-                    }
-                }
-            }
+        String userDirective = properties.getProperty(OLD_CUSTOM_DIRECTIVES);
+        String newDirective = isBlank(userDirective) ? defaultUserDirective : 
userDirective.strip() + "," + defaultUserDirective;
+        properties.setProperty(OLD_CUSTOM_DIRECTIVES, newDirective);
 
-            // 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 (LOG.isDebugEnabled()) {
+            LOG.debug("Initializing Velocity with the following properties 
...");
+            properties.stringPropertyNames().forEach(k -> LOG.debug("    '{}' 
= '{}'", k, properties.getProperty(k)));
+        }
+        return properties;
+    }
 
-            // if we've got something, load 'er up
-            if (in != null) {
-                LOG.info("Initializing velocity using {}", resourceLocation);
-                properties.load(in);
-            }
+    /**
+     * 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>
+     */
+    private void applyUserConfiguration(ServletContext context, Properties 
properties) {
+        String configFile = requireNonNullElse(customConfigFile, 
"velocity.properties").trim();
+        boolean isLoaded = false;
+        try {
+            isLoaded = loadFile(properties, context.getRealPath(configFile));
         } catch (IOException e) {
-            LOG.warn("Unable to load velocity configuration {}", 
resourceLocation, e);
-        } finally {
-            if (in != null) {
-                try {
-                    in.close();
-                } catch (IOException ignore) {
-                }
-            }
+            LOG.warn("Unable to load Velocity configuration from servlet 
context path", e);
         }
-
-        // overide with programmatically set properties
-        if (velocityProperties != null) {
-            for (Object o : velocityProperties.keySet()) {
-                String key = (String) o;
-                properties.setProperty(key, 
this.velocityProperties.getProperty(key));
+        if (!isLoaded) {
+            try {
+                isLoaded = loadFile(properties, 
context.getRealPath("/WEB-INF/" + configFile));
+            } catch (IOException e) {
+                LOG.warn("Unable to load Velocity configuration from WEB-INF 
path", e);
             }
         }
-
-        String userdirective = properties.getProperty("userdirective");
-        if (userdirective == null || userdirective.trim().isEmpty()) {
-            userdirective = defaultUserDirective;
-        } else {
-            userdirective = userdirective.trim() + "," + defaultUserDirective;
+        if (!isLoaded) {
+            try {
+                isLoaded = loadClassPathFile(properties, configFile);
+            } catch (IOException e) {
+                LOG.warn("Unable to load Velocity configuration from 
classpath", e);
+            }
         }
-        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);
+    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;
         }
+    }
 
-        return properties;
+    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 val) {
-        this.customConfigFile = val;
+    public void setCustomConfigFile(String customConfigFile) {
+        this.customConfigFile = customConfigFile;
     }
 
     @Inject(StrutsConstants.STRUTS_VELOCITY_TOOLBOXLOCATION)
     public void setToolBoxLocation(String toolboxLocation) {
-        this.toolBoxLocation = toolboxLocation;
+        if (!isBlank(toolboxLocation)) {
+            this.velocityTools = new VelocityTools(toolboxLocation);
+        }
     }
 
-    public ToolManager getToolboxManager() {
-        return toolboxManager;
+    public VelocityTools getVelocityTools() {
+        return velocityTools;
     }
 
     /**
-     * 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
+     * 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
@@ -344,38 +286,17 @@ public class VelocityManager implements 
VelocityManagerInterface {
     }
 
     /**
-     * Initializes the ServletToolboxManager for this servlet's
-     * toolbox (if any).
-     */
-    protected void initToolbox(ServletContext servletContext) {
-        if (StringUtils.isBlank(toolBoxLocation)) {
-            LOG.debug("Skipping ToolManager initialisation, [{}] was not 
defined", StrutsConstants.STRUTS_VELOCITY_TOOLBOXLOCATION);
-            return;
-        }
-        LOG.debug("Configuring Velocity ToolManager with {}", toolBoxLocation);
-        toolboxManager = new ToolManager();
-        toolboxManager.configure(toolBoxLocation);
-    }
-
-    /**
-     * <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
+     * 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>
@@ -389,21 +310,18 @@ public class VelocityManager implements 
VelocityManagerInterface {
         if (context == null) {
             throw new IllegalArgumentException("Error attempting to create a 
new VelocityEngine from a null ServletContext!");
         }
-
         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);
+        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.
+     * 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>
@@ -414,60 +332,56 @@ public class VelocityManager implements 
VelocityManagerInterface {
      * @param properties velocity properties
      */
     private void applyDefaultConfiguration(ServletContext context, Properties 
properties) {
-        // ensure that caching isn't overly aggressive
-
         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
-         * 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");
+        // 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 {
-            // remove strutsfile from resource loader property
-            String prop = properties.getProperty(Velocity.RESOURCE_LOADER);
-            if (prop.contains("strutsfile,")) {
-                prop = prop.replace("strutsfile,", "");
-            } else if (prop.contains(", strutsfile")) {
-                prop = prop.replace(", strutsfile", "");
-            } else if (prop.contains("strutsfile")) {
-                prop = prop.replace("strutsfile", "");
-            }
-
-            properties.setProperty(Velocity.RESOURCE_LOADER, prop);
+            clearStrutsFileResourceLoader(properties);
         }
 
-        /*
-         * 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
-         */
+        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");
-
-        // components
-        String directives = 
tagLibraries.stream().map(TagLibraryDirectiveProvider::getDirectiveClasses).flatMap(
-                Collection::stream).map(directive -> directive.getName() + 
",").collect(joining());
-
-        String userdirective = properties.getProperty("userdirective");
-        if (userdirective == null || userdirective.trim().isEmpty()) {
-            userdirective = directives;
-        } else {
-            userdirective = userdirective.trim() + "," + directives;
-        }
-        properties.setProperty("userdirective", userdirective);
     }
 
     /**
diff --git 
a/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/VelocityTools.java
 
b/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/VelocityTools.java
new file mode 100644
index 000000000..dfdfb9d64
--- /dev/null
+++ 
b/plugins/velocity/src/main/java/org/apache/struts2/views/velocity/VelocityTools.java
@@ -0,0 +1,41 @@
+package org.apache.struts2.views.velocity;
+
+import com.opensymphony.xwork2.ActionContext;
+import jakarta.servlet.ServletContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.tools.ToolContext;
+import org.apache.velocity.tools.ToolManager;
+import org.apache.velocity.tools.ToolboxFactory;
+
+import static org.apache.commons.lang3.ObjectUtils.requireNonEmpty;
+
+/**
+ * @since 7.0
+ */
+public class VelocityTools {
+
+    private final String toolLocation;
+    private ToolManager toolManager;
+    private VelocityEngine velocityEngine;
+
+    public VelocityTools(String toolLocation) {
+        this.toolLocation = requireNonEmpty(toolLocation);
+    }
+
+    public void init(ServletContext servletContext, VelocityEngine 
velocityEngine) {
+        this.toolManager = new ToolManager();
+        this.toolManager.configure(toolLocation);
+        this.velocityEngine = velocityEngine;
+    }
+
+    public Context createContext() {
+        if (toolManager == null || velocityEngine == null ||
+                ActionContext.getContext() == null || 
ActionContext.getContext().getServletContext() == null) {
+            return null;
+        }
+        var toolContext = new ToolContext(velocityEngine);
+        
toolContext.addToolbox(toolManager.getToolboxFactory().createToolbox(ToolboxFactory.DEFAULT_SCOPE));
+        return toolContext;
+    }
+}
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/VelocityManagerTest.java
index b95fc8d8a..ec2ebe5ce 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/VelocityManagerTest.java
@@ -20,6 +20,8 @@ package org.apache.struts2.views.velocity;
 
 import com.opensymphony.xwork2.ActionContext;
 import com.opensymphony.xwork2.util.ValueStack;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
 import org.apache.struts2.ServletActionContext;
 import org.apache.struts2.junit.StrutsJUnit4TestCase;
 import org.apache.velocity.context.Context;
@@ -28,8 +30,6 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
 import java.util.Properties;
 
 import static 
org.apache.struts2.views.velocity.VelocityManager.KEY_VELOCITY_STRUTS_CONTEXT;
@@ -75,7 +75,7 @@ public class VelocityManagerTest extends StrutsJUnit4TestCase 
{
         velocityManager.init(servletContext);
 
         assertNotNull(velocityManager.getVelocityEngine());
-        assertNotNull(velocityManager.getToolboxManager());
+        assertNotNull(velocityManager.getVelocityTools());
     }
 
     @Test

Reply via email to