Developing MBeans

As we have mentioned, MBeans are typed components composed by an implementation class and a management interface that is exposed to external applications. As per JMX specifications, standard MBeans do not require implementing any server-specific interface. However, if you want to fully use the JBoss MBeans capabilities, you are strongly encouraged to write MBeans using JBoss service pattern.

Writing JBoss-style MBeans requires implementing the ServiceMBean interface and extending the ServiceMBeanSupport base class that provides a set of life cycle operations. The notifications inform an MBean service when it can create, start, stop, and destroy itself.

For example, if you are writing an MBean that needs a JNDI naming service using JBoss service pattern, it's sufficient to establish a dependency between the two services. When the JNDI life cycle interface signals that the service is started, you can safely start up your service too. The same procedure ranges from difficult to impossible to do with vanilla JMX MBeans, if the only life cycle event is the MBean constructor.

A simple MBean

The first example will be a standard MBean that collects a key-value attribute pair and stores them in the AS system properties.

For packaging our MBeans, we will keep using the Java EE utility project, which is just what we need to settle our classes in an archive.

Tip

Some of you might have noticed the option JBoss Tools | New MBeans stubs. Honestly speaking, choosing this option doesn't add any great value to your project as it only lets you define the class name and its interface through a wizard. However, I think it is worth informing you about this choice as some new options will be added to the future releases of JBoss Tools.

Create a new utility project MBeanExample and add acom.packtpub.jmx.example1.SimpleServiceMBean interface. This will be our JMX contract that implements the ServiceMBean interface:

package com.packtpub.jmx.example1;
import org.jboss.system.ServiceMBean;
public interface SimpleServiceMBean extends ServiceMBean {
public String getProperty(String property);
public void setProperty(String key, String value);
}

Tip

Make sure that the MBean interface adheres to the naming standard where the word "MBean" is appended at the end of any service name.

The interface simply exposes getter and setter methods for storing and retrieving a system property, where com.packtpub.jmx.example1.SimpleService is the implementing class:

package com.packtpub.jmx.example1;
import org.jboss.system.ServiceMBeanSupport;
public class SimpleService extends ServiceMBeanSupport
implements SimpleServiceMBean {
protected void startService() [1]
{
log.info("MBean SimpleService started ");
}
protected void stopService() throws Exception [2]
{
log.info("MBean SimpleService stopped ");
}
public String getProperty(String property) {
String value = System.getProperty(property);
log.info("MBean SimpleService returning: "+value);
return value;
}
public void setProperty(String key, String value) {
System.setProperty(key, value);
}
}

This class extends ServiceMBeanSupport, which is an abstract base class. JBoss services can extend it to write JBoss-compliant services. This class overrides the startService [1] and stopService [2] called by the application server when the SimpleService is started (startService) or when it's stopped (stopService). The getProperty and setProperty methods are conceivably used to store and read a system property.

Tip

Be aware that this example is only for the purpose of learning MBeans, in a production environment, you would not expose the server properties, at least not without an appropriate security authorization!

Before packaging our MBean, we need to add an MBean configuration file. This is a standalone XML descriptor with a naming pattern that matches *-service.xml. In the last section of this chapter, we will illustrate how we can skip this step by using POJO MBeans that can be configured entirely through annotations. Anyway, writing an MBeans configuration file allows your components backward compatibility with earlier releases of JBoss too.

In our example, we will add the following simple-service.xml under the META-INF folder of your project:

<server>
<mbean code="com.packtpub.jmx.example1.SimpleService"
name="com.packtpub.jmx.example1:service=SimpleService">
</mbean>
</server>

The MBean element specifies that you are declaring an MBean service. The code attribute gives the fully qualified name of the MBean implementation class. The required name attribute provides the JMX ObjectName of the MBean.

The latter attribute is composed of a mandatory element, the domain name, followed by a list a properties as depicted by the following diagram:

A simple MBean

The following is a screenshot of the Project Explorer before deploying the application to JBoss:

A simple MBean

Add the project to JBoss 5 and select Full Publish from JBoss Perspective. The outcome of this action will be a file named MBeanExample.jar in the deploy folder of JBoss.

Tip

What happened to SAR extension?

As you can see, your MBeans are flawlessly deployed on JBoss 5 as JAR archives. Using earlier releases of JBoss, you had to package the archive in an SAR file, otherwise the JMX deployer would not recognize the application as an MBean.

