FileDocCategorySizeDatePackage
MonitoringStatsImplBase.javaAPI DocGlassfish v2 API26750Fri May 04 22:23:32 BST 2007com.sun.enterprise.management.monitor

MonitoringStatsImplBase.java

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.enterprise.management.monitor;

import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Collections;
import java.io.Serializable;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;

import javax.management.ObjectName;
import javax.management.MBeanInfo;
import javax.management.MBeanAttributeInfo;
import javax.management.AttributeNotFoundException;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.OpenDataException;
import javax.management.InstanceNotFoundException;

import javax.management.j2ee.statistics.Statistic;
import javax.management.j2ee.statistics.*;
import com.sun.appserv.management.j2ee.statistics.StatsImpl;

import com.sun.appserv.management.base.AMXDebug;

import com.sun.appserv.management.monitor.MonitoringStats;

import com.sun.appserv.management.util.j2ee.J2EEUtil;
import com.sun.appserv.management.util.misc.ClassUtil;
import com.sun.appserv.management.util.jmx.JMXUtil;
import com.sun.appserv.management.util.misc.MapUtil;
import com.sun.appserv.management.util.misc.GSetUtil;
import com.sun.appserv.management.util.misc.StringUtil;
import com.sun.appserv.management.util.misc.ExceptionUtil;
import com.sun.appserv.management.util.misc.ArrayUtil;
import com.sun.appserv.management.j2ee.statistics.MapStatistic;
import com.sun.appserv.management.j2ee.statistics.MapStatisticImpl;
import com.sun.appserv.management.j2ee.statistics.StatisticImpl;
import com.sun.appserv.management.j2ee.statistics.StatisticFactory;

import com.sun.appserv.management.util.jmx.AttributeNameMapper;
import com.sun.appserv.management.util.jmx.AttributeNameMapperImpl;

import com.sun.enterprise.management.support.Delegate;
import com.sun.enterprise.management.support.DelegateToMBeanDelegate;

/**
	Base implementation class for Monitoring MBeans that provide Stats.
*/
public abstract class MonitoringStatsImplBase extends MonitoringImplBase
{
	private MBeanInfo					mMBeanInfo;
	private final AttributeNameMapper	mFieldNameMapper;
	private final AttributeNameMapper	mStatisticNameMapper;
	
	private Set<String>					mStatisticNames;
	
		public
	MonitoringStatsImplBase( final String j2eeType, final Delegate delegate )
	{
		super( j2eeType, delegate );
		
		mMBeanInfo	= null;
		
		mFieldNameMapper		= new AttributeNameMapperImpl();
		mStatisticNameMapper	= new AttributeNameMapperImpl();
		
		mStatisticNames	= null;
	}

		public
	MonitoringStatsImplBase( final String j2eeType )
	{
		this( j2eeType, null );
	}

	/**
		The interface available in underlying "old" MBean.
	 */
	private static interface OldMonitoringMBean
	{
		public String[]		getStatisticNames();
		public Statistic[]	getStatistics();
	}
	
	/**
		Get the underlying Delegate.  Note that this proxy
		may not actually implement all the routines in MonitoringStats;
		we use only a few however, so that is OK.
	 */
		protected OldMonitoringMBean
	getMonitoringMBeanDelegate()
	{
		return (OldMonitoringMBean)getDelegateProxy(OldMonitoringMBean.class);
	}
	
	/**
		The prefix of a getter method.
	 */
	private static final String	GET	= "get";
	
	/**
		The delimiter between a Statistic name and its field value when
		exposed as an Attribute.
	 */
	private static final String	STATISTIC_DELIM	= "_";
	
	
	static private final Set<String>   IGNORE_MISSING_SUFFIXES =
	    Collections.unmodifiableSet( GSetUtil.newSet( new String[]
	    {
	        "current", "count", "description", "name",
	        "lowwatermark", "highwatermark", "unit",
	        "lastsampletime", "starttime", 
	        "lowerbound", "upperbound",
	        "maxtime", "mintime", "totaltime", "average",
	        
	        "children",
	    }));
	    
	/**
	    We don't map any of the Attributes derived from statistics.
	 */
	    protected void
	handleMissingOriginals( final Set<String> missingOriginals )
	{
	    final Set<String>   stillMissing    = new HashSet<String>();
	    
	    for( final String name : missingOriginals )
	    {
	        final int idx   = name.lastIndexOf( '-' );
	        final String suffix = name.substring( idx + 1, name.length() );
	        
	        if ( ! IGNORE_MISSING_SUFFIXES.contains( suffix ) )
	        {
	            stillMissing.add( name );
	        }
	    }
        
        super.handleMissingOriginals( stillMissing );
	}
	
	
		protected final AttributeNameMapper
	getFieldNameMapper()
	{
		return( mFieldNameMapper );
	}
		protected final AttributeNameMapper
	getStatisticNameMapper()
	{
		return( mStatisticNameMapper );
	}


	protected final static String[]	STD_FIELDS	= new String[]
	{
		// none, by default
	};
	
	/**
		Initialize the field-name mapping for any names that required
		special conversion.
	 */
		protected void
	initFieldNameMapper()
	{
		final AttributeNameMapper	m	= getFieldNameMapper();
		
		assert( (STD_FIELDS.length % 2) == 0 );
		for( int i = 0; i < STD_FIELDS.length - 1; ++i )
		{
			m.addMapping( STD_FIELDS[ i ], STD_FIELDS[ i + 1 ] );
		}
	}
	
	
	protected final static String[]	STD_STATISTICS	= new String[]
	{
		"id", "ID",
		"Id", "ID",
	};
	
	/**
		Initialize the Statistic-name mapping for any names that required
		special conversion.
	 */
		protected void
	initStatisticNameMapper()
	{
		final AttributeNameMapper	m	= getStatisticNameMapper();
		
		final String[]	mappings	= STD_STATISTICS;
		
		for( int i = 0; i < mappings.length -1; ++i )
		{
			m.addMapping( mappings[ i ], mappings[ i + 1 ] );
		}
	}
	
	/**
		Get an Attribute value by looking for a corresponding Statistic.
		
		@param statisticName as seen in the MBeanInfo of this MBean
		@param fieldName as seen in the MBeanInfo of this MBean
	 */
		protected Object
	getAttributeFromStatistic(
		final String	statisticName,
		final String	fieldName )
		throws AttributeNotFoundException
	{
		try
		{
			final Statistic s	= getStatistic( statisticName );
			assert( s instanceof MapStatistic );
			
			final String	methodName	= JMXUtil.GET + fieldName;
			final Method	m	= s.getClass().getMethod( methodName, (Class[])null);
			
			//final MapStatisticImpl	ms	= new MapStatisticImpl( s );
			final Object result	= m.invoke( s, (Object[])null );
			return( result );
		}
		catch( Exception e )
		{
		    debug( "getAttributeFromStatistic: exception getting statistic " +
		        statisticName + e  + "\n" + 
		        ExceptionUtil.getStackTrace( ExceptionUtil.getRootCause( e ) ) );
			throw new AttributeNotFoundException( statisticName );
		}
	}
	
	/**
		Handle getting of artificial Attributes which are derived from Statistics.
		
		@param name the Attribute name
	 */
		protected Object
	getAttributeManually( final String name )
		throws AttributeNotFoundException
	{
		final int	idx	= name.indexOf( STATISTIC_DELIM );
		
		Object	result	= null;
		
		if ( idx > 0 )
		{
			// Attribute name is of the form <statistic-name>_<field-name>
			final String	statisticName	= name.substring( 0, idx );
			final String	fieldName	= name.substring( idx + 1, name.length() );
			
			result	= getAttributeFromStatistic( statisticName, fieldName );
		}
		else
		{
		    result  = super.getAttributeManually( name );
		}
		
		return( result );
	}
	
