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

schultz pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/main by this push:
     new dc5b18d898 Add experimental FilterValve, which allows a Filter to be 
run within the Valve chain.
dc5b18d898 is described below

commit dc5b18d8989cf816c0870f236f6ccaade8b16ff3
Author: Christopher Schultz <ch...@christopherschultz.net>
AuthorDate: Mon Oct 9 15:45:42 2023 -0400

    Add experimental FilterValve, which allows a Filter to be run within the 
Valve chain.
---
 .../apache/catalina/startup/ContextRuleSet.java    |   3 +
 .../org/apache/catalina/startup/EngineRuleSet.java |   3 +
 java/org/apache/catalina/startup/HostRuleSet.java  |   3 +
 java/org/apache/catalina/valves/FilterValve.java   | 262 +++++++++++++++++++++
 4 files changed, 271 insertions(+)

diff --git a/java/org/apache/catalina/startup/ContextRuleSet.java 
b/java/org/apache/catalina/startup/ContextRuleSet.java
index 4aa081e083..5864a104c2 100644
--- a/java/org/apache/catalina/startup/ContextRuleSet.java
+++ b/java/org/apache/catalina/startup/ContextRuleSet.java
@@ -212,6 +212,9 @@ public class ContextRuleSet implements RuleSet {
                                  null, // MUST be specified in the element
                                  "className");
         digester.addSetProperties(prefix + "Context/Valve");
+        digester.addCallMethod(prefix + "Context/Valve/init-param", 
"addInitParam", 2);
+        digester.addCallParam(prefix + "Context/Valve/init-param/param-name", 
0);
+        digester.addCallParam(prefix + "Context/Valve/init-param/param-value", 
1);
         digester.addSetNext(prefix + "Context/Valve",
                             "addValve",
                             "org.apache.catalina.Valve");
diff --git a/java/org/apache/catalina/startup/EngineRuleSet.java 
b/java/org/apache/catalina/startup/EngineRuleSet.java
index f98e285a86..153d364854 100644
--- a/java/org/apache/catalina/startup/EngineRuleSet.java
+++ b/java/org/apache/catalina/startup/EngineRuleSet.java
@@ -111,6 +111,9 @@ public class EngineRuleSet implements RuleSet {
                                  null, // MUST be specified in the element
                                  "className");
         digester.addSetProperties(prefix + "Engine/Valve");
+        digester.addCallMethod(prefix + "Engine/Valve/init-param", 
"addInitParam", 2);
+        digester.addCallParam(prefix + "Engine/Valve/init-param/param-name", 
0);
+        digester.addCallParam(prefix + "Engine/Valve/init-param/param-value", 
1);
         digester.addSetNext(prefix + "Engine/Valve",
                             "addValve",
                             "org.apache.catalina.Valve");
diff --git a/java/org/apache/catalina/startup/HostRuleSet.java 
b/java/org/apache/catalina/startup/HostRuleSet.java
index 3114cc1f9a..135ceb06c4 100644
--- a/java/org/apache/catalina/startup/HostRuleSet.java
+++ b/java/org/apache/catalina/startup/HostRuleSet.java
@@ -115,6 +115,9 @@ public class HostRuleSet implements RuleSet {
                                  null, // MUST be specified in the element
                                  "className");
         digester.addSetProperties(prefix + "Host/Valve");
+        digester.addCallMethod(prefix + "Host/Valve/init-param", 
"addInitParam", 2);
+        digester.addCallParam(prefix + "Host/Valve/init-param/param-name", 0);
+        digester.addCallParam(prefix + "Host/Valve/init-param/param-value", 1);
         digester.addSetNext(prefix + "Host/Valve",
                             "addValve",
                             "org.apache.catalina.Valve");
