Custom MBean in Tomcat - cannot find javaURLContextFactory when creating InitialContext

Will picture Will · Sep 27, 2011 · Viewed 8.6k times · Source

I've written a custom MBean that deploys in Tomcat 6. One of its tasks is to query a database value. I'm doing this by loading up the database resource using JNDI - the resource is defined in Tomcat's server.xml.

The problem is that when I create an instance of javax.naming.InitialContext it throws a ClassNotFoundException as it can't find org.apache.naming.java.javaURLContextFactory. This class is in catalina.jar and loaded by the common classloader. The jar containing my MBean code is loaded by a shared classloader.

Any ideas as to how I can get around this?

To note: my MBean is loaded by a ContextListener which I've defined in the tomcat/conf/web.xml. I've also defined it in a webapp web.xml which makes no difference. I can't really move my jar so as to be loaded by the common classloader as it relies on classes loaded by the shared classloader.

Thanks in advance,

Will

Answer

palacsint picture palacsint · Oct 3, 2011

It looks a weird classloading or security/permission issue. Below is a workaround.

The main idea: Since the ServletContextListener could call the new InitialContext() without the ClassNotFoundException get it in the listener and pass it to the constructor of the MBean object before you register the MBean. I used a simple web application and I have not modified tomcat/conf/web.xml.

Resource configuration in the tomcat/conf/context.xml:

<Context>
...
    <Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"
        maxActive="100" maxIdle="30" maxWait="10000"
        username="root" password="..." driverClassName="com.mysql.jdbc.Driver"
        url="jdbc:mysql://localhost:3306/javatest?autoReconnect=true"/>
...
<Context>

The web.xml resource configuration:

<resource-ref>
    <description>DB Connection</description>
    <res-ref-name>jdbc/TestDB</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
</resource-ref>

The ServletContextListener which registers the MBean:

import java.lang.management.ManagementFactory;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class ContextListener implements ServletContextListener {

    private ObjectName objectName;

    public void contextInitialized(final ServletContextEvent sce) {
        System.out.println("---> bean context listener started");

        final MBeanServer mbeanServer = 
            ManagementFactory.getPlatformMBeanServer();
        try {
            final InitialContext initialContext = new InitialContext();
            final Context envContext = 
                (Context) initialContext.lookup("java:/comp/env");

            objectName = new ObjectName("com.example:type=Hello");
            final Hello helloMbean = new Hello(envContext);
            mbeanServer.registerMBean(helloMbean, objectName);
            System.out.println("---> registerMBean ok");
        } catch (final Exception e) {
            e.printStackTrace();
        }
    }

    public void contextDestroyed(final ServletContextEvent sce) {
        System.out.println("---> bean context listener destroyed");
        final MBeanServer mbeanServer = 
            ManagementFactory.getPlatformMBeanServer();
        try {
            mbeanServer.unregisterMBean(objectName);
            System.out.println("---> unregisterMBean ok");
        } catch (final Exception e) {
            e.printStackTrace();
        }
    }
}

MBean interface:

public interface HelloMBean {
    void sayHello();
}

MBean implementation:

import java.sql.Connection;

import javax.naming.Context;
import javax.sql.DataSource;

public class Hello implements HelloMBean {

    private final Context envContext;

    public Hello(final Context envContext) {
        this.envContext = envContext;
        System.out.println("new hello " + envContext);
    }

    @Override
    public void sayHello() {
        System.out.println("sayHello()");

        try {
            final DataSource ds = 
                (DataSource) envContext.lookup("jdbc/TestDB");
            final Connection conn = ds.getConnection();
            System.out.println("   conn: " + conn);
            // more JDBC code
        } catch (final Exception e) {
            e.printStackTrace();
        }
    }
}

The MBean Descriptor How To says a mbeans-descriptor.xml is required but it's worked without it well. I could connect to the HelloMBean with jconsole. Calling sayHello() through jconsole printed the following:

conn: jdbc:mysql://localhost:3306/javatest?autoReconnect=true, \
UserName=root@localhost, MySQL-AB JDBC Driver

Sources: