One of the most insightful articles that I have read (in terms of both general daily life and a software development career) is Steven Kerr's On the Folly of Rewarding A, While Hoping for B. In this article originally published in 1975, Kerr employs descriptive examples to demonstrate how a reward system can often lead to different and even opposite results than intended. Using lines of code to measure effort and developer productivity often feels like something that would be another good example for this article.
In this blog entry, I have no intention of delving into the details of most of the advantages and disadvantages of measuring and collecting metrics by counting lines of code. These are outlined in many online resources such as the Wikipedia entry, Lines of Code - Dispelling the Myths, Estimation Technique: Lines of Code, and You Can't Manage What You Don't Measure. As these articles point out, there are advantages (such as quantifiable nature and simplicity) and disadvantages (such as language differences, developer efficiency differences, problem complexity differences) to using lines of code as a metric.
For this blog entry, my focus is on how the use of lines of code as a metric may serve as a detrimental motivational force for the software developer. In other words, while using lines of code may be meant primarily to measure productivity and to understand how far along a particular task is, the unintended consequence may be poor decisions that favor larger code counts in shorter periods of time. If we applied Kerr's article directly to this discussion, we might rename the article "On the folly of rewarding potentially worse development practices that happen to lead to more lines of code, while hoping for a measurement and estimation approach that helps developers and managers manage their work loads more effectively." This is not nearly as succinct a title as the original title, but does make the point. To be clear, I am not saying that fewer lines of code is always and necessarily better than more lines of code. What I am writing about here are situations when fewer lines of code is preferable to more lines of code, but the motivation to keep that count up might influence the decision.
So how can using lines of code as a primary metric serve as a detrimental motivational force? I will outline some examples where a developer could make a decision to choose a less efficient software development technique because of pressures of delivering a certain expected number of lines of code per day or week or month. I admit that the realization that I am reducing my expected line of code count has occurred to me more than once as I refactored many lines of code into code that was cleaner, more maintainable, and more readable with much fewer lines of code.
Code Generation
I want to avoid the debate about generated code versus hand-written code here. As with so many other things in life, there are times when each has its advantages and may be more appropriate. However, even in cases where hand-writing code may generate superior code, knowledge that one's performance might be measured by the lines of code produced could lead to one using the generation tools.
For example, I like to only provide "set" (or "mutator") methods on my objects when they are known to be needed. I prefer immutable objects generally. However, "set" methods can add significantly to my overall line of code count if I put them in all classes blindly. This is really easy to do with all modern Java IDEs that will automatically generate these methods for me. In this example, measuring my productivity via lines of code might tempt me to deviate from my hard-earned preference for immutable or carefully crafted mutable objects.
This can also go the other way. For example, one might choose to directly write object-relational mapping code rather than use a third-party object-relational mapping (ORM) mapping tool to increase the line of code count. There might be valid reasons for writing one's own ORM code, but I hope that increasing the lines of code count is not one of them. In this example, I might be tempted to make a decision about ORM handling based on lines of code rather than on more technical criteria.
Code generation can be a highly useful tool and truly can make me more productive. However, it can also be used improperly to "hide" the real efficiency and even lead to unneeded code that still must be tested and maintained.
Use of COTS Products
Several of the blog entries and articles on using lines of code as a measurement tool point out one of the challenges of this approach being how to count libraries. For example, the Spring Framework brings much functionality to an application. This is provided not only by the Spring libraries themselves, but also by all the dependencies libraries it employs. Spring can help one avoid much boilerplate code (as discussed in Add Some Spring to Your Oracle JDBC Access). In extreme cases, one might be tempted to not take advantage of Spring or similar frameworks and libraries because rolling one's own code will lead to a higher count of lines of code. There is also a temptation among many of us to succumb to Not Invented Here Syndrome (even when it is not really appropriate) and the lines of code metric motivation does not make this any easier to avoid.
Redundant Code
Years ago, I saw a well-respected project manager for a major software vendor outline how this person's company's IDE made reuse easy. The person went on to demonstrate this feature, which really boiled down to a fancy CTRL-C, CTRL-V copy-and-paste implementation. This, of course, did not appeal to my ideas of proper reuse. However, such copy-and-paste reuse might be appealing to a developer that knows his or her productivity is being measured simply by lines of code rather than by the maintainability of the code.
Refactoring
While there can be drawbacks to inappropriately chosen refactoring (such as breaking working software or delaying delivery at when implemented with poor timing), I have found that refactoring can often provide significant long-term advantages to the software I develop. Perhaps more surprising is that sometimes it even helps in the short-term because it makes my next task easier and quicker to implement. For example, if I see that I'm doing the same thing I've done before in multiple locations, taking the time to refactor this redundant code into methods often makes even the short-term job easier. However, refactoring like this can be especially egregious in terms of performance measured against lines of code because time is being spent to actually reduce the measured lines of code. It can sometimes be difficult to explain (or difficult for that person to believe) how fewer counted lines of code this week than last actually corresponds to more functional software.
Doing Things the Easy Way Rather than the Better Way
If a developer knows that he or she will be rewarded or punished based on the number of lines of code he or she generates, it is tempting to take the easiest problems with the highest degree of boilerplate coding. Likewise, a developer may choose to implement something in the most wordy manner imaginable rather than taking advantage of polymorphism, reflection, and similar techniques to write more concise code. Note that I am not saying that polymorphism and reflection are always the best approaches. In fact, they can be overused and abused and there are times when the complications of these approaches are overkill. However, I am saying that there are times when they are elegant and the most efficient. Unfortunately, writing code that effectively uses reflection or polymorphism can often be more difficult than writing the code with many more lines. So, as a developer with some degree of motivation based on lines of code, it is tempting to write the lengthier code that is easier and quicker to write rather than take the time to carefully craft the smaller code that requires more time to initially write.
Dead Code (UPDATE: This section added on 11 September 2008)
Most of us have witnessed and understand the lurking dangers of dead code or unreachable code. However, placing too much emphasis on total lines of code without any regard for the quality of the underlying code can be a demotivating factor that influences one's decision to eliminate dead code. Because many of the most common negative side effects of dead code (such as maintainability, extra compilation time, more things to break) are spread out over the long term, the total cost of dead code may not be apparent and so it is easy to think that avoiding confrontation over reduced lines of code is easier and better than removing the dead code (and the lines of code that get credited for it). This is representative of many of the factors discussed here: the negative consequences may be spread out enough or incurred far enough in the future that the idea of reducing negative consequences of reducing code count now outweighs the idea of fixing long-term problems or problems whose negative cost will not be incurred until much later.
How Motivating is the Lines of Code Metric?
This is the most difficult question for me to answer quantitatively. While using lines of code as a quantitative metric for measuring performance theoretically can motivate developers to make poor decisions as discussed above, it is not easy to estimate the degree of motivation this metric has. This difficulty arises from the fact that all individuals are different in terms of their responses to the same motivations. I have also observed that the best software developers often place great importance on the quality of their work and their own craftsmanship. Therefore, it may require a high degree of motivation to sway them from doing what they feel is best. However, if one feels his or her job is on the line or that there might be a nasty confrontation or embarrassing review, even such a person with a strong desire to write the best code can be influenced by such a motivation. The degree that lines of code counting will motivate the developer is also highly dependent on the importance and frequency of its use in the work environment. The closer that the measurement of lines of code is seen to being related to rewards or punishments, the more likely it is that lines of code will lead to poor software development decisions.
Is There a Better Way to Measure Productivity and Progress?
It is generally easy to identify how any reward system motivates unintended and/or undesired results. Unfortunately, it is much more difficult to provide better systems because these alternatives often have their own advantages and disadvantages, including their own side effects or unintended consequences. In other words, it is easier to criticize and find fault with any proposed system than it is to come up with a better system.
I have read about many proposed alternatives to using lines of code as a metric and they all have their own problems and disadvantages. No matter what system we use to measure our progress and our efficiency, we can always keep in mind that these numbers are far from absolute or clear. Instead, focus should be on "orders of magnitude" and the realization that not all lines of code are created equal. Other factors to consider include the difficulty coding a particular problem, languages used, newness of the technology, and the other many inadequacies of this measurement approach. If we don't take lines of code as an absolute measurement and consider lines of code as only one facet in a much larger set of considerations, I believe we can reduce the degree of potential negative motivation that lines of code counting can entail. Most importantly, lines of code count should be only considered with other factors such as recent refactoring, difficulty of work involved, and the relationship of lines of code to actual implemented functionality.
Measuring Affects Performance
Just as measuring performance of software actually impacts the very performance we are measuring, it seems that measuring lines of code may actually impact the number of lines of code generated. When we measure software performance, we try to use tools and techniques that have as little impact on the actual performance of the software as possible. Likewise with measuring code productivity and progress, we should try to use whatever tools and approaches will have the least impact on the generated lines of code. We want code to be generated to best solve a particular problem rather than to meet a certain expected total number of lines of code.
Conclusion
The idiom/proverb "The road to Hell is paved with good intentions" is widely popular because of the many true, real-life instances of it that we have all witnessed. Clients, managers, and even developers themselves want and need methods for estimating project sizes and measuring their progress. Unfortunately, these types of estimations and measurements have never been particularly easy. Measuring lines of code is one method for approaching the measurement of progress of a software development project and does have some obvious advantages. However, measuring lines of code also has its disadvantages. One of the primary disadvantages of measuring progress with lines of code is the potentially adverse motivational effect it can have on developers when they must choose between coding to meet this motivation (more lines of code) or applying tried-and-true software development best practices.
UPDATE (11 October 2008): A somewhat related Geek Hero Webcomic is now available and is called Programmer's Salary Policy.
UPDATE (24 December 2008): The following are some related resources that have been published since my original post or that I have come across since my original post.
"Measuring programming progress by lines of code is like measuring aircraft building progress by weight." (quote commonly attributed to Bill Gates)
- Programming Quotes
- A Double Handful of Programming Quotes
Measuring Productivity of Individual Programmers
Lines of Code as a Measure of Progress
Measuring Programming Progress by Lines of Code
Thứ Sáu, 5 tháng 9, 2008
Thứ Hai, 1 tháng 9, 2008
Google Chrome Web Browser
The Google Comic Book on Google Chrome provides insight into what to expect from the Google Chrome web browser. The AppleInsider article Google Planning New Chrome Browser Based on WebKit is a thorough and useful summary of some of the highlights of this project.
The Official Google Blog entry A Fresh Take on the Browser states that Google Chrome will be launched tomorrow (2 September 2008) in over one hundred countries and will be available for Windows-based systems.
My biggest concerns about Google Chrome are:
1. What effect will it have on the success of Mozilla Firefox?
2. Will the Flash plug-in player be supported fully in Google Chrome?
Google has supported Firefox in the past and helped make Firefox an important force in the web browser space. Google Analytics reports that nearly 70 percent of visitors to my blog are using Firefox. I explain this unusually high percentage of Firefox users by rationalizing that most people visiting my blog are software developers and most software developers I know strongly prefer Firefox or some other non-IE browser over IE. As someone who uses Firefox heavily, I'd hate to see support for it dwindle. The Google Blog entry announcing Google Chrome's release specifically mentions use of Firefox components and talks of providing code and support back to the community. Apparently, I'm not the only one wondering about the impact of Google's support being focused on its own web browser instead of Firefox.
The AppleInsider article referenced previously states that plug-ins such as Flash will be supported in Google Chrome. The blog entry Google Chrome, the security tidbits points out that current plug-ins will circumvent some of Google Chrome's security mechanisms.
Because Google Chrome focus areas include standards compliance and speedy JavaScript performance, I hope to see improved browser experiences for developers and users in the future. Whether or not Google Chrome ever captures significant market share, it is likely that it will provide ideas and components that make other web browsers better. I will be curious to see if Google Chrome has success closer to the market-leading Google search engine or closer to one of the numerous web browsers that significantly lack the market share of the browsers such as Microsoft Internet Explorer and Firefox.
Other useful blogs on Google Chrome include Google OS is Actually a Browser and Google Chrome: Not Invented Here Syndrome Invades Mountain View.
The Official Google Blog entry A Fresh Take on the Browser states that Google Chrome will be launched tomorrow (2 September 2008) in over one hundred countries and will be available for Windows-based systems.
My biggest concerns about Google Chrome are:
1. What effect will it have on the success of Mozilla Firefox?
2. Will the Flash plug-in player be supported fully in Google Chrome?
Google has supported Firefox in the past and helped make Firefox an important force in the web browser space. Google Analytics reports that nearly 70 percent of visitors to my blog are using Firefox. I explain this unusually high percentage of Firefox users by rationalizing that most people visiting my blog are software developers and most software developers I know strongly prefer Firefox or some other non-IE browser over IE. As someone who uses Firefox heavily, I'd hate to see support for it dwindle. The Google Blog entry announcing Google Chrome's release specifically mentions use of Firefox components and talks of providing code and support back to the community. Apparently, I'm not the only one wondering about the impact of Google's support being focused on its own web browser instead of Firefox.
The AppleInsider article referenced previously states that plug-ins such as Flash will be supported in Google Chrome. The blog entry Google Chrome, the security tidbits points out that current plug-ins will circumvent some of Google Chrome's security mechanisms.
Because Google Chrome focus areas include standards compliance and speedy JavaScript performance, I hope to see improved browser experiences for developers and users in the future. Whether or not Google Chrome ever captures significant market share, it is likely that it will provide ideas and components that make other web browsers better. I will be curious to see if Google Chrome has success closer to the market-leading Google search engine or closer to one of the numerous web browsers that significantly lack the market share of the browsers such as Microsoft Internet Explorer and Firefox.
Other useful blogs on Google Chrome include Google OS is Actually a Browser and Google Chrome: Not Invented Here Syndrome Invades Mountain View.
Thứ Năm, 28 tháng 8, 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.
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.
Thứ Hai, 25 tháng 8, 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
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.
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.
Thứ Bảy, 23 tháng 8, 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
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
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
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
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
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):
To run JConsole to "see" the OpenDMK and WS-JMX stuff, I used this command:
Finally, to run the simple programmatic client, I used this command:
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.
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 ListconnectorServers
= 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 ListconnectorServers)
{
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 Listdomains = 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("Pressto 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 Listconnectors)
{
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 ListconnectorServers)
{
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 ListconnectorServers
= 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.
Thứ Sáu, 15 tháng 8, 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
My first code listing shows a class called
DustinMBean.java
The class shown above makes it easy to set a Standard MBean's
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)
SimpleCalculator.java (Standard MBean Implementation)
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)
LessSimpleCalculator.java (MXBean Implementation)
Both the Standard MBean and MXBean code listings above use the
OperationType.java
With the Standard MBean, MXBean, enum, and DustinMBean classes all shown above, it is time to look at the class that calls
StandardMBeanDemonstrator.java
In the class above, the method that sets up the metadata [
It is inside the try block in the
For instance, instead of using reflection like this:
it could be done less generically but much more simply like this
In the
I won't show it here, but the
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
MXBean Registered on MBean Server in Standard Way

MXBean Registered on MBean Server via

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.
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 DustinMBeanextends 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,
ClassmbeanInterface,
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
* methodbuildModelMBeanOperationInfo()in the class
*ModelMBeanDemonstratorin 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.
Đăng ký:
Bài đăng (Atom)