Chapter 12. Managing with JMX – OSGi in Depth

Chapter 12. Managing with JMX

 

This chapter covers

  • Using the Java Management Extension (JMX) API
  • Remotely connecting to JMX agents
  • Retrieving and changing the state of the OSGi framework while in production
  • Managing OSGi services

 

When you’re developing, you can manage the OSGi framework and its bundles and services by using the shell, as we’ve done in the previous chapters, or by installing custom management agent bundles. But this isn’t the case when you’re in production. In production, it’s unlikely that you or the IT person has direct access to the OSGi framework. For example, it would be improbable that you’d be able to log in or telnet into the machine that’s hosting the running OSGi framework instance. Among other reasons, this is a matter of security; the IT personnel wouldn’t want you to inadvertently take up the CPU resources of a server by logging into it. This is magnified by the fact that increasingly these machines are being hosted in the cloud, that is, in some form of application grid.

This isn’t an issue related exclusively to the OSGi framework; any other application in production would be kept under the same restraints. The question that arises then is how do you manage and monitor running Java applications? Fortunately, Java provides a standard mechanism for the management of running applications, namely, the Java Management Extension (JMX) API. Using JMX, you can dynamically install new applications, introspect exported packages, and even change the active start-level of bundles all in a running system.

We’ll start by describing the basic concepts of JMX. Next, we’ll walk through several management scenarios, starting with managing the bundles, then the services, then the packages, and finally the framework itself. Following that, we’ll examine JMX notifications and how they can be used to keep management agents up to date. Finally, we’ll explore common JMX usage patterns. Let’s start with the basics.

12.1. Java’s management API

JMX follows a similar model to other management technologies, such as SNMP (Simple Network Management Protocol). A special Java class called Managed Bean, or MBean, is defined for the resources that need to be managed. The MBeans act as a management façade for these resources and provide the metadata for management, which is sometimes referred to as the management information base (MIB).

You can reference an MBean by specifying its object name. An object name is defined by a namespace and a set of properties. For example, OSGi defines the following object name for the MBean that represents the OSGi framework:

osgi.core: type=framework,version=1.5

In this case, the namespace is osgi.core, and it has two properties, type=framework and version=1.5.

The MBeans are collectively contained within an MBean server. The MBean server works as a local agent running within each JVM that’s to be managed, and it provides connectors to remote clients. The MBeans are organized in a hierarchical form within the MBean server by means of grouping their properties. Remote clients establish connections to an MBean server using different transport protocols, such as RMI. Having established the JMX connection, the client can manage the resources by invoking operations on the MBeans, as shown in figure 12.1.

Figure 12.1. Remote client using JMX to connect to an MBean, which acts as a façade for managed Java classes

Let’s investigate JMX by going through a series of management scenarios. We’ll begin by enabling the JVM hosting the OSGi framework for remote management. To do this, set the following system properties when starting the JVM:

java
-Dcom.sun.management.jmxremote.port=9999 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
     -jar bin/felix.jar

The property com.sun.management.jmxremote.port tells the JVM to create a management agent and register it in the RMI registry using port 9999. Make sure that port 9999 is free, or choose a different port. The next two properties disable security. We’re disabling security to keep things simple, but in a real environment you should keep these enabled.

 

Note

If you’re using JDK 6.0 or higher, the management agent is automatically started and enabled for local access, that is, for a client running in the same machine. But should the client be remote (on a different machine), you’d still need to the set the jmxremote properties.

 

You can validate the connection by starting jconsole, the out-of-the-box management console provided in the JDK:

$JAVA_HOME/bin/jconsole localhost:port

We’re now ready to manage the OSGi framework, which we’ll do in the next sections by looking at each one of the major OSGi MBeans:

  • BundleStateMBean
  • ServiceStateMBean
  • PackageStateMBean
  • FrameworkMBean
  • ConfigurationAdminMBean

 

Warning

