Thứ Bảy, 18 tháng 10, 2008

Command-line JMX Management with jManage

Although JConsole and VisualVM offer freely available and highly useful JMX clients, there are some features of jManage that make it worth using in conjunction with these other JMX clients.

Two of jManage's features that are particularly useful are the interfaces it exposes for managing and monitoring JMX-enabled applications. jManage provides a web interface that can be useful in situations where the managing and monitoring client needs to access server-side JMX support behind a firewall. This same advantage can be achieved with an HTML Adapter or with with the JMX Web Services Connector, but the Adapter is not a standard JSR 160 connector and the JSR-160-compliant Web Services Connector is not yet finalized.

The following screen snapshot shows what the jManage web interface looks like.



For this blog entry, I want to focus on the second interface mechanism that jManage provides for managing and monitoring JMX-enabled applications. The command-line interface (CLI) that jManage provides can be used to run scripts or to run jManage interactively. In this blog entry, I'll focus on running jManage's command-line interface (CLI) interactively.

For the brief examples I demonstrate in this blog entry, I will be using the server-side code from my previous blog entry on JMX Querying to run jManage against.

If you have not set up jManage before, there are only a few steps to follow to get it up and running. I summarize the steps here, but you should reference the JManage Installation wiki page for complete details. The steps you must follow to start using jManage include:

1. Set JAVA_HOME appropriately. Mine is currently set to C:\Program Files\Java\jdk1.6.0_07 for these examples.

2. Unzip/unjar the contents of the downloaded jManage file (such as jmanage-2.0-RC1.zip in my case) into an installation directory (C:\jmanage-2.0-RC1 in my case).

3. Go to the "bin" directory of your jManage directory with the newly unjarred/unzipped contents of the jManage download and run the appropriate (for your operating system) "keygen" command. When prompted for the password (password for "admin"), I just used the default "123456" (no quotes) for this example.

4. From the same "bin" subdirectory in your jManage installation, run the appropriate (for your operating system) "startup" command. You will need to provide the password you specified in step #3.

5. In a web browser, navigate to http://localhost:9090/ if running the web browser from the same machine as jManage or else replace localhost with the name of the machine on which jManage is running.

6. At this point, you'll have a typical web form login page. Enter "admin" for the "Username" and the password you created in step #3 for "Password." Once you have authenticated you can browse this web application. For now, though, I will do just enough to use the command-line interface. The step necessary for this is to create the jManage application and associate it with my application exposing a JSR-160 compliant connector.

7. The following screen snapshot shows the fields that need to be set up for a JSR-160 JMX connection. I got to this screen by choosing to create a new jManage application and then selecting JSR-160 as the type of the application.



8. When step #7 has been accomplished, jManage's web page will list the associated applications as shown in the next screen snapshot.



Rather than using the web browser to manage our applications, we can use the jManage command-line interface to manage and monitor our JMX-friendly application. While we could write and run a script file with the jManage command-line tool, I'll focus on using the command-line tool interactively.

It is simple to start the interactive jManage command-line tool. Assuming the default password setting for admin, it is simply a matter of running the command "jmanage -username admin -password 123456". Because no script file was specified, the command-line tool will run interactively as evidenced in the next screen snapshot by the jmanage> prompt.



The above screen snapshot not only demonstrates running of the jManage command-line tool, but also demonstrates how the "help" command displays usage information and the "apps" command displays applications jManage is managing. In this case, there are three applications: jManage itself, an Oracle database connection, and the JMX Querying Example.

From the usage information printed out for the "help" request, we see that we can run several different and interesting management commands. In the next screen snapshot, I demonstrate using the "mbeans" command to see the MBeans associated with the "JMX Querying Example" application. This screen snapshot also demonstrates a very important point. You cannot specify single values with spaces by surrounding them with quotes. Instead, you must use the tilde character (~) in place of any spaces. So, the request for the MBeans for the JMX Querying Example was actually expressed as "mbeans JMX~Querying~Example". This displays the MBeans associated with this application that we would see when using similar queries with JConsole.



If I wish to find metadata about one of the MBeans returned from the "mbeans" command, I can use the "info" command as demonstrated in the next screen snapshot. As the snapshot indicates, I use the command "info JMX~Querying~Example/dustin:type=complex,name=One". The returned output is metadata information about that particular MBean, including the ObjectName, the underlying class, a description, the attributes, and the available operations.



I can use the "execute" command of the jManage CLI to execute an operation. This is demonstrated in the next screen snapshot where I use the command "execute JMX~Querying~Example/dustin:type=complex,name=One/retrieveStatusString" and get that result: "Successful".



The "help" usage information supplies other commands that can be run from the jManage CLI as well. Also, additional information can be retrieved for each command by running that command without arguments. The command's usage will then be displayed.

I mostly use JConsole and VisualVM for my JMX client needs. However, jManage provides significant advantages such as firewall friendliness via its web browser interface and script-friendliness via its command-line interface. In this blog entry, I've attempted to demonstrate how easy it is to apply jManage to manage JMX-enabled applications supporting a JSR-160 compliant connector. I did not demonstrate the use of a script with jManage and I also did not demonstrate many of jManage's other features, including its JFreeChart-based and customizable dashboards.

Thứ Ba, 14 tháng 10, 2008

Looking Forward to the Colorado Software Summit

Several colleagues I work with have asked me about the Colorado Software Summit after hearing my enthusiasm regarding attending it (and presenting at it) next week. I realized that because they are not aware of these advantages of this conference, others probably are not aware either. This blog entry is my attempt at explaining some of the things that I believe makes Colorado Software Summit unique and exciting.

The Colorado Software Summit offers several advantages that your boss or client will love while also offering several advantages your more selfish side will love as well (and some of the advantages fit in both categories). In no particular order, here are the advantages that I am looking forward to experiencing again next week.

* Cutting-edge technologies covered such as iPhone development coverage in this year's conference that is conveniently occurring right after Apple removed the Non-Disclosure Agreement from released software.

* Wide variety and interesting mixture of topics is another advantage of this conference. While I like the many cutting-edge presentations, I have found that there is a good mix of topics that I can apply immediately and that I will be able to apply in months or years. In the past, I have received my first absolute exposure or first significant exposure to several technologies that made a difference in my work almost immediately. These included SVG, XSL:FO/FOP, Maven, J2SE 5, XQuery, Java Generics, and many more.

* Highly skilled and experienced attendees that are often more familiar with the topic being covered than the speaker. I am a speaker this year and am well aware of this fact.

* Practitioner Speakers work with the topics they cover regularly and are enthusiastic about their topics.

* Wide spectrum of industry leaders represented as evidenced by these glowing blog entries on the Colorado Software Symposium from Simon Phipps (Sun Microsystems), Kelvin Lawrence (IBM), and Dave Landers (formerly BEA, now Oracle). It is nice to have representatives of some of these major players in our industry so active in this conference. Apache Software Foundation is also typically heavily represented at this conference and SpringSource has an employee presenting there this year as well.

* Sessions are offered three times each and this is a tremendous benefit that I cannot overstate. At most conferences that I have attended, as excellent as they are, I always find myself disappointed when three of my top five presentations that I wish to see most are in the same slot and the other two are in the same second slot. As a first-time presenter at this conference, presenting two 90-minute presentations three times each over five days sounds a little daunting, but I know the value of this format after having attended the conference multiple times in the past.

* Excellent food may be one of my more selfish motivations, but the food at this conference is truly memorable and motivating.

