-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

All,

I think I already know the answer to this question, but I'm going to
ask anyway in case it helps others understand what's going on.

I have a custom MBean for a loadable-object in my web application.
This is an object that lives in the application-scope and needs to be
periodically re-loaded from our relational database when we add new
data to it.

The bean looks like this:

    public static class ReloadableObjectBean
        implements ReloadableObjectBeanMBean
    {
        private ReloadableObject _rlo;
        private Date _loadTime;

        public ReloadableObjectBean()
        {
            _rlo = null;
            _loadTime = null;
        }

        public ReloadableObjectBean(ReloadableObject dxe)
        {
            _loadTime = new Date();
            _rlo = dxe;
        }

        @Override
        public int getItemCount() {
            return _rlo.getItems().size();
        }

        protected Date getLoadTimeInternal()
        {
            return _loadTime;
        }

        @Override
        public Date getLoadTime() {
            return (Date)getLoadTimeInternal().clone();
        }

        @Override
        public void reload() throws ReloadableObjectException,
ServiceException
        {
            ReloadableObject rlo =
ReloadableObjectHelper.newReloadableObject();
            _loadTime = new Date();
            _rlo = rlo;
        }
    }

This is an inner class defined in one of my ServletContextListeners,
and it's inserted into the MBeanServer like this:

        ReloadableObject rlo =
ReloadableObjectHelper.newReloadableObject();

        try {
            MBeanServer mbs = getServer();
            ObjectName objectName = new
ObjectName("com.chadis:type=ReloadableObject");

            if(mbs.isRegistered(objectName))
                mbs.unregisterMBean(objectName);

            mbs.registerMBean(new ReloadableObjectBean(rlo), objectName)
;
        } catch (MBeanException mbe) {
            logger.warn("Cannot register MBean", mbe);
        } catch (InstanceAlreadyExistsException iaee) {
            logger.warn("Cannot register MBean", iaee);
        } catch (NotCompliantMBeanException ncme) {
            logger.warn("Cannot register MBean", ncme);
        } catch (MalformedObjectNameException mone) {
            logger.warn("Cannot register MBean", mone);
        } catch (InstanceNotFoundException infe) {
            logger.warn("Cannot de-register MBean", infe);
        }

        application.setAttribute("reloadbleObject", rlo);

Everything goes well until I try to invoke the "reload" operation on
this MBean from a JMX client, where I get ClassNotFoundException:

2018-03-06 10:26:07,271 [RMI TCP Connection(1)-169.254.211.40] FATAL
ReloadableObjectHelper- Could not load ReloadableObject:
ServiceException: Cannot obtain database connection
        at ReloadableObjectHelper.getConnection(ReloadableObjectHelper.java:147
)
        at
ReloadableObjectHelper.newDiagnosisEngine(ReloadableObjectHelper.java:55
)
        at InitListener$ReloadableObjectBean.reloadEngine(InitListener.java:159
)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.jav
a:62)
    ....
Caused by:
Caused by: javax.naming.NoInitialContextException: Cannot instantiate
class: org.apache.naming.java.javaURLContextFactory [Root exception is
java.lang.ClassNotFoundException:
org.apache.naming.java.javaURLContextFactory]
        at
javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:674)
        at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:31
3)
        at javax.naming.InitialContext.init(InitialContext.java:244)
        at javax.naming.InitialContext.<init>(InitialContext.java:192)
        at ReloadbleObjectHelper.getConnection(ReloadableObjectHelper.java:136)
        ... 39 more
Caused by: java.lang.ClassNotFoundException:
org.apache.naming.java.javaURLContextFactory
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:348)
        at
com.sun.naming.internal.VersionHelper12.loadClass(VersionHelper12.java:7
2)
        at
com.sun.naming.internal.VersionHelper12.loadClass(VersionHelper12.java:6
1)
        at
javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:672)
        ... 43 more


The error I get on VisualVM (my JMX client, here) is that it can't
load my ServiceException class -- one that is defined only within the
web application.

I suspect the problem is that the thread's context class loader (TCCL)
is set to Tomcat's ClassLoader, since the request is being handled by
Tomcat's internal JMX server. If I were to invoke this operation via
the Manager's JMXProxyServlet, I'd probably be dealing with the
Manager's WebappClassLoader, instead, but the problem would be the
same: those ClassLoaders are unaware of my application's classes.

I *believe* the solution is to change the TCCL in this "reload"
method, but that means I'll need to capture the TCCL during the
invocation of the MBean itself and hang on to it... something like this:

        ClassLoader originalCL =
Thread.currentThread().getContextClassLoader();
        ReloadableObject rlo =
ReloadableObjectHelper.newReloadableObject();

        try {
            MBeanServer mbs = getServer();
            ObjectName objectName = new
ObjectName("com.chadis:type=ReloadableObject");

            if(mbs.isRegistered(objectName))
                mbs.unregisterMBean(objectName);

            mbs.registerMBean(new ReloadableObjectBean(rlo,
originalCL), objectName);
        }

Then, later, in the reload() method:


        @Override
        public void reload() throws ReloadableObjectException,
ServiceException
        {
            ClassLoader tccl =
Thread.currentThread().getContextClassLoader();

            try {
                Thread.currentThread().setContextClassLoader(originalCL)
;

                ReloadableObject rlo =
ReloadableObjectHelper.newReloadableObject();
                _loadTime = new Date();
                _rlo = rlo;
            } finally {
                Thread.currentThread().setContextClassLoader(tccl);
            }
        }

Does that sound about right?

I'll probably want to future-proof it by wrapping those calls into
PrivilegedActions, etc. but is this the right approach for what I'm
trying to do, here? Or am I missing something?

- -chris
-----BEGIN PGP SIGNATURE-----
Comment: GPGTools - http://gpgtools.org
Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/

iQJRBAEBCAA7FiEEMmKgYcQvxMe7tcJcHPApP6U8pFgFAlqetzEdHGNocmlzQGNo
cmlzdG9waGVyc2NodWx0ei5uZXQACgkQHPApP6U8pFgN8RAAwaaFjruUtxisKCrl
SvCxe+8UYbVjOq889x8UdBirnt0fQI6q53W4q51l72pvJ4yi83R1XrtjjcltbQ+4
A/OXS6VKg0L9gY6heM38LRp6Yp0Jjy9jIMjNMxCdjU9hqqfT+7jWinvxelFpMfFj
QXMILFHD0BRkwpc83ntyFGG/9i0cGdzaYb28GhvCff+48gk0CF7T2IO4VNN+UvYU
TnWbfiPhO8ng+Cqja4QwrMv2CcBvTp4OuFU7rg/hBAZ545H2hBIPhzaVMCTzqBdO
z1UOHhluBc2srvoVzXg7OlX4w4PRShu4QqvYECm/JQBKa8kWlgfLQgeKJbms6xc8
4Y+tuEZMaROPVITpncyj24gur+gD3PjZh4+JRKucdZg5Bt/ci7Rb9lpvwJSL9pmq
pV05I4tXakU5JibTNFydZNEn6vHWEz9DuQeYr2EQb4JeJ81FlOAuuqF6kgBVRINQ
F0RCdW5D8BXh2QLs9/ijUnnUbk83rmbah/4zKYdHGtlsiaJ1lmhZmmfAvZRek1vK
8NZ6A0/YaGw34LK2bYEctllZYSBs6G2AMQgO0KPU09jDXe4bVfjz00XR96A+1cX1
RNL/jRhcZlfizPbLtg4GDwxWzJj664p+1F236Zg8haUjQXI4WrV6UaeCf5X3SIdQ
rxhOvK+OGoSOl6TWP5OaqMHrKaQ=
=snnR
-----END PGP SIGNATURE-----

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

Reply via email to