At the time of this writing, Apache Felix doesn’t support the JMX OSGi MBeans. In fact, neither does Apache Aries nor Eclipse Equinox nor Gemini.

 

We’ll start by looking at how to retrieve the state of the bundles of a running OSGi instance.

12.2. Managing bundles

Let’s consider a simple management scenario, where we check to see if the OSGi framework has started and is ready for service. You can do this by verifying whether the state of the system bundle is set to active. This is demonstrated in the following listing.

Listing 12.1. Retrieving the state of the System bundle

Note how we’ve created a standalone Java application, with its own main() method. This application has no dependencies except for the OSGi MBeans API and can run in any remote machine that has access to the (managed) machine that’s hosting the JVM process running the OSGi framework.

You start by specifying a URL for the remote MBean server of the JVM that’s hosting the OSGi framework . Replace the localhost with the name of your machine, and change the 9999 port to the appropriate values in your environment. With the JMX service URL, you can now establish a connection to the remote MBean server using the method JMXConnectorFactory.connect(). Next, you specify the name of the MBean that you’d like to use. In this case, it’s the ObjectName for the BundleStateMBean . Having specified the ObjectName, you can now create a local proxy to it. This is done using the method JMX.newMBeanProxy() . This method takes the JMX connection, the ObjectName of the intended target, and a Java class to be implemented by the proxy, which in this case is the BundleStateMBean class.

The BundleStateMBean class allows you to find the complete state of all the installed bundles in OSGi, such as their imported packages, their headers, and even the services they’ve registered. In this example, you retrieve the bundle state for the bundle whose ID is 0 and verify whether it’s equal to ACTIVE . The method BundleStateMBean.getState(long) takes as input a long value, which represents the bundle ID, and returns this bundle’s state. Which bundle always has ID equal to 0? The System bundle, which by definition is always the first bundle to be installed.

 

Avoiding proxies

In listing 12.1, you created an MBean proxy for the BundleStateMBean. Proxies make programming using MBeans easier, but it also means that the proxy classes, like the BundleStateMBean class, must be available for the remote client application.

An alternative approach is to use the MBeanServerConnection class to generically handle an MBean’s attributes and operations. For example, the following code retrieves the value of the BundleStateMBean’s attribute named state, similarly to invoking the method BundleStateMBean.getState():

String state =
    (String) msc.getAttribute(mbeanName, "state");

 

This is a bit of cheating, though, because generally you can’t know beforehand the ID of a bundle from a remote client. We’ll improve on this in the next listing, where you look for a bundle whose symbolic name is mybundle, and only then do you check its bundle state.

Listing 12.2. Searching for a bundle’s symbolic name

In this case, you assume you’ve already created a proxy for BundleStateMBean, which can be done much like in the previous scenario. Next, you invoke the method BundleStateMBean.listBundles() , which returns a special collection called TabularData containing the information for all installed bundles. What’s tabular data? You can think of tabular data as a table containing rows of composite values, or CompositeData to be precise. CompositeData represents a structure, or in this case a bundle. You retrieve all the rows of the TabularData using the method TabularData.values(). Next, you iterate through each CompositeData, and for each one you get the value of the column (item) named SYMBOLIC_NAME . This is done using the method CompositeData.get(BundleStateMBean.SYMBOLIC_NAME). Finally, you check to see if the composite data’s symbolic name matches with your bundle name of mybundle, and if it does, you retrieve the bundle’s state.

 

Open MBeans

TabularData and CompositeData are the mechanisms that JMX uses to represent a table-like collection of items in a generic way, without having to rely on user-defined Java classes. User-defined Java classes complicate matters for remote clients, because they would need to find a way to include these classes in their class path and make sure they’re in sync with the server. To avoid all of these issues, MBeans should use only built-in types in their signatures, something known as open types.

 

BundleStateMBean has several other useful methods, such as getBundleSymbolicName(long), getHeaders(long), getImportedPackages(long), getLocation(long), and so on. Hopefully, the pattern of usage is clear: the BundleStateMBean works as a façade and thus isn’t tied to a particular Bundle instance, as you may be inclined to think. Instead, you specify the bundle ID, and the MBean will take care of retrieving the corresponding bundle using the OSGi framework API. In other words, BundleStateMBean is stateless.

Why use the bundle ID instead of the more user-friendly bundle’s symbolic name? For starters, the symbolic name alone doesn’t uniquely identify a bundle; you’d have to compound it with the bundle’s version. Furthermore, a long value is much easier to handle in an optimal form. Therefore, it’s common for the management client to cache the bundle ID of the bundles it needs to manage regularly, as in the following example:

long myBundleId = -1;

for (CompositeData bundleInfo : bundles) {
    if (bundleInfo.get(BundleStateMBean.SYMBOLIC_NAME).
            equals("mybundle")) {
        myBundleId =
            (Long) bundleInfo.get(BundleStateMBean.IDENTIFIER);
    }
}

But keep in mind that a bundle ID is valid for only a particular instance of the OSGi framework; the value is no longer valid after a restart of the server.

 

Warning

A bundle ID is associated with a bundle when it is installed or reinstalled during the launching of the OSGi framework and potentially changes for every reinstall. If you remember from chapter 4, only the PID is persisted across launches of the OSGi framework.

 

You’ve learned how to retrieve the state of a bundle and, in particular, how to check if a bundle is active. Next, let’s find the services being registered by the active bundle.

12.3. Managing services

The ServiceStateMBean can be used to retrieve information about all of the registered services in the OSGi service registry. As you’ll see, it works similarly to the BundleStateMBean. Let’s expand the previous example by finding all services that are being registered by the mybundle bundle, and then verify which bundles are using these services. This is illustrated in the next listing.

Listing 12.3. Retrieving registered services

You start with the BundleStateMBean and invoke the method getRegistered-Services() to retrieve all services registered by myBundleId . This returns a list of long values, which represent service IDs. Next, you need to access the ServiceStateMBean, so you instantiate its ObjectName, which is osgi.core:type=ServiceState,version=1.5 , creating a proxy as usual. You retrieve all bundles that are using a particular service ID by invoking the method ServiceStateMBean.getUsingBundles() . You also retrieve the service interface, aka its ObjectClass, by using the method getObjectClass(long) . Finally, you iterate through the bundle IDs returned from the method getUsingBundles(), and use the BundleStateMBean to find their symbolic names.

Other methods of interest in the ServiceStateMBean are getProperties(long), which returns all the service properties of a particular service, and listServices(). The latter is particularly interesting, because it returns in tabular format all of the registered services in OSGi. Using listServices() in combination with ServiceStateMBean.getBundleIdentifier(long) allows you to find the owning bundle of all registered services in the framework.

Next, let’s take a look at the one remaining aspect of a bundle: the packages it exports and imports.

12.4. Managing import and export packages

In the previous section, you discovered all bundles that use the services that the bundle mybundle registers. Let’s follow up on this example and discover all bundles that are importing the packages that the bundle mybundle is exporting.

As you probably guessed, the pattern for doing this should be similar to what you did in the previous example. But there’s one major difference: unlike bundles and services, packages aren’t identified by a long ID, which complicates matters somewhat. Check this out in the next listing.

Listing 12.4. Retrieving exported packages

As usual, you start with the BundleStateMBean, which you use to retrieve all packages exported by the bundle myBundleId . Unlike before, though, you get back a String formatted as package;version. For example, if a bundle has the MANIFEST header entry Export-Package: manning.osgi;version=1.0.0.0, then you’d get the following return value: manning.osgi;1.0.0.0. You need to break this String into two values: one for the package and another for the actual version. You do this by looking for the semicolon (;) character .

Next, you create a proxy for the PackageStateMBean . Using the PackageStateMBean, you invoke its getImportingBundles() method, passing along the parsed package and version Strings. You also pass along the bundle ID of the exporting bundle .

The first two arguments are obvious, but why do you also need the bundle ID of the exporting bundle? You need this because more than one bundle may be exporting the same version of a package, perhaps specifying different export attributes or use clauses.

You’ve now learned how to find the state of bundles, services, and packages. In the following section, we’ll look into how you can change the state of the OSGi framework itself.

12.5. Managing an OSGi framework instance

Whereas the BundleStateMBean allows you to manage the state of an installed bundle, the FrameworkMBean allows you to manage the bundles themselves, that is, the installation, starting, stopping, updating, and uninstallation of bundles.

The following code installs the bundle mybundle into OSGi:

mbeanName = new ObjectName("osgi.core:type=Framework,version=1.5");

FrameworkMBean frameworkMBean =
    JMX.newMBeanProxy(msc, mbeanName, FrameworkMBean.class);

long bundleId =
    frameworkMBean.installBundle("bundle/mybundle.jar");

You start by specifying the FrameworkMBean’s ObjectName, which is osgi.core:type=Framework,version=1.5. This is used to create a proxy to the FrameworkMBean. Next, you invoke the method installBundle(), which takes as an argument the location of the bundle’s JAR file. The method installBundle() returns the bundle ID of the newly installed bundle. Now that you have the bundle ID, you can use it with the other OSGi MBeans, for example, to get the bundle’s state using the management operation BundleStateMBean.getState(long).

 

Note

A bundle’s install location is related to the current directory used to launch the OSGi framework. This is specified by the environment property user.dir.

 

What if you need to manage several bundles instead of just one? For example, it’s not uncommon to install all the bundles located in a directory. In this case, should you invoke installBundle() multiple times? This would not only be unproductive, but it also would increase the network usage between the remote JMX client and the OSGi framework, a resource that may not be freely available when the OSGi framework is embedded on a device. Fortunately, the FrameworkMBean supports the batching of operations, as shown in the next listing.

Listing 12.5. Batch install of bundles

A batch install is done using the method installBundles() , which takes as an argument a String array of bundle locations. This isn’t a transactional operation, where all bundles get installed or none get installed; instead, OSGi sequentially installs the bundles as specified in the bundle locations argument until all are installed or the first error is encountered. For example, in the previous listing you’re trying to install bundles bundleA, bundleB, and bundleC. If bundleB has an error and doesn’t install, then OSGi would have installed bundleA but would never attempt to install bundleC.

All bundles that installed successfully are returned in an array of longs representing their bundle IDs . You can retrieve this information by getting the composite item FrameworkMBean.COMPLETED. This array contains the installed bundles in the same order that they were listed in the input bundle locations argument (bundleLocations).

Next, you check the result of the batch operation by getting the composite item FrameworkMBean.SUCCESS . If there are no errors, then this is true; otherwise, it’s false. If there’s an error, you can find the bundle location that failed to install by getting the composite item FrameworkMBean.BUNDLE_IN_ERROR . Because OSGi stops when it encounters the first error, this composite item returns a single String rather than an array, as was the case of the FrameworkMBean.COMPLETED item. You can get the reason for the error by using the composite item FrameworkMBean.ERROR . Finally, you can get the locations of the bundles that remain to be installed by using the item FrameworkMBean.REMAINING . In our case, this would be bundleC.

Batch operations may seem rather complex, but they yield better performance and provide information about their results. You can batch pretty much all other bundle-related operations, such as uninstall, start, stop, and update.

The FrameworkMBean also supports some other interesting operations. For example, you can manage start levels with the following methods:

  • get/setFrameworkStartLevel()—These allow you to get and set the active start level of the OSGi framework. As you’ve seen previously, changing this value may cause bundles to be started or stopped.
  • get/setInitialBundleStartLevel()—These allow you to get and set the initial start level assigned by default to an installed bundle. Changes to this value are only applicable to new bundles.
  • get/setBundleStartLevel()—These allow you to get and set a bundle’s start level. As you’ve seen, a bundle is started or stopped depending on whether its start level is lower or greater than that of the OSGi framework (active) start level.

You can also restart and shut down the OSGi framework itself using the methods restartFramework() and shutdownFramework(); therefore access to the FrameworkMBean should be kept secured.

Next, suppose you want to develop a management tool that promptly shows all installed bundles and registered services. You can think of it as an OSGi browser, except that your tool is a standalone application instead of a web application, such as the Felix Web Management console. You could implement your OSGi browser by periodically invoking BundleStateMBean.listBundles() and ServiceStateMBean.listServices(), but this approach not only wastes CPU resources but also won’t guarantee that you have the latest updated state. Fortunately, JMX has a better solution, through the use of JMX notifications, which we’ll examine next.

12.6. Management notifications

MBeans not only provide attributes and operations, but they can also emit notifications. Remote listeners can register to receive these notifications. In particular, the BundleStateMBean and the ServiceStateMBean MBeans emit notifications signaling changes to the state of a bundle, such as when a new bundle has been installed, and changes to the state of a service, such as when a new service is registered. In other words, these notifications reflect the OSGi bundle event (org/osgi/framework/BundleEvent) and the OSGi service event (org/osgi/framework/ServiceEvent), as explained in chapter 6.

Let’s continue our previous example by looking at a JMX client that handles bundle events. First, you need to register a JMX notification listener, as in the following listing:

mbeanName = new ObjectName("osgi.core:type=bundleState,version=1.5");

BundleStateMBean bundleStateMBean =
    JMX.newMBeanProxy(msc, mbeanName, BundleStateMBean.class, true);

((NotificationEmitter) bundleStateMBean).addNotificationListener(
new BundleEventListener(), null, null);

As usual, you create a proxy to the BundleStateMBean, but in this case you need to invoke the overloaded method:

JMX.newMBeanProxy(MBeanServerConnection connection,
ObjectName objectName,
Class<T> interfaceClass,
boolean notificationBroadcaster)

This method has a fourth argument that must be set to true and indicates that the created proxy also implements the interface NotificationEmitter. Finally, you cast the proxy to the NotificationEmitter class and invoke the method addNotificationListener(), passing along the BundleEventListener implementation , which is described in the next listing.

But before we look into the BundleEventListener implementation, let’s examine the addNotificationListener() method. It takes three arguments: The first is the callback class that must implement the NotificationListener interface. The second argument is a notification filter; you can use this to filter the notifications that you’re interested in. For example, you can specify a filter to receive notifications related only to a specific ObjectName or related to a particular MBean attribute. In our case, we haven’t registered any filters; this is because we’ve registered our notification listener directly in the BundleStateMBean proxy, so we automatically receive notifications related only to this MBean. But had we invoked MBeanServerConnection.addNotificationListener(), then we’d receive notifications from all MBeans, and it would be appropriate to specify a notification filter, as in the following example:

MBeanServerNotificationFilter filter = new MBeanServerNotificationFilter();
filter.enableObjectName(
   new ObjectName("osgi.core:type=bundleState,version=1.5"));

Finally, the third argument to NotificationEmitter.addNotificationListener() is a hand-back object; that is, it’s an opaque object that’s handed back to the notification listener when it’s called. This is useful if you need to pass any context from the code that’s registering the listener to the listener itself.

Next, let’s look at the BundleEventListener implementation, shown in the following listing.

Listing 12.6. The BundleEventListener class

The method handleNotification() is invoked when a new BundleEvent is available. The BundleEvent is retrieved using the method Notification.getUserData() . As usual, it’s a CompositeData object.

 

JMX notification type

You know that the BundleStateMBean only emits BundleEvent notifications, so you can assume that getUserData() returns the appropriate CompositeData object. But in cases where the notification listener can’t assume this, for example, because it may be the target of several MBeans, you can check the type of the object returned by getUserData() by using the method Notification.getType(). This returns a String naming the notification type. For example, in the case of the BundleEvent, it would return org.osgi.jmx.framework.BUNDLE_EVENT_TYPE, which is a reference to the CompositeType declared in BundleStateMBean.BUNDLE_EVENT_TYPE.

 

A BundleEvent informs you of the source bundle that generated the event and the type of the event. For example, if a new bundle, bundleA, is installed, then the source bundle is bundleA and the type of the event is INSTALLED. The symbolicName of the source bundle is retrieved using the composite item BundleStateMBean.SYMBOLIC_NAME . Similarly, you could retrieve the source bundle ID using the composite item BundleStateMBean.IDENTIFIER. Finally, the type of the bundle change event is retrieved using the composite item BundleStateMBean.EVENT_ITEM . It’s an integer defined as follows:

  • INSTALLED=1
  • STARTED=2
  • STOPPED=4
  • UPDATED=8
  • UNINSTALLED=16

 

Note

Why does the BundleEvent use an integer to enumerate its stage change instead of a String? Mostly to save bandwidth; this is a general guideline when dealing with JMX notifications.

 

You’ve learned how to register a notification listener to handle bundle event changes. You can use the bundle event to promptly know when a new bundle gets installed or uninstalled. Next, let’s see how you can do the same for service event changes.

First, you register a new notification listener in the ServiceStateMBean:

ServiceStateMBean serviceStateMBean =
    JMX.newMBeanProxy(msc, mbeanName, ServiceStateMBean.class);

((NotificationEmitter) serviceStateMBean).addNotificationListener(
    new ServiceEventListener(), null, null);

The following listing details the ServiceEventListener code.

Listing 12.7. The ServiceEventListener class

Handling a ServiceEvent isn't much different than handling BundleEvents. Like a BundleEvent, a ServiceEvent is CompositeData retrieved using the Notification.getUserData() method. You can discover the service interfaces of the service that changed its state by retrieving the composite item ServiceStateMBean.OBJECT_CLASS . Likewise, you can find its service ID using the composite item ServiceStateMBean.IDENTIFIER. Finally, you can find the actual type of change of the service state by retrieving the composite item ServiceStateMBean.EVENT_ITEM , which returns an Integer defining the following enumeration:

  • REGISTERED=1
  • MODIFIED=2
  • UNREGISTERING=3

By subscribing to receive BundleEvents and ServiceEvents, you can implement real-time JMX applications that are constantly up to date.

So far, you’ve learned to how to manage the OSGi framework, as well as the state of the OSGi bundles and services. But you know that the OSGi framework itself is a small part of the overall OSGi Service Platform, which in addition includes several compendium and enterprise services. We’ve used several of these enterprise services, such as the Configuration Admin service, in the previous chapters, and you’ve learned how important they are toward contributing to a full-fledged platform for developing applications. In the next section, we’ll look at how to manage some of the OSGi services.

12.7. Managing bundle configuration

In addition to the mandatory FrameworkMBean, BundleStateMBean, ServiceStateMBean, and PackageStateMBean, OSGi defines several other MBeans that a platform may optionally include in its MBean server. These are related to the management of OSGi services, such as the ConfigurationAdminMBean, the PermissionAdminMBean, the ProvisioningServiceMBean, and finally the UserAdminMBean.

As the Enterprise OSGi matures, you should see the proliferation of MBeans in the OSGi Service Platform, but at the time of writing only the aforementioned ones are specified as part of the Enterprise Specification Release 4.2.

In particular, the ConfigurationAdminMBean is very useful, because it’s likely that a management tool may need to update the configuration of a running system. Let’s say you’d like to remotely update the port of the Notification service using a JMX client application. This is shown in the next listing.

Listing 12.8. Updating the port property using ConfigurationAdminMBean

