Tuesday, October 03, 2006

Stopping an MDB via JMX - Instance

Carrying on my ongoing thread on JMS and MDB, my next task was to figure out how to stop a MDB programmatically. If you browse around in the OracleAS 10.1.3 MBean browser you will see all sorts of operations you might want to perform programmatically and in my case, looking at the MDB I deployed earlier on this week (http://mike-lehmann.blogspot.com/2006/09/simple-mdb-with-oracle-database-jms.html) I was interested MBean operations available on it.

The trick behind MBeans - at least for starters, is simply finding the darn things. Fortunately, a lot of standard JMX tools can hook up to Oracle Application Server, including JConsole as Steve Button, Mr. JMX at Oracle, blogged about here - http://buttso.blogspot.com/2006/06/more-info-on-remote-jconsole.html.

In my case continuing the "take the easiest route" I simply used the MBean browser inside of ASControl. The steps are illustrated below where I first go to the administrative tab of ASControl, click the System MBean browser and lastly navigate to my MDB application (myMDB) and expand it to find my MDB MessageProcessor and the operations on available on it:







What I was interested in was starting and stopping that MDB within the application itself, and importantly I would like to do it programmatically in a single OC4J instance within an Oracle Applicaiton Server instance. It turns out this is pretty easy to do once you have a basic understanding of JMX. I will take the shortest route there rather than generalizing the solution here - just so you can see the bare minimum.

First you need to know the MBean name - at the top of the ASControl page for MessageTopicProcessor you will see the breakdown of the MDB name in JMX format:

oc4j:j2eeType=MessageDrivenBean,EJBModule="myMDB",J2EEApplication=myMDB,
J2EEServer=standalone,name="MessageTopicProcessor"

Then you write a bunch of boiler plate code to hook up to the MBean server and finally the few lines to lookup the Mbean and do the operation. The full code for doing this is in [1]. The 6 lines that
matter are these - they are pretty self explanatory once you see them:

First connect to that J2EE instance - in my case called j2ee1 - and get its MBean server. Note that the OPMN port used - 6006 is the request port of my OracleAS instance:

JMXConnector clusterConnect = omdb.connect("service:jmx:rmi:///opmn://127.0.0.1:6006/j2ee1", "oc4jadmin", "welcome1");
MBeanServerConnection mbs = clusterConnect.getMBeanServerConnection();

Then look up the MessageDrivenBean in the myMDB application:

ObjectName myMDBObjectName = new ObjectName("oc4j:j2eeType=MessageDrivenBean,
EJBModule=\"myMDB\",
J2EEApplication=myMDB,
J2EEServer=standalone,
name=\"MessageTopicProcessor\"");

Then instantiate a local proxy for that MBean:

MessageDrivenBeanMBeanProxy MDBMBean = (MessageDrivenBeanMBeanProxy)MBeanServerInvocationHandler.newProxyInstance(mbs, myMDBObjectName, MessageDrivenBeanMBeanProxy.class, false);

And finally, stop it:

MDBMBean.stop();

This is using what is called a dynamic proxy, a feature of JMX 1.2 that OracleAS 10.1.3 supports which gives you the ability to work the MBean methods like ordinary Java methods rather than marshalling up the number of arguments and argument types as previously.

In general (there turns out to be exceptions), the way you determine the dynamic proxy is simply take your MBean name you are looking up - in this case MessageDrivenBean and add a "MBeanProxy" on the end of it and away you go. For J2EEApplication, another common MBean people will want to manipulate, it would be J2EEApplicationMBeanProxy.

What's nice about dynamic Mbeans is in your IDE you actually will get code insight into the methods available on your OracleAS MBean (assuming you have admin_client.jar in the classpath). Check this out:


To run this client I just needed to add adminclient.jar to my classpath (it is part of OC4J and JDeveloper in the $ORACLE_HOME\j2ee\home\lib) and away I went. I have been told that dynamic proxies may still have a dependency on oc4j-internal.jar but am not sure that will stick when 10.1.3.1 goes production. Note adminclient.jar is also part of the client distributable that you can download from here:

http://www.oracle.com/technology/software/products/ias/htdocs/utilsoft_preview.html

In the case of MDB, not only can you start and stop them via MBeans in OracleAS 10.1.3.1 you can also control it via an annotation for enabling and disabling them. The reason I mention this is that start and stop operations are runtime operations and do not persist - that is if you stop the MDB and then bounce the container the MDB will come back in a started mode. The enable flag, on the other hand, is a persistent property turning the MDB off or on.

Using the previous MessageProcessor bean as an example, the MDB could be deployed as disabled/not-started as follows with the MessageDrivenDeployment property containing an extra "enabled" attribute:

@MessageDrivenDeployment(resourceAdapter = "simpleOemsRA" enabled="false")

and then later running a client similar to that illustrated, first enabling it and then starting it. Obviously real life will have permutations but the combination of JMX, JMX Consoles and annotations, the ability to do what you need whether at deployment time or runtime is clearly possible. Note this particular enable/disable feature is new in OracleAS 10.1.3.1 and is available at a patch to OracleAS 10.1.3.0 for those interested - corresponding to bug 4619599.

And that's that. Thanks to Steve Button whose JMX code I pillaged down to this tiny sample - in the near future he is working to put out a set of helper classes that generalize the solution below as part of a bigger scripting solution using Groovy on top of JMX. This was my poor man's way to get a quick and dirty example out and about while they work getting 10.1.3.1 out the door!


[1] Full Code:

package demo.oc4j.jmx;

import java.net.URL;

import java.util.ArrayList;
import java.util.Hashtable;

import java.util.Iterator;
import java.util.Set;

import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.ObjectName;

import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import oracle.oc4j.admin.management.mbeans.proxies.JVMMBeanProxy;
import oracle.oc4j.admin.management.mbeans.proxies.MessageDrivenBeanMBeanProxy;

public class OperateOnMDBInstance {
public OperateOnMDBInstance() {
}



private JMXConnector connect (String URL, String username, String password) {

JMXConnector jmxCon = null;

try {

Hashtable credentials = new Hashtable();
credentials.put("login", username);
credentials.put("password", password);

// Properties required to use the OC4J ORMI protocol
Hashtable env = new Hashtable();
env.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES, "oracle.oc4j.admin.jmx.remote");
env.put(JMXConnector.CREDENTIALS, credentials);

JMXServiceURL serviceUrl = new JMXServiceURL(URL);
jmxCon = JMXConnectorFactory.newJMXConnector(serviceUrl, env);

// Do it!
jmxCon.connect();

} catch (Exception ex) {
ex.printStackTrace();
}

return jmxCon;
}

public static void main(String[] args) {
try {
OperateOnMDBInstance omdb = new OperateOnMDBInstance();

JMXConnector clusterConnect = omdb.connect("service:jmx:rmi:///opmn://127.0.0.1:6006/j2ee1", "oc4jadmin", "welcome1");
MBeanServerConnection mbs = clusterConnect.getMBeanServerConnection();
ObjectName myMDBObjectName = new ObjectName("oc4j:j2eeType=MessageDrivenBean,EJBModule=\"myMDB\",J2EEApplication=myMDB,J2EEServer=standalone,name=\"MessageTopicProcessor\"");
MessageDrivenBeanMBeanProxy MDBMBean = (MessageDrivenBeanMBeanProxy)MBeanServerInvocationHandler.newProxyInstance(mbs, myMDBObjectName, MessageDrivenBeanMBeanProxy.class, false);
MDBMBean.stop();
System.out.println("Success!");

} catch (Exception ex) {
ex.printStackTrace();
} finally {
}
}

}


No comments: