Thursday, August 28, 2008

From JConsole to VisualVM

With the release of VisualVM as a standard part of Sun's JDK distribution since JDK 6 Update 7, it is likely that Java developers will begin using VisualVM in situations in which they previously used the separate tools such as jinfo, jmap, jstack, jstat, and JConsole. Fortunately, two of the main features JMX developers were likely to use in JConsole can be used with VisualVM as well.

One feature I use very often in JConsole is the "MBeans" tab for displaying manageable and monitorable attributes, operations, and notifications related to my custom MBeans. While this tab does not exist in VisualVM when it is first run from the command-line in Java SE 6, it is trivial to add the MBeans plugin to get behavior very similar to that provided in JConsole.

To run VisualVM provided with the JDK distribution, use the command jvisualvm from the command prompt. This is demonstrated in the following screen snapshot:



When first started up, VisualVM does not have an MBeans tab like that used in JConsole. However, one can easily add the MBeans plugin by selecting Tools->Plugins from VisualVM. The following screen snapshots show how to do this.

This first screen snapshot shows selection of Tools->Plugins.



The next screen snapshot shows the MBean tab selection from among the available VisualVM plugins.




The next screen snapshot demonstrates what the MBeans tab looks like in VisualVM.



The MBeans tab in VisualVM is very familiar to anyone who has used the MBeans tab in JConsole.


Another useful JConsole feature is the ability to add custom tabs to JConsole. While the VisualVM JConsole Plug-in Wrapper Tab document recommends using VisualVM's own customizability features when developing new support for VisualVM, it can be handy to use existing JConsole plug-ins with VisualVM. The just-referenced document VisualVM JConsole Plug-in Wrapper Tab does a nice job of covering how to use a custom JConsole plug-in tab in VisualVM. That example uses the JDK-provided JTop custom plugin for the example.

The three screen snapshots that follow show how easy it is to install the JConsole plugins tab wrapper in VisualVM and to select a JConsole plugin for use in VisualVM. The final image shows how one JConsole plugin, the JTop plugin provided with the SDK, appears in VisualVM.

This first image shows the details related to the JConsole plugin tab wrapper.



The next image shows selection of a particular JConsole tab plugin once the wrapper plug-in for VisualVM has been installed.



This final image demonstrates what the JTop JConsole tab looks like in VisualVM via VisualVM's JConsole plug-in tab wrapper.




For individuals and organizations with large investments in JConsole custom tabs, the VisualVM wrapper plug-in allows those plug-ins to be leveraged in VirtualVM without massive re-writes. For those who appreciate JConsole as an easy-to-use client of custom JMX applications, VisualVM can provide similar capabilities via its MBeans plugin. Besides providing many of the advantages of JConsole via plug-ins and out-of-the-box support, VisualVM provides graphical representation of the monitoring data provided by many tools other than JConsole. With the JConsole-friendly plug-ins mentioned in this blog entry in use, VisualVM is able to combine the strengths of JConsole with the value found in other Sun JDK-provided management and monitoring tools.

Monday, August 25, 2008

Flex Charts with Google Charts and Eastwood Charts

Flex provides a rich, sophisticated set of dynamic charts, but a license fee is required (via purchase of FlexBuilder Professional) to use these Flex charts for most realistic production situations. There are several other charting options including Open Flash Chart (runs as its own SWF and reads JSON data files to generate charts), amCharts (not open source and uses ActionScript 2 so cannot be used directly in Flex), Fusion Charts Free (sends SWF and data files from server to browser), Google Charts, and JFreeChart/Eastwood Charts. In this blog entry, I'll briefly look at using Google Charts and Eastwood Charts 1.1.0 with Flex because I believe these are the easiest to use in a Flex application.

Because the Eastwood Chart Servlet implements the most of the APIs of Google Charts, my example will be able, in most cases, to call either charting provider based on user selection. There are some extensions in Eastwood beyond the Google Chart API and the Google Chart API has some features still not implemented in Eastwood, but most of the common functionality is commonly implemented in both charting providers.

The concept behind Google Chart API (and hence applies to Eastwood Chart Servlet as well) involves providing data for chart generation to the chart provider via HTTP URL parameters and getting an image (PNG) as a response.

While URLs constructed to request chart generation from Google Charts and Eastwood Charts are almost always the same (because Eastwood is implemented to the Google Charts API), there are differences between the two implementations of these APIs. Using Google Charts is really easy because no work needs to be done on the server-side. All the developer needs to do is construct the appropriate URL conforming to the Google Charts API and invoke that to get the returned image. With Eastwood Charts, the developer needs to install the Eastwood-provided WAR file on his or her favorite Java EE container. It is important to note that this is all that must be done. The WAR files does not need to be tweaked or manually unzipped, but simply needs to be deployed by dropping it in the appropriate container directory or else through the server's administration tool.

The Eastwood Developer Manual that is included with the Eastwood download contains a list of several bullets outlining some advantages of the Eastwood Charts implementation of the Google Charts API. Most of these advantages stem ability to run the charting server on one's own network. Advantages of running the charting provider on one's own machine is include the ability to keep potentially sensitive data within one's own environment and the ability to run the chart generation in environments in which Internet connectivity is not available.

The following MXML code is a simple but complete example that accesses both Google Charts and Eastwood Charts via the same URL. I intentionally included 3D bar chart examples to demonstrate an extension provided by Eastwood Charts, but not by the Google Charts. While I don't show it here, Google Charts similarly provide some features not currently supported in Eastwood Charts (such as Maps).

Here is the source code for the Flex application taking advantage of both Google Charts and Eastwood Charts:

MXML Code for Application Using Eastwood Charts and Google Charts

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
width="900" height="900">
<mx:Script>
<![CDATA[
import mx.events.FlexEvent;

/**
* Generate chart using Eastwood Chart Servlet or Google Charts using
* submitted values.
*/
private function generateChart(event:FlexEvent):void
{
chartDisplayPanel.visible = true;
chartDisplayPanel.title = chartType.selectedValue.toString();
const url:String = buildUrl(chartType.selection.id);
chartUrlText.text = url;
generatedChart.source = url;
}

/**
* Determine maximum value entered for balances.
*
* @return Maximum balance from provided input balances.
*/
private function findMaximumDataValue():Number
{
const maximumBalance:Number =
Math.max(
Number(savingsInput.text),
Number(checkingInput.text),
Number(moneyMarketInput.text),
Number(taxableStocksInput.text),
Number(taxableBondsInput.text),
Number(iraInput.text),
Number(pensionInput.text) );
return maximumBalance;
}

/**
* Return the appropriate chart provider URL domain.
*
* @return URL domain for chart provider.
*/
private function findChartProvider():String
{
var chartProviderUrl:String;
if ( chartProvider.selection.id == "google" )
{
chartProviderUrl = "http://chart.apis.google.com";
}
else
{
chartProviderUrl = "http://localhost:8080/eastwood-1.1.0";
}
return chartProviderUrl;
}

/**
* Construct labels portion of chart generation URL.
*
* @return Labels portion of chart generation URL.
*/
private function constructLabels():String
{
const labelsStr:String =
"Savings|Checking|Money Market|Taxable Stocks|"
+ "Taxable Bonds|IRA|Pension";
return labelsStr;
}

/**
* Construct URL for accessing Eastwood-generated or Google-generated chart.
*/
private function buildUrl(chartType:String):String
{
const maximumBalance:Number = findMaximumDataValue();
const chartProviderDomainUrl:String = findChartProvider();
const urlString:String =
chartProviderDomainUrl + "/chart?cht="
+ chartType // type of chart
+ "&chs=400x400" // chart size: width x height
+ "&chd=t:" // build up the data over next several lines
+ savingsInput.text + ","
+ checkingInput.text + ","
+ moneyMarketInput.text + ","
+ taxableStocksInput.text + ","
+ taxableBondsInput.text + ","
+ iraInput.text + ","
+ pensionInput.text
+ "&chds=0," + Math.round(maximumBalance) // data scaling
+ "&chl=" + constructLabels();
return urlString;
}
]]>
</mx:Script>

<mx:HBox id="mainHorizontalBox" width="95%">
<mx:Panel id="inputPanel" title="Flex and Eastwood Charts">
<mx:Form>
<mx:FormHeading label="Investments Distribution" />
<mx:FormItem label="Savings ($)">
<mx:TextInput id="savingsInput" />
</mx:FormItem>
<mx:FormItem label="Checking ($)">
<mx:TextInput id="checkingInput" />
</mx:FormItem>
<mx:FormItem label="Money Market ($)">
<mx:TextInput id="moneyMarketInput" />
</mx:FormItem>
<mx:FormItem label="Taxable Stocks ($)">
<mx:TextInput id="taxableStocksInput" />
</mx:FormItem>
<mx:FormItem label="Taxable Bonds ($)">
<mx:TextInput id="taxableBondsInput" />
</mx:FormItem>
<mx:FormItem label="IRA ($)">
<mx:TextInput id="iraInput" />
</mx:FormItem>
<mx:FormItem label="Pension ($)">
<mx:TextInput id="pensionInput" />
</mx:FormItem>
<mx:FormItem label="Chart Type" id="chartTypeFormItem">
<mx:RadioButtonGroup id="chartType" />
<mx:RadioButton groupName="chartType" id="p"
label="Pie"
value="Investments Pie Chart" />
<mx:RadioButton groupName="chartType" id="p3"
label="3D Pie"
value="Investments 3D Pie Chart" />
<mx:RadioButton groupName="chartType" id="bvs"
label="Vertical Bar"
value="Investments Vertical Bar" />
<mx:RadioButton groupName="chartType" id="bvs3"
label="Vertical 3D Bar"
value="Investments Vertical 3D Bar"
selected="true" />
<mx:RadioButton groupName="chartType" id="bhs"
label="Horizontal Bar"
value="Investments Horizontal Bar" />
<mx:RadioButton groupName="chartType" id="bhs3"
label="Horizontal 3D Bar"
value="Investments Horizontal 3D Bar" />
</mx:FormItem>
<mx:FormItem label="Chart Provider"
id="chartProviderFormItem">
<mx:RadioButtonGroup id="chartProvider" />
<mx:RadioButton groupName="chartProvider" id="google"
label="Google Charts API"
value="Google Charts API" />
<mx:RadioButton groupName="chartProvider" id="eastwood"
label="Eastwood Chart Servlet"
value="Eastwood Chart Servlet"
selected="true" />
</mx:FormItem>
<mx:Button id="submitButton" label="Generate Chart"
buttonDown="generateChart(event);" />
</mx:Form>
</mx:Panel>
<mx:Panel id="chartDisplayPanel" height="{inputPanel.height}"
visible="false">
<mx:Image id="generatedChart" />
</mx:Panel>
</mx:HBox>
<mx:Panel id="summaryPanel" width="{mainHorizontalBox.width}"
title="URL Being Used for Chart Generation">
<mx:TextArea id="chartUrlText" width="{summaryPanel.width}"
height="75" />
</mx:Panel>

</mx:Application>


The above example provides a radio button group for the user to select which chart provider (Google Charts or Eastwood Charts) should be used. Another radio button group allows the user to select which chart type should be generated. The text fields allow for dynamic data values to be supplied that are used in the chart generation. The sample code takes these input values, the user's selection for chart type, and the user's selection for chart provider and builds a URL from these data points that meets the Google Charts API.

The following screen snapshots show charts generated from both Google Charts and Eastwood Charts (click on the images to see larger versions).

Eastwood Charts-Generated 3D Pie Chart



Google Charts-Generated 3D Pie Chart



Eastwood Charts-Generated Vertical Bar Chart



Google Charts-Generated Vertical Bar Chart



As you can see in the screen snapshots, there are some slight differences in the implementations of Eastwood Charts and Google Charts. However, they are remarkably similar and it is pretty convenient to be able to have the same code call either implementation to generate these charts. There are many more types of charts available than those shown in this example. Examples of these types of charts can be found in the Google Charts API page and in the Eastwood Chart Servlet samples page.

Saturday, August 23, 2008

Remote JMX: Connectors and Adapters