	/**
		A bug in the underlying MBeans is present.
	 */
	private static final boolean	BUG_STATISTIC_NAMES	= true;
	
	
		private String[]
	originalToDerivedStatisticNames( final String[] names )
	{
		final String[]	derived	= new String[ names.length ];
		
		for( int i = 0; i < names.length; ++i )
		{
			derived[ i ]	= originalToDerivedStatisticName( names[ i ] );
		}
		
		return( derived );
	}
	
	
		private void
	checkUnderlyingMBean()
	{
		assert( BUG_STATISTIC_NAMES ); 
                
		if ( BUG_STATISTIC_NAMES  /* && ! getJ2EEType().equals( "X-BeanMethodMonitor" ) */ )
		{
		    final Delegate  delegate    = getDelegate();
		    
            if ( delegate == null) return;
            
			final String[]	claimedNames	= getMonitoringMBeanDelegate().getStatisticNames();
			if ( claimedNames == null )
			{
			    throw new RuntimeException( "Delegate " + 
			        " used by AMX MBean " + getObjectName() +
			        " returned null StatisticNames array" );
			}
			else if ( claimedNames.length == 0 )
			{
			    throw new RuntimeException( "Delegate " + 
			        " used by AMX MBean " + getObjectName() +
			        " returned empty StatisticNames array" );
			}

			final Statistic[]	statistics	= getMonitoringMBeanDelegate().getStatistics();
			if ( statistics == null )
			{
			    throw new RuntimeException( "Delegate " + 
			        " used by AMX MBean " + getObjectName() +
			        " returned null Statistics array" );
			}
			else if ( statistics.length == 0 )
			{
			    throw new RuntimeException( "Delegate " + 
			        " used by AMX MBean " + getObjectName() +
			        " returned empty Statistics array" );
			}
			   
			try
			{
				final Set<String>		actualSet	= new HashSet<String>();
				
				final String[]	namesFromGetStatistics	= new String[ statistics.length ];
				
				for( int i = 0; i < statistics.length; ++i )
				{
					final String	name	= StringUtil.upperCaseFirstLetter( statistics[ i ].getName() );
					namesFromGetStatistics[ i ]	= name;
					
					if ( ! actualSet.contains( name ) )
					{
						actualSet.add( name );
					}
					else
					{
						logWarning( "MBean delegate " + 
						    " for " + getObjectName() +
							" returns Statistic with duplicate name: " + name +
							" from getStatistics() call.\n" );
					}
				}
				
				final Set<String>	claimedSet	= GSetUtil.newStringSet( claimedNames );
				if ( ! claimedSet.equals( actualSet ) )
				{
					final Set<String>	missing	= new HashSet<String>( claimedSet );
					missing.removeAll( actualSet );
					
					// assume workarounds are in place
					final String msg = "\nMBean delegate " + " for " + getObjectName() +
						" does not provide Statistic(s): " + missing + " from getStatistics() call, " +
					"\ngetStatisticNames() = " + toString( claimedSet ) + 
					"\nnames from getStatistics() = " + toString( namesFromGetStatistics ) + "\n";
					
	                AMXDebug.getInstance().getOutput(
	                    "MonitoringStatsImplBase.checkUnderlyingMBean" ).println( msg );
					logFine( msg );
				}
			}
			catch( Exception e )
			{
				final Throwable rootCause	= ExceptionUtil.getRootCause( e );
				logWarning( "MBean delegate " + 
				    " doesn't work, used by AMX MBean: " +
					getObjectName() + "\n" +
					rootCause.getClass().getName() + "\n" +
					ExceptionUtil.getStackTrace( rootCause ) );
			}
		}
	}
	
	
		private final String[]
	initStatisticNames()
	{
		String[]	names	= null;
		
		if ( BUG_STATISTIC_NAMES )
		{
			names	= getStats().getStatisticNames();
			if ( names == null || names.length == 0 )
			{
			    throw new RuntimeException( "Stats are null or empty for: " + getObjectName());
			}
		}
		else
		{
			names	= originalToDerivedStatisticNames( getMonitoringMBeanDelegate().getStatisticNames() );
		}
		
		return( names );
	}
	