* Location, Location, Location - This is another nearly entirely selfish motivation, but this location (Keystone, Colorado) is beautiful, serene (in between summer and winter crowds), and the perfect environment to focus on software technologies in a gorgeous setting. You can walk up the stairs to walk out on the roof of the Keystone Conference Center and I have found this to be very refreshing when my brain is too crammed full to think another software-related thought.

* Total Immersion is the best way I can summarize why I really, really like this conference. While it is exhausting to learn so much in so short a period of time, I am ready to go dive in again.

Thứ Hai, 13 tháng 10, 2008

Querying in JMX 2.0

While some portions of forthcoming Java SE 7 still appear to be controversial, the JMX 2 portion (JSR-255) of Java SE 7 seems to be solidifying nicely. In this blog entry, I will look at some of the new JMX querying capabilities in JMX 2 such as the JMX Query Language.

JMX Specification Lead Eamonn McManus has posted several blog entries on JMX 2 including JMX Namespaces Now Available in JDK 7, Dropping Proposed Features from the New JMX API, and Playing with the JMX 2.0 API. Eamonn wrote about the JMX 2 Query Language several months ago in the blog entry A Query Language for the JMX API. My blog entry here will expand upon the concepts highlighted in that blog entry. Additional information on advancements in JMX 2 related to querying MBeans can be found in the 2008 JavaOne presentation JMX Technology Update (see slide 39).

To better appreciate what JMX 2 offers in terms of querying capabilities, one must first understand how JMX queries are performed in JMX 1.x. I'll look at the approaches to querying registered MBeans in JMX 1.x before moving onto demonstrating advancements in JMX 2.

When I think about JMX 1.x querying capabilities, I generally think of JMX 1.x querying falling into three different types. The first type of JMX MBeans querying is accomplished by querying against MBean ObjectNames and ObjectName patterns. The second type of JMX 1.x MBeans querying is accomplished by querying against characteristics of the MBeans such as MBean attributes, operation return values, or even the MBean's class type. The third type of MBean querying is a combination of the first two where the matching MBeans have matching ObjectNames or ObjectName patterns and also have matching characteristics.

To demonstrate querying the MBean server for registered MBeans, it is important to register some MBeans with the MBean server. While I could have used the JVM-provided MBeans to show off querying capabilities, I thought it might be a little more interesting if I used custom MBeans instead. However, JMX querying capabilities can be used with the JVM-provided MBeans in the same way they are used against custom MBeans.

The next two code listings show the interface and class for the first custom MBean. I'll reuse the particular MBean defined by this interface and class multiple times as different instances of the same MBean class with different ObjectNames and different characteristics.

SimpleMBeanIf.java


package dustin.jmx.query;

import javax.management.MXBean;

/**
* Simple MBean interface.
*
* @author Dustin
*
* @see <a href="http://marxsoftware.blogspot.com">Dustin's Software Development
* Cogitations and Speculations</a>
*/
@MXBean
public interface SimpleMBeanIf
{
/**
* Possible status values.
*
* @see <a href="http://marxsoftware.blogspot.com">Dustin's Software
* Development Cogitations and Speculations</a>
*/
public enum StatusEnum
{
/** Failed status. */
FAILED ("Failed"),
/** Successful status. */
SUCCESSFUL ("Successful"),
/** Unknown status. */
UNKNOWN ("Unknown");

/** Alternative String representation. */
private String representation;

/** Constructor accepting alternate String representation. */
StatusEnum(final String newString) {this.representation = newString;}

/**
* Provide a String representation as an alternate to that returned by
* toString().
*
* @return Alternative String representation.
*/
public String getStringRepresentation()
{
return this.representation;
}
};

/**
* Provide my resource status.
*
* @return My resource status.
*/
public StatusEnum getStatus();

/**
* Set/change my resource status.
*
* @param newStatus New value for my resource status.
*/
public void setStatus(final StatusEnum newStatus);

/**
* Provide status as String. This will be treated as an operation in JMX
* rather than simply as an attribute as the get/set methods are.
*
* @return Status as String.
*/
public String retrieveStatusString();

/**
* Provide my priority.
*
* @return My priority.
*/
public int getPriority();

/**
* Set/change my priority.
*
* @param newPriority My new priority value.
*/
public void setPriority(final int newPriority);

/**
* Provide the Name of the Resource that I manage/monitor.
*
* @return Name of the resource that I manage/monitor.
*/
public String getResourceName();
}



SimpleMBean.java


package dustin.jmx.query;

/**
* Implementation of a simple MBean.
*
* @author Dustin
*
* @see <a href="http://marxsoftware.blogspot.com">Dustin's Software Development
* Cogitations and Speculations</a>
*/
public class SimpleMBean implements SimpleMBeanIf
{
/** Status of the underlying resource. */
private StatusEnum status = StatusEnum.UNKNOWN;

/** Priority of this resource. */
private int priority;

/** Name of the resource I manage/monitor. */
private String resourceName;

/**
* Constructor accepting arguments to populate my state.
*
* @param newStatus Status value to use for my status.
* @param newPriority New value for my priority.
* @param newResourceName Name of the resource that I manage/monitor.
*/
public SimpleMBean(
final StatusEnum newStatus,
final int newPriority,
final String newResourceName)
{
this.status = newStatus;
this.priority = newPriority;
this.resourceName = newResourceName;
}

/**
* Provide my resource status.
*
* @return My resource status.
*/
@Override
public StatusEnum getStatus()
{
return this.status;
}

/**
* Set/change my resource status.
*
* @param newStatus New value for my resource status.
*/
@Override
public void setStatus(final StatusEnum newStatus)
{
this.status = newStatus;
}

/**
* Provide status as String. This will be treated as an operation in JMX
* rather than simply as an attribute as the get/set methods are.
*
* @return Status as String.
*/
@Override
public String retrieveStatusString()
{
return this.status.getStringRepresentation();
}

/**
* Provide my priority.
*
* @return My priority.
*/
@Override
public int getPriority()
{
return this.priority;
}

/**
* Set/change my priority.
*
* @param newPriority My new priority value.
*/
@Override
public void setPriority(final int newPriority)
{
this.priority = newPriority;
}

/**
* Provide the Name of the Resource that I manage/monitor.
*
* @return Name of the resource that I manage/monitor.
*/
@Override
public String getResourceName()
{
return this.resourceName;
}
}


The above interface and implementation class make this an MXBean thanks to the @MXBean annotation on the interface. For my second MBean class, shown in the next two code listings, I'll also use an MXBean, but this one will be implemented the more traditional way following the naming convention pattern of the interface having the same name as the implementation plus an MXBean suffix.


AnotherMXBean.java


package dustin.jmx.query;

/**
* Another MBean example used in querying. This one will primarily be used to
* demonstrate the Java SE 6 Query.isInstanceOf(StringValueExp) functionality.
*
* @see <a href="http://marxsoftware.blogspot.com">Dustin's Software Development
* Cogitations and Speculations</a>
*/
public interface AnotherMXBean
{
/**
* Provide my status string.
*
* @return My status string.
*/
public String getStatusString();

/**
* Set/change my status string.
*
* @param newStatusString New status string.
*/
public void setStatusString(final String newStatusString);

/**
* Same as getStatusString(), but enables JMX Clients to see this as an
* operation rather than as an attribute getter.
*
* @return Status String.
*/
public String provideStatusString();
}



Another.java


package dustin.jmx.query;

/**
* This MBean is intended to be an MBean defined by a different class than the
* class used for most MBeans in the querying examples. This MBean will be used
* to demonstrate the Query.isInstanceOf(StringValueExp) that was introduced
* in Java SE 6.
*
* @see <a href="http://marxsoftware.blogspot.com">Dustin's Software Development
* Cogitations and Speculations</a>
*/
public class Another implements AnotherMXBean
{
/** Status string attribute of MBean. */
private String statusString;

/**
* Provide my status string.
*
* @return My status string.
*/
@Override
public String getStatusString()
{
return this.statusString;
}

/**
* Set/change my status string.
*
* @param newStatusString New status string.
*/
@Override
public void setStatusString(final String newStatusString)
{
this.statusString = newStatusString;
}

/**
* Same as getStatusString(), but enables JMX Clients to see this as an
* operation rather than as an attribute getter.
*
* @return Status String.
*/
@Override
public String provideStatusString()
{
return this.statusString;
}
}



With two MBean classes defined above (SimpleMBeanIf and AnotherMXBean), it is time to register instances of these MBean class types with the MBean server. The following class, SimpleServer, does this. It registers multiple instances of the SimpleMBean with different ObjectNames and different MBean characteristics. It also registers one instance of the AnotherMXBean to demonstrate querying by MBean class type. Here is the code listing for SimpleServer.


SimpleServer.java


package dustin.jmx.query.server;

import dustin.jmx.PrintUtility;
import dustin.jmx.query.Another;
import dustin.jmx.query.SimpleMBean;
import dustin.jmx.query.SimpleMBeanIf.StatusEnum;
import static dustin.jmx.JmxQueryConstants.JMX_SERVICE_URL_STR;

import java.io.Console;
import java.io.IOException;

import java.lang.management.ManagementFactory;
import java.net.MalformedURLException;
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;

/**
* Simple JMX application intended to register some JMX MBeans with the MBean
* Server so that different JMX queries can be performed against them by a JMX
* client.
*
* @author Dustin
*
* @see <a href="http://marxsoftware.blogspot.com">Dustin's Software Development
* Cogitations and Speculations</a>
*/
public class SimpleServer
{
/**
* Obtain the platform JMX server and register some example MBeans with it.
*/
public static void configureJmxServerAndRegisterMBeans()
{
final MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
final JMXConnectorServer connectorServer = startConnectorServer(mbeanServer);
registerMBeanWithJMXServer(
new SimpleMBean(StatusEnum.SUCCESSFUL, 1, "Dustin-Host-1"),
"dustin:type=simple,name=One",
mbeanServer);
registerMBeanWithJMXServer(
new SimpleMBean(StatusEnum.SUCCESSFUL, 2, "Dustin-Host-2"),
"dustin:type=complex,name=One",
mbeanServer);
registerMBeanWithJMXServer(
new SimpleMBean(StatusEnum.UNKNOWN, 3, "Dustin-Application-1"),
"dustin:type=simple,name=Two",
mbeanServer);
registerMBeanWithJMXServer(
new SimpleMBean(StatusEnum.FAILED, 2, "Dustin-Application-2"),
"dustin:type=complex,name=Two",
mbeanServer);
registerMBeanWithJMXServer(
new Another(),
"dustin:type=alternate,name=Another",
mbeanServer);
waitForInput();
stopConnectorServer(connectorServer);
}

/**
* Start the JSR-160 JMX connector server.
*
* @param mbeanServer MBean Server for which connector server should be started.
* @return JMXConnectorServer for provided MBean server; may be null if there
* is an issue trying to set up the JMX Connector Server.
*/
private static JMXConnectorServer startConnectorServer(
final MBeanServer mbeanServer)
{
final String serviceUrl = JMX_SERVICE_URL_STR;
JMXConnectorServer connectorServer = null;
try
{
final JMXServiceURL jmxServiceUrl = new JMXServiceURL(serviceUrl);
connectorServer =
JMXConnectorServerFactory.newJMXConnectorServer(
jmxServiceUrl,
null,
mbeanServer);
connectorServer.start();
}
catch (MalformedURLException badJmxServiceUrl)
{
System.err.print(
"ERROR trying to create JMX server connector with service URL "
+ serviceUrl + ":\n" + badJmxServiceUrl.getMessage() );
}
catch (IOException ioEx)
{
System.err.println(
"ERROR trying to access server connector.\n"
+ ioEx.getMessage() );
}
return connectorServer;
}

/**
* Stop the provided JSR-160 JMX Connector Server.
*
* @param jmxConnectorServer JMX Connector Server to be stopped.
*/
public static void stopConnectorServer(final JMXConnectorServer jmxConnectorServer)
{
try
{
jmxConnectorServer.stop();
}
catch (IOException ioEx)
{
System.err.println(
"IOException encountered trying to close JMXConnectorServer:\n"
+ ioEx.getMessage() );
}
}

/**
* Register the provided MBean-compatible object in the provided MBean
* Server under the provided ObjectName.
*
* @param mbeanToRegister MBean-compatible object to be registed with MBeanServer.
* @param mbeanObjectNameStr ObjectName to be used to register MBean under.
* @param mBeanServer MBean Server on which the provided MBean should be registered.
*/
private static void registerMBeanWithJMXServer(
final Object mbeanToRegister,
final String mbeanObjectNameStr,
final MBeanServer mBeanServer)
{
try
{
final ObjectName objectName = new ObjectName(mbeanObjectNameStr);
mBeanServer.registerMBean(mbeanToRegister, objectName);
}
catch (MalformedObjectNameException badObjectNameEx)
{
System.err.println(
"The provided ObjectName [" + mbeanObjectNameStr + "] is improper:\n"
+ badObjectNameEx.getMessage() );
}
catch (InstanceAlreadyExistsException redundantMBeanEx)
{
System.err.println(
"You have already tried to register an MBean with the name "
+ mbeanObjectNameStr + ":\n" + redundantMBeanEx.getMessage() );
}
catch (MBeanRegistrationException mbeanRegistrationEx)
{
System.err.println(
"MBean registration exception encountered trying to register "
+ "MBean " + mbeanObjectNameStr + ":\n"
+ mbeanRegistrationEx.getMessage() );
}
catch (NotCompliantMBeanException badMBeanEx)
{
System.err.println(
"The MBean [" + mbeanToRegister.getClass().getName() + "]"
+ "with ObjectName " + mbeanObjectNameStr + " is NOT a compliant "
+ "MBean:\n" + badMBeanEx.getMessage() );
}
}

/**
* Wait until user presses ENTER. The purpose of this is to allow JMX MBeans
* to remain registed in MBean server while client interacts with it.
*/
public static void waitForInput()
{
final Console console = System.console();
if ( console == null )
{
System.err.println(
"Please use Java SE 6 in an environment with a console.");
System.exit(-1);
}
console.printf("Press ENTER to exit.");
final String unusedReturnString = console.readLine();
}

/**
* Main function for setting up JMX MBeans that can be queried.
*
* @param arguments The command line arguments; none anticipated.
*/
public static void main(String[] arguments)
{
PrintUtility.writeAttributionInformation(
"SimpleServer in JMX Querying Example",
System.out);
configureJmxServerAndRegisterMBeans();
}
}



With the MBeans for the example registered with the MBean server, it is time to turn to the client. The client class is called SimpleClient. I will show its code listing first and then make some key observations about that code afterward.


SimpleClient.java


package dustin.jmx.query.client;

import dustin.jmx.PrintUtility;
import dustin.jmx.query.SimpleMBeanIf.StatusEnum;
import static dustin.jmx.JmxQueryConstants.JMX_SERVICE_URL_STR;

import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.Query;
import javax.management.QueryExp;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