One of the things that was most difficult for me to learn when first learning about Remote JMX was the difference between a JMX Connector and a JMX Adapter (also spelled Adaptor in many cases). Part of this confusion was a result from trying to understand the difference based on books written before the Remote JMX specification was written or finalized. Another part of the confusion is in the actual names. In this blog entry, I'll point to a few references and resources that I think best explain the JMX Connector and the JMX Adapter and then I'll go through some source code examples because I believe that using these in practice makes them easier to understand than simply reading the words.

The JMX 1.4 specification includes both "regular" JMX and remote JMX in a single, consolidated specification. Part III of this consolidated JMX specification is focused on Remote JMX and is called "JMX Remote API Specification." The first chapter in this Part III, Chapter 13, covers JMX Connectors in general and is followed by Chapter 14 focusing on the RMI-based JMX Connector and Chapter 15 focusing on the Generic Connector.

I think two sentences in Chapter 13 of the JMX 1.4 specification are particularly important to understanding JMX connectors:

1. "The client end of a connector exports essentially the same interface as the
MBean server."

2. "A connector consists of a connector client and a connector server."

Section 5.3 of the JMX 1.4 specification is called "Protocol Adaptors and Connectors" and covers basics of both adapters and connectors. Related to JMX connectors, this section makes the important observation that "A connector is specific to a given protocol, but the management application can use any connector indifferently because they have the same remote interface."

This section also clearly outlines differentiating characteristics of JMX Protocol Adaptors:

1. "Protocol adaptors provide a management view of the JMX agent through a given protocol. They adapt the operations of MBeans and the MBean server into a representation in the given protocol, and possibly into a different information
model."

2. "Management applications that connect to a protocol adaptor are usually specific to the given protocol."

With the key characteristics of JMX connectors and protocol adaptors highlighted above, there are some quickly identifiable differences between the two. These differences may be most succinctly summarized in Daniel Fuchs's blog entry What is JMX?, where he states that JMX Protocol Connectors represent the MBeans to the remote client the same way they would be represented to a local client and that a JMX Protocol Adaptor adapts the server-side model to what the client expects.

JMX Protocol Connectors and JMX Protocol Adaptors both typically work with a single protocol. The difference between the two is that Adaptors massage the management interface for the client's benefit while the Connectors provide a protocol-independent API that is essentially the same as the local API.

The JMX Reference Implementation (the implementation of JMX included in Sun's Java SE 6 distribution) provides the one Connector required of the specification: the Remote Method Invocation (RMI) Connector. The JMX 1.4 specification only requires a JMX implementation to provide an RMI-based Connector, but the specification outlines an optional JMXMP-based Connector. For this example, I use the JMXMP (JMX Message Protocol) Connector provided by OpenDMK. The third JMX Connector used in this example is the JSR-262 JMX Web Services Connector.

I now delve into some code samples to illustrate JMX Connectors versus JMX Adaptors. For simplicity, I have a single class that runs three types of JMX Connector Servers and also runs an Adapter Agent. This class is called JmxServerMain and its main() method is listed here first.

JmxServerMain.java main() Method

/**
* Main method to set up various server-side remote JMX constructs.
*
* @param arguments Command-line arguments.
*/
public static void main(final String[] arguments)
{
final List connectorServers
= new ArrayList();
registerExampleMBean("dustinApp:type=exampleMBean");
useJmxServiceUrlBasedConnector(
"service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi",
"connector:type=standard_rmi",
connectorServers );
useJmxServiceUrlBasedConnector(
"service:jmx:jmxmp://localhost:1098",
"connector:type=optional_jmxmp",
connectorServers );
useJmxServiceUrlBasedConnector(
"service:jmx:ws://localhost:1097/jmxws",
"connector:type=jsr262_jmxws",
connectorServers);
final HtmlAdaptorServer htmlAdaptor =
useHtmlAdaptor(1096, "adaptor:type=opendmk_html");
waitForInput();
stopConnectorServers(connectorServers);
stopAdaptors(htmlAdaptor);


I will show the implementations of the methods called above later in this entry, but even this high-level look reveals clear and easily identifiable differences between Connectors and Adapters. Note that all three JMX Connectors can be treated the same and can take advantage of the same method. In other words, all three Connectors can be set up using the useJmxServiceUrlBasedConnector method. The Adapter, on the other hand, can not take advantage of the same method as the Connectors and must be set up with its own useHtmlAdaptor method. Note that this method name, useHtmlAdaptor, is specific to the adaptor involved.

The definition of the useJmxServiceUrlBasedConnector method is shown next.

JmxServerMain and its useJmxServiceUrlBasedConnector Method

/**
* Use the platform MBean server in conjunction with the JMX connector
* specified in the provided JMXServiceURL.
*
* @param serviceUrl JMXServiceURL to be used in connector.
* @param connectorMBeanName MBean registration name for the connector.
* @param connectorServers Collection to which my instantiated JMX Connector
* Server should be added.
*/
public static boolean useJmxServiceUrlBasedConnector(
final String serviceUrl,
final String connectorMBeanName,
final List connectorServers)
{
boolean success = true;
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
printOutputHeader(
"Setting up connector for JMXServiceURL " + serviceUrl,
System.out);
try
{
final JMXServiceURL jmxServiceUrl = new JMXServiceURL(serviceUrl);
final JMXConnectorServer connectorServer =
JMXConnectorServerFactory.newJMXConnectorServer(
jmxServiceUrl,
null,
mbs);
connectorServer.start();
registerProvidedMBean(connectorServer, connectorMBeanName);
connectorServers.add(connectorServer);
}
catch (MalformedURLException badJmxServiceUrl)
{
System.err.print(
"ERROR trying to create JMX server connector with service URL "
+ serviceUrl + ":\n" + badJmxServiceUrl.getMessage() );
success = false;
}
catch (IOException ioEx)
{
System.err.println(
"ERROR trying to access server connector.\n"
+ ioEx.getMessage() );
success = false;
}

if ( success )
{
System.out.println(
connectorMBeanName
+ " registered in MBean server as connector with JMXServiceURL of "
+ serviceUrl + ".");
}
else
{
System.out.println("\n\nERROR encountered.");
}
System.out.println("\n\n");

return success;
}


The useJmxServiceUrlBasedConnector method shown above is generic and supports all types of specification-compliant JMX connectors provided to it. In fact, the only thing that differentiates one type of connector from another is the protocol embedded within the string that forms the JMXServiceURL. For connector management purposes, it is a recommended practice to register connectors themselves as MBeans and that is also done in this code.

We have now seen that the same generic code can be used to set up all the Connectors for Remote JMX access. In the example above, the RMI, JMXMP, and JMX Web Services Connector are all used this way. The next code listing shows the code for the useHtmlAdaptor method, which is also part of the JmxServerMain class.

JmxServerMain.java useHtmlAdaptor Method

/**
* Provide server-side functionality for HTML Adaptor to be used by client
* web page.
*
* @param htmlAdaptorPort Port on which web browser will see this.
* @param adaptorMBeanName Name of MBean by which adaptor will be registered.
* @return Handle to the HTML Adaptor that can be stopped when finished.
*/
public static HtmlAdaptorServer useHtmlAdaptor(
final int htmlAdaptorPort,
final String adaptorMBeanName)
{
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
final HtmlAdaptorServer htmlAdaptor = new HtmlAdaptorServer(htmlAdaptorPort);

printOutputHeader(
"Setting up HtmlAdaptor for port " + htmlAdaptorPort,
System.out);

registerProvidedMBean(htmlAdaptor, adaptorMBeanName);
htmlAdaptor.start();
System.out.println("HTML Adaptor started.\n\n");
return htmlAdaptor;
}


The HTML Adaptor is not acquired with a standardized JMXConnectorServerFactory.newJMXConnectorServer call like the connectors were able to be acquired. Instead, the HtmlAdaptorServer is explicitly instantiated. The HTMLAdaptorServer used here is provided by OpenDMK.

All three JMX Connectors (RMI, JMXMP, and WS-JMX) can be connected to by JConsole or by any other standard JMX client. I'll first show the code for accessing these three connectors from a simple client.

ClientMain.java

package dustin.jmx.client;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Arrays;
import java.util.List;
import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

/**
* This simple example demonstrates a flexible/generic remote JMX client.
*
* @author Dustin
*/
public class ClientMain
{
private enum ConnectionProtocol
{
RMI ("service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"),
JMXMP ("service:jmx:jmxmp://localhost:1098"),
JMXWS ("service:jmx:ws://localhost:1097/jmxws");

String jmxServiceUrl;

ConnectionProtocol(final String jmxServiceUrl)
{
this.jmxServiceUrl = jmxServiceUrl;
}

public String getJmxServiceUrl() { return this.jmxServiceUrl; }
};

/**
* The functionality here is available directly through the provided
* MBeanServerConnection with or without a proxy or reflection. Because it
* is information only at the MBeanServer level (not at each individually
* hosted MBean's level), no ObjectName is required.
*
* @param mbsc MBeanServerConnection for connecting to remote JMX agent.
*/
public static void demonstrateCommonMBeanServerInfo(
final MBeanServerConnection mbsc)
{
try
{
System.out.println( "MBean Count: " + mbsc.getMBeanCount() );
System.out.println( "MBean Default Domain: " + mbsc.getDefaultDomain() );
final List domains = Arrays.asList(mbsc.getDomains());
System.out.println("DOMAINS:");
for ( final String domain : domains )
{
System.out.println ("\t- " + domain);
}
}
catch (IOException ioEx)
{
System.err.println( "ERROR encountered trying to get MBeanCount and "
+ "Default Domain for provided MBeanServer:\n"
+ ioEx.getMessage() );
}
}

/**
* Run client that can talk to a JMX Connector Server of one of three types:
* Remote Method Invocation (RMI), JMX Message Protocol (JMXMP), or JMX-WS
* (JMX Web Services Connector/JSR-262).
*
* @param aArguments Command-line arguments; one expected to indicate
* Remote JMX protocol (RMI, JMXMP, or JMXWS).
*/
public static void main(String[] aArguments)
{
ConnectionProtocol connectionProtocol = null;
if ( aArguments.length > 0 )
{
final String protocolStr = aArguments[0].toUpperCase();
System.out.println("Protocol String: " + protocolStr);
connectionProtocol = ConnectionProtocol.valueOf(protocolStr);
}
if ( connectionProtocol == null )
{
connectionProtocol = ConnectionProtocol.RMI;
}
final String jmxServiceUrl = connectionProtocol.getJmxServiceUrl();
System.out.println("JMXServiceURL: " + jmxServiceUrl);

try
{
final JMXServiceURL jmxUrl = new JMXServiceURL(jmxServiceUrl);
final JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxUrl);
final MBeanServerConnection mbsc = jmxConnector.getMBeanServerConnection();

System.out.println("Using " + connectionProtocol.toString() + "!!!");
demonstrateCommonMBeanServerInfo(mbsc);
}
catch (MalformedURLException badUrl)
{
System.err.println( "ERROR: Problem with JMXServiceURL based on "
+ jmxServiceUrl + ": " + badUrl.getMessage() );
}
catch (IOException ioEx)
{
System.err.println( "ERROR: IOException trying to connect to JMX "
+ "Connector Server: " + ioEx.getMessage() );
}
}
}


The simple client whose code is shown above allows the user to specify which protocol connector to use (RMI, JMXMP, JMXWS) as a command-line argument and RMI is used if none is specified. What this simple client demonstrates is that the JMX Connector Servers are accessed in the identical manner regardless of the underlying protocol. Only the JMXServiceURL needs to be different.

The three JMX Connector Servers can also be accessed from JConsole. As with the simple client, the only thing that needs to be different is the JMXServiceURL provided to JConsole in the remote field.

The HTML Adaptor is not accessed via the simple client shown above or via JConsole. Rather, as an HTML adapter, it has adapted the model for HTML presentation and one accesses its exposed management interfaces via web browser with URL of http://localhost:1096 (1096 in this example because we established this as the port for the HtmlAdaptorServer).

I am not going to show the simple client output, the JConsole output, or the HTML web page output here, because they are nothing different from what one would see using these tools for other JMX uses. For convenience, I am including the entire JmxServerMain class next. I included significant portions of it above, but this listing includes the entire class with all of its convenience methods.

Entire JmxServerMain.java Class

package dustin.jmx.server;

import dustin.jmx.ApplicationState;

import com.sun.jdmk.comm.HtmlAdaptorServer;

import java.io.Console;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.management.ManagementFactory;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;

import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;

/**
* Main executable to showcase various commonly used JMX connectors and adapters
* (or adaptors).
*
* @author Dustin
*/
public class JmxServerMain
{
/**
* Artificial pause until ENTER button is pressed.
*/
public static void waitForInput()
{
final Console console = System.console();
if ( console == null )
{
System.err.println(
"ERROR. Please run this application on a machine with a console.");
return;
}
console.printf("Press to exit.");
final String unusedInput = console.readLine();
}

/**
* Print out header to provided output stream.
*
* @param headerText Text to be displayed in output header/separator.
* @param os OutputStream to which header should be written (such as
* System.out).
*/
public static void printOutputHeader(
final String headerText,
final OutputStream os )
{
final String newLine = System.getProperty("line.separator");
final String headerLine = "-- " + headerText + newLine;
final String separator =
"------------------------------------------------------------------"
+ newLine;

try
{
os.write(separator.getBytes());
os.write(headerLine.getBytes());
os.write(separator.getBytes());
}
catch (IOException ioEx)
{
System.err.println(
"ERROR trying to write header '" + headerText + "' out to the "
+ " provided OutputStream:\n" + ioEx.getMessage() );
}
}

/**
* Stop all opened and started JMXConnectorServer instances.
*
* @param connectors JMXConnectorServer instances.
* @return Number of connector servers stopped.
*/
public static int stopConnectorServers(final List connectors)
{
System.out.println("Stopping Connector Servers ...");
int numberOfConnectorsClosed = 0;
for ( final JMXConnectorServer connector : connectors )
{
numberOfConnectorsClosed++;
try
{
connector.stop();
}
catch (IOException ioEx)
{
System.err.println(
"ERROR trying to close JMXConnectorServer:\n" + ioEx.getMessage());
}
}
System.out.println(
"Stopped " + numberOfConnectorsClosed + " connector servers.");
return numberOfConnectorsClosed;
}

/**
* Stop the adaptors.
*
* @param htmlAdaptor Handle to HTML Adaptor to be stopped.
* @return Number of adaptors stopped.
*/
public static int stopAdaptors(HtmlAdaptorServer htmlAdaptor)
{
System.out.println("Stopping Adaptors ...");
htmlAdaptor.stop();
System.out.println("Stopped HtmlAdaptorServer.");
return 1;
}

/**
* Register the provided object as an MBean with the provided ObjectName.
*
* @param objectToBeRegisteredAsMBean Object to be registered with MBean
* Server.
* @param nameForMBean Name to be used for ObjectName of registered MBean.
*/
public static void registerProvidedMBean(
final Object objectToBeRegisteredAsMBean,
final String nameForMBean)
{
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
try
{
mbs.registerMBean(
objectToBeRegisteredAsMBean,
new ObjectName(nameForMBean) );
}
catch (MalformedObjectNameException badMBeanNameEx)
{
System.err.println(
"ERROR trying to create ObjectName with " + nameForMBean + ":\n"
+ badMBeanNameEx.getMessage() );
}
catch (MBeanRegistrationException badMBeanRegistrationEx)
{
System.err.println(
"ERROR trying to register MBean " + nameForMBean + ":\n"
+ badMBeanRegistrationEx.getMessage() );
}
catch (NotCompliantMBeanException nonCompliantEx)
{
System.err.println(
"ERROR: " + nameForMBean + " is not a compliant MBean.:\n"
+ nonCompliantEx.getMessage() );
}
catch (InstanceAlreadyExistsException redundantMBeanEx)
{
System.err.println(
"ERROR: MBean instance " + nameForMBean + " already exists:\n"
+ redundantMBeanEx.getMessage() );
}
}

/**
* Register an example MBean with the Platform MBean server to be looked up
* by clients connected via a connector or adapter. Note that this is not
* absolutely necessary in Java SE 6, but it is interesting.
*
* @param nameForMBean Name for MBean to be registered with MBeanServer.
*/
public static void registerExampleMBean(
final String nameForMBean )
{
registerProvidedMBean(new ApplicationState(), nameForMBean);
}

/**
* Use the platform MBean server in conjunction with the JMX connector
* specified in the provided JMXServiceURL.
*
* @param serviceUrl JMXServiceURL to be used in connector.
* @param connectorMBeanName MBean registration name for the connector.
* @param connectorServers Collection to which my instantiated JMX Connector
* Server should be added.
*/
public static boolean useJmxServiceUrlBasedConnector(
final String serviceUrl,
final String connectorMBeanName,
final List connectorServers)
{
boolean success = true;
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
printOutputHeader(
"Setting up connector for JMXServiceURL " + serviceUrl,
System.out);
try
{
final JMXServiceURL jmxServiceUrl = new JMXServiceURL(serviceUrl);
final JMXConnectorServer connectorServer =
JMXConnectorServerFactory.newJMXConnectorServer(
jmxServiceUrl,
null,
mbs);
connectorServer.start();
registerProvidedMBean(connectorServer, connectorMBeanName);
connectorServers.add(connectorServer);
}
catch (MalformedURLException badJmxServiceUrl)
{
System.err.print(
"ERROR trying to create JMX server connector with service URL "
+ serviceUrl + ":\n" + badJmxServiceUrl.getMessage() );
success = false;
}
catch (IOException ioEx)
{
System.err.println(
"ERROR trying to access server connector.\n"
+ ioEx.getMessage() );
success = false;
}

if ( success )
{
System.out.println(
connectorMBeanName
+ " registered in MBean server as connector with JMXServiceURL of "
+ serviceUrl + ".");
}
else
{
System.out.println("\n\nERROR encountered.");
}
System.out.println("\n\n");

return success;
}

/**
* Provide server-side functionality for HTML Adaptor to be used by client
* web page.
*
* @param htmlAdaptorPort Port on which web browser will see this.
* @param adaptorMBeanName Name of MBean by which adaptor will be registered.
* @return Handle to the HTML Adaptor that can be stopped when finished.
*/
public static HtmlAdaptorServer useHtmlAdaptor(
final int htmlAdaptorPort,
final String adaptorMBeanName)
{
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
final HtmlAdaptorServer htmlAdaptor = new HtmlAdaptorServer(htmlAdaptorPort);

printOutputHeader(
"Setting up HtmlAdaptor for port " + htmlAdaptorPort,
System.out);

registerProvidedMBean(htmlAdaptor, adaptorMBeanName);
htmlAdaptor.start();
System.out.println("HTML Adaptor started.\n\n");
return htmlAdaptor;
}

/**
* Main method to set up various server-side remote JMX constructs.
*
* @param arguments Command-line arguments.
*/
public static void main(final String[] arguments)
{
final List connectorServers
= new ArrayList();
registerExampleMBean("dustinApp:type=exampleMBean");
useJmxServiceUrlBasedConnector(
"service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi",
"connector:type=standard_rmi",
connectorServers );
useJmxServiceUrlBasedConnector(
"service:jmx:jmxmp://localhost:1098",
"connector:type=optional_jmxmp",
connectorServers );
useJmxServiceUrlBasedConnector(
"service:jmx:ws://localhost:1097/jmxws",
"connector:type=jsr262_jmxws",
connectorServers);
final HtmlAdaptorServer htmlAdaptor =
useHtmlAdaptor(1096, "adaptor:type=opendmk_html");
waitForInput();
stopConnectorServers(connectorServers);
stopAdaptors(htmlAdaptor);
}
}


Because the RMI connector is included with the Reference Implementation of JMX that is included with Java SE 6, I did not need to explicitly include anything on the classpath to use it. However, to use the JSR-262 WS-JMX and to use OpenDMK for both the HTMLAdaptor and for the JMXMP Connector, I did need to add relevant JARs to the classpaths.

I ran JmxServerMain as follows (thankfully, Java SE 6 supports wildcards for including JAR files in the classpath or else the WS-JMX entries would be numerous):


java -cp C:\OpenDMK-bin\lib\jdmkrt.jar;C:\OpenDMK-bin\lib\jmxremote_optional.jar;dist\RemoteJMX.jar;C:\jmx-ws-262\jsr262-ri\lib\* dustin.jmx.server.JmxServerMain


To run JConsole to "see" the OpenDMK and WS-JMX stuff, I used this command:


jconsole -J-Djava.class.path=C:\jmx-ws-262\jsr262-ri\lib\*;"C:\Program Files\Java\jdk1.6.0_07"\lib\jconsole.jar;"C:\Program Files\Java\jdk1.6.0_07"\lib\tools.jar;C:\OpenDMK-bin\lib\jmxremote_optional.jar


Finally, to run the simple programmatic client, I used this command:


java -cp dist\RemoteJMX.jar;C:\jmx-ws-262\jsr262-ri\lib\*;C:\OpenDMK-bin\lib\* dustin.jxm.client.ClientMain


If you don't include the appropriate JAR files for WS-JMX or for OpenDMK's JMXMP Connector, you will see errors like those shown in the following two images ("Unsupported protocol: jmxmp" and "Unsupported protocol: ws"):

Failure to Include OpenDMK JARs on ClassPath




Failure to Include JSR-262 JARs on ClassPath



Failure to include the OpenDMK JARs for HTMLAdaptor will be revealed at compile time instead of runtime because of the direct instantiation of the HtmlAdaptorServer class described above. Of course, the runtime classpath will need them as well, but failure to do that is marked by the well-known NoClassDefFoundError.

In this blog entry, I've attempted to demonstrate key differences between JMX Protocol Adaptors and JMX Protocol Connectors. In general, I prefer Connectors because of the standardized approach that can be used to work with them both on the Connector Client and Connector Server side. I also generally like having the same interface both locally and remotely. However, there are times when adapters have their advantages. For example, it is nice not to have to include OpenDMK or WS-JMX libraries on the client machine when using a web browser with the HTML Adaptor. Perhaps at least partially for this reason (simplicity on the client side), early JMX books often favored the HTML Adaptor as the view into managed applications. The fact that JConsole was not yet available probably had something to do with the prevalent use of HTML Adaptor as well. With JConsole, VisualVM, and the ability to easily write JMX Connector Clients, I find myself generally favoring Connectors over Adaptors unless there is something specific about the client format (such as HTML or SNMP) that is required.

Other resources describing JMX Connectors and Adaptors include the JMX Tutorial (focus on connectors), the JMX Accelerated How-to, and the Remote Management Applications section of the Sun Java Dynamic Management Kit Tutorial.

Friday, August 15, 2008

Extraordinary Standard MBeans

Standard MBeans are commonly used for several reasons. They are easy to use, the interfaces can be applied to client-side proxies, and the interfaces allow for easy Javadoc-based documentation of the management interface on the Standard MBean. MXBeans share these advantages and add a few of their own such as looser required naming and package conventions and greater interoperability support via Open MBean Types.

However, as discussed in my blog entry The JMX Model MBean, there are some disadvantages of Standard MBeans (and MXBeans) that Model MBeans can address. The most significant reason for using the Model MBean (to wrap existing non-JMX resources such as employed in the Spring Framework) is not possible with Standard or MXBeans due to their static nature. However, the advantage of being able to provide information on operations, parameters, constructors, attributes, and notifications is not a sole advantage of Model MBeans or even of the class of dynamic MBeans. I will demonstrate in this blog entry one approach to specifying operation metadata for Standard MBeans and MXBeans that can be displayed in JConsole and other clients that know how to handle MBeanInfo.

The key to providing additional metadata above and beyond what is normally associated with a Standard MBean is use of the class javax.management.StandardMBean. The description portion of this class's Javadoc documentation shows two ways to use this class. They are using StandardMBean's public constructors or by having the MBean implementation class extend the StandardMBean class (but still implement the same interface). Using the StandardMBean class allows the naming conventions normally associated with Standard MBeans to be relaxed because arbitrary implementation classes and interfaces can be associated with this class. However, in this blog entry, I will keep the normal implementation/interface naming conventions and use the class StandardMBean to provide metadata to the Standard MBean and MXBean.

My first code listing shows a class called DustinMBean that extends StandardMBean so that I can override the StandardMBean.cacheMBeanInfo method.

DustinMBean.java

package dustin.jmx;

import javax.management.MBeanInfo;
import javax.management.StandardMBean;

/**
* Child of StandardMBean overridden for customization purposes.
*
* @author Dustin
*/
public class DustinMBean extends StandardMBean
{
/**
* Single constructor accepting MBean implementation class, MBean interface,
* and whether or not it is an MXBean (Standard MBean if not MXBean). Note
* that my parent, StandardMBean, has more than one constructor, but I'm only
* using one of them.
*
* @param implementation MBean implementation class.
* @param mbeanInterface MBean implementation class's interface.
* @param isMXBean true if MXBean; false if Standard MBean.
*/
public DustinMBean(
T implementation,
Class mbeanInterface,
boolean isMXBean )
{
super(implementation, mbeanInterface, isMXBean);
}

/**
* Specify an MBeanInfo to be used with a StandardMBean-turned-DynamicMBean.
*
* @param mbeanInfo MBeanInfo describing this standard MBean turned into a
* Dynamic MBean.
*/
public void setMBeanInfo(final MBeanInfo mbeanInfo)
{
cacheMBeanInfo(mbeanInfo);
}
}


The class shown above makes it easy to set a Standard MBean's MBeanInfo instance.

I will be using a Standard MBean and an MXBean in this blog entry. The next two code listings show the interface and implementation class for the Standard MBean.

SimpleCalculatorMBean.java (Standard MBean Interface)

package dustin.jmx;

/**
* Interface for a Standard MBean example using the Simple Calculator.
*
* @author Dustin
*/
public interface SimpleCalculatorMBean
{
/**
* Calculate the sum of the augend and the addend.
*
* @param augend First integer to be added.
* @param addend Second integer to be added.
* @return Sum of augend and addend.
*/
public int add(final int augend, final int addend);

/**
* Calculate the difference between the minuend and subtrahend.
*
* @param minuend Minuend in subtraction operation.
* @param subtrahend Subtrahend in subtraction operation.
* @return Difference of minuend and subtrahend.
*/
public int subtract(final int minuend, final int subtrahend);

/**
* Calculate the product of the two provided factors.
*
* @param factor1 First integer factor.
* @param factor2 Second integer factor.
* @return Product of provided factors.
*/
public int multiply(final int factor1, final int factor2);

/**
* Calculate the quotient of the dividend divided by the divisor.
*
* @param dividend Integer dividend.
* @param divisor Integer divisor.
* @return Quotient of dividend divided by divisor.
*/
public double divide(final int dividend, final int divisor);

/**
* Provides the type of operation last executed against this calculator class.
*
* @return Type of operation last executed against this calculator.
*/
public OperationType whatWasTheLastOperation();
}


SimpleCalculator.java (Standard MBean Implementation)

package dustin.jmx;

/**
* Simple calculator class intended to demonstrate how a class with no knowledge
* of JMX or management can be used as a Standard MBean.
*
* @author Dustin
*/
public class SimpleCalculator implements SimpleCalculatorMBean
{
private OperationType lastOperation = OperationType.NO_OPERATIONS_INVOKED;

/**
* Calculate the sum of the augend and the addend.
*
* @param augend First integer to be added.
* @param addend Second integer to be added.
* @return Sum of augend and addend.
*/
public int add(final int augend, final int addend)
{
lastOperation = OperationType.INTEGER_ADDITION;
return augend + addend;
}

/**
* Calculate the difference between the minuend and subtrahend.
*
* @param minuend Minuend in subtraction operation.
* @param subtrahend Subtrahend in subtraction operation.
* @return Difference of minuend and subtrahend.
*/
public int subtract(final int minuend, final int subtrahend)
{
lastOperation = OperationType.INTEGER_SUBTRACTION;
return minuend - subtrahend;
}

/**
* Calculate the product of the two provided factors.
*
* @param factor1 First integer factor.
* @param factor2 Second integer factor.
* @return Product of provided factors.
*/
public int multiply(final int factor1, final int factor2)
{
lastOperation = OperationType.INTEGER_MULTIPLICATION;
return factor1 * factor2;
}

/**
* Calculate the quotient of the dividend divided by the divisor.
*
* @param dividend Integer dividend.
* @param divisor Integer divisor.
* @return Quotient of dividend divided by divisor.
*/
public double divide(final int dividend, final int divisor)
{
lastOperation = OperationType.INTEGER_DIVISION;
return dividend / divisor;
}

/**
* Provides the type of operation last executed against this calculator class.
*
* @return Type of operation last executed against this calculator.
*/
public OperationType whatWasTheLastOperation()
{
return this.lastOperation;
}
}


The two code listings immediately above are the interface and implementation for the Standard MBean. The next two listings are for the interface and implementation for a very similar MXBean. The MXBean is not really any more complicated than the Standard MBean, but I needed to name it something different.

LessSimpleCalculatorMXBean.java (MXBean Interface)

package dustin.jmx;

/**
* Interface for a standard MXBean example using the Simple Calculator.
*
* @author Dustin
*/
public interface LessSimpleCalculatorMXBean
{
/**
* Calculate the sum of the augend and the addend.
*
* @param augend First integer to be added.
* @param addend Second integer to be added.
* @return Sum of augend and addend.
*/
public int add(final int augend, final int addend);

/**
* Calculate the difference between the minuend and subtrahend.
*
* @param minuend Minuend in subtraction operation.
* @param subtrahend Subtrahend in subtraction operation.
* @return Difference of minuend and subtrahend.
*/
public int subtract(final int minuend, final int subtrahend);

/**
* Calculate the product of the two provided factors.
*
* @param factor1 First integer factor.
* @param factor2 Second integer factor.
* @return Product of provided factors.
*/
public int multiply(final int factor1, final int factor2);

/**
* Calculate the quotient of the dividend divided by the divisor.
*
* @param dividend Integer dividend.
* @param divisor Integer divisor.
* @return Quotient of dividend divided by divisor.
*/
public double divide(final int dividend, final int divisor);

/**
* Provides the type of operation last executed against this calculator class.
*
* @return Type of operation last executed against this calculator.
*/
public OperationType whatWasTheLastOperation();
}


LessSimpleCalculator.java (MXBean Implementation)

package dustin.jmx;

/**
* Simple calculator class intended to demonstrate how a class with no knowledge
* of JMX or management can be used as an MXBean.
*
* @author Dustin
*/
public class LessSimpleCalculator implements LessSimpleCalculatorMXBean
{
private OperationType lastOperation = OperationType.NO_OPERATIONS_INVOKED;

/**
* Calculate the sum of the augend and the addend.
*
* @param augend First integer to be added.
* @param addend Second integer to be added.
* @return Sum of augend and addend.
*/
public int add(final int augend, final int addend)
{
lastOperation = OperationType.INTEGER_ADDITION;
return augend + addend;
}

/**
* Calculate the difference between the minuend and subtrahend.
*
* @param minuend Minuend in subtraction operation.
* @param subtrahend Subtrahend in subtraction operation.
* @return Difference of minuend and subtrahend.
*/
public int subtract(final int minuend, final int subtrahend)
{
lastOperation = OperationType.INTEGER_SUBTRACTION;
return minuend - subtrahend;
}

/**
* Calculate the product of the two provided factors.
*
* @param factor1 First integer factor.
* @param factor2 Second integer factor.
* @return Product of provided factors.
*/
public int multiply(final int factor1, final int factor2)
{
lastOperation = OperationType.INTEGER_MULTIPLICATION;
return factor1 * factor2;
}

/**
* Calculate the quotient of the dividend divided by the divisor.
*
* @param dividend Integer dividend.
* @param divisor Integer divisor.
* @return Quotient of dividend divided by divisor.
*/
public double divide(final int dividend, final int divisor)
{
lastOperation = OperationType.INTEGER_DIVISION;
return dividend / divisor;
}

/**
* Provides the type of operation last executed against this calculator class.
*
* @return Type of operation last executed against this calculator.
*/
public OperationType whatWasTheLastOperation()
{
return this.lastOperation;
}
}


Both the Standard MBean and MXBean code listings above use the OperationType enum. The main reason for adding an enum into the mix is to demonstrate differences between Standard MBean and MXMBean.

OperationType.java

package dustin.jmx;

/**
* Simple enum representing a type of calculator operation.
*
* @author Dustin
*/
public enum OperationType
{
INTEGER_ADDITION,
INTEGER_SUBTRACTION,
INTEGER_MULTIPLICATION,
INTEGER_DIVISION,
NO_OPERATIONS_INVOKED
}


With the Standard MBean, MXBean, enum, and DustinMBean classes all shown above, it is time to look at the class that calls DustinMBean to turn the Standard and MXBeans into dynamic MBeans with metadata descriptions.

StandardMBeanDemonstrator.java

package dustin.jmx;

import java.lang.management.ManagementFactory;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.ReflectionException;

/**
* This class is intended to demonstrate a more powerful Standard MBean
* available from overriding the javax.management.StandardMBean class.
*
* @author Dustin
*/
public class StandardMBeanDemonstrator
{
/**
* Pause for the specified number of milliseconds.
*
* @param millisecondsToPause Milliseconds to pause execution.
*/
public static void pause(final int millisecondsToPause)
{
try
{
Thread.sleep(millisecondsToPause);
}
catch (InterruptedException threadAwakened)
{
System.err.println("Don't wake me up!\n" + threadAwakened.getMessage());
}
}

/**
* Construct the meta information for the SimpleCalculator
* Standard-turned-Dynamic MBeans operations and the operations' parameters.
*
* Note that this method was adapted from a very similar method for
* constructor Model MBean operation info data that was discussed in the
* method buildModelMBeanOperationInfo() in the class
* ModelMBeanDemonstrator in the blog entry "The JMX Model MBean"
* (http://marxsoftware.blogspot.com/2008/07/jmx-model-mbean.html).
*
* @return Metadata about MBean's operations.
*/
private static MBeanOperationInfo[] buildMBeanOperationInfo()
{
//
// Build the PARAMETERS and OPERATIONS meta information for "add".
//

final MBeanParameterInfo augendParameter =
new MBeanParameterInfo(
"augend",
Integer.TYPE.toString(),
"The first parameter in the addition (augend)." );
final MBeanParameterInfo addendParameter =
new MBeanParameterInfo(
"addend",
Integer.TYPE.toString(),
"The second parameter in the addition (addend)." );

final MBeanOperationInfo addOperationInfo =
new MBeanOperationInfo(
"add",
"Integer Addition",
new MBeanParameterInfo[] {augendParameter, addendParameter},
Integer.TYPE.toString(),
MBeanOperationInfo.INFO );


//
// Build the PARAMETERS and OPERATIONS meta information for "subtract".
//

final MBeanParameterInfo minuendParameter =
new MBeanParameterInfo(
"minuend",
Integer.TYPE.toString(),
"The first parameter in the substraction (minuend)." );

final MBeanParameterInfo subtrahendParameter =
new MBeanParameterInfo(
"subtrahend",
Integer.TYPE.toString(),
"The second parameter in the subtraction (subtrahend)." );

final MBeanOperationInfo subtractOperationInfo =
new MBeanOperationInfo(
"subtract",
"Integer Subtraction",
new MBeanParameterInfo[] {minuendParameter, subtrahendParameter},
Integer.TYPE.toString(),
MBeanOperationInfo.INFO );

//
// Build the PARAMETERS and OPERATIONS meta information for "multiply".
//

final MBeanParameterInfo factorOneParameter =
new MBeanParameterInfo(
"factor1",
Integer.TYPE.toString(),
"The first factor in the multiplication." );

final MBeanParameterInfo factorTwoParameter =
new MBeanParameterInfo(
"factor2",
Integer.TYPE.toString(),
"The second factor in the multiplication." );

final MBeanOperationInfo multiplyOperationInfo =
new MBeanOperationInfo(
"multiply",
"Integer Multiplication",
new MBeanParameterInfo[] {factorOneParameter, factorTwoParameter},
Integer.TYPE.toString(),
MBeanOperationInfo.INFO );

//
// Build the PARAMETERS and OPERATIONS meta information for "divide".
//

final MBeanParameterInfo dividendParameter =
new MBeanParameterInfo(
"dividend",
Integer.TYPE.toString(),
"The dividend in the division." );

final MBeanParameterInfo divisorParameter =
new MBeanParameterInfo(
"divisor",
Integer.TYPE.toString(),
"The divisor in the division." );

final MBeanOperationInfo divideOperationInfo =
new MBeanOperationInfo(
"divide",
"Integer Division",
new MBeanParameterInfo[] {dividendParameter, divisorParameter},
Double.TYPE.toString(),
MBeanOperationInfo.INFO );

//
// Build the PARAMETERS and OPERATIONS meta information for
// "whatWasTheLastOperation" method.
//

final MBeanOperationInfo lastOperationOperationInfo =
new MBeanOperationInfo(
"whatWasTheLastOperation",
"Last Calculator Operation Performed",
null,
"OperationType",
MBeanOperationInfo.INFO );

return new MBeanOperationInfo[]
{ addOperationInfo, subtractOperationInfo,
multiplyOperationInfo, divideOperationInfo,
lastOperationOperationInfo };

}

/**
* Create a SimpleCalculator-specific MBeanInfo.
*
* @param mbeanClassName Name of MBean class being used.
* @param description Description for MBean display.
* @return Generated MBeanInfo.
*/
private static MBeanInfo createCalculatorMBeanInfo(
final String mbeanClassName,
final String description )
{

final MBeanInfo mbeanInfo =
new MBeanInfo(
mbeanClassName, // underlying class name
description, // description of MBean meant for humans
null, // attributes
null, // constructors
buildMBeanOperationInfo(), // operations
null); // notifications
return mbeanInfo;
}

/**
* Register the provided MBean (implementation and interface provided) with
* the provided ObjectName on the provided MBeanServer as a Standard MBean
* if isMXBean is false or as an MXBean if isMXBean is true.
*
* @param mBeanClass MBean implementation class.
* @param interfaceClass MBean interface.
* @param objectNameStr ObjectName under which MBean will be registered.
* @param isMXBean true if this is MXBean; false if Standard MBean.
* @param mbs MBean Server on which to register MBean.
*/
private static void registerStandardMBean(
final Class mBeanClass,
final Class interfaceClass,
final String objectNameStr,
final boolean isMXBean,
final MBeanServer mbs)
{
try
{
final Object objectForMBean = mBeanClass.newInstance();
final ObjectName objectName = new ObjectName(objectNameStr);
final DustinMBean standardMBean =
new DustinMBean(
mBeanClass.cast(objectForMBean),
interfaceClass,
isMXBean);
standardMBean.setMBeanInfo(
createCalculatorMBeanInfo(
mBeanClass.getName(),
"Simple Standard MBean-turned Dynamic Example.") );
mbs.registerMBean(standardMBean, objectName);
System.out.println(
"MBean with ObjectName " + objectNameStr + " based on class "
+ mBeanClass.getCanonicalName() + " and interface "
+ interfaceClass.getCanonicalName() + " has been registered.");
}
catch (InstantiationException ex)
{
System.err.println(
"Unable to instantiate provided class or interface.\n"
+ ex.getMessage() );
}
catch (IllegalAccessException ex)
{
System.err.println(
"Cannot access the provided class for a new instance.\n"
+ ex.getMessage() );
}
catch (MalformedObjectNameException ex)
{
System.err.println(
"Cannot create an ObjectName " + objectNameStr + ":\n"
+ ex.getMessage() );
}
catch (InstanceAlreadyExistsException ex)
{
System.err.println(
"An MBean instance already exists with name " + objectNameStr
+ ":\n" + ex.getMessage() );
}
catch (MBeanRegistrationException ex)
{
System.err.println(
"Failed to register MBean " + objectNameStr + " based on class "
+ mBeanClass.getCanonicalName() + ":\n" + ex.getMessage() );
}
catch (NotCompliantMBeanException ex)
{
System.err.println(
"The class " + mBeanClass.getCanonicalName() + " is not a "
+ "compliant MBean: " + ex.getMessage() );
}
}

/**
* Create and register an MBean given the provided MBean's implementation
* class name, the desired Object Name for the MBean, and the MBeanServer
* on which to register the MBean.
*
* @param mBeanClass MBean implementation class.
* @param objectNameStr ObjectName to be used for the MBean instance.
* @param mbs MBean Server that will be hosting the MBean.
*/
private static void createAndRegisterStandardMBean(
final Class mBeanClass,
final String objectNameStr,
final MBeanServer mbs)
{

String standardMBeanClassName = "";
try
{
final ObjectName standardMBeanName = new ObjectName(objectNameStr);
standardMBeanClassName = mBeanClass.getCanonicalName();
mbs.createMBean(standardMBeanClassName, standardMBeanName);
System.err.println(
"Standard MBean " + standardMBeanName + " created and registered "
+ "using the class " + standardMBeanClassName );
}
catch (ReflectionException ex)
{
System.err.println(
"Problem trying to get name of class for MBean:\n"
+ ex.getMessage() );
}
catch (InstanceAlreadyExistsException ex)
{
System.err.println(
"MBean already exists and is registered with name "
+ objectNameStr + ":\n" + ex.getMessage() );
}
catch (MBeanRegistrationException ex)
{
System.err.println(
"Error trying to register MBean " + objectNameStr + ":\n"
+ ex.getMessage() );
}
catch (NotCompliantMBeanException ex)
{
System.err.println(
"Class " + standardMBeanClassName + " is not a compliant MBean:\n"
+ ex.getMessage() );
}
catch (MalformedObjectNameException ex)
{
System.err.println( "Bad objectname " + objectNameStr + ":\n"
+ ex.getMessage() );
}
catch (MBeanException ex)
{
System.err.println(
"MBeanException encountered trying to register class "
+ standardMBeanClassName + " as MBean with ObjectName of "
+ objectNameStr + ":\n" + ex.getMessage() );
}
}

/**
* Main executable for running Standard and MXBean examples.
*
* @param arguments The command line arguments.
*/
public static void main(String[] arguments)
{
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

// Create and register a standard MBean the standard way.
createAndRegisterStandardMBean(
SimpleCalculator.class, // SimpleCalculator is Standard MBean
"standardmbean:type=standard", // ObjectName string
mbs); // MBean server to register on

// Create and register an MXBean the standard way.
createAndRegisterStandardMBean(
LessSimpleCalculator.class,
"standardmbean:type=mxbean",
mbs);

// Register a standard MBean using the StandardMBean-extended class.
registerStandardMBean(
SimpleCalculator.class,
SimpleCalculatorMBean.class,
"dynamicmbean:type=standard",
false,
mbs);

// Register an MXBean using the StandardMBean-extended class.
registerStandardMBean(
LessSimpleCalculator.class,
LessSimpleCalculatorMXBean.class,
"dynamicmbean:type=mxbean",
true,
mbs);

pause(100000);
}
}


In the class above, the method that sets up the metadata [buildMBeanOperationInfo()] is extremely similar to the same method used in an earlier blog entry to set up metadata on operations for the Model MBean class.

It is inside the try block in the registerStandardMBean method that most of the action occurs in terms of using the StandardMBean-derived DustinMBean. This method is a little more complicated than it would need to be if was less generic and reflection was not used. The example usage code shown in the Javadoc for StandardMBean shows how easy it can be.

For instance, instead of using reflection like this:


final Object objectForMBean = mBeanClass.newInstance();
final ObjectName objectName = new ObjectName(objectNameStr);
final DustinMBean standardMBean =
new DustinMBean(
mBeanClass.cast(objectForMBean),
interfaceClass,
isMXBean);
standardMBean.setMBeanInfo(
createCalculatorMBeanInfo(
mBeanClass.getName(),
"Simple Standard MBean-turned Dynamic Example.") );
mbs.registerMBean(standardMBean, objectName);


it could be done less generically but much more simply like this


final ObjectName objectName = new ObjectName(objectNameStr);
final DustinMBean standardMBean =
new DustinMBean(
new SimpleCalculator(),
SimpleCalculatorMBean.class,
isMXBean);
standardMBean.setMBeanInfo(
createCalculatorMBeanInfo(
"dustin.jmx.SimpleCalculator",
"Simple Standard MBean-turned Dynamic Example.") );
mbs.registerMBean(standardMBean, objectName);


In the main() of the StandardMBeanDemonstrator class, the code demonstrates that the Standard MBean and the MXBean are each used twice, once in the standard way and once via the StandardMBean class. The standard approach depends entirely on naming conventions between implementation and interface while the StandardMBean approach allows the implementation and interface to be explicitly associated in code. The latter approach also allows extra metadata to be specified.

I won't show it here, but the whatWasTheLastOperation() operation only works in JConsole for the two uses of MXBean and not for the two uses of Standard MBean for reasons displayed in a previous blog entry.

I'll end this entry with two screen snapshots. One shows the MXBean that was created and registered with the MBeanServer in the standard way. It works perfectly well, but lacks descriptive information in JConsole. The second shows the same MXBean registered with the StandardMBean approach. It works as well, but also shows dynamic MBean level of description.

MXBean Registered on MBean Server in Standard Way




MXBean Registered on MBean Server via StandardMBean Class




In conclusion, use of the Java SE-provided StandardMBean class allows JMX developers to treat Standard and MXBeans more like dynamic MBeans. Because all MBean types support Descriptors as of Java SE 6, the differences between the types of MBeans continue to blur.

Monday, August 11, 2008

Playing with JMX 2.0 Annotations

Eamonn McManus's blog entry Playing with JMX 2.0 API provided me with the encouragement and advice I needed to try out JMX 2.0's likely annotation implementation. Toward the end of his blog entry, Eamonn provides detailed instructions on how Java SE 7 can be downloaded and the jmx.jar built and run. I followed those instructions and everything worked fine for me on my Vista-based laptop.

As explained in Playing with JMX 2.0 API (see that entry for additional/different details), the following steps can be followed to start experimenting with the current in-work version of JMX 2.0:

1. Download the JAR with the JDK source (jdk-7-ea-src-b32-jrl-04_aug_2008.jar in my case) from the Java SE 7 download site (http://download.java.net/jdk7/).

2. Execute the downloaded JAR with a command like this: java -jar jdk-7-ea-src-b32-jrl-04_aug_2008.jar and follow the wizard (including accepting the JRL license terms). Make note of where you have the source code placed. For my examples, I had the contents placed in C:\java7.

3. Change directory to the "jdk" directory under the expanded Java SE 7 source code and run Ant like this: ant -f make/netbeans/jmx/build.xml

(The -f option in Ant specifies the location of an alternative build file instead of Ant looking for a build.xml file in the same directory. Alternatively, one could change directories all the way to the make/netbeans/jmx directory and simply run "ant" there.)

4. If all has gone well, there will be a generated jmx.jar file in the dist/lib directory. The next screen snapshot shows how this appears on my machine.



I copied this generated jmx.jar file into my C:\jmx2 directory for easy reference.


5. Write the JMX 2.0 annotation-based Standard MBean and MBean Server management code.

With jmx.jar built, it can be placed on the classpath to build and run examples based on JMX 2.0. In my case, I added it to NetBeans 6.1. In a later step, I'll show how I ran the sample using it in the runtime classpath. For now, though, I will show the two code listings for my example. The first is a class called SimpleCalculator adapted from my blog entries on Model MBeans (direct, with Spring, with Apache Commons Modeler, and with EasyMBean).

SimpleCalculator.java


package dustin.jmx2;

import javax.management.Description;
import javax.management.DescriptorFields;
import javax.management.MBean;
import javax.management.ManagedOperation;

import static javax.management.Impact.INFO;

/**
* Simple calculator class intended to demonstrate use of the JMX 2.0
* annotations as currently implemented for proposed JSR-255 and Java SE 7.
*
* @author Dustin
*/
@MBean
@Description(
"Example of using JMX 2.0/JSR-255 JMX annotations to expose "
+ "an otherwise normal class as a Standard MBean.")
public class SimpleCalculator
{
/**
* Calculate the sum of the augend and the addend.
*
* @param augend First integer to be added.
* @param addend Second integer to be added.
* @return Sum of augend and addend.
*/
@ManagedOperation(impact=INFO)
@Description(
"Integer Addition: First parameter is the augend and second parameter "
+ "is the addend.")
@DescriptorFields({"p1=augend","p2=addend"})
public int add(final int augend, final int addend)
{
return augend + addend;
}

/**
* Calculate the difference between the minuend and subtrahend.
*
* @param minuend Minuend in subtraction operation.
* @param subtrahend Subtrahend in subtraction operation.
* @return Difference of minuend and subtrahend.
*/
@ManagedOperation(impact=INFO)
@Description(
"Integer Subtraction: First parameter is minuend and second parameter "
+ "is subtrahend.")
@DescriptorFields({"p1=minuend","p2=subtrahend"})
public int subtract(final int minuend, final int subtrahend)
{
return minuend - subtrahend;
}

/**
* Calculate the product of the two provided factors.
*
* @param factor1 First integer factor.
* @param factor2 Second integer factor.
* @return Product of provided factors.
*/
@ManagedOperation(impact=INFO)
@Description(
"Integer Multiplication: First parameter is one factor and second "
+ "parameter is other factor.")
@DescriptorFields({"p1=factor1","p2=factor2"})
public int multiply(final int factor1, final int factor2)
{
return factor1 * factor2;
}

/**
* Calculate the quotient of the dividend divided by the divisor.
*
* @param dividend Integer dividend.
* @param divisor Integer divisor.
* @return Quotient of dividend divided by divisor.
*/
@ManagedOperation(impact=INFO)
@Description(
"Integer Division: First parameter is the dividend and second "
+ "parameter is divisor.")
@DescriptorFields({"p1=dividend","p2=divisor"})
public double divide(final int dividend, final int divisor)
{
return dividend / divisor;
}
}


This version of SimpleCalculator differs from the various versions used in my previous Model MBeans examples because this version uses JMX 2.0 annotations. This example is different as well in the sense that these JMX 2.0 annotations allow this class to be used as a Standard MBean rather than as a Model MBean (the Spring and EasyMBean annotations exposed their decorated SimpleCalculator class as ModelMBeans).

The four import statements in SimpleCalculator correspond to the four JMX 2.0 annotations used in the creation of the Standard MBean. The @MBean annotation on the class itself states that it will be a Standard MBean (and @MXBean does the same for MXBeans). The @ManagedOperation annotation specifies which operations are exposed by the class-turned-Standard MBean. Because I cannot specify information on the parameters to the operations like I can do with Model MBeans, I have included parameter information via the @DescriptorFields annotation. Finally, I used @Description annotations to describe the class and each of the operations. Information on each of these annotations is available in the Javadoc documentation for the javax.management package.

The next class, Main, registers the annotated SpringCalculator Standard MBean with the platform MBean server.

Main.java


package dustin.jmx2;

import java.lang.management.ManagementFactory;

import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;

/**
* Giving JMX 2.0 (JSR-255) a spin.
*
* @author Dustin
*/
public class Main
{
/**
* Pause for the specified number of milliseconds.
*
* @param millisecondsToPause Milliseconds to pause execution.
*/
public static void pause(final int millisecondsToPause)
{
try
{
Thread.sleep(millisecondsToPause);
}
catch (InterruptedException threadAwakened)
{
System.err.println("Don't wake me up!\n" + threadAwakened.getMessage());
}
}

/**
* Main executable to test out JMX 2.0 annotation-powered MBean.
*
* @param arguments the command line arguments
*/
public static void main(final String[] arguments)
{
final SimpleCalculator calculator = new SimpleCalculator();
final String mbeanName = "jmx2:type=annotatedMBean";
try
{
final ObjectName objectName = new ObjectName(mbeanName);
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
mbs.registerMBean(calculator, objectName);
}
catch (MBeanRegistrationException cannotRegisterMBean)
{
System.err.println(
"ERROR: Could not register MBean with name " + mbeanName + ":\n"
+ cannotRegisterMBean.getMessage() );
}
catch (MalformedObjectNameException badObjectName)
{
System.err.println(
"ERROR: Had trouble with ObjectName based on string "
+ mbeanName + ":\n" + badObjectName.getMessage() );
}
catch (InstanceAlreadyExistsException redundantMBean)
{
System.err.println(
"ERROR: MBean with name " + mbeanName + " already exists:\n"
+ redundantMBean.getMessage() );
}
catch (NotCompliantMBeanException notCompliantMBean)
{
System.err.println(
"ERROR: Object attempted to be used as an MBean ("
+ calculator.getClass().getCanonicalName()
+ ") is not a compliant MBean:\n" + notCompliantMBean.getMessage());
}
System.out.println("JMX 2/JSR-255 Annotation-Based MBean Registered...");
pause(100000);
}
}



6. Run the Code and Monitor with JConsole

With the above two classes implemented, they can be built and the resulting .class or .jar files can be executed. In my case, I built the classes with NetBeans 6.1 and it generated a JAR file called JMX2.jar containing the compiled Main.class and SimpleCalculator.class files.

To run the example and use the Java SE 7 jmx.jar in my Java SE 6 environment, I took advantage of Eamonn's recommendation regarding the use of the non-standard option bootclasspath to prepend (the p in the command) the JMX 2.0/Java SE 7 jmx.jar to the front of my boot class path. Running this application with this JMX 2.0/Java SE 7 jmx.jar in the boot class path looks like this:


java -Xbootclasspath/p:C:\jmx2\jmx.jar -cp JMX2.jar dustin.jmx2.Main


I can then run JConsole and view the exposed Standard MBean. The next two screen snapshots show this. The first screen snapshot shows some general MBean information and the second screen snapshot shows a particular operation being invoked via JConsole. Of special interest are the descriptor and description sections. I took advantage of both of these (the descriptors and the descriptions) to specify information about the operations' parameters that would be labeled simply as "p1" and "p2" otherwise.





I have attempted to demonstrate in this blog entry how easy it is to turn an otherwise normal Java class into a JMX Standard MBean using the proposed JMX 2.0/JSR-255 annotations. MXBeans are similarly annotated (the primary difference being the use of @MXBean rather than @MBean). The obvious advantage of these JMX 2.0 annotations is convenience and ease of use. However, Eammon points out in the "Pros and Cons of @MBeans" section of Defining MBeans with Annotations (and the Javadoc documentation points out in Defining Standard MBeans with annotations section of
javax.management documentation) that there are disadvantages to using these annotations to define standard and MXBeans. The primary disadvantages result from the mixing of potentially JMX-exposed methods in a class with non-JMX-exposed methods in the same class (and hence confusing Javadoc documentation) and from the lack of an interface to use with JMX client proxies.

NetBeans Code Folding and the Case for Code Folding

In this blog entry, I will briefly cover how to use code folding in NetBeans 6.1 while attempting to make a case for code folding in the correct situations.

I often agree with Jeff Atwood and what he writes in his Coding Horror blog, but I thought I was going to disagree strongly with him when I saw one of his posts titled The Problem with Code Folding. After reading it, it turns out that we don't disagree as much as we each have a different focus on code folding. He warns against using it for many things related to actual executable code (which I agree with), but I have found code folding to be highly useful in cases on non-executable code that I don't need to be burdened with when reviewing and maintaining code.

I agree that we all need to give and take a little in our software development teams. However, there are situations when the team is split roughly 50/50 on what is the best approach. Rather than irritate half the team every time they look at the code just because one approach is arbitrarily chosen, code folding can often help everyone be happier. In this blog entry, I will attempt to outline some of these cases and use NetBeans 6.1 code folding to illustrate. I'm going to try to not state which side of these issues I prefer and instead focus on how code folding allows both sides to be happier. This does not mean I don't have a preference.




One area of disagreement that can be easily handled with code folding is the specification of Java classes and interfaces to be imported. One school of thought holds that all imported classes and interfaces should be individually and explicitly imported while the other school of thought maintains that the entire package should be imported using the asterisk. The Java Tutorials section Using Package Members suggests that explicit imports are okay if only a small number of classes and/or interfaces in a given package are needed, but that the asterisk should be used to include the entire package if several members of that package are needed.

NetBeans provides automatic code folding for import statements. The advantage of automatic IDE-provided code folding is that the code is not touched in anyway and the code has nothing in it specific to code folding. The following three screen snapshots (taken from my recent EasyMBean blog example) demonstrate NetBeans 6.1's automatic import statement code folding in action. The first image shows an example piece of code with the import statements expanded as one would normally see them in any text editor. The second image shows these same import statements collapsed (by clicking on the minus sign to the left of the first import) and the third image shows them still collapsed, but shows the preview that is shown if the mouse cursor is moved over the collapsed marking (. . . ).

Import Statements Shown Normally (or Expanded)



Import Statements Collapsed



Import Statements Collapsed with Preview of Collapsed Content




The above code folding of import statements is obtained in NetBeans without any additional effort from the developer and can allow the team to have explicit and individually specified classes and interfaces without irritating those who see those as extraneous or cumbersome for their normal daily needs. Because it is automatic in NetBeans, there is no trace of any special treatment of important statements in the source code. Anyone looking at the source code in an alternate editor that doesn't itself fold import statements will just see the lines of code like shown next:


package dustin.jmx.modelmbeans;

import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import org.jmanage.easymbean.EasyMBean;





Another area of controversy among experienced and highly skilled software developers is the appropriate level of commenting. In truth, this is almost as much art (and personal opinion) as science, but I think most of us can agree that the more readable the code is, the less commenting is often needed. As is often cited, comments are sometimes used as the deodorant for smelly code. Note also that there are different types of comments. For example, Javadoc comments are often very different animals from inline comments within methods.

NetBeans offers automatic code folding for Javadoc comments similar to what it provides for import statements. I will again use the example code from my EasyMBean blog example to illustrate NetBeans's code folding of Javadoc comments. The next two screen snapshots show a class-level Javadoc comment and a method-level Javadoc comment expanded (normal/default) in the first image and collapsed in the second image. Note that control of the Javadoc comments is separate; I just happened to have both open for one snapshot and both closed for the other.

Javadoc Comments Expanded



Javadoc Comments Collapsed



I did not show a preview screen snapshot with collapsed Javadoc comments. This works just as I showed it working for import statements. Without expanding the collapsed Javadoc comments, I can move my mouse cursor over the collapsed Javadoc comments section in NetBeans and see a preview of what is being collapsed.

You may have noticed that even in the screen snapshot in which the Javadoc comments were closed, there was a minus sign icon for collapsing (code folding) the method itself. Use of code folding to hide a method as can be done with that icon is definitely not something I'd choose to do often. This is where I agree with many of Jeff Atwood's assertions in The Problem with Code Folding. I don't mind it as provided by NetBeans automatically because it doesn't change the code at all (NetBeans implements these code folding sections without any special demarcation in the code) and the code is expanded by default. I might occasionally find it helpful to collapse a method to better focus on what's going on, but I generally favor having executable code viewable by default. Because of this opinion, I won't be demonstrating method collapsing. However, it works similarly to the collapsing already shown of import statement sections and Javadoc comment sections.




So far, the NetBeans code folding examples have demonstrated the type of code folding that I prefer and that is my first choice when available and sufficient. This automatic and implicit code folding does not require any effort by the developer and do not in any way adulterate the source code.

The next example of NetBeans code folding that I will demonstrate is the NetBeans wizard-generated servlet code folding. This code folding is automatically added by NetBeans, but the servlet code folding differs from the previous code folding examples because it does rely on explicit code folding comments in the source code. The next screen snapshot shows what the servlet looks like immediately after being generated with NetBeans's servlet wizard.



If the developer clicks on the + icon to expand the collapsed servlet code, he or she sees something like this:


// <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
/**
* Handles the HTTP <code>GET</code> method.
* @param request servlet request
* @param response servlet response
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

/**
* Handles the HTTP <code>POST</code> method.
* @param request servlet request
* @param response servlet response
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

/**
* Returns a short description of the servlet.
*/
public String getServletInfo() {
return "Short description";
}// </editor-fold>


The code that was collapsed by default after the servlet wizard was run are the three standard methods expected of servlets. The doGet and doPost methods are not all that interesting because all they do is turn around and call the processRequest method. The collapsed getServletInfo method is slightly more interesting, but would also not be too interesting once a useful string was set to be returned from the method.

The servlet wizard example provides a taste of another class of NetBeans code folding where NetBeans automatically generates the code folding, but it requires explicit comments to be placed in the code to perform the code folding. These commented-out lines will be ignored by other editors, but will be respected by NetBeans. Another example of this was present in my blog entry on Using Orson Charts to Extend JConsole, in which I used the NetBeans GUI editor to create code and that code was placed (by NetBeans) within code folding comments.




All of the previous code folding examples were automatically provided by NetBeans where code had to be changed or not to perform the code folding. The third class of NetBeans code folding is customized code folding where the developer marks sections of code that he or she wants to support NetBeans code folding. The last example of the servlet generation showed the syntax that developers can use to implement custom folding. As shown in the servlet example and as described in Custom Code Folding in NetBeans, one can add custom code folding in NetBeans simply be enclosing the code to be folded within // <editor-fold> and // </editor-fold> comments.

Custom code folding allows the developer to be more selective in which types of code are folder. This can be a nice advantage, but can also be a problem if used indiscriminately. The situation in which I think that custom code folding is most desirable is when things not directly related to the successful execution of the code is included within the code. The primary way this can happen is via comments and via annotations.

There are times when developers may choose to or be forced to include comments and/or annotations in their code that are not required to successfully execute the code. Examples of such situations include the decision to include requirements in the code, to include contact or other personnel information in the code, or to include architectural or other high-level software characteristics in the code. Often, a client or management wants this additional non-executing code included for various reasons that may not be at all related to the successful execution of the code itself. Code folding can be used to keep such metadata in the code, but remove the non-executing metadata from the developer's view when using NetBeans. In other words, code folding allows this non-executing code to be removed as a distraction without actually removing it from the code.

In the previous screen snapshot, I showed Javadoc commented out. However, there were regular (not Javadoc) comments within the method processRequest that were still visible. These are actually working servlet code lines generated by NetBeans servlet generation wizard, but they are commented out. It is easy to fold or collapse these lines by adding the comments before and after that indicate code folding as shown in the next code listing.


try {
//<editor-fold defaultstate="collapsed" desc="Generated code">
/* TODO output your page here
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet FakeServlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Servlet FakeServlet at " + request.getContextPath () + "</h1>");
out.println("</body>");
out.println("</html>");
*/
//</editor-fold>
} finally {
out.close();
}


The code now looks like this in NetBeans:



This shows that any types of comments could be easily folded out of the current NetBeans view. This might be useful for verbose comments providing background, history, requirement, or details that are often not desirable when editing code. The one major issue with this, however, is the increased tendency for comments to become obsolete or even outright wrong compared to the actual code. This is because it is difficult enough remembering to keep comments current with code changes even when they're right there in front of you and hiding them only makes that maintenance that much more difficult. This is one of the arguments for "less commenting is better," but that is not always a choice.

Annotations are sometimes used where they arguably should not be used, including being used as "processable comments" (not much more than comments except that a third-party piece of software can process them easily and mine them for comment information that would be more difficult to parse out of actual comments). In such cases, the annotations might have nothing to do with the execution of the code and so most developers will benefit from folding these out of their normal NetBeans view. For example if some custom annotations were used to associate methods with certain business requirements, the developer might enclose the requirements annotations within commented lines //<editor-fold defaultstate="collapsed" desc="Requirements Annotations"> and </editor-fold>.

Code folding can lead to problems when applied indiscriminately. However, there are many advantages to be gained from carefully chosen, effective code folding. Code folding has the least downside when it is automatically generated on-the-fly by the IDE without any impact to the code. Code folding is arguably most dangerous when it is custom code folding, but this can also sometimes provides its greatest value when used carefully.

Developers are not always fortunate enough to work with code as clean as they'd like. Code folding can be used to hide away some of the most distracting non-executing code to make the executable code a little easier to read and maintain.

Thursday, August 7, 2008

JMX Model MBeans with EasyMBean

I wrote previous blog entries on the advantages of using Model MBeans and showed examples of using Model MBeans directly, using Model MBeans with the Spring Framework, and using Model MBeans with Apache Commons Modeler. In this blog entry, I will look at using Model MBeans with EasyMBean.

A JMX developer might choose to use EasyMBeans for his or her Model MBean needs if he or she was not using the Spring Framework anywhere else in a given Java application, but wanted Spring-like JMX ModelMBean annotations. Other choices include direct Model MBean coding and using Apache Commons Modeler, but EasyMBean is most like Spring in terms of JMX Model MBean exposure.

EasyMBean has a close relationship with JManage. Not only is EasyMBean discussed heavily at the jManage site, but "jmanage" appears in the package names for EasyMBeans classes as shown in this blog entry's code examples. However, it is important to note that jManage is not required and that a standalone version of EasyMBean.jar can be downloaded from the EasyMBean project site.

For my example of using EasyMBean in this blog entry, I have adapted the class SimpleCalculatorAnnotated that I demonstrated with Spring and Model MBeans in my previous blog entry. The name of this adapted class is SimpleCalculatorAnnotatedEasyMBean and its code lsiting is shown next:

SimpleCalculatorAnnotatedEasyMBean.java

package dustin.jmx.modelmbeans;

import org.jmanage.easymbean.annotations.ManagedConstructor;
import org.jmanage.easymbean.annotations.ManagedOperation;
import org.jmanage.easymbean.annotations.ManagedResource;


/**
* Simple calculator class intended to demonstrate how a class with no knowledge
* of JMX or management can be annotated to be exposed as Model MBean by EasyMBean.
*
* @author Dustin
*/
@ManagedResource(
name="modelmbean:type=spring-annotated",
description="A simple integer calculator." )
public class SimpleCalculatorAnnotatedEasyMBean implements SimpleCalculatorIf
{
@ManagedConstructor(
name="SimpleCalculatorAnnotatedEasyMBean",
description="No argument constructor")
public SimpleCalculatorAnnotatedEasyMBean()
{
}

/**
* Calculate the sum of the augend and the addend.
*
* @param augend First integer to be added.
* @param addend Second integer to be added.
* @return Sum of augend and addend.
*/
@ManagedOperation(
name="add",
displayName="Integer Addition",
presentationString="Integer Addition",
targetClass="SimpleCalculatorAnnotatedEasyMBean",
description="Integer Addition")
public int add(final int augend, final int addend)
{
return augend + addend;
}

/**
* Calculate the difference between the minuend and subtrahend.
*
* @param minuend Minuend in subtraction operation.
* @param subtrahend Subtrahend in subtraction operation.
* @return Difference of minuend and subtrahend.
*/
@ManagedOperation(
name="subtract",
displayName="Integer Subtraction",
presentationString="Integer Subtraction",
targetClass="SimpleCalculatorAnnotatedEasyMBean",
description="Integer Subtraction")
public int subtract(final int minuend, final int subtrahend)
{
return minuend - subtrahend;
}

/**
* Calculate the product of the two provided factors.
*
* @param factor1 First integer factor.
* @param factor2 Second integer factor.
* @return Product of provided factors.
*/
@ManagedOperation(
name="multiply",
displayName="Integer Multiplication",
presentationString="Integer Multiplication",
targetClass="SimpleCalculatorAnnotatedEasyMBean",
description="Integer Multiplication")
public int multiply(final int factor1, final int factor2)
{
return factor1 * factor2;
}

/**
* Calculate the quotient of the dividend divided by the divisor.
*
* @param dividend Integer dividend.
* @param divisor Integer divisor.
* @return Quotient of dividend divided by divisor.
*/
@ManagedOperation(
name="divide",
displayName="Integer Division",
presentationString="Integer Division",
targetClass="SimpleCalculatorAnnotatedEasyMBean",
description="Integer Division")
public double divide(final int dividend, final int divisor)
{
return dividend / divisor;
}
}


The code listing for SimpleCalculatorAnnotatedEasyMBean shares many similarities with the Spring version, but there are significant differences. The most obvious and expected difference is the different set of import statements. However, there are deeper differences as well. In this case, the focus is on describing Model MBean operations (methods) and EasyMBean does not allow the parameters to the operations to be explicitly described like Spring does. While this does make it simpler for the developer (as stated in EasyMBean documentation), it is also a limitation because the parameters show up in JConsole and other generic JMX consoles as something like "p1" and "p2" rather than with their descriptive names.

One other thing worthy of special note here is the use of an EasyMBean-annotated no-argument constructor in the bean class. I found this necessary to allow my sample application to run without a NullPointerException.

The next code listing, for class EasyMBeanDemonstrator, shows the code to get access to the platform MBean server and then to use the EasyMBean static call to EasyMBean.getMBean(Object,ObjectName,MBeanServer) method.

EasyMBeanDemonstrator.java

package dustin.jmx.modelmbeans;

import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import org.jmanage.easymbean.EasyMBean;

/**
* This class demonstrates use of EasyMBean to annotate classes for exposure
* as Model MBeans.
*
* For more information on EasyMBeans, see the JManage documentation at
* http://www.jmanage.org/wiki/index.php/EasyMBean along with the main project
* page at http://sourceforge.net/projects/easymbean/.
*
* @author Dustin
*/
public class EasyMBeanDemonstrator
{
/**
* Register the ModelMBean in the MBeanServer using the provided
* ObjectName String.
*
* @param objectNameString ObjectName of the registered ModelMBean.
*/
private void registerModelMBean(
final String objectNameString)
{
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
SimpleCalculatorAnnotatedEasyMBean mbean =
new SimpleCalculatorAnnotatedEasyMBean();
try
{
final ObjectName objectName =
new ObjectName(objectNameString);
final ObjectInstance objInstance =
EasyMBean.getMBean(mbean, objectName, mbs);
/*
this.mbeanServer.registerMBean( modelMBean,
objectName );*/
}
catch (MalformedObjectNameException badObjectName)
{
System.err.println( "ERROR trying to generate ObjectName based on "
+ objectNameString + ":\n"
+ badObjectName.getMessage() );
}
}

/**
* Create a ModelMBean in the raw and register it with the MBeanServer.
*/
public void createAndRegisterEasyMBean()
{
final String easyModelMBeanObjectNameString = "modelmbean:type=easyMBean";
registerModelMBean(easyModelMBeanObjectNameString);
}

/**
* Pause for the specified number of milliseconds.
*
* @param millisecondsToPause Milliseconds to pause execution.
*/
public static void pause(final int millisecondsToPause)
{
try
{
Thread.sleep(millisecondsToPause);
}
catch (InterruptedException threadAwakened)
{
System.err.println("Don't wake me up!\n" + threadAwakened.getMessage());
}
}

/**
* Demonstrate use of ModelMBean via EasyMBean..
*
* @param args the command line arguments
*/
public static void main(String[] args)
{
final EasyMBeanDemonstrator me = new EasyMBeanDemonstrator();
me.createAndRegisterEasyMBean();
pause(1000000);
}
}


With the above code in place, I will now show two screen snapshots that show the results of this simple work in JConsole. There is more detail in JConsole than one would see in a non-annotated Spring Model MBean, but less than one would see when Spring's annotation-based support is used. The most noticeable feature missing is the lack of names for the parameters to the operations.





I've attempted to demonstrate the simplicity of EasyMBean in employing Model MBeans in JMX development. When I am using Spring in my Java-based applications, I'd almost certainly stick with Spring's support, but EasyMBean is worth consideration when not otherwise using Spring.

Perhaps the most approachable resource available online that describes how to use EasyMBean is provided as part of the jManage site. The EasyMBean Wiki entry at this site provides a "Quick Start for the impatient" section and a more descriptive (but still pretty brief) "Understanding EasyMBean Annotations for the Cool Headed" section. The first section demonstrates code examples of using EasyMBean similar to those shown here and the second section explains each major EasyMBean annotation and its attributes in detail. My blog entry focused on operations, but this resource shows how EasyMBean annotations can also be used with JMX Notifications and attributes.

Finally, JMX 2.0 appears likely to provide JMX developers with the standard support for JMX annotations, which will likely be the favorite choice of most JMX developers. For more details on the forthcoming JMX 2.0 support for JMX annotations, see Eamonn McManus's blog entries Playing with the JMX 2.0 API and Defining MBeans with Annotations.

Wednesday, August 6, 2008

Free "Getting Started with Flex 3" in PDF

You can download a free copy of the PDF version of the book Getting Started with Flex 3 by taking either of two quizzes available at SitePoint. Details regarding the free PDF are available here. The gist of it is that you can download the PDF without registration by simply taking a very short quiz at the end of either of two Flex-related articles by Jack Herrington. I believe that this promotion runs through 22 September 2008.

The first article is called Build a Web 2.0 Voting Widget with Flex, Part 1 and the second article is called Building a Web 2.0 Voting Widget with Flex, Part 2. Both articles provide nice background and information on using Flex and the quizzes are short and painless and help to ascertain your knowledge of Flex.

While at the SitePoint site, other Flex/AIR-related resources you may want to check out include An Absolute Beginner's Tutorial of Flex 3, Walk on AIR: Create a To-do List in Five Minutes, and Create a 3D Product Viewer in Flex 3.

Monday, August 4, 2008

Using Orson Chart Beans with JConsole Custom Plugin

In this blog entry, I will be looking at using Orson Chart Beans to generate charts in a custom tab added to JConsole. Orson Charts (also sometimes called JFreeBeans in some of the documentation) are backed by JFreeChart and present an easier approach to including JFreeChart-generated charts in Swing applications than using JFreeChart directly.

I could have generated the charts shown in this blog entry's custom JConsole tab using JFreeChart directly (see Example 3/Listing 4 in the article Visualize Your Oracle Database Data with JFreeChart article for an example of using JFreeChart directly in Swing). However, I am using Orson Charts so that I can demonstrate their use and so that more emphasis can stay on implementing the JConsole custom tab.

Java SE 6 provides new features (service provider lookup and SwingWorker) that enable custom tabs in JConsole. The Sun Java SE 6 JDK distribution includes an example custom tab implementation called JTop that demonstrates how to take advantage of the service provider lookup and SwingWorker to create custom tabs for JConsole.

For my example, I'm not dynamically changing the data (for simplicity) and so I don't need to implement as many methods as does the JTop example. Instead, I am building the Orson charts with static, hard-coded data to simplify the examples. However, in a real implementation, one would likely use dynamic data gathered from various sources to build the charts. Data might be retrieved related to databases, networks, file systems, etc., to complement the JVM data already made available in JConsole.

The next class, OrsonBasedJConsolePlugin, shows the simple code needed to create a JConsole Plugin. Most of the interesting functionality is actually in a class this class references.

OrsonBasedJConsolePlugin

package dustin.jconsole.orson;

import com.sun.tools.jconsole.JConsolePlugin;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.swing.JPanel;
import javax.swing.SwingWorker;

/**
* This is a simplistic example of extending JConsole with a custom plugin
* that takes advantage of the Orson (JFreeBeans).
*/
public class OrsonBasedJConsolePlugin
extends JConsolePlugin implements PropertyChangeListener
{
private OrsonPanel orsonPanel = null;
private Map<String, JPanel> customTabs = null;

public OrsonBasedJConsolePlugin()
{
addContextPropertyChangeListener(this);
}

@Override
public Map<String, JPanel> getTabs()
{
if ( customTabs == null )
{
// Using LinkedHashMap because it is a Map implementation that
// maintains its predictable order (usually the order of insertion
// into the LinkedHashMap. Using the LinkedHashMap ensures that the
// new custom tabs appear in the desired order in JConsole.
customTabs = new LinkedHashMap();
orsonPanel = new OrsonPanel();
}

customTabs.put("Orson-Based Chart", new OrsonPanel());
return customTabs;
}

@Override
public SwingWorker<?, ?> newSwingWorker()
{
return orsonPanel.newSwingWorker();
}

@Override
public void propertyChange(PropertyChangeEvent evt)
{
// Implement behavior upon property change.
}
}


The next class is the OrsonPanel class that actually does most of the work related to Orson Charts generation. This panel was created in NetBeans 6.1 GUI builder and some of the "fold" comments indicate this. See the Java SE 6-provided JTop demonstration for a more complete demonstration of implementation of a SwingWorker. The image before the code listing shows how it appears in NetBeans. Note that the generic images for Orson bar chart and Orson pie chart help see what will go where on the final HMI.



OrsonPanel

package dustin.jconsole.orson;

//import java.util.concurrent.ExecutionException;
import javax.swing.SwingWorker;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.general.DefaultPieDataset;

/**
* Simple JPanel using a chart generated JFreeChart-based Orson library.
*/
public class OrsonPanel extends javax.swing.JPanel
{
/** Creates new form OrsonPanel */
public OrsonPanel()
{
initComponents();
populatePieChart();
populateUsersMachinesBarChart();
}

/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {

orsonUsersMachineBarChartPanel = new javax.swing.JLabel();
orsonBarChart = new org.jfree.beans.JBarChart();
orsonPieChart = new org.jfree.beans.JPieChart();

orsonUsersMachineBarChartPanel.setText("Displaying Charts in JConsole with JFreeChart-based Orson is Easy!");

javax.swing.GroupLayout orsonBarChartLayout = new javax.swing.GroupLayout(orsonBarChart);
orsonBarChart.setLayout(orsonBarChartLayout);
orsonBarChartLayout.setHorizontalGroup(
orsonBarChartLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 360, Short.MAX_VALUE)
);
orsonBarChartLayout.setVerticalGroup(
orsonBarChartLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 230, Short.MAX_VALUE)
);

javax.swing.GroupLayout orsonPieChartLayout = new javax.swing.GroupLayout(orsonPieChart);
orsonPieChart.setLayout(orsonPieChartLayout);
orsonPieChartLayout.setHorizontalGroup(
orsonPieChartLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 360, Short.MAX_VALUE)
);
orsonPieChartLayout.setVerticalGroup(
orsonPieChartLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 230, Short.MAX_VALUE)
);

javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(orsonBarChart, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(orsonPieChart, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(orsonUsersMachineBarChartPanel)
.addGap(197, 197, 197))))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(orsonUsersMachineBarChartPanel)
.addGap(18, 18, 18)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(orsonBarChart, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(orsonPieChart, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap(27, Short.MAX_VALUE))
);
}// </editor-fold>


/**
* Populate Orson/JFreeChart-based pie chart.
*/
private void populatePieChart()
{
final DefaultPieDataset data = new DefaultPieDataset();
data.setValue("Opened", 23);
data.setValue("Closed", 10);
this.orsonPieChart.setDataset(data);
this.orsonPieChart.setTitle("Database Connections");
this.orsonPieChart.setSubtitle("Opened and Closed Database Connections");
}

/**
* Populate Orson/JFreeChart-based bar chart that displays numbers of types
* of users on each machine.
*/
private void populateUsersMachinesBarChart()
{
final DefaultCategoryDataset data = new DefaultCategoryDataset();
final String PRIVILEGED_USERS = "Privileged Users";
final String REGULAR_USERS = "Regular Users";
data.setValue(7, PRIVILEGED_USERS, "host1" );
data.setValue(5, PRIVILEGED_USERS, "host2" );
data.setValue(50, REGULAR_USERS, "host1" );
data.setValue(38, REGULAR_USERS, "host2");
this.orsonBarChart.setDataset(data);
this.orsonBarChart.setTitle("Types of Users Using Machines");
this.orsonBarChart.setSubtitle("Privileged and Regular Users");
this.orsonBarChart.setCategoryAxisLabel("Machine Hosts");
this.orsonBarChart.setValueAxisLabel("Number of Users");
}


