Chapter 9. MX4J Developer's guide

Table of Contents

Introduction
Coding conventions
Using logging
Writing documentation
Releasing MX4J
MX4J Implementation Architecture

Introduction

This chapter aims to give some suggestion to developers of MX4J, in order to have some common base to start from. It gives also some indication on how to create and distribute a new MX4J release, and the general architecture of the MX4J implementation.

Coding conventions

The base to start from are the coding conventions for Java, see here.
We require as additional convention the one-line one-brace style:

Example 9.1. One-line one-brace style

	
public class Main { // bad
	private String name;
	public Main(String n) { // bad
		if (n != null) { // bad
			name = n;
		}
	}
}

public class Main
{ // good
	private String name;
	public Main(String n)
	{ // good
		if (n != null)
		{ // good
			name = n;
		}
	}
}
	
	

Using logging

It is good practice to insert logging statements in the code, to help debugging and to record information about what the program is doing.

MX4J has a built-in logging system based on the mx4j.log.Logger class, whose usage is very similar to the Category class of the Log4J project.

Class Logger has six methods that logs at a different priority; from the lowest priority to the highest they are:

  • public void trace(...);

  • public void debug(...);

  • public void info(...);

  • public void warn(...);

  • public void error(...);

  • public void fatal(...);

They all have two overloaded version, one that takes one argument of type Java.lang.Object, and one that takes two arguments of type Java.lang.Object and java.lang.Throwable.

Class Logger has another useful method that returns the priority enabled for that instance of Logger:

  • public boolean isEnabledFor(...);

Using correctly this method will save the run-time cost associated with the concatenation of String objects:

Example 9.2. Saving run-time cost of String concatenation

	
Logger logger = ...;
if (logger.isEnabledFor(Logger.TRACE))
{
	logger.trace("Result is: " + result + " on item: " + item + " for process: " + process);
}
	
	

Saving the time associated with the creation of temporary objects used only for the logging statement is mandatory when the priority is trace, debug or info, to avoid unnecessary drop of performance.

Another useful way to avoid creation of temporary String objects is to use the StringBuffer class, following this example:

Example 9.3. Saving run-time cost of String concatenation with StringBuffer

	
Logger logger = ...;
if (logger.isEnabledFor(Logger.TRACE))
{
	StringBuffer message = new StringBuffer("Result is: ").append(result).append(" on item: ").append(" for process: ").append(process);
	logger.trace(message);
}
	
	

This version, when the logging is performed because the priority is enabled for the logger instance, is more efficient than the previous example that used String concatenation.

How do you obtain an instance of the Logger class ? You must use the mx4j.log.Log class, this way:

Example 9.4. Retrieving a Logger instance

			
Logger logger = Log.getLogger("MyCategory");
			
			

Every logger instance is associated with a category, i.e. a string that normally represent a correlated group of logging requests.

Choosing the right priority

Choosing the right priority is important, and here there are few guidelines:

  • Use Logger.trace to log execution flow. Always surround a log with trace priority with a call to Logger.isEnabledFor.

  • Use Logger.debug to log variables values. Always surround a log with debug priority with a call to Logger.isEnabledFor.

  • Use Logger.info to log information that can be of interest for the user. For every public method there should be at least one log with info priority. Always surround a log with info priority with a call to Logger.isEnabledFor.

  • Use Logger.warn to log recoverable errors that in normal situations should not happen. The warn priority is the default priority for MX4J.

  • Use Logger.error to log exceptions. Typically, log with error priority are inside catch blocks, just before rethrowing:

    Example 9.5. Logs with error priority

    	
    Logger logger = ...;
    try
    {
    	thisMethodThrows();
    }
    catch (Exception x)
    {
    	logger.error("Exception happened: ", x);
    	throw x;
    }
    	
    	

  • Use Logger.fatal(...); to log fatal errors that will force the JVM to terminate. Typically log with fatal priority are inside catch blocks, just before calling System.exit:

    Example 9.6. Logs with fatal priority

    	
    Logger logger = ...;
    try
    {
    	Class.forName("java.lang.Object");
    }
    catch (ClassNotFoundException x)
    {
    	logger.fatal("Can't find core classes", x);
    	System.exit(1);
    }
    	
    	

    However, it is mandatory that in the MX4J code System.exit is never called.