As usual, you start by specifying the ObjectName for the ConfigurationAdminMBean and use it to create the proper MBean proxy. To update the configuration of a service using the ConfigurationAdminMBean, you need to create a TabularData, whose rows represent each property to be updated. To do this, you first create a TabularDataSupport instance and specify its type as JmxConstants.PROPERTIES_TYPE . Next, you create the actual property representing the port configuration. A configuration property, as defined by the CompositeType JmxConstants.PROPERTY_TYPE, consists of three items: the KEY, which you can think of as its name, its VALUE, and its TYPE. In our case, the key is port, the value is the integer 9000, and its type is Integer. You add these three items to a Map and use it to create a CompositeData whose CompositeType is JmxConstants.PROPERTY_TYPE. Finally, you can invoke the update() method , passing along the configuration PID of manning.enterpriseosgi.notification.broker and the TabularData you created with the port property.

Having set the new value for the port property, you can check it with the following code:

TabularData newProperties =
    cmBean.getProperties("manning.enterpriseosgi.notification.broker");

CompositeData portProperty =
    newProperties.get(new String[] {"port"});

System.out.println("The value of the new port configuration is: " +
        portProperty.get(JmxConstants.VALUE));

ConfigurationAdminMBean.getProperties() returns all properties associated with a particular configuration PID in the format of a TabularData. Next, you retrieve the property whose key is port and print its value using the composite item JmxConstants.VALUE.

Using JMX in general and TabularData and CompositeData in particular isn’t exactly trivial, mostly because JMX is designed as a generic mechanism to manage all types of resources. But hopefully by now you have noticed some coding patterns emerge. We’ll explore these in the next section.

12.8. OSGi JMX patterns

Let’s start by dissecting the ObjectNames used for the OSGi MBeans. Here are some of them:

  • osgi.core: type=framework, version=1.5
  • osgi.core: type=bundleState, version=1.5
  • osgi.compendium: service=cm, version=1.3
  • osgi.compendium: service=useradmin, version=1.1

The pattern is clear; there are two categories of OSGi MBeans: those pertaining to the OSGi framework itself (for example, the first two items in the previous list) and those pertaining to the OSGi compendium (enterprise) services (for example, the latter two items in the previous list).

The first category uses the JMX namespace of osgi.core, followed by two predefined properties: type and version. The type property is somewhat arbitrary and denotes the OSGi MBean’s actual type, such as framework, bundleState, serviceState, and packageState. The version property specifies the actual interface version of the targeted managed class. You can find this by looking at the Export-Package MANIFEST header entry of the intended Java package. For example, the package org.osgi.framework is being exported with version 1.5 by the OSGi Service Platform Specification Release 4.2.

The compendium service–related MBeans follow a similar pattern. Their namespace is defined as osgi.compendium, and they likewise use two predefined properties: service and version. The version property has the same semantic as in the previous case. The service property is used to identify the actual service and is inferred from the target service’s package name. For example, the full class name of the Configuration Admin service is org.osgi.service.cm.ConfigurationAdmin; therefore the service property value is cm. The service property value is the local part after the org.osgi.service, not including the actual class name, as shown in figure 12.2.

Figure 12.2. The ObjectName’s service property is the local part after the org.osgi.service namespace.

Next, have you wondered how you can find out what types are returned from TabularData and CompositeData? To understand this, let’s revisit listing 12.2, which uses the BundleState.listBundles() method. Listing 12.2 is duplicated here for your convenience:

TabularData bundlesTable =
    bundleStateMBean.listBundles();

Collection<CompositeData> bundles =
    (Collection<CompositeData>) bundlesTable.values();

for (CompositeData bundleInfo : bundles) {
    if (bundleInfo.get(BundleStateMBean.SYMBOLIC_NAME).equals("mybundle")){
        System.out.println("Application state = " +
                bundleInfo.get(BundleStateMBean.STATE));
    }
}