/** SwingWorker used exclusively for JConsolePlugin. */
class OrsonWorker extends SwingWorker
{
OrsonWorker() {}

public CategoryDataset doInBackground()
{
final DefaultCategoryDataset data = new DefaultCategoryDataset();
return data;
}

@Override
protected void done()
{
}
}

// Return a new SwingWorker for UI update
public SwingWorker<?,?> newSwingWorker()
{
return new OrsonWorker();
}


// Variables declaration - do not modify
private org.jfree.beans.JBarChart orsonBarChart;
private org.jfree.beans.JPieChart orsonPieChart;
private javax.swing.JLabel orsonUsersMachineBarChartPanel;
// End of variables declaration
}


To use the service provider mechanism to allow JConsole to detect the custom tab implemented above, a special file must be included in the JAR with the two above classes. This special file must have the name >com.sun.tools.jconsole.JConsolePlugin and must contain the fully qualified name of the plugin class. In other words, for our example with only a single plugin, it should contain the following line:

com.sun.tools.jconsole.JConsolePlugin

dustin.jconsole.orson.OrsonBasedJConsolePlugin


A single JAR with the compiled versions of the above classes and the just-mentioned file should be built. This will be provided to JConsole when it is run. The JAR contents look like this in my example:

Contents of JConsoleWithOrson.jar