/**
* Simple JMX Client intended to demonstrate querying with JMX 1.0 style queries
* and JMX 2.0 style queries.
*
* @author Dustin
*
* @see <a href="http://marxsoftware.blogspot.com">Dustin's Software Development
* Cogitations and Speculations</a>
*/
public class SimpleClient
{
final static String NEW_LINE = System.getProperty("line.separator");
final static String MAIN_HEADER_SEPARATOR =
"======================================================================"
+ NEW_LINE;
final static String SUB_HEADER_SEPARATOR =
" ----------------------------------------------------------------"
+ NEW_LINE;

/**
* Demonstrate how to query JMX MBeans using JMX 1.0 style querying.
*
* @param mbsc MBeanServerConnection.
*/
public static void demonstrateJmxOneQuerying(
final MBeanServerConnection mbsc)
{
printMainHeader("Querying with JMX 1.x", System.out);

// === Use ObjectName pattern matching to query by ObjectNames ===

printSubHeader(
"Querying with ObjectName Pattern Matching (dustin:type=simple,*)",
System.out);
final List<String> matchedMBeansByObjectNamePattern =
queryMBeansViaObjectNamePattern(
"dustin:type=simple,*",
mbsc);
printContentsOfListOfStrings(matchedMBeansByObjectNamePattern, System.out);


// === Use QueryExp to query by JMX 1.x query expression ===

final int examplePriority = 2;
printSubHeader(
"Querying with QueryExp (using null for all ObjectNames) and a "
+ "priority of " + examplePriority,
System.out);

final List<String> matchedMBeansByQueryExp =
queryMBeansWithQueryExpForIntegerAttribute(
null, // All registed MBeans considered
"Priority", // Attribute needs to be capitalized (not "priority")
examplePriority,
mbsc);
printContentsOfListOfStrings(matchedMBeansByQueryExp, System.out);


// === Get MBeans matching priority and resource name.

final String exampleResourceName = "Dustin-Host-2";
printSubHeader(
"Querying JMX MBeans based on Priority ("
+ examplePriority + ") and Resource Name ("
+ exampleResourceName + ")",
System.out);

final List<String> matchedMBeansByCompoundQueryExp =
queryMBeansWithQueryExpForIntAttrAndStringAttr(
null, // All registered MBeans considered
"Priority", // Attribute needs to be capitalized
examplePriority,
"ResourceName", // Attribute needs to be capitalized
exampleResourceName,
mbsc);
printContentsOfListOfStrings(matchedMBeansByCompoundQueryExp, System.out);


// === Get MBeans matching priority and status.

final String exampleStatus = StatusEnum.FAILED.toString();
printSubHeader(
"Querying JMX MBeans based on Priority ("
+ examplePriority + ") and Status ("
+ exampleStatus + ")",
System.out);

final List<String> matchedMBeansByCompoundQueryEnumExp =
queryMBeansWithQueryExpForIntAttrAndStringAttr(
null, // All registered MBeans considered
"Priority", // Attribute needs to be capitalized
examplePriority,
"Status", // Attribute needsto be capitalized
exampleStatus,
mbsc);
printContentsOfListOfStrings(matchedMBeansByCompoundQueryEnumExp, System.out);


// === Get all registered MBeans that are of class AnotherMXBean.

printSubHeader(
"Querying MBeans based on MBean class type",
System.out);

final List<String> matchedMBeansByClassType =
queryMBeansByClassType("dustin.jmx.query.AnotherMXBean", mbsc);
printContentsOfListOfStrings(matchedMBeansByClassType, System.out);


// === Get all registered MBeans.

printSubHeader(
"Querying with two nulls passed to queryMBeans to get all MBeans",
System.out);

final List<String> allMBeans = queryAllMBeans(mbsc);
printContentsOfListOfStrings(allMBeans, System.out);
}

/**
* Query MBeans using ObjectName pattern matching available since JMX 1.0.
*
* @param objectNamePattern Pattern to use for matching ObjectNames.
* @param mbsc MBeanServerConnection.
*/
private static List<String> queryMBeansViaObjectNamePattern(
final String objectNamePatternStr,
final MBeanServerConnection mbsc)
{
final List<String> matchedMBeans =
queryMBeanServerForMatchingMBeansNames(
objectNamePatternStr,
null, // no query expression
mbsc);

return matchedMBeans;
}

/**
* Query MBeans using QueryExp and Query classes and looking for an
* Integer value.
*
* @param objectNameStr ObjectName pattern to be used in MBeans query.
* @param queryAttrStr Name of attribute to be used in query.
* @param queryValueInt Integer value to be used in query for attribute.
* @param mbsc MBean Server Connection.
*/
private static List<String> queryMBeansWithQueryExpForIntegerAttribute(
final String objectNameStr,
final String queryAttrStr,
final int queryValueInt,
final MBeanServerConnection mbsc)
{
final QueryExp queryExp =
Query.eq(
Query.attr(queryAttrStr),
Query.value(queryValueInt) );

final List<String> matchedMBeans =
queryMBeanServerForMatchingMBeansNames(
objectNameStr,
queryExp,
mbsc);

return matchedMBeans;
}

/**
* Query MBeans using JMX 1.x QueryExp approach with a combination of an
* integer comparison and a String comparison.
*
* @param objectNameStr ObjectName pattern to be used in MBeans query.
* @param queryIntAttrStr Name of integer attribute to be used in query.
* @param queryIntValue Integer value to be used in query for integer attribute.
* @param queryStrAttrStr Name of String attribute to be used in query.
* @param queryStrValue String value to be used in query for String attribute.
* @param mbsc MBean Server Connection.
*/
private static List<String> queryMBeansWithQueryExpForIntAttrAndStringAttr(
final String objectNameStr,
final String queryIntAttrStr,
final int queryIntValue,
final String queryStrAttrStr,
final String queryStrValue,
final MBeanServerConnection mbsc)
{
/*
* The three statements below that set up the compound JMX 1.x query
* could also be written as separate statements like this:
*
* final QueryExp queryIntExp =
* Query.eq(
* Query.attr(queryIntAttrStr),
* Query.value(queryIntValue) );
* final QueryExp queryStringExp =
* Query.eq(
* Query.attr(queryStrAttrStr),
* Query.value(queryStrValue) );
* final QueryExp queryExp =
* Query.and(queryIntExp, queryStringExp);
*/
final QueryExp queryExp =
Query.and(
Query.eq(
Query.attr(queryIntAttrStr),
Query.value(queryIntValue) ),
Query.eq(
Query.attr(queryStrAttrStr),
Query.value(queryStrValue) ) );

final List<String> matchedMBeans =
queryMBeanServerForMatchingMBeansNames(
objectNameStr,
queryExp,
mbsc);

return matchedMBeans;
}

/**
* Query MBeans using JMX 1.x QueryExp approach using the Query.isInstanceOf()
* feature introduced with Java SE 6.
*
* @param mbeanClassTypeStr MBean's class's type in String format.
* @param mbsc MBean Server Connection.
* @return Names of registered MBeans of the provided class type.
*/
private static List<String> queryMBeansByClassType(
final String mbeanClassTypeStr,
final MBeanServerConnection mbsc)
{
final QueryExp queryExp =
Query.isInstanceOf(Query.value(mbeanClassTypeStr));
final List<String> matchingMBeans =
queryMBeanServerForMatchingMBeansNames(
null,
queryExp,
mbsc);
return matchingMBeans;
}

/**
* Query for all MBeans.
*
* @param mbsc MBean Server Connection.
* @return Names of MBeans returned from query.
*/
private static List<String> queryAllMBeans(final MBeanServerConnection mbsc)
{
final List<String> matchedMBeans =
queryMBeanServerForMatchingMBeansNames(
null, null, mbsc);

return matchedMBeans;
}

/**
* Demonstrate how to query JMX MBeans using JMX 2.0 style querying.
*
* See Javadoc documentation on Java SE 7 (and JMX 2) at
* http://download.java.net/jdk7/docs/api/.
*/
public static void demonstrateJmxTwoQuerying(
final MBeanServerConnection mbsc)
{
printMainHeader("Querying with JMX 2.0", System.out);

printSubHeader(
"JMX 2.0 Single Attribute Query (Priority = 2)",
System.out);
final QueryExp simpleSingleAttrQuery = Query.fromString("Priority = 2");
final List<String> matchingMBeansSimpleSingleQuery =
queryMBeanServerForMatchingMBeansNames(null, simpleSingleAttrQuery, mbsc);
printContentsOfListOfStrings(matchingMBeansSimpleSingleQuery, System.out);

printSubHeader(
"JMX 2.0 Compound Query (Priority = 2 and Status = '"
+ StatusEnum.FAILED.toString() + "')",
System.out);
final QueryExp compoundAttrQuery =
Query.fromString(
"Priority = 2 and Status = '"
+ StatusEnum.FAILED.toString() + "'");
final List<String> matchingMBeansPriorityAndStatus =
queryMBeanServerForMatchingMBeansNames(null, compoundAttrQuery, mbsc);
printContentsOfListOfStrings(matchingMBeansPriorityAndStatus, System.out);
}

/**
* Query the MBean Server for MBeans matching the conditions specified in
* the provided ObjectName pattern and the QueryExp.
*
* @param objectNameStr Object Name (or pattern) of matching MBeans.
* @param queryExp Query expression for matching MBeans.
* @param mbsc MBean Server Connection.
* @return Names of MBeans matching the provided pattern and query expression.
*/
private static List<String> queryMBeanServerForMatchingMBeansNames(
final String objectNameStr,
final QueryExp queryExp,
final MBeanServerConnection mbsc)
{
final List<String> matchedMBeans = new ArrayList<String>();
try
{
ObjectName objectName = null;
if ( objectNameStr != null )
{
objectName = new ObjectName(objectNameStr);
}
final Set<ObjectInstance> matchingMBeans =
mbsc.queryMBeans(objectName, queryExp);
for ( final ObjectInstance mbeanName : matchingMBeans )
{
matchedMBeans.add(mbeanName.getObjectName().getCanonicalName());
}
}
catch (IOException ioEx)
{
System.err.println(
"IOException encountered while attempting to query MBeans:\n"
+ ioEx.getMessage() );
}
catch (MalformedObjectNameException badObjectNameEx)
{
System.err.println(
"The ObjectName " + objectNameStr + " is not valid:\n"
+ badObjectNameEx.getMessage() );
}
return matchedMBeans;
}

/**
* Start the JMX Connector Client.
*
* @return MBeanServerConnection to connector server.
*/
private static MBeanServerConnection startConnectorClient()
{
MBeanServerConnection mbsc = null;
final String jmxServiceUrl = JMX_SERVICE_URL_STR;
try
{
final JMXServiceURL jmxUrl = new JMXServiceURL(jmxServiceUrl);
final JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxUrl);
mbsc = jmxConnector.getMBeanServerConnection();
}
catch (MalformedURLException badServiceUrlEx)
{
System.err.println(
"The JMXServiceURL [" + jmxServiceUrl + "] is improper:\n"
+ badServiceUrlEx.getMessage() );
}
catch (IOException ioEx)
{
System.err.println(
"IOException encountered while trying to start JMX Connector Client:\n"
+ ioEx.getMessage() );
}
return mbsc;
}