If you are curious to know some inner details, the magic trick is performed by the new JBoss 5 Virtual Deployment Framework(VDF) . The deployment recognition phase is now split into two rounds. The first one, which is based on the structure of the deployment unit, recognizes the MBeans deployment descriptors in the META-INF folder and proceeds immediately to the second round, which is about parsing the files, class loading, and installation. That being said, using SAR archives is still worthwhile if you need backward compatibility of your MBean applications.

Testing your MBean from the JMX console

The JMX console has been already introduced to you in Chapter 3, so you should already know that it is a web application used to inspect MBeans' attributes and invoke service operations. Launch it the usual way:

http://localhost:8080/jmx-console

Now look for the domain com.packtpub.jmx.example1. In the Agent View, you will find a single service available:

Testing your MBean from the JMX console

Follow the link that will take you to the MBean View. This is your playground for testing the MBean. Find the setProperty operation, which should be located in the lower area of your view and enter one dummy property name and value:

Testing your MBean from the JMX console

Then you can check the value of your property by clicking getProperty, which accepts as input the key property:

Testing your MBean from the JMX console

Testing your application programmatically

The same test can be performed using the JMX API. This approach will teach you to create your JMX interfaces for interacting with the agents and their managed components. Just add a web project to your workspace named JMX Web Client. Take care (as usual) to include the libraries from the MBean project in the build path for your web project so that your servlets will compile correctly from Eclipse.

Testing your application programmatically

The following servlet needs to be added to your web project:

package com.packtpub.jmxweb.example1
import java.io.*;
import javax.management.MBeanServer;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import org.jboss.mx.util.MBeanProxyExt;
import org.jboss.mx.util.MBeanServerLocator;
import com.packtpub.jmx.example1.SimpleServiceMBean;
public class TestJMXServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
SimpleServiceMBean service = null;
String property= request.getParameter("property");
String value= request.getParameter("value");
try {
MBeanServer server = MBeanServerLocator.locate();
service = (SimpleServiceMBean) MBeanProxyExt. create(SimpleServiceMBean.class,"com.packtpub.jmx. example1:service=SimpleService",server);
service.setProperty(property,value);
out.println("Set property "+property + "=" + value);
}
catch (Exception e) {
e.printStackTrace ();
}
}
}

In this sample code, we are creating an instance of the service through the MBeanProxyExt class that is a factory for producing MBeans proxies. The factory returns an instance of the SimpleServiceMBean that exposes the setProperty and getProperty methods in its interface.

What if you need a standalone client? JBoss AS supplies an RMI interface for connecting to the JMX MBeanServer. This interface is org.jboss.jmx.adaptor.rmi.RMIAdaptor. The RMIAdaptor interface is bound to JNDI in the default location of jmx/invoker/RMIAdaptor, as well as jmx/rmi/RMIAdaptor for backwards compatibility with older clients.

In the following example, you can see a standalone JMX client that uses the RMIAdaptor to query the MBeanInfo for the SimpleServiceMBean. As it is a plain Java class that uses reflection to invoke the MBeans operations, you can place it anywhere in any project or in a Java project of its own:

package com.packtpub.jmxclient.example1;
import java.util.Hashtable;
import javax.management.ObjectName;
import javax.naming.Context;
import javax.naming.InitialContext;
import org.jboss.jmx.adaptor.rmi.RMIAdaptor;
public class SimpleServiceTest {
public static void main(String args[]) {
try {
Hashtable hash = new Hashtable();
hash.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
hash.put("java.naming.provider.url", "jnp://localhost:1099");
hash.put("java.naming.factory.url.pkgs", "org.jnp.interfaces");
Context ic = new InitialContext(hash);
RMIAdaptor server = (RMIAdaptor) ic.lookup("jmx/rmi/RMIAdaptor");
// Get the InitialValues attribute
ObjectName name = new ObjectName("com.packtpub.jmx. example1:service=SimpleService");
// Invoke the setProperty(string1,string2) op String[] sig = {"java.lang.String","java.lang.String"};
Object[] opArgs = {"name","frank"};
Object result = server.invoke(name, "setProperty", opArgs, sig);
}
catch (Exception e) {
e.printStackTrace ();
}
}
}

As you can see, this client doesn't use any JBoss-specific class to access the MBean and can be considered a valid alternative if you need to write a portable solution for accessing your Mbeans.

MBeans dependency