Writing documentation

The documentation that comes with MX4J is written using DocBook. It is very easy to use DocBook, and an on-line manual is available here.

Generally, the layout is defined in the file index.xml. All other files are DocBook sections belonging to a chapter, also defined in index.xml.

Releasing MX4J

The following steps are required for a new release of MX4J:

  • Fresh checkout from CVS. Do NOT update, checkout to a new directory the whole CVS
  • Add to lib directory all the optional libraries
  • Build the code and run the test suite. All tests must pass
  • Build the distribution kit
  • Check that the distribution kit contains all the jars, the sources and licensing information (also for third party software used)
  • Tag CVS with the suitable release tag
  • Upload the new kit to SourceForge, creating a new release version
  • Update the Change Notes tracker in SourceForge
  • Submit a news in SourceForge
  • Submit a news in FreshMeat
  • Submit a news in JavaLobby
  • Submit a news in TheServerSide
  • Submit a news in jmx-forum@java.sun.com
  • Submit a news in comp.lang.java.announce

MX4J Implementation Architecture

The primary implementation class of the MX4J JMX Agent is mx4j.server.MX4JMBeanServer.

The MBeanServer implementation class accomplishes these roles:

  • Returns information about the Agent
  • It is a repository for MBeans
  • Introspect the MBeans to determine their type and compliance
  • It acts as invoker, on behalf of the user, of operations on the registered MBeans

The information about the JMX Agent are returned by several methods such as, for example, getDefaultDomain(), and are implemented directly in the mx4j.server.MX4JMBeanServer class.

The registered MBeans are stored into a repository.
The MBeanServer implementation delegates to implementations of the mx4j.server.MBeanRepository interface the repository task; therefore the MBeanServer implementation acts as a factory for MBeanRepository instances, but the implementation is delegated to MBeanRepository instances.
It is possible to specify custom MBeanRepository implementations by specifying the full qualified name of the implementation class as value of the system property "mx4j.mbeanserver.repository".

When an MBean is registered several checks must be made to determine the MBean's type, if it is a compliant MBean or not and to retrieve its MBeanInfo information.
The MBeanServer implementation delegates this task to the mx4j.server.MBeanIntrospector class.
Objects of this class are first asked to fully introspect the given MBean; after the MBeanIntrospector has gathered all information about the MBean into an instance of mx4j.server.MBeanMetaData class, the MBeanServer implementation asks the MBeanIntrospector to check the compliance of the MBean.
If the MBean is a standard one, the MBeanIntrospector must create the MBeanInfo information for it using introspection on the MBean's management interface, and create the MBean invoker for it, see below.
The role of the MBeanIntrospector is thus to check the MBean compliance, to create the MBeanInfo information for standard MBeans and to act as a factory for MBean invokers.

The MBeanServer implementation acts as an invoker, on behalf of the user, of operations on the registered MBeans.
The architecture is interceptor-based, that is whenever you call from a client an MBeanServer method that will end up to call the MBean instance, the call is dispatched to the interceptor chain and eventually to the MBean instance.
The interceptors are configurable via the MBeanServer Interceptor Configurator MBean, that is an MBean of class mx4j.server.MBeanServerInterceptorConfigurator registered under the "JMImplementation" domain.
When the call is about to arrive to the MBean instance, the last interceptor dispatches the call depending on the MBean type: if the MBean is a dynamic MBean, the call is dispatched directly using the methods of the DynamicMBean interface; if the MBean is a standard MBean, then an MBean invoker is delegated to invoke the operation on the MBean instance.
MBean invokers are implementations of the mx4j.server.MBeanInvoker interface.
There are 2 default MBean invoker implementations: the first is based on reflection to invoke the MBean instance, the second is based on an on-the-fly generated class that invokes the MBean directly. This on-the-fly generated class is created at registration time; its bytecode is built at runtime using the Byte Code Engineering Library.
Both versions make use of TernaryTree as fast caches for MBean information, so that the invocations on standard MBeans are really FAST.
Early performance benchmarks reveal that the on-the-fly generated version is 2 times faster than the one that use reflection.

For further information, see the Javadocs of the cited classes.