0 Sun Aug 03 21:39:48 MDT 2008 META-INF/
162 Sun Aug 03 21:39:46 MDT 2008 META-INF/MANIFEST.MF
0 Sun Aug 03 21:17:56 MDT 2008 META-INF/services/
0 Sun Aug 03 21:17:14 MDT 2008 dustin/
0 Sun Aug 03 21:17:14 MDT 2008 dustin/jconsole/
0 Sun Aug 03 21:17:14 MDT 2008 dustin/jconsole/orson/
46 Sun Aug 03 21:17:56 MDT 2008 META-INF/services/com.sun.tools.jconsole.JConsolePlugin
1458 Sun Aug 03 21:17:14 MDT 2008 dustin/jconsole/orson/OrsonBasedJConsolePlugin.class
1095 Sun Aug 03 21:39:48 MDT 2008 dustin/jconsole/orson/OrsonPanel$OrsonWorker.class
4863 Sun Aug 03 21:39:48 MDT 2008 dustin/jconsole/orson/OrsonPanel.class


While JConsole can normally be run by simply typing "jconsole" at the command-line, we now need to provide this JAR file, JConsoleWithOrson.jar, to the "jconsole" command. In addition, we also need to include Orson and JFreeChart libraries used to JConsole as well to avoid exceptions related to not finding these classes. The run command to run JConsole with this custom plugin JAR and associated Orson and JFreeChart libraries is:


jconsole -pluginpath JConsoleWithOrson.jar -J-Djava.class.path="C:\Program Files\Java\jdk1.6.0_07\lib\jconsole.jar";"C:\Program Files\Java\jdk1.6.0_07\lib\tools.jar";C:\orson-0.5.0\orson-0.5.0.jar;C:\orson-0.5.0\lib\jfreechart-1.0.6.jar;C:\orson-0.5.0\lib\jcommon-1.0.10.jar


You can see from the command for running JConsole above that I am using the latest version of Orson Charts (0.5). I am also using the version of JFreeChart and JCommon that come with the Orson distribution. There are newer versions of JFreeChart (1.0.10) available, but I wanted to use the version that came with the Orson download. Note also that if you did use JDBC database connections in a real situation, you'd need JDBC drivers and other database accessing classes as well.

When the JAR is compiled as described above and run as shown above, a new tab appears on JConsole as shown here:



Even with all the detail I tried to include in this blog entry, I hope that it is evident how easy it is to use Orson to add charts to your custom JConsole plug-in tabs.