/**
* Print main separation header to provided OutputStream.
*
* @param headerString Text string to be included in header separator.
* @param out OutputStream to which to write the header separator; will be
* written to System.out if an IOException is encountered while trying to
* write to this OutputStream.
*/
private static void printMainHeader(
final String headerString,
final OutputStream out)
{
try
{
out.write((NEW_LINE + MAIN_HEADER_SEPARATOR).getBytes());
out.write(MAIN_HEADER_SEPARATOR.getBytes());
out.write(("== " + headerString + NEW_LINE).getBytes());
out.write(("== (" + PrintUtility.BLOG_URL + ")" + NEW_LINE).getBytes());
out.write(MAIN_HEADER_SEPARATOR.getBytes());
out.write(MAIN_HEADER_SEPARATOR.getBytes());
}
catch (IOException ioEx)
{
System.out.println(MAIN_HEADER_SEPARATOR);
System.out.println("== " + headerString + NEW_LINE);
System.out.println("== (" + PrintUtility.BLOG_URL + ")" + NEW_LINE);
System.out.println(MAIN_HEADER_SEPARATOR);
}
}

/**
* Print secondary separation header to provided OutputStream.
*
* @param subHeaderString Text string to be included in header separator.
* @param out OutputStream to which to write the header separator; will be
* written to System.out if an IOException is encountered while trying to
* write to this OutputStream.
*/
private static void printSubHeader(
final String subHeaderString,
final OutputStream out)
{
try
{
out.write((NEW_LINE + SUB_HEADER_SEPARATOR).getBytes());
out.write((" -- " + subHeaderString + NEW_LINE).getBytes());
out.write((" -- (" + PrintUtility.BLOG_URL + ")" + NEW_LINE).getBytes());
out.write(SUB_HEADER_SEPARATOR.getBytes());
}
catch (IOException ioEx)
{
System.out.println(NEW_LINE + SUB_HEADER_SEPARATOR);
System.out.println(" -- " + subHeaderString + NEW_LINE);
System.out.println(" -- (" + PrintUtility.BLOG_URL + ")" + NEW_LINE);
System.out.println(SUB_HEADER_SEPARATOR);
}
}

/**
* Write the contents ofthe provided list of Strings to the provided
* OutputStream.
*
* @param listToPrint List of Strings to be printed.
* @param out OutputStream to be written to.
*/
private static void printContentsOfListOfStrings(
final List<String> listToPrint,
final OutputStream out)
{
try
{
for ( String itemToPrint : listToPrint )
{
out.write((itemToPrint + NEW_LINE).getBytes());
}
}
catch (IOException ioEx)
{
for ( String itemToPrint : listToPrint )
{
System.out.println(itemToPrint);
}
}
}

/**
* The main function for running the client that demonstrates JMX querying.
*
* @param arguments Command-line arguments; none anticipated.
*/
public static void main(final String[] arguments)
{
PrintUtility.writeAttributionInformation(
"SimpleClient in JMX Querying Example",
System.out);
final MBeanServerConnection mbsc = startConnectorClient();
demonstrateJmxOneQuerying(mbsc);
demonstrateJmxTwoQuerying(mbsc);
}
}



The SimpleClient class actually demonstrates JMX 1.x and JMX 2 MBeans querying, but I'll focus on JMX 1.x-style querying first. The method demonstrateJmxOneQuerying() in the SimpleClient class calls individual methods that each demonstrate a different type of JMX 1.x querying.

All JMX queries (1.x and 2) boil down eventually to making a call on the MBeanServerConnection class's queryMBeans(ObjectName,QueryExp) method for MBeans themselves or queryNames(ObjectName,QueryExp) for names of the matching MBeans. The SimpleClient.queryMBeanServerForMatchingMBeansNames method makes use of the first of these two methods to perform all queries used in this blog entry's examples, both for JMX 1.x and JMX 2 style querying. I actually could have used the second method (for querying names) because the names are all I am returning from this method, but I wanted to demonstrate the ability to get the entire MBean object.