	/**
		Gets the names of all the Statistics.
		@return a String[] of statistic names
	*/
		public String[]
	getStatisticNames()
	{
		return( GSetUtil.toStringArray( mStatisticNames ) );
	}

		public CompositeDataSupport
	getOpenStatistic(String name)
	{
		final Statistic statistic = getStatistic( name );
		
		try
		{
			return J2EEUtil.statisticToCompositeData(statistic);
		}
		catch(OpenDataException e)
		{
			throw new RuntimeException(e);
		}
	}

		public CompositeDataSupport[]
	getOpenStatistics( final String[] names)
	{
		final CompositeDataSupport[] result	= new CompositeDataSupport[names.length];
		
		for(int i = 0; i < names.length; i++)
		{
			result[ i ] = getOpenStatistic( names[ i ] );
		}
		return result;
	}

		public CompositeDataSupport
	getOpenStats()
	{
		final Stats	stats	= getStats();
		
		CompositeDataSupport	result	= null;
		// can't make a CompositeDataSupport if there are no Statistics
		if ( stats.getStatisticNames().length != 0 )
		{
			try
			{
				result	= J2EEUtil.statsToCompositeData( stats );
			}
			catch(OpenDataException e)
			{
				throw new RuntimeException(e);
			}
		}
		else
		{
			logWarning( "No Statistics available for: " + getObjectName() );
		}
		
		return( result );
	}

		public Statistic
	getStatistic( final String name)
	{
		final Stats		stats = getStats();
		
		return stats.getStatistic( name );
	}

		public Statistic[]
	getStatistics(final String[] desiredNames)
	{
		final Stats			stats	= getStats();
		final Statistic[]	result	= new Statistic[ desiredNames.length ];
		
		for( int i = 0; i < desiredNames.length; ++i )
		{
			final Statistic	statistic	= stats.getStatistic( desiredNames[ i ] );
			
			// OK to return null if not found--see Javadoc
			result[ i ]	= statistic;
		}
		
		return( result );
	}

	/**
		Convert a Statistic-name from its underlying name to the one we expose.
		
		@param underlyingName
	 */
		protected String
	originalToDerivedStatisticName( final String underlyingName )
	{
		String		result	= getStatisticNameMapper().originalToDerived( underlyingName );
		result	= StringUtil.upperCaseFirstLetter( result );
		
		return( result );
	}
	
		protected String
	derivedToOriginalStatisticName( final String derivedName )
	{
		return( getStatisticNameMapper().derivedToOriginal( derivedName ) );
	}
	
		protected final Statistic[]
	getStatisticsFromDelegate()
	{
	    if ( getDelegate() == null )
	    {
	        throw new NullPointerException();
	    }
		return( getStatisticsFromDelegate( getDelegate() ) );
	}

		protected final Statistic[]
	checkDuplicateStatistics(
		final Delegate d,
		final Statistic[] statistics)
	{
		// check to see if any names are duplicated
		final Set<String>	actualNames	= new HashSet<String>();
		for ( int i = 0; i < statistics.length; ++i )
		{
			final String	name	= statistics[ i ].getName();
			
			if ( actualNames.contains( name ) )
			{
				throw new RuntimeException( 
					"MonitoringStatsImplBase.checkDuplicateStatistics: " +
					getObjectName() +
						"Statistic " + StringUtil.quote( name ) + " is duplicated in getStatistics(): " +
					 	" as supplied from Delegate of " + StringUtil.quote( getObjectName() )+
					 	", please see bug #6179364"  );
			}
			else
			{
				actualNames.add( name );
			}
		}
		
		if ( actualNames.size() != statistics.length )
		{
			final String[] claimedNames = (String[])d.invoke( "getStatisticNames", null,  null );
		
			final Set<String>	missingNames	= GSetUtil.newStringSet( claimedNames );
			missingNames.removeAll( actualNames );
			
			throw new RuntimeException(
				"MonitoringStatsImplBase.getStatisticsFromDelegateRaw: " + missingNames.size() + 
				" Statistic names as found in Statistics from getStatistics() are missing: {" +
				toString( missingNames ) +
			 	"} from Delegate of " + StringUtil.quote( getObjectName() ) + ", please see bug #6179364" );
		}
		
		return( statistics );
	}