Our second example will serve two different purposes. First, we will illustrate how you can define an MBean as dependent on other services. This MBean will invoke a stored procedure defined on our database, so the dependency will be on the DataSourceBinding service that is responsible for binding a DataSource in the JNDI tree.

The second purpose of this example is to show how you can configure your MBean to run as a startup class. Add a new interface to your project, and name it com.packtpub.jmx.example2.StartupServiceMBean. The interface will contain the methods for getting and setting the JNDI value of the DataSource and another method clearSessions that can be used to launch the stored procedure on demand too.

package com.packtpub.jmx.example2;
import org.jboss.system.ServiceMBean;
public interface StartupServiceMBean extends ServiceMBean {
public String getJndi();
public void setJndi(String jndi);
public void clearSessions();
}

The implementation class, com.packtpub.jmx.example2.StartupService, is as follows:

package com.packtpub.jmx.example2;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import org.jboss.system.ServiceMBeanSupport;
public class StartupService extends ServiceMBeanSupport
implements
StartupServiceMBean {
public StartupService() { }
private String jndi = null;
@Override
protected void startService() [1]
{
log.info("[StartupService ] MBean Startup started ");
clearSessions();
log.info("[StartupService ] MBean Session Cleaning complete");
}
@Override
protected void stopService() throws Exception
{
log.info("[StartupService ] Stopping Startup Mbean");
}
@Override
public String getJndi() {
return jndi;
}
@Override
public void setJndi(String jndi) {
this.jndi = jndi;
}
public void clearSessions() {
Connection conn = null;
CallableStatement cs1 = null;
try {
InitialContext ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup(jndi);
conn = ds.getConnection();
cs1 = conn.prepareCall("{call ClearSessions}");
cs1.execute();
}
catch (Exception exc) {
exc.printStackTrace();
}
finally {
try {
cs1.close();
conn.close();
}
catch (SQLException e) {
e.printStackTrace();
}
}
}
}

As you can see, turning your MBean into a startup class is only a matter of overriding the startService [1] method and inserting your logic there. In our example, the MBean will issue a CallableStatement that does some database cleanup. This can be useful if you persist your session data on a table and you need to start your application with a clean state.

As you might guess, the getter and setter methods will be used to inject the JNDI attribute into the class that corresponds to the DataSource JNDI.

Now, let's register our example in the -service.xml descriptor, specifying a dependency of the component with the DataSourceBinding service for the DataSource bound in the JNDI tree as MySqlDS. (For further information on how to configure and install this DataSource, refer Chapter 5, Developing JPA Entities.)

<mbean code="com.packtpub.jmx.example2.StartupService"
name="com.packtpub.jmx.example2:service=StartupService">
<attribute name="Jndi">java:/MySqlDS</attribute>
<depends>jboss.jca:name=MySqlDS, service=DataSourceBinding</depends>
</mbean>

Your second example is completed. To get it working, you need a stored procedure in your database named ClearSessions — this is a sample procedure that deletes all data found in the table SESSION_DATA:

CREATE TABLE 'hibernate`.`SESSION_DATA` (
`SESSION_ID` INTEGER UNSIGNED,
PRIMARY KEY (`SESSION_ID`)
)
ENGINE = InnoDB;

And here's the ClearSessions procedure definition:

DELIMITER $$
CREATE PROCEDURE `ClearSessions`()
BEGIN
DELETE FROM SESSION_DATA;
COMMIT;
END $$

You can insert some proof-of-concept data in this table to make sure that your example worked correctly. Redeploy your JMX project. In your console, you should immediately notice the MBean-started logs.

09:08:03,299 [StartupService ] MBean Startup started

09:08:03,694 [StartupService ] MBean Session Cleaning complete

Sending MBeans notifications

In general, MBeans have attributes/operations and they can optionally emit and consume notifications. Notifications provide a convenient way for an MBean to be informed about various events that occur inside the MBeanServer and its registered MBeans.

For example, an MBean can be dedicated to monitoring the system memory and emit notifications when the level falls below a certain threshold.

JBoss provides a built-in helper class, org.jboss.system.ServiceMBeanSupport that can be subclassed to implement services that conform to the ServiceMBean interface. This class provides an excellent base for writing standard MBeans that act as notification broadcasters.

However, if you need to specify at runtime the set of MBeans/notifications that the service wants to subscribe/receive, you'll find it indispensable to extend the abstract class org.jboss.system.ListenerServiceMBeanSupport that acts as JBoss service and in addition, as notification listener.

Sending MBeans notifications

Let's see a concrete example. Create a new class named com.packtpub.jmx.example3.SampleNotificationListener. This class will extend the ListenerServiceMBeanSupport class and implement its MBean interface, SampleNotificationListenerMBean.

package com.packtpub.jmx.example3;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedLong;
import javax.management.Notification;
import javax.management.ObjectName;
import org.jboss.logging.DynamicLogger;
import org.jboss.logging.Logger;
import org.jboss.system.ListenerServiceMBeanSupport;
public class SampleNotificationListener extends ListenerServiceMBeanSupport implements SampleNotificationListenerMBean
{
public SampleNotificationListener() { }
public void startService()
throws Exception
{
super.subscribe(true); [1]
}
public void stopService()
throws Exception
{
super.unsubscribe(); [2]
}
public void handleNotification2(Notification notification, Object handback)
{
log.info("Got notification: " + notification + ", handback: " + handback); [3]
}
}

The MBean interface doesn't expose any method but needs to extend the ListenerServiceMBean contract that contains the JMX subscription list.

package com.packtpub.jmx.example3;
import javax.management.ObjectName;
import org.jboss.system.ListenerServiceMBean;
public interface SampleNotificationListenerMBean
extends ListenerServiceMBean { }

As you can see, turning an MBean into a notification listener only requires activating subscriptions in the startService method [1] and switching them off in the corresponding stopService [2]. Your notifications will be handled in the handleNotification2() method [3] as soon as they are emitted.

Be aware that your implementation class is also able to override the handleNotification() method. Be careful, don't override this method, which is the implementation provided by the JBossNotificationBroadcasterSupport class to handle the notification synchronously.

Receiving heartbeat notifications

Before deploying your SampleNotificationListener, you need to specify what kind of notification you're interested to receive. For impatient readers, note that JBoss has already got some services emitting notifications. For example, the useful TimerService can be used to send notifications at predetermined time intervals. As this MBean is already bundled in JBoss, you simply need to add the following MBean descriptor in your project in order to activate it. (You can also deploy it as a standalone service.xml file.)

<server>
<mbean code="org.jboss.monitor.services.TimerService" name="jboss.monitor:name=Heartbeat,type=TimerService">
<attribute name="NotificationType">jboss.monitor.heartbeat</attribute>
<attribute name="NotificationMessage">JBoss is alive!</attribute>
<attribute name="TimerPeriod">5sec</attribute>
<depends optional-attribute-name="TimerMBean">
<mbean code="javax.management.timer.Timer"
name="jboss.monitor:name=Heartbeat,type=Timer"></mbean>
</depends>
</mbean>
</server>

This descriptor will trigger a notification every five seconds to all subscribers of the TimerService. All we need now is to subscribe to our TimerService from our SampleNotificationListener:

<mbean code="com.packtpub.jmx.example3.SampleNotificationListener" name="com.packtpub.jmx.example3:service=NotificationListener">
<attribute name="SubscriptionList">
<subscription-list>
<mbean name="jboss.monitor:name=Heartbeat,type=Timer"></mbean>
</subscription-list>
</attribute>
</mbean>

Now redeploy your JMX project and watch on the JBoss console to see if every step was executed correctly. You should see the JBoss is alive! message popping up on the console.

Sending your own notifications

In the previous example, we were listening passively for notifications coming from an external channel. However, you can be in charge of sending notifications from your own MBeans as well.

This will not be a big effort for us. Recall our SimpleServiceExample where we set a system property. Let's add a notification that warns us about a system property being changed:

public void setProperty(String key, String value) {
System.setProperty(key, value);
Notification notification = new Notification("SimpleService", this, getNextNotificationSequenceNumber(), "Warning: Changed system property: "+key);
sendNotification(notification);
}

Now update your subscription list so that the SampleNotificationListener will be tuned in to the SimpleService MBean:

<mbean code="com.packtpub.jmx.example3.SampleNotificationListener" name="com.packtpub.jmx.example3:service=NotificationListener">
<attribute name="SubscriptionList">
<subscription-list>
<mbean name="com.packtpub.jmx.example1:service=SimpleService">
</mbean>
</subscription-list>
</attribute>
</mbean>

Redeploy your MBean application and try setting some properties from your SimpleService. You should be able to intercept the notification emitted in the handleNotification2 method.

Got Notification: Warning: Changed system property: myproperty

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.156.255