The first example of a JMX 1.x MBean query queries only on ObjectName. This is done by passing the ObjectName pattern ultimately to the MBeanServerConnection.queryMBeans method for the ObjectName parameter and passing null for the QueryExp parameter. By passing null for the QueryExp parameter, only the ObjectName or pattern in the ObjectName is used in querying JMX MBeans and any other characteristics are insignificant in the query.

In this example, the ObjectName pattern was "dustin:type=simple,*". The Javadoc document for ObjectName explains ObjectName patterns and what is allowed in detail. For this entry, the most important observation is that the pattern "dustin:type=simple,*" means that any MBean with an ObjectName that includes "dustin:type=simple" will be returned. The asterisk is a wildcard. A query by ObjectName can also be for exact ObjectName rather than for a pattern. The results of running this particular SimpleClient.queryMBeansViaObjectNamePattern method with the ObjectName pattern of "dustin:type=simple,*" is shown next.



From the above screen snapshot, we see that the two returned MBeans do indeed have ObjectNames that include "dustin:type=simple" in them. Another observation here is that ObjectName is "smarter" than just a simple String. It actually treats "dustin:name=One,type=simple" and "dustin:name=Two,type=simple" as both instances matching the wildcard pattern "dustin:type=simple,*" even though there is a "name=" portion in between the "dustin:" and "type=simple" portions.

The next style of JMX 1.x MBean query demonstrated in SimpleClient is not providing an ObjectName pattern at all, but instead querying by an attribute of the MBean. In this case, the int attribute "priority" is the differentiating characteristic of the MBean query. The QueryExp instance is built up to query on the attribute "Priority" (capitalization is important) with a value of "2".

Other examples of querying MBeans by attributes (including by String and Enum attributes and by a combination of attributes) are also shown in the SimpleClient. One of the most interesting is the preparation of a QueryExp for a JMX 1.x-style query. This is where JMX 2 and its JMX Query Language shine, so it is important here to show the code required to do it in JMX 1.x. The method SimpleClient.queryMBeansWithQueryExpForIntAttrAndStringAttr demonstrates how a compound QueryExp can be constructed. In this case, it builds up a query based on an integer attribute of the MBean and a String attribute of the MBean. For convenience and emphasis, that method is reproduced here:


/**
* Query MBeans using JMX 1.x QueryExp approach with a combination of an
* integer comparison and a String comparison.
*
* @param objectNameStr ObjectName pattern to be used in MBeans query.
* @param queryIntAttrStr Name of integer attribute to be used in query.
* @param queryIntValue Integer value to be used in query for integer attribute.
* @param queryStrAttrStr Name of String attribute to be used in query.
* @param queryStrValue String value to be used in query for String attribute.
* @param mbsc MBean Server Connection.
*/
private static List<String> queryMBeansWithQueryExpForIntAttrAndStringAttr(
final String objectNameStr,
final String queryIntAttrStr,
final int queryIntValue,
final String queryStrAttrStr,
final String queryStrValue,
final MBeanServerConnection mbsc)
{
/*
* The three statements below that set up the compound JMX 1.x query
* could also be written as separate statements like this:
*
* final QueryExp queryIntExp =
* Query.eq(
* Query.attr(queryIntAttrStr),
* Query.value(queryIntValue) );
* final QueryExp queryStringExp =
* Query.eq(
* Query.attr(queryStrAttrStr),
* Query.value(queryStrValue) );
* final QueryExp queryExp =
* Query.and(queryIntExp, queryStringExp);
*/
final QueryExp queryExp =
Query.and(
Query.eq(
Query.attr(queryIntAttrStr),
Query.value(queryIntValue) ),
Query.eq(
Query.attr(queryStrAttrStr),
Query.value(queryStrValue) ) );

final List<String> matchedMBeans =
queryMBeanServerForMatchingMBeansNames(
objectNameStr,
queryExp,
mbsc);

return matchedMBeans;
}


The output for running these JMX 1.x-style queries is shown in the next screen snapshot.




Before moving onto JMX 2 querying, I want to focus on one additional characteristic that can be used for querying JMX MBeans in JMX 1.x. That approach is to query on the MBean class type of the MBean instance. This is done with the Query.isInstanceOf(StringValueExp) method introduced with Java SE 6.

The SimpleClient.queryMBeansByClassType method demonstrates use of the Query.isInstanceOf method and the most important statement of that method is reproduced here:


final QueryExp queryExp =
Query.isInstanceOf(Query.value(mbeanClassTypeStr));


This leads to the output shown in the next screen snapshot.



The JMX 1.x querying examples so far have demonstrated querying based on ObjectName and querying based on MBean characteristic (including MBean class type). If you wish to query all registered MBeans, you should pass null to both the ObjectName and QueryExp parameters of the MBeanServerConnection.queryMBeans() method. When this is done, output like that shown in the next screen snapshot is observed.



Up until this point, I've only used JMX 1.x querying techniques. So why are improvements needed in JMX 2? The most significant improvement to JMX 2 addresses the complexity of building up QueryExp instances as shown above. In JMX 1.x style, I had to build the composite query of an integer attribute and String attribute like this:


/*
* The three statements below that set up the compound JMX 1.x query
* could also be written as separate statements like this:
*
* final QueryExp queryIntExp =
* Query.eq(
* Query.attr(queryIntAttrStr),
* Query.value(queryIntValue) );
* final QueryExp queryStringExp =
* Query.eq(
* Query.attr(queryStrAttrStr),
* Query.value(queryStrValue) );
* final QueryExp queryExp =
* Query.and(queryIntExp, queryStringExp);
*/
final QueryExp queryExp =
Query.and(
Query.eq(
Query.attr(queryIntAttrStr),
Query.value(queryIntValue) ),
Query.eq(
Query.attr(queryStrAttrStr),
Query.value(queryStrValue) ) );


The beauty of JMX 2 and its JMX Query Language is that the above can be written much more simply as:


final QueryExp compoundAttrQuery =
Query.fromString(
"Priority = 2 and Status = '"
+ StatusEnum.FAILED.toString() + "'");


In the JMX 2 case, I used an enum instead of a string, but the principle is the same. The JMX 2 Query Language makes the code much more succinct and readable. While JMX 1.x queries were "inspired" by SQL queries, JMX 2 queries are much more obviously SQL-based than JMX 1.x queries.

The next screen snapshot demonstrates the JMX 2 query output as generated by SimpleClient.



It is important to note that the SimpleClient code shown above will not run in Java SE 6 because of the JMX 2 dependencies. To run SimpleClient, one must either remove (or comment out) the JMX 2 specific methods or one must download the latest released JMX drop from the Java 7 distribution. The steps for doing the latter are covered in Playing with the JMX 2.0 API and Playing with JMX 2.0 Annotations.

For building the code above, I added the JMX2 JAR to my NetBeans project classpath and prepended it to my NetBeans Boot ClassPath as shown in the next screen snapshot.



I ran the examples from the terminal window and so needed to ensure that the JMX2 JAR was first on that boot class path as well. This was done with the command

java -Xbootclasspath/p:C:\jmx2\jmx.jar -cp dist\JMXQueryExample.jar dustin.jmx.query.client.SimpleClient


I did not need to do this for the SimpleServer because it did not have anything JMX2-specific in it.

Finally, I used a couple utility classes in this blog entry that I will include here for convenience. Following these code listings, I will bring this rather lengthy blog entry to a conclusion.