		protected Statistic[]
	getStatisticsFromDelegateRaw( final Delegate d )
	{
	    try
	    {
    		final Statistic[] statistics = (Statistic[])d.invoke( "getStatistics", null,  null );
    		
    		checkDuplicateStatistics( d, statistics );
    		
    		return( statistics );
		}
		catch( Exception e )
		{
		    final Throwable rootCause   = ExceptionUtil.getRootCause( e );
		    
		    logWarning( "MonitoringStatsImplBase: the com.sun.appserv Delegate MBean for AMX MBean " +
		        getObjectName() + " threw an exception: " + rootCause +
		    ", stack = \n" + ExceptionUtil.getStackTrace( rootCause ) );
		}
		return new Statistic[0];
	}


	private void
	debug( String s )
	{
		System.out.println( s );
	}
	
	
	/**
		Get all Statistics from the delegate (our only available call API).
		Statistic names are translated appropriately.
	 */
		protected Statistic[]
	getStatisticsFromDelegate( final Delegate d )
	{
		try
		{
			final Statistic[] statistics = getStatisticsFromDelegateRaw( d );
			
			// translate the names to be the ones we expose in MBeanInfo
			for( int i = 0; i < statistics.length; ++i )
			{
				final Statistic	origStatistic	= statistics[ i ];
			
				final MapStatistic	m	= new MapStatisticImpl( origStatistic );
				
				final String	convertedName	= originalToDerivedStatisticName( origStatistic.getName() );
				if ( ! convertedName.equals( origStatistic.getName() ) )
				{
					m.setName( convertedName );
				}
				
				final Class<? extends Statistic> theClass	=
				    StatisticFactory.getInterface( origStatistic );
				assert( theClass != null );
				
				// this will create one which implements the requisite interfaces
				statistics[ i ]	= StatisticFactory.create( theClass, m.asMap() );
				
				assert( theClass.isAssignableFrom( statistics[ i ].getClass() ));
			}
			
			return( statistics );
		}
		catch (Exception e)
		{
			final Throwable	rootCause	= ExceptionUtil.getRootCause( e );
			
			if ( ! ( rootCause instanceof InstanceNotFoundException ) )
			{
				// don't rethrow--will make MBeanServer unuseable as it has a bug if we throw
				// an exception of of getMBeanInfo() which halts any further processing of the query
				//NOTE: WARNING_CHANGED_TO_FINE	
				logWarning( "Can't get Statistics from delegate for " + getObjectName() +
					"\n" + rootCause.getMessage() + "\n" + ExceptionUtil.getStackTrace( rootCause ) );
			}
			throw new RuntimeException( e );
		}
	}
	
	// default interface implemented by Stats that we return
	private final Class[]	STATS_IMPL_INTERFACES	= new Class[]
	{
		Serializable.class,
		Stats.class,
	};
	
	/**
		Get the specific type of Stats interface (if any) that should be implemented
		by a Stats returned from getStats().
		
		@return an interface, or null if the interface is generic Stats
	 */
	protected abstract Class	getStatsInterface();
	
	
		protected StatsImpl
	createStatsImpl()
	{
		return new StatsImpl( getStatisticsFromDelegate() );
	}
	
