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.

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

No Block Scope in ActionScript

One of the things that I most liked about ActionScript when I first began to use it was how easy it was to pick up after using Java for several years. I have blogged about some of the similarities Java and ActionScript share, but there are also some differences (such as ActionScript's support for switching on Strings). In this brief blog entry, I will demonstrate one subtle difference in behavior in ActionScript from what I am used to from years of C++ and Java experience.

While ActionScript 3.0 supports global variables and constants and also supports variables and constants local to a function, it does not support block scoping. The "Variables" section of Chapter 4 ("ActionScript Language and Syntax") of Programming ActionScript 3 describes this in more detail. In this blog entry, I'll demonstrate this with a brief example.

VariableScopeTest.mxml

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
width="900" height="900"
applicationComplete="demonstrateVariableScope()">
<mx:Script>
import mx.controls.Alert;
const aConstant:String = "GlobalConstant";
var aVariable:String = "GlobalVariable";
private function demonstrateVariableScope():void
{
const firstConstant:String = "firstConstant";
var firstVariable:String = "firstVariable";
const aConstant:String = "LocalConstant";
var aVariable:String = "LocalVariable";
if ( 1 )
{
const secondConstant:String = "secondConstant";
var secondVariable:String = "secondVariable";
}
Alert.show(
"First Constant: " + firstConstant + "\n"
+ "First Variable: " + firstVariable + "\n"
+ "Second Constant: " + secondConstant + "\n"
+ "Second Variable: " + secondVariable + "\n"
+ "Global/Local Constant: " + aConstant + "\n"
+ "Global/Local Variable: " + aVariable + "\n" );
}
</mx:Script>
</mx:Application>


The output from running the SWF that results from compiling the above code looks like this:



This example demonstrates a couple important points about variable and constant scoping in ActionScript:

1. Like Java, local variables (variables local to a function) override variables of the same name on a more global level.

2. Unlike Java, ActionScript variables defined within a block are actually scoped to the entire function containing that block rather than to simply the block itself.



Where something like this can cause a little trouble is when a developer tries to use the same constant name in two different blocks. This will result in a compiler error ("Error: A conflict exists with definition ...") because the compiler will see the constants declared in two separate blocks within the same function as being an attempt to redefine a constant.

This is not a huge deal, but it is a subtlety that can be a surprise to a Java developer learning ActionScript.

Thứ Năm, 2 tháng 10, 2008

Similarities of Measuring Software Performance and Measuring Development Progress

I have observed that many lessons from software development experience can be applied to management of software development teams and that the opposite is also true -- many lessons from management of software development teams can be applied in hands-on software development. In this blog entry, I want to examine how lessons learned from software performance monitoring can be applied to appropriate monitoring of software development progress.

There are several issues that make it difficult to accurately measure software performance. Brian Goetz has written extensively on some of the difficulties. In Dynamic Compilation and Performance Management, Goetz outlines how dynamic compilation (such as is used with Java) complicates performance testing. This is one of the more subtle impacts of performance testing. Other and related problems measuring software performance include the fact that performance measurement directly impacts the performance itself (though many have gone to great lengths to reduce this effect), performance metrics can be collected in unrealistic situations (different hardware, different load, different actual running software, etc.), and performance metrics can be misinterpreted.

All of these problems that lurk in performance monitoring (monitoring affecting performance itself, unrealistic tests, and misinterpreted metrics) have counterparts in the similar effort to measure software development progress. Just as one must be careful when measuring software performance, measurement of software development progress must be approached carefully as well.

Too much focus on collecting software development metrics can actually slow down the very software development process that is being measured. Just as using resources to measure software performance impacts that very software's performance, measuring the development progress has some effect on that progress. In measuring software performance, we have learned to use tools and techniques that reduce the impact of the measurements on the performance itself. We need to similarly approach our software development metrics and ensure that the collection of metrics has only a minimum impact on that software development.

One way to reduce the effect of software development metrics on the development is to keep the number of requests for metrics down. Another obvious approach is to only request data that is easily provided and does not require significant effort to collect, organize, and present. There are many tools that are marketed to help reduce the impact on software development that is incurred for metrics collection, but even these can have a detrimental effect on the development progress when used improperly. For example, these tools may require developers to take extra steps or follow extra processes to ensure that their progress is adequately captured. The amount of time spent collecting and preparing reports on the progress of the software development effort can grow to be very expensive and add significant delays and hurdles to the development progress.

Just as software performance tests are useless or even dangerous (because they lead to bad decisions) when they are obtained against situations and environments that are not representative of the actual production environment, measuring the wrong things in software development progress can lead to useless and even detrimental results. I blogged previously on how using lines of code as too granular of a metric can lead to negative consequences because of the unintended consequences of this motivator. Similarly, other poorly chosen metrics can actually lead to bad decisions of developers who are trying to satisfy the metric rather than developing the best code.

Finally, misinterpreted performance results can lead to unnecessary optimizations. In the worst cases, these misinterpreted results might even lead to "optimizations" that actually make the real problem even worse. This can be the case with measuring software development progress as well. Lines of code, number of classes, and similar metrics can be misleading and misinterpreted. Poor decisions can be made on these inadequate metrics that actually hinder the software development process rather than helping.

While measuring the performance of software and measuring software development progress can both be difficult to perform properly, we still attempt to measure these things. In fact, as difficult as they are, we do need to measure them. We need our software to perform to certain levels depending on the context and the expectations of the software's users. Similarly, we need to deliver software by certain agreed dates to meet expectations of customers and potential customers. The key is to perform both of these measurements carefully and to constantly strive to reduce the impact of the measurement itself on what is being measured, to ensure that we are measuring the appropriate things, and to ensure that we interpret the metrics carefully.

The negative consequences of overzealous software development metrics collection has been a known problem for some time. In the software development classic The Mythical Man-Month, Frederick P. Brooks, Jr., articulates on this concept with vivid examples and illustrations obviously earned from his own personal experiences.

So, why don't we do a better job at this in many cases? Perhaps the most plausible explanation is that it is far more difficult to measure appropriately than it is to use the easiest measurement techniques that come to mind. It is easier to test our software's performance without concern for minimizing the impact on the performance itself and it is easier to measure our progress without trying to carefully craft metrics collection techniques that have minimal impact on the developers.

Similarly, it is easier to just test our performance in the first environment that is available rather than putting in the extra effort to replicate the environment and load as accurately as possible. It is also easier to count some arbitrary items such as classes, lines of code, etc. than it is to really try to measure delivered functionality that is not as easy to quantify.

Finally, misinterpretation of results of performance metrics or development progress metrics tends to happen when we are unwilling to put extra effort into really understanding why we are seeing the results. It is always easier to go with the first thing that comes to mind as we look at the results than it is to actually try to dig down into the real meaning of the results.

Many of the lessons we have learned from measuring and optimizing software execution performance can be applied to measuring and optimizing software development progress. Unfortunately, these lessons learned in one side don't always seem to be applied to the other side.

Colorado Software Summit 2008 Detailed Daily Schedule Posted

The Detailed Daily Summary for the Colorado Software Summit 2008 has been posted. When I have attended this conference in the past, I have always enjoyed mapping out which sessions I plan to attend. Because each session (except keynote sessions) is offered three times each, there are all types of scheduling permutations to consider. I have even more interest than usual in the posting of the detailed schedule for this year's conference (17th edition) because I am presenting on two topics.

My presentations are called Applying Flash to Java: Flex and OpenLaszlo and Java Management Extensions Circa 2008. The Schedule Sorted By Speaker Name indicates that I will be presenting each of the five weekdays of the conference with two presentations on Wednesday and one presentation each on Monday, Tuesday, Thursday, and Friday.

I am looking forward to attending sessions that should benefit me immediately and in the near future (such as presentations on REST (and RESTful approaches), on Comet, and on Spring 3.0. I also enjoy the mix of presentations I regularly attend at this conference that may not benefit me immediately or even for some time, but are like "brain candy." For this year's conference, subjects falling into these categories for me are the multiple presentations on iPhone development and a presentation on Android development.

The Colorado Software Summit 2008 begins in just over two weeks on the evening of Sunday, October 19, and runs through lunch on Friday, October 24.