You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by fh...@apache.org on 2020/07/21 15:22:44 UTC

[tomcat] branch master updated: Avoid reflection for default instantiation

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

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


The following commit(s) were added to refs/heads/master by this push:
     new c08bf81  Avoid reflection for default instantiation
c08bf81 is described below

commit c08bf81f0db7742779ab0c5da45818dde8465d34
Author: Filip Hanik <fh...@pivotal.io>
AuthorDate: Mon Jul 13 12:43:55 2020 -0700

    Avoid reflection for default instantiation
    
    (Most commonly used codepath)
    
    Avoid having to load APR classes in the Connector
    
    Ensure that IntrospectionUtils can call setProperty on
    PersistentProviderRegistrations
---
 .../auth/message/config/AuthConfigFactory.java     |  8 ++-
 .../jaspic/PersistentProviderRegistrations.java    | 12 ++++-
 java/org/apache/catalina/connector/Connector.java  |  8 +--
 .../apache/catalina/core/AprLifecycleListener.java | 32 +++++-------
 java/org/apache/catalina/core/AprStatus.java       | 60 ++++++++++++++++++++++
 java/org/apache/catalina/core/StandardHost.java    |  4 +-
 java/org/apache/catalina/loader/WebappLoader.java  |  4 ++
 java/org/apache/catalina/startup/Tomcat.java       |  8 ++-
 8 files changed, 109 insertions(+), 27 deletions(-)

