JBoss JDBC MBean Prevent Startup If Server Not Found

1.4k Views Asked by At

During JBoss startup I have a Persistence Manager that depends on a JDBC connection (DefaultDS). The JDBC connection starts fine whether or not it can actually connect to the database so when the Persistence Manager starts it thinks it has a connection. Then it blows up because it cannot connect to the database and never starts. This prevents my DestinationManager from starting and causes all kinds of headache.

Is there a way to make MBeans that depend on the JDBC connection not start unless the JDBC connection can actually connect to the database? As an alternative, is there a way to make the JDBC connection depend on an MBean that is only active while the database can be connected to?

tl;dr; All I need is for my MBeans/DestinationManager to wait until the database (DefaultDS) is available before booting.

Please comment if you need more info about the enviornment.

  • JBoss version 4.2.3

  • Database: MsSql

2

There are 2 best solutions below

3
On BEST ANSWER

If I understand the issue correctly, you're having a problem because even though the DefaultDS data source reports that it has started, since it has not acquired any connections, you don't necessarily know that connections can be made .

Unfortunately, even with the prefill option enabled, the datasource service will still start normally even if it cannot make a connection.

Your best bet is to implement a ServiceMBean that checks an actual connection from the datasource before it reports being started. For this example, we'll call it org.bob.ConnChecker and will deployed using the ObjectName org.bob:service=ConnChecker.

Your deployment descriptor should look something like this:

  <mbean code="org.bob.ConnChecker" name="jboss.mq:service=DestinationManager">
    <depends optional-attribute-name="DataSource">jboss.jca:name=DefaultDS,service=ManagedConnectionPool</depends>
  </mbean>

So your service will not be started until the data source has started. Your service will not start unless it can get a connection. Now you just have to add org.bob:service=ConnChecker as a dependency of the DestinationManager:

jboss.mq:service=MessageCache jboss.mq:service=PersistenceManager jboss.mq:service=StateManager jboss.mq:service=ThreadPool jboss:service=Naming org.bob:service=ConnChecker

The code for ConnChecker will look something like this:

....
import org.jboss.system.ServiceMBeanSupport;
....
public class ConnChecker extends ServiceMBeanSupport implements ConnCheckerMBean {
    /** The ObjectName of the data source */
    protected ObjectName dataSourceObjectName = null;
    /** The Datasource reference */
    protected DataSource dataSource = null;
    /**
     * Called by JBoss when the dataSource has started
     * @throws Exception This will happen if the dataSource cannot provide a connection
     * @see org.jboss.system.ServiceMBeanSupport#startService()
     */
    public void startService() throws Exception {
        Connection conn = null;
        try {
            // Get the JNDI name from the DataSource Pool MBean
            String jndiName = (String)server.getAttribute(dataSourceObjectName, "PoolJndiName");
            // Get a ref to the DataSource from JNDI
            lookupDataSource(jndiName);
            // Try getting a connection
            conn = dataSource.getConnection();
            // If we get here, we successfully got a connection and this service will report being Started
        } finally {
            if(conn!=null) try { conn.close(); } catch (Exception e) {}
        }
    }
    /**
     * Configures the service's DataSource ObjectName
     * @param dataSourceObjectName The ObjectName of the connection pool
     */
    public void setDataSource(ObjectName dataSourceObjectName) {
        this.dataSourceObjectName = dataSourceObjectName;
    }
    /**
     * Acquires a reference to the data source from JNDI
     * @param jndiName The JNDI binding name of the data source
     * @throws NamingException
     */
    protected void lookupDataSource(String jndiName) throws NamingException {
        dataSource = (DataSource)new InitialContext().lookup(jndiName);
    }
}

The code for ConnCheckerMBean looks like this:

....
import org.jboss.system.ServiceMBeanSupport;
....
public interface ConnCheckerMBean extends ServiceMBean {
    public void setDataSource(ObjectName dataSourceObjectName);
}

So you will still get errors if connections cannot be made to the database, but the DestinationManager will not start, and hopefully that will be better than the headaches you're having now.

0
On

So there is no way to have a bunch of beans just "waiting" and still allow Jboss to boot all the way up?

Not in any standard fashion. The JBoss boot cycle either runs through to completion or reports a dependency failure. The process is sequential and single-threaded (until JBoss 7).

What you could do (and I only briefly tested this) is:

  • Re-implement the ConnChecker to run its connection test in a separate thread. It will be considered started as soon as that thread is spawned.
  • Pull out all the XML config files for services you want to depend on ConnChecker (I guess this would be all JMS deployment XML) files into another directory outside of deploy, say for example /jboss/server/bob/late-deploy.
  • Since the late-service files are now not in the URLDeploymentScanner's list of paths, they will not be deployed as part of the default deployment process.

The trick to getting the late-service files to deploy is that your new ConnChecker will happily spin, waiting to get a connection (and might possibly timeout and stop right there) but when it does successfully acquire a connection, it will execute code that looks like this:

import javax.management.*;
.....
// The JBoss URL Deployment Scanner MBean ObjectName
ObjectName on = new ObjectName("jboss.deployment:flavor=URL,type=DeploymentScanner");
// server is the JBossMBean server. ServiceMBeans automatically have this reference.
server.invoke(on, "addURL", new Object[]{new URL("file:/jboss/server/bob/late-deploy")}, new String[]{String.class.getName});

So what this does is tell the deployment scanner "start looking in this directory too" and a couple of seconds later, your late-services will deploy, hopefully error free. Additionally, since you added the late-service at runtime (and therefore non-persistently), when the server restarts, the deployment scanner will be reverted back to it's original configuration, waiting for ConnChecker to add new URLs to it.

Just make sure that the deployer has ScanEnabled set to true and that the ScanPeriod is low enough that you get the required response time to deploy your late-services once the JDBC connection is made. That MBean configuration is in

<jboss-home>/server/<server-name>/conf/jboss-service.xml

Look for this:

   <mbean code="org.jboss.deployment.scanner.URLDeploymentScanner"
      name="jboss.deployment:type=DeploymentScanner,flavor=URL">
....
      <!-- Frequency in milliseconds to rescan the URLs for changes -->
      <attribute name="ScanPeriod">5000</attribute>
      <!-- A flag to disable the scans -->
      <attribute name="ScanEnabled">true</attribute>
....
   </mbean>