JmxQueryConstants.java


package dustin.jmx;

/**
* Constant used in client and server portions of JMX application intended to
* demonstrate querying with JMX in JMX 1.x and JMX 2.0.
*
* @author Dustin
*
* @see <a href="http://marxsoftware.blogspot.com">Dustin's Software Development
* Cogitations and Speculations</a>
*/
public class JmxQueryConstants
{
public static final String JMX_SERVICE_URL_STR =
"service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi";
}



PrintUtility.java


package dustin.jmx;

import java.io.IOException;
import java.io.OutputStream;

/**
* Utility class for JMX Querying example that prints out attribution details.
*
* @author Dustin
*
* @see <a href="http://marxsoftware.blogspot.com">Dustin's Software Development
* Cogitations and Speculations</a>
*/
public class PrintUtility
{
public static final String BLOG_URL = "http://marxsoftware.blogspot.com/";

/**
* Write attributtion information to provided OutputStream.
*
* @param workName Name of work to which attribution applies.
* @param out OutputStream to which attribution information should be'
* written.
*/
public static void writeAttributionInformation(
final String workName,
final OutputStream out)
{
final String newLine = System.getProperty("line.separator");
final String headerSeparator =
"==================================================================="
+ newLine;
try
{
out.write(headerSeparator.getBytes());
out.write(("===== " + workName + newLine).getBytes());
out.write(("===== (" + BLOG_URL + ")" + newLine).getBytes());
out.write(headerSeparator.getBytes());
}
catch (IOException ioEx)
{
System.out.println(headerSeparator);
System.out.println("===== " + workName);
System.out.println("===== {" + BLOG_URL + ")");
System.out.println(headerSeparator);
}
}
}



JMX 2 offers simpler JMX MBean querying when the querying needs to be done against attributes of the registered MBeans. ObjectName matching was already relatively straightforward in terms of syntax, so it is not surprising that the main effort in JMX 2 improvements to querying were on the QueryExp side of things. For a list of some of the other syntax of the JMX 2 query language as well as additional improvements in querying JMX and potential gotchas associated with Query.isInstanceOf (and the new dotted attribute syntax), see A Query Language for the JMX API. It is currently anticipated that JMX 2 will be part of Java SE 7.

Thứ Bảy, 11 tháng 10, 2008

A Few Details about serialver

I have recently been asked by several different people in several different situations about Java serialization, about the Serializable interface, about serialVersionUID, and about how to generate a serialVersionUID. In this blog entry, I'll focus on using the serialver tool provided with Sun's JDK to create a serialVersionUID.

There is more to making a class Serializable than just implementing the Serializable interface. In fact, Josh Bloch devotes an entire chapter of his Effective Java to serialization (Chapter 10 in the First Edition and Chapter 11 in the Second Edition). Bloch also discusses serialization in the JavaOne 2006 presentation Effective Java Reloaded. If you have a copy of either edition of Bloch's book (and I believe every developer who uses Java heavily should) refer to the first item in the Serialization chapter regarding the reasons for using Serializable lightly ("Implement Serializable judiciously"). You can find similar text on the subject at the Java Practices site under Implementing Serializable.

The Javadoc-generated API documentation for the Serializable interface "strongly recommends" creating an explicit serialVersionUID for classes implementing the Serializable interface. However, the example shown in the description sets the serialVersionUID to 42L. This example meets the criteria that it be a long that is static and private, but Bloch points out in Effective Java that this minimally qualifying serialVersionUID is not a good one.

Although it is recommended that an explicit serialVersionUID be added to each Serializable class, this is not technically required. Java will create a serialVersionUID dynamically when it is not explicitly stated in the class. No error or even warning is reported by default for a Serializable class without a servialVersionUID. However, one can use the non-standard javac nonstandard option -Xlint:serial with Sun's JDK to have warning messages printed out when a Serializable class does not possess an explicit serialVersionUID.

This concept is illustrated with the next two screen snapshots. Both screen snapshots show the compilation of a Serializable class (explicitly extends Serializable) that does not explicitly declare a serialVersionUID. The first image shows that the default behavior of the javac compiler is to not display any warnings about the missing serialVersionUID. The second screen image demonstrates that the -Xlint:serial nonstandard javac option does display warnings about a Serializable class that does not explicitly declare a serialVersionUID.

No Warnings of Errors without -Xlint:serial




-Xlint:serial Generates Warnings




Once we've taken Bloch's advice about implementing Serializable judiciously and have selected classes that do need to be serializable, one of the next steps is to generate a serialVersionUID. While these can be made up, it is a common practice to take advantage of the Sun-provided serialver tool to do this for us.

The serialver tool is located in the same directory in the JDK as java, javac, jar, javadoc, jconsole, jvisualvm, xjc (JAXB), policytool, rmiregistry, and several other Sun-provided Java tools many of us find ourselves using on a daily basis.

The serialver tool allows the developer to either enter the fully qualified Serializable class name whose serialVersionUID is desired on the command-line or with an HMI interface. To use the HMI, the developer uses the -show option. To use the command-line, the developer obviously omits the -show option, but the developer also specifies the fully qualified class name as the final argument to serialver. Whether the developer uses the command-line or the HMI, the classpath must be provided with the -classpath option (-cp does not work for serialver). This classpath must include the class whose serialVersionUID is desired as well as any classes used by that class.

There are a few nuances to note about serialver. First, you cannot specify the classname to be analyzed and the -show option at the same time. In other words, the ambiguous condition of implying command-line (by passing the name of the class to be evaluated) while at the same time explicitly requesting the HMI with the -show option is avoided by not allowing the two to be used together. Another thing to note about serialver is that it will report an error if you pass it a class that does not implement Serializable (probably a good thing because there is no use to adding a serialVersionUID data member to a non-Serializable class).

One last nuance is that if a Serializable class already has a serialVersionUID, serialver will simply report that serialVersionUID to the developer. This means that if you want to generate a new serialVersionUID after changing a class significantly enough to affect its serialization status, you will want to comment out the serialVersionUID data member, recompile the class, and then run serialver against the compiled class with that attribute commented out.

The next three screen snapshots illustrate some of these key principles of using serialver.