diff --git a/java/jakarta/security/auth/message/config/AuthConfigFactory.java b/java/jakarta/security/auth/message/config/AuthConfigFactory.java
index d0e1cbd..6f02fde 100644
--- a/java/jakarta/security/auth/message/config/AuthConfigFactory.java
+++ b/java/jakarta/security/auth/message/config/AuthConfigFactory.java
@@ -72,8 +72,12 @@ public abstract class AuthConfigFactory {
                             // this class. Note that the Thread context class loader
                             // should not be used since that would trigger a memory leak
                             // in container environments.
-                            Class<?> clazz = Class.forName(className);
-                            return (AuthConfigFactory) clazz.getConstructor().newInstance();
+                            if (className.equals("org.apache.catalina.authenticator.jaspic.AuthConfigFactoryImpl")) {
+                                return new org.apache.catalina.authenticator.jaspic.AuthConfigFactoryImpl();
+                            } else {
+                                Class<?> clazz = Class.forName(className);
+                                return (AuthConfigFactory) clazz.getConstructor().newInstance();
+                            }
                         }
                     });
                 } catch (PrivilegedActionException e) {
diff --git a/java/org/apache/catalina/authenticator/jaspic/PersistentProviderRegistrations.java b/java/org/apache/catalina/authenticator/jaspic/PersistentProviderRegistrations.java
index a1ba60c..8ffe8ec 100644
--- a/java/org/apache/catalina/authenticator/jaspic/PersistentProviderRegistrations.java
+++ b/java/org/apache/catalina/authenticator/jaspic/PersistentProviderRegistrations.java
@@ -41,7 +41,7 @@ import org.xml.sax.SAXException;
  * Utility class for the loading and saving of JASPIC persistent provider
  * registrations.
  */
-final class PersistentProviderRegistrations {
+public final class PersistentProviderRegistrations {
 
     private static final StringManager sm =
             StringManager.getManager(PersistentProviderRegistrations.class);
@@ -233,6 +233,16 @@ final class PersistentProviderRegistrations {
         public void addProperty(Property property) {
             properties.put(property.getName(), property.getValue());
         }
+
+        /**
+         * Used by IntrospectionUtils via reflection.
+         * @param name - the name of of the property to set on this object
+         * @param value - the value to set
+         * @see #addProperty(String, String)
+         */
+        public void setProperty(String name, String value) {
+            addProperty(name, value);
+        }
         void addProperty(String name, String value) {
             properties.put(name, value);
         }
diff --git a/java/org/apache/catalina/connector/Connector.java b/java/org/apache/catalina/connector/Connector.java
index c35c4cf..c2e7e25 100644
--- a/java/org/apache/catalina/connector/Connector.java
+++ b/java/org/apache/catalina/connector/Connector.java
@@ -29,7 +29,7 @@ import org.apache.catalina.Globals;
 import org.apache.catalina.LifecycleException;
 import org.apache.catalina.LifecycleState;
 import org.apache.catalina.Service;
-import org.apache.catalina.core.AprLifecycleListener;
+import org.apache.catalina.core.AprStatus;
 import org.apache.catalina.util.LifecycleMBeanBase;
 import org.apache.coyote.AbstractProtocol;
 import org.apache.coyote.Adapter;
@@ -1022,15 +1022,15 @@ public class Connector extends LifecycleMBeanBase  {
             setParseBodyMethods(getParseBodyMethods());
         }
 
-        if (protocolHandler.isAprRequired() && !AprLifecycleListener.isInstanceCreated()) {
+        if (protocolHandler.isAprRequired() && !AprStatus.isInstanceCreated()) {
             throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprListener",
                     getProtocolHandlerClassName()));
         }
-        if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
+        if (protocolHandler.isAprRequired() && !AprStatus.isAprAvailable()) {
             throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprLibrary",
                     getProtocolHandlerClassName()));
         }
-        if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
+        if (AprStatus.isAprAvailable() && AprStatus.getUseOpenSSL() &&
                 protocolHandler instanceof AbstractHttp11JsseProtocol) {
             AbstractHttp11JsseProtocol<?> jsseProtocolHandler =
                     (AbstractHttp11JsseProtocol<?>) protocolHandler;
diff --git a/java/org/apache/catalina/core/AprLifecycleListener.java b/java/org/apache/catalina/core/AprLifecycleListener.java
index 8e1d85c..9321cf8 100644
--- a/java/org/apache/catalina/core/AprLifecycleListener.java
+++ b/java/org/apache/catalina/core/AprLifecycleListener.java
@@ -46,7 +46,6 @@ public class AprLifecycleListener
     implements LifecycleListener {
 
     private static final Log log = LogFactory.getLog(AprLifecycleListener.class);
-    private static boolean instanceCreated = false;
     /**
      * Info messages during init() are cached until Lifecycle.BEFORE_INIT_EVENT
      * so that, in normal (non-error) cases, init() related log messages appear
@@ -76,9 +75,6 @@ public class AprLifecycleListener
     protected static String FIPSMode = "off"; // default off, valid only when SSLEngine="on"
     protected static String SSLRandomSeed = "builtin";
     protected static boolean sslInitialized = false;
-    protected static boolean aprInitialized = false;
-    protected static boolean aprAvailable = false;
-    protected static boolean useOpenSSL = true;
     protected static boolean fipsModeActive = false;
 
     /**
@@ -101,16 +97,16 @@ public class AprLifecycleListener
 
     public static boolean isAprAvailable() {
         //https://bz.apache.org/bugzilla/show_bug.cgi?id=48613
-        if (instanceCreated) {
+        if (AprStatus.isInstanceCreated()) {
             synchronized (lock) {
                 init();
             }
         }
-        return aprAvailable;
+        return AprStatus.isAprAvailable();
     }
 
     public AprLifecycleListener() {
-        instanceCreated = true;
+        AprStatus.setInstanceCreated(true);
     }
 
     // ---------------------------------------------- LifecycleListener Methods
@@ -130,7 +126,7 @@ public class AprLifecycleListener
                     log.info(msg);
                 }
                 initInfoLogMessages.clear();
-                if (aprAvailable) {
+                if (AprStatus.isAprAvailable()) {
                     try {
                         initializeSSL();
                     } catch (Throwable t) {
@@ -150,7 +146,7 @@ public class AprLifecycleListener
             }
         } else if (Lifecycle.AFTER_DESTROY_EVENT.equals(event.getType())) {
             synchronized (lock) {
-                if (!aprAvailable) {
+                if (!AprStatus.isAprAvailable()) {
                     return;
                 }
                 try {
@@ -173,8 +169,8 @@ public class AprLifecycleListener
         Method method = Class.forName("org.apache.tomcat.jni.Library")
             .getMethod(methodName, (Class [])null);
         method.invoke(null, (Object []) null);
-        aprAvailable = false;
-        aprInitialized = false;
+        AprStatus.setAprAvailable(false);
+        AprStatus.setAprInitialized(false);
         sslInitialized = false; // Well we cleaned the pool in terminate.
         fipsModeActive = false;
     }
@@ -188,10 +184,10 @@ public class AprLifecycleListener
         int rqver = TCN_REQUIRED_MAJOR * 1000 + TCN_REQUIRED_MINOR * 100 + TCN_REQUIRED_PATCH;
         int rcver = TCN_REQUIRED_MAJOR * 1000 + TCN_RECOMMENDED_MINOR * 100 + TCN_RECOMMENDED_PV;
 
-        if (aprInitialized) {
+        if (AprStatus.isAprInitialized()) {
             return;
         }
-        aprInitialized = true;
+        AprStatus.setAprInitialized(true);
 
         try {
             Library.initialize(null);
@@ -253,7 +249,7 @@ public class AprLifecycleListener
                 Boolean.valueOf(Library.APR_HAS_SO_ACCEPTFILTER),
                 Boolean.valueOf(Library.APR_HAS_RANDOM)));
 
-        aprAvailable = true;
+        AprStatus.setAprAvailable(true);
     }
 
     private static void initializeSSL() throws Exception {
@@ -397,17 +393,17 @@ public class AprLifecycleListener
     }
 
     public void setUseOpenSSL(boolean useOpenSSL) {
-        if (useOpenSSL != AprLifecycleListener.useOpenSSL) {
-            AprLifecycleListener.useOpenSSL = useOpenSSL;
+        if (useOpenSSL != AprStatus.getUseOpenSSL()) {
+            AprStatus.setUseOpenSSL(useOpenSSL);
         }
     }
 
     public static boolean getUseOpenSSL() {
-        return useOpenSSL;
+        return AprStatus.getUseOpenSSL();
     }
 
     public static boolean isInstanceCreated() {
-        return instanceCreated;
+        return AprStatus.isInstanceCreated();
     }
 
 }
diff --git a/java/org/apache/catalina/core/AprStatus.java b/java/org/apache/catalina/core/AprStatus.java
new file mode 100644
index 0000000..5374d07
--- /dev/null
+++ b/java/org/apache/catalina/core/AprStatus.java
@@ -0,0 +1,60 @@
+/*
+ * 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.catalina.core;
+
+/**
+ * Holds APR status without the need to load other classes.
+ */
+public class AprStatus {
+    private static volatile boolean aprInitialized = false;
+    private static volatile boolean aprAvailable = false;
+    private static volatile boolean useOpenSSL = true;
+    private static volatile boolean instanceCreated = false;
+
+
+    public static boolean isAprInitialized() {
+        return aprInitialized;
+    }
+
+    public static boolean isAprAvailable() {
+        return aprAvailable;
+    }
+
+    public static boolean getUseOpenSSL() {
+        return useOpenSSL;
+    }
+
+    public static boolean isInstanceCreated() {
+        return instanceCreated;
+    }
+
+    public static void setAprInitialized(boolean aprInitialized) {
+        AprStatus.aprInitialized = aprInitialized;
+    }
+
+    public static void setAprAvailable(boolean aprAvailable) {
+        AprStatus.aprAvailable = aprAvailable;
+    }
+
+    public static void setUseOpenSSL(boolean useOpenSSL) {
+        AprStatus.useOpenSSL = useOpenSSL;
+    }
+
+    public static void setInstanceCreated(boolean instanceCreated) {
+        AprStatus.instanceCreated = instanceCreated;
+    }
+}
diff --git a/java/org/apache/catalina/core/StandardHost.java b/java/org/apache/catalina/core/StandardHost.java
index 48e9dfb..bf8a096 100644
--- a/java/org/apache/catalina/core/StandardHost.java
+++ b/java/org/apache/catalina/core/StandardHost.java
@@ -42,6 +42,7 @@ import org.apache.catalina.LifecycleListener;
 import org.apache.catalina.Valve;
 import org.apache.catalina.loader.WebappClassLoaderBase;
 import org.apache.catalina.util.ContextName;
+import org.apache.catalina.valves.ErrorReportValve;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.ExceptionUtils;
@@ -827,7 +828,8 @@ public class StandardHost extends ContainerBase implements Host {
                     }
                 }
                 if(!found) {
-                    Valve valve =
+                    Valve valve = ErrorReportValve.class.getName().equals(errorValve) ?
+                        new ErrorReportValve() :
                         (Valve) Class.forName(errorValve).getConstructor().newInstance();
                     getPipeline().addValve(valve);
                 }
diff --git a/java/org/apache/catalina/loader/WebappLoader.java b/java/org/apache/catalina/loader/WebappLoader.java
index 91cb3ad..3c3026b 100644
--- a/java/org/apache/catalina/loader/WebappLoader.java
+++ b/java/org/apache/catalina/loader/WebappLoader.java
@@ -398,6 +398,10 @@ public class WebappLoader extends LifecycleMBeanBase implements Loader{
     private WebappClassLoaderBase createClassLoader()
         throws Exception {
 
+        if (ParallelWebappClassLoader.class.getName().equals(loaderClass)) {
+            return new ParallelWebappClassLoader(context.getParentClassLoader());
+        }
+
         Class<?> clazz = Class.forName(loaderClass);
         WebappClassLoaderBase classLoader = null;
 
diff --git a/java/org/apache/catalina/startup/Tomcat.java b/java/org/apache/catalina/startup/Tomcat.java
index 5ac0154..db6fb8e 100644
--- a/java/org/apache/catalina/startup/Tomcat.java
+++ b/java/org/apache/catalina/startup/Tomcat.java
@@ -1011,6 +1011,7 @@ public class Tomcat {
      * @return newly created {@link Context}
      */
     private Context createContext(Host host, String url) {
+        String defaultContextClass = StandardContext.class.getName();
         String contextClass = StandardContext.class.getName();
         if (host == null) {
             host = this.getHost();
@@ -1019,8 +1020,13 @@ public class Tomcat {
             contextClass = ((StandardHost) host).getContextClass();
         }
         try {
-            return (Context) Class.forName(contextClass).getConstructor()
+            if (defaultContextClass.equals(contextClass)) {
+                return new StandardContext();
+            } else {
+                return (Context) Class.forName(contextClass).getConstructor()
                     .newInstance();
+            }
+
         } catch (ReflectiveOperationException  | IllegalArgumentException | SecurityException e) {
             throw new IllegalArgumentException(sm.getString("tomcat.noContextClass", contextClass, host, url), e);
         }


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


Re: [tomcat] branch master updated: Avoid reflection for default instantiation

Posted by Christopher Schultz <ch...@christopherschultz.net>.
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

Filip,

On 7/22/20 12:41, Filip Hanik wrote:
> Hi Christopher,
>>> environments. -                            Class<?> clazz =
>>> Class.forName(className); -                            return
>>> (AuthConfigFactory) clazz.getConstructor().newInstance(); + if
>>> (className.equals("org.apache.catalina.authenticator.jaspic.AuthConf
ig
>>
>>>
FactoryImpl"))
>>> {
>>
>> Why not use AuthConfigFactoryImpl.class.getName()? It may help in
>> the future with refactoring.
>
> [Filip Hanik] Trying to avoid a circular dependency. You see the
> javax/jakarta package should not import org.apache.catalina code. I
> should be able to execute the AuthConfigFactory code without
> needing to load
> org.apache.catalina.authenticator.jaspic.AuthConfigFactoryImpl
> class. The JVM is smart enough that if the execution doesn't enter
> the if statement block, it won't attempt the classloading of the
> AuthConfigFactoryImpl class. However, if the AuthConfigFactoryImpl
> Class itself is part of the evaluation statement, it will be
> loaded.
>
> The previous implementation also had it as a string, instead of
> AuthConfigFactoryImpl.class.getName() for the same reason.
> https://github.com/apache/tomcat/blob/35dc7b9288aad4a7d70750c157543d4f
f1593c98/java/jakarta/security/auth/message/config/AuthConfigFactory.jav
a#L48-L49
>
>  This way, I can build a jakarta.security.auth.message library,
> that can be used without the org.apache.catalina library.

That's a very good reason. Thanks for explaining.

> I need to change my commit to use the constant, instead of the
> duplicated string in the IF statement.
>
> if (className.equals(DEFAULT_JASPI_AUTHCONFIGFACTORYIMPL)) { return
> new
> org.apache.catalina.authenticator.jaspic.AuthConfigFactoryImpl(); }
> else { Class<?> clazz = Class.forName(className); return
> (AuthConfigFactory) clazz.getConstructor().newInstance(); }

:)

- -chris

-----BEGIN PGP SIGNATURE-----
Comment: Using GnuPG with Thunderbird - https://www.enigmail.net/

iQIzBAEBCAAdFiEEMmKgYcQvxMe7tcJcHPApP6U8pFgFAl8ZyrUACgkQHPApP6U8
pFghOg//UxcPA7Xm+SKXtyCWEIcabjFkbdHv9o8kOHTiWVSUOrhXLBvTznDKYmYk
+5zFjxsFbBjj1amQnGER/3zJSSJmvdcEUohpHpYHUHFGLh59YyXTVb0ou8PSiX+B
iFEZCqKrkylZPCn21tPXN4wgmnvQxcD9S3++vZBWyCCiQw/BoUYwYGLtg9/sgCjj
eqk2r/yqGRlVtHsMEu04wMadcqpum6f14LO1b8J1C5jP0N9fwiGTEsD4HXskcUfQ
PeDjHtG6tJPXwfYjwRjzZolQIFmQwJ1B6WLufsyw0ZrUVBUENkU4xQwBy3pcVewn
0xc9+vgg+VSXblrDMnoswUBf2hLmfmpw49evcjeKSY7q0G0Fdoe1lUmt+OM74ppK
EZ6qmvCphWtXakyCU5uXx82nQMsNXdwmgLG3Dni4ya79dVoGvn8j3Guh/7g45Jet
0bc7x7KsouqJIbqQAPqdt8E2xKRARsdUE4BH9S0ENkvTqnhhbCWmlxJk3CiPpwoy
3zD5xt9CoXuKX/CjK92hP+nw4b1j+Mdhmwj4whM4FbcvLC6Rq8JXt0obysArweT8
FbfZD45OsWqbgVrVpwcCt19z25+6Ar02DvUz0CkI1sbxzS+hd1yhp2BRbiqTd6HY
EkUodl2R7H95ZmIRZ0QhcnEyq5tPJUcspXLvmzQ5Li25oJEWLrc=
=XYLJ
-----END PGP SIGNATURE-----

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


RE: [tomcat] branch master updated: Avoid reflection for default instantiation

Posted by Filip Hanik <fh...@vmware.com>.
Hi Christopher,
> > environments. -                            Class<?> clazz =
> > Class.forName(className); -                            return
> > (AuthConfigFactory) clazz.getConstructor().newInstance(); + if
> > (className.equals("org.apache.catalina.authenticator.jaspic.AuthConfig
> FactoryImpl"))
> > {
> 
> Why not use AuthConfigFactoryImpl.class.getName()? It may help in the
> future with refactoring.

[Filip Hanik] 
Trying to avoid a circular dependency. You see the javax/jakarta package should not import org.apache.catalina code. I should be able to execute the AuthConfigFactory code without needing to load org.apache.catalina.authenticator.jaspic.AuthConfigFactoryImpl class. The JVM is smart enough that if the execution doesn't enter the if statement block, it won't attempt the classloading of the AuthConfigFactoryImpl class. However, if the AuthConfigFactoryImpl Class itself is part of the evaluation statement, it will be loaded.

The previous implementation also had it as a string, instead of AuthConfigFactoryImpl.class.getName() for the same reason.
https://github.com/apache/tomcat/blob/35dc7b9288aad4a7d70750c157543d4ff1593c98/java/jakarta/security/auth/message/config/AuthConfigFactory.java#L48-L49

This way, I can build a jakarta.security.auth.message library, that can be used without the org.apache.catalina library.

I need to change my commit to use the constant, instead of the duplicated string in the IF statement.

                            if (className.equals(DEFAULT_JASPI_AUTHCONFIGFACTORYIMPL)) {
                                return new org.apache.catalina.authenticator.jaspic.AuthConfigFactoryImpl();
                            } else {
                                Class<?> clazz = Class.forName(className);
                                return (AuthConfigFactory) clazz.getConstructor().newInstance();
                            }

> 
> - -chris 


Re: [tomcat] branch master updated: Avoid reflection for default instantiation

Posted by Christopher Schultz <ch...@christopherschultz.net>.
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

Filip,

On 7/21/20 11:22, fhanik@apache.org wrote:
> This is an automated email from the ASF dual-hosted git
> repository.
>
> fhanik pushed a commit to branch master in repository
> https://gitbox.apache.org/repos/asf/tomcat.git
>
>
> The following commit(s) were added to refs/heads/master by this
> push: new c08bf81  Avoid reflection for default instantiation
> c08bf81 is described below
>
> commit c08bf81f0db7742779ab0c5da45818dde8465d34 Author: Filip Hanik
> <fh...@pivotal.io> AuthorDate: Mon Jul 13 12:43:55 2020 -0700
>
> Avoid reflection for default instantiation
>
> (Most commonly used codepath)
>
> Avoid having to load APR classes in the Connector
>
> Ensure that IntrospectionUtils can call setProperty on
> PersistentProviderRegistrations ---
> .../auth/message/config/AuthConfigFactory.java     |  8 ++-
> .../jaspic/PersistentProviderRegistrations.java    | 12 ++++-
> java/org/apache/catalina/connector/Connector.java  |  8 +--
> .../apache/catalina/core/AprLifecycleListener.java | 32
> +++++------- java/org/apache/catalina/core/AprStatus.java       |
> 60 ++++++++++++++++++++++
> java/org/apache/catalina/core/StandardHost.java    |  4 +-
> java/org/apache/catalina/loader/WebappLoader.java  |  4 ++
> java/org/apache/catalina/startup/Tomcat.java       |  8 ++- 8 files
> changed, 109 insertions(+), 27 deletions(-)
>
> diff --git
> a/java/jakarta/security/auth/message/config/AuthConfigFactory.java
> b/java/jakarta/security/auth/message/config/AuthConfigFactory.java
> index d0e1cbd..6f02fde 100644 ---
> a/java/jakarta/security/auth/message/config/AuthConfigFactory.java
> +++
> b/java/jakarta/security/auth/message/config/AuthConfigFactory.java
> @@ -72,8 +72,12 @@ public abstract class AuthConfigFactory { //
> this class. Note that the Thread context class loader // should not
> be used since that would trigger a memory leak // in container
> environments. -                            Class<?> clazz =
> Class.forName(className); -                            return
> (AuthConfigFactory) clazz.getConstructor().newInstance(); +
> if
> (className.equals("org.apache.catalina.authenticator.jaspic.AuthConfig
FactoryImpl"))
> {

Why not use AuthConfigFactoryImpl.class.getName()? It may help in the
future with refactoring.

- -chris
-----BEGIN PGP SIGNATURE-----
Comment: Using GnuPG with Thunderbird - https://www.enigmail.net/

iQIzBAEBCAAdFiEEMmKgYcQvxMe7tcJcHPApP6U8pFgFAl8YXgIACgkQHPApP6U8
pFgHmA/8CweFBvWtEn14ZzUZHkA/7HVaLPG6r7Y4qzkcrzJfQImYzV7E0x3NL59m
fDagGxJrxugESEKf3HLmq5VIzAlemkbPxYQ7S4KaUJVHbRW/MH9zPVzbvAVXy4Gm
CIpVF/QoXftJ9WYsMkwFFu8ZeRsUQSJ5Z4eFXmHgOzDSj42vUR7VDsFXmpoqdIpC
jp0CV9p+XyfAcvtsJXnTKKmDGFV7liH4d38mz8wNLFw1yFk8jswHeyzzy+6u9QVu
fFno1AZ67UWjeOlMz+kQ4S9n3X+irT03Qpc8+kvWDibnEDYuHivvhvROWOn4ja92
dyF+6YZxOGIf4QHwM0BHL+8IzrcodB15j7Iv0Fw9VKrJvcj55qZTerHvOkiwUDQF
1vsiqPtOEWrE4q87Y7aev3WBpRWfxQFu50IQNIAvPwiBmT9mj+3iztq46m5CTtnt
zjfdzxEMF5n74L+2u+CPIekngJ8i0RJOkq4UGrJmXpbX/82q+eN+TDws6GchRquG
3Hr2EgC3oocITMTbu+5ZjvbBVAh30VhqlOF1GwO8YSBIgvNUyPvIqZXK4Re9gjYm
BSRHl2juGFP3d1OSkdimWBkBc8MHx3QOkcCOzZYgPg3mdAq7beqKgh/DTvqQd5D8
MXGe4cgfHoOEY0X53K/qG1KXcIntRXab9Nue8GriGC9M7oMWgIM=
=48+3
-----END PGP SIGNATURE-----

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