The method listBundles() returns a TabularData. The type of this TabularData, as defined by its Javadoc, is BUNDLES_TYPE:

Answer the bundle state of the system in tabular form. Each row of the
     returned table represents a single bundle. The Tabular Data consists of
     Composite Data that is type by BUNDLES_TYPE.

If you click BUNDLES_TYPE in the documentation, it takes you to the following definition:

TabularType BUNDLES_TYPE =
    Item.tabularType("BUNDLES", "A list of bundles", BUNDLE_TYPE,
        IDENTIFIER);

In other words, BUNDLES_TYPE is a table whose rows are of type BUNDLE_TYPE. Again, if you click BUNDLE_TYPE, you get

CompositeType BUNDLE_TYPE =
    Item.compositeType("BUNDLE", "This type encapsulates OSGi bundles",
        EXPORTED_PACKAGES_ITEM, FRAGMENT_ITEM, FRAGMENTS_ITEM,
        HEADERS_ITEM, HOSTS_ITEM,IDENTIFIER_ITEM, IMPORTED_PACKAGES_ITEM,
        LAST_MODIFIED_ITEM,LOCATION_ITEM, PERSISTENTLY_STARTED_ITEM,
        REGISTERED_SERVICES_ITEM,REMOVAL_PENDING_ITEM, REQUIRED_ITEM,
        REQUIRED_BUNDLES_ITEM,REQUIRING_BUNDLES_ITEM, START_LEVEL_ITEM,
        STATE_ITEM,SERVICES_IN_USE_ITEM, SYMBOLIC_NAME_ITEM, VERSION_ITEM);

This has the actual columns’ types, defined as composite data Items, which is really what we’re interested in. In listing 12.2, you use the SYMBOLIC_NAME item. Clicking the BUNDLE_TYPE’s SYMBOLIC_NAME_ITEM, you get its definition:

Item SYMBOLIC_NAME_ITEM = new Item(SYMBOLIC_NAME,
    "The symbolic name of the bundle", SimpleType.STRING);

This tells you that the symbolic name item can be retrieved using the key BundleState.SYMBOLIC_NAME and that it’s of type String. That’s all you need to know.

JMX is a well-understood and often-used model for managing Java systems, and no solution would be complete without it. OSGi’s MBeans are complete, allowing you to manage most aspects of the OSGi framework and along the way establish the pattern for managing OSGi services.

12.9. Summary

JMX can be used to manage running OSGi frameworks, even when in production. JMX defines its management model based on MBeans, which act as façades to Java managed resources or classes. MBeans are named using an ObjectName, which includes a namespace and a set of properties. MBeans are organized within an MBean server, located within the JVM to be managed. Remote clients can establish connections to the MBean servers using different protocols, such as RMI.

OSGi defines several MBeans. The BundleStateMBean is used to retrieve the state of all installed bundles, such as a bundle’s symbolic name, its location, and its headers.

The ServiceStateMBean is used to retrieve the state of all the available services in the OSGi framework. You can find out the service’s properties, its objectClass (service interface), and the bundles that are using it.

The PackageStateMBean is used to retrieve information about all the packages that are being exported and imported in the OSGi framework.

The FrameworkMBean is used to manage the framework itself. It provides operations for shutting down the server, installing and uninstalling bundles, and even changing its start level.

Furthermore, OSGi also defines optional MBeans for the OSGi services, such as the ConfigurationAdminMBean, with which you can create, delete, and update configuration items.

Finally, both BundleStateMBean and ServiceStateMBean emit JMX notifications representing state changes to bundles and services, such as the installation of a new bundle or the registration of a new service.

In this chapter, you learned how to manage a running OSGi system, including both the framework and the deployed bundles that make up the OSGi applications. In the next chapter, we’ll put together all of what you’ve learned so far for developing an OSGi application—configuration, transactions, data access, and management—by discussing OSGi’s programming model.