	/*
		protected void
	serializeTest( final Object toSerialize )
		throws java.io.IOException, ClassNotFoundException
	{
		final java.io.ByteArrayOutputStream	os	= new java.io.ByteArrayOutputStream( 2048 );
		
		final java.io.ObjectOutputStream	oos	= new java.io.ObjectOutputStream( os );
		
		oos.writeObject( toSerialize );
		oos.close();
		
		final byte[]	bytes	= os.toByteArray();
		
		final java.io.ObjectInputStream	is	= new java.io.ObjectInputStream( new java.io.ByteArrayInputStream( bytes ) );
		
		final Object	result	= is.readObject();
		
		if ( ! result.equals( toSerialize ) )
		{
			assert( false ):
				"statistics not equal: " + toSerialize + " != " + result;
		}
	}
	*/
	
	
		public String
	getStatsInterfaceName()
	{
		return( getStatsInterface().getName() );
	}
	
		public Stats
	getStats()
	{
		final Class	statsInterface	= getStatsInterface();
		assert( statsInterface == null || Stats.class.isAssignableFrom( statsInterface ) );
		
		Class[] implementedInterfaces	= null;
		if ( statsInterface == null )
		{
			implementedInterfaces	= STATS_IMPL_INTERFACES;
			logInfo( "getStats: no Stats interface found for " + getObjectName() );
		}
		else
		{
			implementedInterfaces	= (Class[])
				ArrayUtil.newArray( STATS_IMPL_INTERFACES, statsInterface );
		}
		
		final StatsImpl			impl	= createStatsImpl();
		final ClassLoader		classLoader	= this.getClass().getClassLoader();
		
		final Stats stats	= (Stats)
			Proxy.newProxyInstance( classLoader, implementedInterfaces, impl );
	
	
	/*try
	{
		serializeTest( stats );
	} catch( Throwable t )
	{
		System.out.println( "getStats: Stats proxy serialization FAILED for " + getObjectName() );
		ExceptionUtil.getRootCause( t ).printStackTrace();
	}
	*/
		
		return( stats );
	}

	
		public final boolean
	refresh()
	{
		mMBeanInfo	= null;
		clearAttributeInfos();
		
		return( true );
	}
	
	/**
		Given a Statistic-name and a field-name, create an Attribute name.
		
		@param statisticName	Statistic-name
		@param fieldName
		@return a name suitable for an Attribute
	 */
		private String
	makeAttributeName(
		final String 	statisticName,
		final String	fieldName )
	{
		final String	attributeName	= statisticName + STATISTIC_DELIM + fieldName;

		return( attributeName );
	}
	
	/**
		JSR 77 defines these fields as having type "long", so we must define them that
		way as well, in spite of the underlying bug in the old monitoring MBeans, which
		declares them all as Strings.
	 */
	private static final Set<String> LONG_FIELDS = GSetUtil.newUnmodifiableStringSet(
		 "Count", "LastSampleTime", "StartTime", "LowerBound", "UpperBound",
		 "HighWaterMark", "LowWaterMark", "MaxTime", "MinTime", "TotalTime" );
	
	/**
		Determine the Java class of a field.
	 */
		private Class
	determineFieldType(
		final String	fieldName,
		final Object	value )
	{
		Class	theClass	= String.class;
		
		if ( value != null )
		{
			theClass	= ClassUtil.ObjectClassToPrimitiveClass( value.getClass() );
		}
		else if ( LONG_FIELDS.contains( fieldName ) )
		{
			theClass	= long.class;
		}
		
		return( theClass );
	}
	
	/**
		Convert a single Statistic to MBeanAttributeInfo.
		
		@return a Map, keyed by the Attribute name, value of MBeanAttributeInfo
	 */
		private Map<String,MBeanAttributeInfo>
	statisticToMBeanAttributeInfos( final Statistic s )
	{
		final Map<String,Object>	src	= new MapStatisticImpl( s ).asMap();
		
		final String	statisticName	= s.getName();
		
		final Map<String,MBeanAttributeInfo>	result	= new HashMap<String,MBeanAttributeInfo>();
		for( final String fieldName : src.keySet() )
		{
			final Object	value	= src.get( fieldName );
			// if the value is null, always make it a String
			final String	type	= determineFieldType( fieldName, value ).getName();
			
			final String	attributeName	= makeAttributeName( statisticName, fieldName );
			
			final MBeanAttributeInfo	attributeInfo	= new MBeanAttributeInfo( attributeName, type, "",
												true, false, false );
			result.put( attributeName, attributeInfo );
		}
		return( result );
	}
	
	/**
		Create MBeanInfo by taking default MBeanInfo and adding Attributes to it corresponding
		to all available Statistics.
	 */
		private synchronized MBeanInfo
	createMBeanInfo()
	{
		final MBeanInfo	baseMBeanInfo	= super.getMBeanInfo();
		
		MBeanInfo	mbeanInfo	= baseMBeanInfo;
		
		if ( getMBeanServer() != null && getDelegate() != null )
		{
			assert( getDelegate() != null ) : "null delegate for: " + getObjectName();
			
			final Map<String,MBeanAttributeInfo>
			    newAttrs	= new HashMap<String,MBeanAttributeInfo>();
			
			final Statistic[]	statistics	= getStats().getStatistics();
			for( int i = 0; i < statistics.length; ++i )
			{
				final Map<String,MBeanAttributeInfo> attrInfos	=
				    statisticToMBeanAttributeInfos( statistics[ i ] );
				
				newAttrs.putAll( attrInfos );
			}
			
			final MBeanAttributeInfo[]	dynamicAttrInfos	=
				new MBeanAttributeInfo[ newAttrs.keySet().size() ];
			newAttrs.values().toArray( dynamicAttrInfos );
			
			final MBeanAttributeInfo[]	attrInfos	=
				JMXUtil.mergeMBeanAttributeInfos( dynamicAttrInfos, baseMBeanInfo.getAttributes() );
			
			mbeanInfo	= JMXUtil.newMBeanInfo( baseMBeanInfo, attrInfos );
		}
		
		return( mbeanInfo );
	}
	
		protected ObjectName
	preRegisterHook( final ObjectName   objectName )
	{
		initFieldNameMapper();
		initStatisticNameMapper();
		
		// ensure that it gets generated anew now that we are registered and can access everything
		refresh();
		
		assert( MonitoringStats.class.isAssignableFrom( getInterface() )  ) :
			"MBean extends MonitoringStatsImpl but does not have MonitoringStats interface: " + getObjectName();
		
		if ( BUG_STATISTIC_NAMES )
		{
			checkUnderlyingMBean();
		}
		
		mStatisticNames	= GSetUtil.newUnmodifiableStringSet( initStatisticNames() );
	    
	    return objectName;
	}
	
	private static final MBeanInfo	EMPTY_MBEAN_INFO	=
		new MBeanInfo( "Empty", "Failed to create MBeanInfo", null, null, null, null);
		
		public synchronized MBeanInfo
	getMBeanInfo()
	{
		if ( mMBeanInfo == null )
		{
			try
			{
				mMBeanInfo	= createMBeanInfo();
			}
			catch( Throwable t )
			{
				// if we throw an exception, it will kill the MBeanServer's ability
				// to function
				final Throwable rootCause	= ExceptionUtil.getRootCause( t );
				
				// when an old mbean gets unregistered, this object does too,
				// but the MBeanServer calls getMBeanInfo() before unregistering us,
				// putting this MBean in the awkward position of supplying MBeanInfo
				// from a delegate that has disappeared.
				if ( ! ( rootCause instanceof InstanceNotFoundException) )
				{
					getMBeanLogger().warning( "can't create MBeanInfo for: " + getObjectName() +
						"\n" + rootCause.getClass() + ": " + rootCause.getMessage() + ":\n" +
						ExceptionUtil.getStackTrace( rootCause ) );
				}
				
				mMBeanInfo	= EMPTY_MBEAN_INFO;
			}
		}
		
		return( mMBeanInfo );
	}
}