diff --git a/java/org/apache/catalina/valves/FilterValve.java 
b/java/org/apache/catalina/valves/FilterValve.java
new file mode 100644
index 0000000000..44192671c9
--- /dev/null
+++ b/java/org/apache/catalina/valves/FilterValve.java
@@ -0,0 +1,262 @@
+package org.apache.catalina.valves;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.tomcat.util.threads.ScheduledThreadPoolExecutor;
+
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+
+/**
+ * <p>A Valve to wrap a Filter, allowing a user to run Servlet Filters as a
+ * part of the Valve chain.</p>
+ *
+ * <p>There are some caveats you must be aware of when using this Valve
+ * to wrap a Filter:</p>
+ *
+ * <ul>
+ *   <li>You get a <i>separate instance</i> of your Filter class distinct
+ *       from any that may be instantiated within your application.</li>
+ *   <li>Calls to {@link FilterConfig#getFilterName()} will return 
<code>null</code>.</li>
+ *   <li>Calling {@link FilterConfig#getServletContext()} will return
+ *       the proper ServletContext for a Valve/Filter attached to a
+ *       <code>&lt;Context&gt;</code>, but will return a ServletContext
+ *       which is nearly useless for any Valve/Filter specified on
+ *       an <code>&lt;Engine&gt;</code> or <code>&gt;Host&lt;</code>.</li>
+ *   <li>Your Filter <b>MUST NOT</b> wrap the {@link ServletRequest} or
+ *       {@link ServletResponse} objects, or an exception will be thrown.</li>
+ * </ul>
+ *
+ * @see Valve
+ * @see Filter
+ */
+public class FilterValve
+    extends ValveBase
+    implements FilterConfig
+{
+    /**
+     * The name of the Filter class that will be instantiated.
+     */
+    private String filterClassName;
+
+    /**
+     * The init-params for the Filter.
+     */
+    private HashMap<String,String> filterInitParams;
+
+    /**
+     * The instance of the Filter to be invoked.
+     */
+    private Filter filter;
+
+    /**
+     * The ServletContet to be provided to the Filter if it requests one.
+     */
+    private ServletContext application;
+
+    /**
+     * Sets the name of the class for the Filter.
+     *
+     * @param filterClassName The class name for the Filter.
+     */
+    public void setFilterClass(String filterClassName) {
+        setFilterClassName(filterClassName);
+    }
+
+    /**
+     * Sets the name of the class for the Filter.
+     *
+     * @param filterClassName The class name for the Filter.
+     */
+    public void setFilterClassName(String filterClassName) {
+        this.filterClassName = filterClassName;
+    }
+
+    /**
+     * Gets the name of the class for the Filter.
+     *
+     * @return The class name for the Filter.
+     */
+    public String getFilterClassName() {
+        return filterClassName;
+    }
+
+    /**
+     * Adds an initialization parameter for the Filter.
+     *
+     * @param paramName The name of the parameter.
+     * @param paramValue The value of the parameter.
+     */
+    public void addInitParam(String paramName, String paramValue) {
+        if(null == filterInitParams) {
+            filterInitParams = new HashMap<>();
+        }
+
+        filterInitParams.put(paramName, paramValue);
+    }
+
+    /**
+     * Gets the name of the Filter.
+     *
+     * @return <code>null</code>
+     */
+    @Override
+    public String getFilterName() {
+        return null;
+    }
+
+    /**
+     * Gets the ServletContext.
+     *
+     * Note that this will be of limited use if the Valve/Filter is not
+     * attached to a <code>&lt;Context&gt;</code>.
+     */
+    @Override
+    public ServletContext getServletContext() {
+        if(null == application) {
+            throw new IllegalStateException("Filter " + filter + " has called 
getServletContext from FilterValve, but this FilterValve is not ");
+        } else {
+            return application;
+        }
+    }
+
+    /**
+     * Gets the initialization parameter with the specified name.
+     *
+     * @param name The name of the initialization parameter.
+     *
+     * @return The value for the initialization parameter, or
+     *         <code>null</code> if there is no value for the
+     *         specified initialization parameter name.
+     */
+    @Override
+    public String getInitParameter(String name) {
+        if(null == filterInitParams) {
+            return null;
+        } else {
+            return filterInitParams.get(name);
+        }
+    }
+
+    /**
+     * Gets an enumeration of the names of all initialization parameters.
+     *
+     * @return An enumeration of the names of all initialization parameters.
+     */
+    @Override
+    public Enumeration<String> getInitParameterNames() {
+        if(null == filterInitParams) {
+            return Collections.emptyEnumeration();
+        } else {
+            return Collections.enumeration(filterInitParams.keySet());
+        }
+    }
+
+    @Override
+    protected synchronized void startInternal() throws LifecycleException {
+        super.startInternal();
+
+        if(null == getFilterClassName()) {
+            throw new LifecycleException("No filterClass specified for 
FilterValve: a filterClass is required");
+        }
+        Container parent = super.getContainer();
+        if(parent instanceof Context) {
+            application = ((Context)parent).getServletContext();
+        } else {
+            final ScheduledExecutorService executor = 
Container.getService(super.getContainer()).getServer().getUtilityExecutor();
+
+            // I don't feel like writing a whole trivial class just for this 
one thing.
+            application = 
(ServletContext)Proxy.newProxyInstance(getClass().getClassLoader(),
+                    new Class<?>[] { ServletContext.class },
+                    new InvocationHandler() {
+                        @Override
+                        public Object invoke(Object proxy, Method method, 
Object[] args) throws Throwable {
+                            if("getAttribute".equals(method.getName())
+                                    && null != args
+                                    && 1 == args.length
+                                    && 
ScheduledThreadPoolExecutor.class.getName().equals(args[0])) {
+                                return executor;
+                            } else {
+                                throw new UnsupportedOperationException("This 
ServletContet is not really meant to be used.");
+                            }
+                        }
+            });
+        }
+
+        try {
+            filter = (Filter) 
Class.forName(getFilterClassName()).getConstructor(new Class[0]).newInstance();
+
+            filter.init(this);
+        } catch (ServletException | InstantiationException | 
IllegalAccessException | IllegalArgumentException | InvocationTargetException | 
NoSuchMethodException | SecurityException | ClassNotFoundException se) {
+            throw new LifecycleException("Failed to start FilterValve for 
filter " + filter, se);
+        }
+    }
+
+    @Override
+    protected synchronized void stopInternal() throws LifecycleException {
+        super.stopInternal();
+
+        if(null != filter) {
+            try {
+                filter.destroy();
+            } finally {
+                filter = null;
+            }
+        }
+    }
+
+    @Override
+    public void invoke(final Request request, final Response response) throws 
IOException, ServletException {
+        // Allow our FilterChain object to share data back to us.
+        //
+        // FilterCallInfo captures information about what the Filter
+        // ultimately did, specifically whether or not FilterChain.doFilter
+        // was called, and with what arguments.
+        final FilterCallInfo filterCallInfo = new FilterCallInfo();
+
+        filter.doFilter(request, response, filterCallInfo);
+
+        if(!filterCallInfo.stop) {
+            if(request != filterCallInfo.request) {
+                throw new IllegalStateException("Filter " + filter + " has 
illegally changed or wrapped the request");
+            }
+
+            if(response != filterCallInfo.response) {
+                throw new IllegalStateException("Filter " + filter + " has 
illegally changed or wrapped the response");
+            }
+
+            getNext().invoke(request, response);
+        }
+    }
+
+    private static final class FilterCallInfo implements FilterChain {
+        private boolean stop = true;
+        private ServletRequest request;
+        private ServletResponse response;
+
+        @Override
+        public void doFilter(ServletRequest request, ServletResponse response) 
throws IOException, ServletException {
+            this.request = request;
+            this.response = response;
+            this.stop = false;
+        }
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to