The first screen snapshot demonstrates the serialver HMI reporting a class that is not Serializable. The second image demonstrates that serialver will simply return any serialVersionUID already explicitly set for a class (I chose 42L because that is what is used in the Serializable documentation and in Bloch's treatment and it is also the answer to the ultimate question). The third screen snapshot demonstrates how to use both the command-line and HMI versions of serialver and also demonstrates that they both return the same serialVersionUID.


Cannot Run serialver on a Non-Serializable Class




serialver Returns Explicitly Declared serialVersionUID




Running serialver as HMI and on Command-Line




With the serialver tool, a Java developer can generate more meaningful serialVersionUIDs than he or she might otherwise create.



UPDATE (20 December 2008): A relatively popular article (23 "up" votes so far) on serialVersionUID has been published on JavaLobby. The article is called Don't Ignore serialVersionUID.

Thứ Sáu, 10 tháng 10, 2008

Identify Class Path Problems with -Xlint:Path

When I am working with developers new to Java, I often forget to tell them about the little tools that make Java development much more efficient. These are the types of tools that aren't necessarily used everyday, but are very valuable when the appropriate need arises. I remember to tell them about these tools when they run into a perplexing problem and I am helping them resolve the problem and use these tools almost without thinking about them. In this blog entry, I plan to focus on one of these tools, which is actually a non-standard javac (Sun) option: -Xlint:path.

While it is very common to see the ClassNotFoundException during Java development. Usually, such issues are easily addressed by ensuring that the appropriate JAR or directory with .class files is specified via the -classpath (-cp) option. However, this sometimes doesn't solve the problem and so the next step is often to ensure that the desired class is really located in the JAR or directory on the classpath.

Every once in a while, and especially in cases of very long classpaths, it is easy to mistype the classpath and not even notice a difference in case or missing subdirectory. These classpath issues can be troublesome for any Java developer, but they can be especially disconcerting for a new Java developer. The problem is compounded by the fact that the javac compiler doesn't automatically warn the developer when a specified classpath entry is nonexistent. When I was first learning Java, I just assumed that it would report this and I have heard other developers who are new to Java make the same assumption. Fortunately, this is when the nonstandard option (-Xlint:path) that comes with Sun's Java distribution can be used to aid in debugging classpath issues.

The -Xlint:path option is not a standard Java option, but it does identify anything on the classpath that does not actually exist. This is valuable because the normal behavior is to not report any classpath errors and so it is easy for the developer to assume that entries on the classpath were all found. The warning produced by -Xlint:path provides a clue to the developer to fix the classpath issue that is explicitly spelled out in the warning message.

The following screen snapshot indicates use of this nonstandard javac option.



As the screen snapshot above shows, the classpath entries that don't exist don't cause any errors to occur or warnings to be printed when the -Xlint:path option is not used. The javac compiler silently goes on without those classpath entries. However, specifying the -Xlint:path option leads to warning messages specifically calling out the classpath entries that cannot be found. The second example demonstrates that even multiple missing entries can be shown at once with this option.

As a final note, the classpath entries were not needed in my example anyway, so the application compiled fine with javac even when they were not found. However, in more realistic situations where the classpath actually needs to have dependent libraries on it, the -Xlint:path nonstandard javac option can help the developer to ensure that all the necessary entries are on that classpath. If a class is not found, this is especially useful in tracking down the cause.

Thứ Hai, 6 tháng 10, 2008

The Java SE 6 Deque

I feel a little disappointment when I occasionally hear fellow Java developers make statements like this: "Java 6 doesn't really provide all that much." My experience with Java SE 6 has been very different and I find myself missing many of its features when I must use an earlier version of Java. While it is true that JDK 1.4 and J2SE 5 each introduced several significant language and syntax enhancements, Java SE 6 has provided many of its own highly useful new features as well. In this blog entry, I look at a feature that I don't use often, but is very nice to have available when I need it. I will be looking at the Java SE 6 additions to the Java Collections Framework of the Deque and the interfaces that extend it and classes that implement it.

The Deque (typically pronounced "deck" rather than "de-queue") interface supports the concept of a double-ended queue (and is not related to dequeue used to indicate removal from a queue). Because the double-ended queue supports addition or removal of elements from either end of the data structure, it can be used as a queue (first-in-first-out/FIFO) or as a stack (last-in-first-out/LIFO). J2SE 5 introduced a Queue interface and the Deque interface extends this interface. Note that a Vector-based Stack class has been present since JDK 1.0, but the Javadoc documentation for this class states that Deque should be used instead because it provides better LIFO operation support. Implementations of Deque are typically expected to perform better than Stack as well.

The Deque interface is extended by the concurrency-supporting interface BlockingDeque and is implemented by classes LinkedBlockingDeque (introduced in Java SE 6), LinkedList (available since JDK 1.2, but now implements Deque), and ArrayDeque (introduced in Java SE 6). For this blog entry, I will demonstrate using one Deque instance as a queue and one Deque instance as a stack using ArrayDeque for both. ArrayDeque is not thread-safe and does not allow for elements of the data structure to be null (a recommended but not required condition of Deque implementations and uses).


package dustin.deque;

import java.util.ArrayDeque;
import java.util.Deque;

/**
* Example of using an implementation of the Deque interface that was added in
* Java SE 6. In particular, this example uses the ArrayDeque implementation
* to implement a queue and a stack. This example is intended to be used for
* the blog "Dustin's Software Development Cogitations and Speculations" at
* <a href="http://marxsoftware.blogspot.com/">http://marxsoftware.blogspot.com/</a>.
*/
public class MainDequeExample
{
/** Deque implementation used as a queue. */
private Deque queue = new ArrayDeque();

/** Deque implementation used as a stack. */
private Deque stack = new ArrayDeque();

/**
* Demonstrate use of Deque as a queue.
*/
public void showOffDequeAsQueue()
{
setUpQueue();
System.out.println(
"The two elements that will be first out of this queue are "
+ this.queue.remove() + " and " + this.queue.remove() + ".");
}

/**
* Set up Deque to be used as a queue by adding several prime integers to it.
*/
private void setUpQueue()
{
this.queue.clear();
this.queue.add(1);
this.queue.add(3);
this.queue.add(5);
this.queue.add(7);
this.queue.add(11);
this.queue.add(13);
this.queue.add(17);
this.queue.add(19);
}

/**
* Demonstate use of Deque as a stack.
*/
public void showOffDequeAsStack()
{
setUpStack();
System.out.println(
"The two elements that will be first out of this stack are "
+ this.stack.pop() + " and " + this.stack.pop() + "." );
}

/**
* Set up Deque to be used as a stack by adding several prime integers to it.
*/
public void setUpStack()
{
this.stack.clear();
this.stack.push(1);
this.stack.push(3);
this.stack.push(5);
this.stack.push(7);
this.stack.push(11);
this.stack.push(13);
this.stack.push(17);
this.stack.push(19);
}

/**
* Demonstrate that a Deque can be populated like a queue, but have its
* elements retrieved like a stack.
*/
public void showOffMixedDequeWithPopulatedQueue()
{
setUpQueue();
System.out.println(
"The two elements that will be first out of this queue accessed like "
+ "a stack are " + this.queue.pop() + " and " + this.queue.pop() + ".");
}

/**
* Demonstrate that a Deque can be populated like a stack, but have its
* elements retrieved like a queue.
*/
public void showOffMixedDequeWithPopulatedStack()
{
setUpStack();
System.out.println(
"The two elements that will be first out of this stack accessed like "
+ "a queue are " + this.stack.remove() + " and " + this.stack.remove()
+ "." );
}

/**
* Main function for executing the examples.
*
* @param arguments the command line arguments; none expected.
*/
public static void main(final String[] arguments)
{
final MainDequeExample me = new MainDequeExample();
me.showOffDequeAsQueue();
me.showOffDequeAsStack();
me.showOffMixedDequeWithPopulatedQueue();
me.showOffMixedDequeWithPopulatedStack();
}
}


When the code above is run, the output looks like that shown in the next screen snapshot.



As the example in this blog entry has shown, the Deque interface (and specifically the ArrayDeque class) provide a useful data structure for working with stacks and queues. Many more useful methods are supported in addition to those shown in the example. These include methods like addFirst, addLast, getFirst, getLast, removeFirst, and removeLast.

It is also worth noting that Java SE 6 added two methods to the Collections class. Of immediate interest is the addition of the Collections.asLifoQueue(Deque) method that accepts a Deque and, as the name suggests, returns a LIFO queue.

Java SE 6 has provided many useful conveniences and new features that improve developer productivity. These include Sun's Java SE 6 inclusion of JAXB, built-in annotations processing, VisualVM (as of Java SE 6 Update 7), Swing goodies such as SwingWorker, JMX and diagnostic tools, and other useful tools and APIs directly incorporated with Java. In addition, changes to the Java language itself such as String.isEmpty, Console, and Deque have also helped developers be more productive.