This is the second part in a series of blogs, that demonstrate how to add management capability to your own application using JMX MBeans.
In Part I we did the bulk of the work. We saw:
- How to implement a custom MBean to manage configuration associated with an application.
- How to package the resulting code and configuration as part of the application's ear file.
- How to register MBeans upon application startup, and unregistered them upon application stop (or undeployment).
- How to use generic JMX clients such as JConsole to browse and edit our application's MBean.
In this second part, we will add descriptions to our:
- MBean
- MBean attributes
- MBean operations
- MBean operation parameters
We saw using JConsole, that default descriptions were generated for all of the above. However those default descriptions didn't provide any meaningful information to our MBean users. We will replace those default descriptions with custom descriptions that can be used by us human to understand the functionality provided by our MBean.
We will also add localization support to our MBean. Our goal is to ensure that our MBean's meta-data is localized based on the WebLogic Server it is deployed to. So if that server uses a French Locale, then we'd expect our MBeans descriptions to be French and not English.
The complete code sample and associated build files for part II are available as a zip file. The code has been tested against WebLogic Server 10.3.1 and JDK6. To build and deploy our sample application, please follow the instruction provided in Part I, as they also apply to part II's code and associated zip file.
Providing custom descriptions
In order to provide custom description we need to modify our MBean implementation to extend the StandardMBean class. Despite its name that class is used to implement both Standard and MXBeans.
We override the StandardMBean class many getDescription
methods to provide our own custom descriptions. We use resources bundles to store our descriptions to ensure that those can be properly localized. The updated code for our MBean implementation is included below:
package blog.wls.jmx.appmbean;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.File;import java.net.URL;import java.util.Map;import java.util.HashMap;import java.util.Properties;import java.util.PropertyResourceBundle;import java.util.ResourceBundle;import javax.management.MBeanServer;import javax.management.ObjectName;import javax.management.MBeanRegistration;import javax.management.StandardMBean;import javax.management.MBeanInfo;import javax.management.MBeanAttributeInfo;import javax.management.MBeanOperationInfo;import javax.management.MBeanParameterInfo;public class PropertyConfig extends StandardMBean implements PropertyConfigMXBean, MBeanRegistration { private String relativePath_ = null; private Properties props_ = null; private File resource_ = null; private ResourceBundle resourceBundle_ = null; private static Map operationsParamNames_ = null; static { operationsParamNames_ = new HashMap(); operationsParamNames_.put("setProperty", new String[] {"key", "value"}); operationsParamNames_.put("getProperty", new String[] {"key"}); } public PropertyConfig(String relativePath) throws Exception { super(PropertyConfigMXBean.class , true); props_ = new Properties(); relativePath_ = relativePath; } public String setProperty(String key, String value) throws IOException { String oldValue = null; if (value == null) { oldValue = String.class.cast(props_.remove(key)); } else { oldValue = String.class.cast(props_.setProperty(key, value)); } save(); return oldValue; } public String getProperty(String key) { return props_.getProperty(key); } public Map getProperties() { return (Map) props_; } private void load() throws IOException { InputStream is = new FileInputStream(resource_); try { props_.load(is); } finally { is.close(); } } private void save() throws IOException { OutputStream os = new FileOutputStream(resource_); try { props_.store(os, null); } finally { os.close(); } } public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception { // MBean must be registered from an application thread // to have access to the application ClassLoader ClassLoader cl = Thread.currentThread().getContextClassLoader(); URL resourceUrl = cl.getResource(relativePath_); resource_ = new File(resourceUrl.toURI()); load(); return name; } public void postRegister(Boolean registrationDone) { } public void preDeregister() throws Exception {} public void postDeregister() {} private synchronized ResourceBundle getResourceBundle() { if ( resourceBundle_ == null ) { resourceBundle_ = PropertyResourceBundle.getBundle( "blog.wls.jmx.appmbean.MBeanDescriptions"); } return resourceBundle_; } protected String getDescription(MBeanAttributeInfo info) { return getResourceBundle().getString("PropertyConfigMXBean.attribute." + info.getName() ); } protected String getDescription(MBeanOperationInfo info) { return getResourceBundle().getString("PropertyConfigMXBean.operation." + info.getName() ); } protected String getDescription(MBeanInfo info) { return getResourceBundle().getString("PropertyConfigMXBean.mbean"); } protected String getDescription(MBeanOperationInfo op, MBeanParameterInfo param, int sequence) { return getResourceBundle().getString( "PropertyConfigMXBean.operation." + op.getName() + "." + operationsParamNames_.get(op.getName())[sequence] ); } protected String getParameterName(MBeanOperationInfo op, MBeanParameterInfo param, int sequence) { return operationsParamNames_.get(op.getName())[sequence]; } }
Our default resource bundle class, that contains our English descriptions contains the following code:
package blog.wls.jmx.appmbean;import java.util.ListResourceBundle;public class MBeanDescriptions extends ListResourceBundle { protected Object[][] getContents() { return new Object[][] { {"PropertyConfigMXBean.mbean", "MBean used to manage persistent application properties"}, {"PropertyConfigMXBean.attribute.Properties", "Properties associated with the running application"}, {"PropertyConfigMXBean.operation.setProperty", "Create a new property, or change the value of an existing property"}, {"PropertyConfigMXBean.operation.setProperty.key", "Name that identify the property to set."}, {"PropertyConfigMXBean.operation.setProperty.value", "Value for the property being set"}, {"PropertyConfigMXBean.operation.getProperty", "Get the value for an existing property"}, {"PropertyConfigMXBean.operation.getProperty.key", "Name that identify the property to be retrieved"} }; } }
Our MBean is quite simple, and only exposes one attribute and two operations. This helps keep our resource bundle class quite small. For real world example that file will be much bigger. Note: We didn't override the getDescription method associated with our MBean constructor, to keep our sample small as this doesn't add much value to our discussion.
To add support for other languages, we only need to implement the corresponding resource bundle class. MBeanDescriptions_fr
for French, and translate the description appropriately.
One interesting thing to note, is the name we used for our resource bundle keys. We didn't use arbitrary values, but we made sure we followed the following convention:
- MBean description: <MBeanInterfaceClass>.mbean
- MBean attribute description: <MBeanInterfaceClass>.attribute.<AttributeName>
- MBean operation description: <MBeanInterfaceClass>.operation.<OperationName>
- MBean operation parameter description: <MBeanInterfaceClass>.operation.<OperationName>.<ParameterName>
- MBean constructor description: <MBeanInterfaceClass>.constructor.<ConstructorName>
- MBean constructor parameter description: <MBeanInterfaceClass>.constructor.<ConstructorName>.<ParameterName>
MBeanDescriptions
and included it as part of the same package as our MBean. The above convention is used by the JDK 7 to localize MBean descriptions without requiring us to extend the StandardMBean class and override its many getDescription
methods. Unfortunately JDK 6 doesn't support built-in JMX localization, so we have to write the above code. However we can anticipate the JDK 7 functionality (and possible early support by WebLogic) by using the above convention when specifying our MBean resource bundle and associated resource keys.
You might have noticed the following code in our updated MBean implementatrion:
protected String getParameterName(MBeanOperationInfo op, MBeanParameterInfo param, int sequence) { return operationsParamNames_.get(op.getName())[sequence]; }
This is used to provide customized name to our operation parameters in place of the default generated 'po', 'p1', ... , 'pn' values.
The result of our hard work can be seen in the following JConsole screen shot:
Consult Part I for information on how to use JConsole to browse/edit our MBean.
What's next?
What if our application is deployed to a WebLogic server running with an English Locale, and our management client wants to use a French Locale. Currently as things stand the Localization is performed based on the server Locale, and not based on the client's Locale. In the last part of this blog series, we will see how to associate a Locale with our JMX client connection, and we will update our MBean code to support client based localization.