/*
* 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.
*/
/*
* $Header: /cvs/glassfish/admin/mbeanapi-impl/src/java/com/sun/enterprise/management/support/AMXImplBase.java,v 1.23 2007/05/05 05:23:40 tcfujii Exp $
* $Revision: 1.23 $
* $Date: 2007/05/05 05:23:40 $
*/
package com.sun.enterprise.management.support;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.Collections;
import java.util.Iterator;
import java.util.Properties;
import java.util.HashSet;
import java.util.logging.Logger;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.io.IOException;
import javax.management.*;
import com.sun.appserv.management.util.stringifier.SmartStringifier;
import com.sun.appserv.management.util.stringifier.ArrayStringifier;
import com.sun.appserv.management.base.AMX;
import com.sun.appserv.management.base.AMXDebug;
import com.sun.appserv.management.base.AMXAttributes;
import com.sun.appserv.management.base.Extra;
import com.sun.appserv.management.base.Container;
import com.sun.appserv.management.base.Singleton;
import com.sun.appserv.management.base.Utility;
import com.sun.appserv.management.base.QueryMgr;
import com.sun.appserv.management.base.XTypes;
import com.sun.appserv.management.base.XTypesMapper;
import com.sun.appserv.management.base.AllTypesMapper;
import com.sun.appserv.management.base.AMXLoggerBase;
import com.sun.appserv.management.config.NamedConfigElement;
import com.sun.appserv.management.base.QueryMgr;
import com.sun.appserv.management.DomainRoot;
import com.sun.appserv.management.base.Util;
import com.sun.appserv.management.client.ConnectionSource;
import com.sun.appserv.management.util.jmx.MBeanServerConnectionSource;
import com.sun.enterprise.management.support.AMXAttributeNameMapper;
import com.sun.appserv.management.util.jmx.Acronyms;
import com.sun.appserv.management.util.jmx.JMXUtil;
import com.sun.appserv.management.util.misc.ClassUtil;
import com.sun.appserv.management.util.misc.ExceptionUtil;
import com.sun.appserv.management.util.misc.GSetUtil;
import com.sun.appserv.management.util.misc.ThrowableMapper;
import com.sun.appserv.management.util.misc.StringUtil;
import com.sun.appserv.management.util.misc.CollectionUtil;
import com.sun.enterprise.management.support.ObjectNames;
import com.sun.enterprise.management.support.oldconfig.OldConfigProxies;
import com.sun.enterprise.management.support.TypeInfo;
import com.sun.enterprise.management.support.TypeInfos;
import com.sun.appserv.management.base.AMX;
import com.sun.appserv.management.base.Container;
import com.sun.appserv.management.client.ProxyFactory;
import com.sun.appserv.management.util.jmx.NotificationBuilder;
import com.sun.appserv.management.util.jmx.AttributeChangeNotificationBuilder;
import com.sun.appserv.management.util.jmx.stringifier.MBeanInfoStringifier;
import com.sun.appserv.management.util.misc.MapUtil;
import com.sun.appserv.management.config.AMXConfig;
import com.sun.appserv.management.config.PropertiesAccess;
import com.sun.appserv.management.j2ee.J2EETypes;
import com.sun.logging.LogDomains;
/**
Base class from which all AMX MBeans should derive (but not "must").
<p>
Note that even though this base class implements a number of interfaces,
the actual MBean interface supplied by the subclass construction-time
determines which of these is actually exposed in the MBeanInfo.
<p>
A subclass should generally <b>not</b> implement get/setAttribute(s) as these
calls are processed in this base class--
<p>
If a subclass implements a getter or setter Method it will be invoked automatically.
If there is no getter or setter Method, then the getAttributeManually() or
setAttributeManually() methods will be invoked; the subclass should implement
these methods instead.
<p>
Method invocation is also handled automatically. If a Method cannot be found,
the invokeManually() method is called; the subclass should implement this method.
<p>
Note that various optimizations are possible, but not implemented. These include
caching Methods for each Attribute and for operations as well. Careful testing
should be done before complicating the code with such optimizations.
*/
public class AMXImplBase extends MBeanImplBase
implements DynamicMBean, MBeanRegistration,
AMX,
NotificationEmitter, DelegateOwner
{
protected static final String GET = "get";
protected static final String SET = "set";
/**
The interface this MBean implements
*/
private final Class mInterface;
/**
The MBeanInfo
*/
private final MBeanInfo mMBeanInterfaceMBeanInfo;
/**
The parent MBean for this MBean.
No need to make volatile; a recompute is OK if one thread doesn't yet see it.
*/
private ObjectName mCachedContainerObjectName;
/**
Flag to enable or disable whether AttributeChangeNotifications are
emitted.
*/
private final boolean mEmitAttributeChangeNotifications;
private volatile QueryMgr mQueryMgr;
private volatile AMX mSelfProxy;
private ConnectionSource mConnectionSource;
/**
An optional Delegate
*/
private final Delegate mSuppliedDelegate;
private volatile Delegate mDelegate;
private AMXAttributeNameMapper mAttributeNameMapper = null;
private Map<String,MBeanAttributeInfo> mAttributeInfos;
private String mFullType;
private final String mJ2EEType;
protected CoverageInfo mCoverage;
/**
Maps a j2eeType to a Map<String,Class> which maps an Attribute name to a Class.
*/
private static final Map<String,Map<String,Class>> ATTRIBUTE_CLASSES =
Collections.synchronizedMap( new HashMap<String,Map<String,Class>>() );
protected Class
getInterface( final String j2eeType )
{
return( AllTypesMapper.getInstance().getInterfaceForType( j2eeType ) );
}
protected String
deduceJ2EEType( final Class c)
{
// it may extend the interface in which case its type is readily available
String j2eeType = (String)ClassUtil.getFieldValue( c, "J2EE_TYPE" );
if ( j2eeType == null )
{
final String strippedName = StringUtil.stripSuffix(
ClassUtil.stripPackageName( c.getName() ), "Impl" );
if ( c.getPackage().getName().endsWith( ".j2ee" ) )
{
// j2ee standard type
j2eeType = strippedName;
}
else
{
j2eeType = "X-" + strippedName;
}
}
return( j2eeType );
}
protected
AMXImplBase( String j2eeType, Delegate delegate )
{
this( j2eeType, null, delegate );
}
protected
AMXImplBase()
{
this( null, null, null );
}
protected
AMXImplBase( final Delegate delegate )
{
this( null, null, delegate );
}
protected boolean
coverageActive()
{
return ! (mCoverage instanceof CoverageInfoDummy);
}
public final CoverageInfo
getCoverageInfo()
{
mCoverage.setMBeanInfo( getMBeanInfo() );
return mCoverage;
}
public final void
clearCoverageInfo()
{
mCoverage.clear();
}
protected CoverageInfo
createCoverageInfo( final boolean enable )
{
final CoverageInfo info = enable ?
new CoverageInfoImpl( getMBeanInfo() ) : new CoverageInfoDummy();
clearCoverageInfo();
return info;
}
public boolean
enableCoverageInfo( final boolean enable )
{
final boolean wasEnabled = ! ( mCoverage instanceof CoverageInfoDummy);
if ( enable != wasEnabled )
{
mCoverage = createCoverageInfo( enable );
}
return wasEnabled;
}
private synchronized MBeanInfo
getInterfaceMBeanInfo(
final Class theInterface )
{
MBeanInfo info = TypeInfos.getInstance().getMBeanInfoForInterface( theInterface );
if ( false || getAMXDebug() )
{
debug( "Interface " + mInterface.getName() +
" has MBeanInfo:\n" +
MBeanInfoStringifier.DEFAULT.stringify( info ) );
info = addDebugMBeanInfo( info );
}
return info;
}
/**
Construct a new implementation that implements the supplied mbeanInterface.
@param j2eeTypeIn (may be null) the j2eeType of this instance
@param theInterface (may be null) the public interface as seen on the client
@param delegate an MBean to which unknown requests are delegated
*/
private
AMXImplBase(
final String j2eeTypeIn,
final Class theInterface,
final Delegate delegate )
{
super();
mCoverage = new CoverageInfoDummy(); // will change below...
if ( delegate != null )
{
delegate.setOwner( this );
}
mJ2EEType = j2eeTypeIn == null ?
deduceJ2EEType( getClass() ): j2eeTypeIn;
if ( getAMXDebug() && j2eeTypeIn != null )
{
final String deducedJ2EEType = deduceJ2EEType( getClass() );
if ( ! deducedJ2EEType.equals( mJ2EEType ) )
{
debug( "warning: deducedj2eeType " + deducedJ2EEType +
" does not match: " + j2eeTypeIn );
}
}
mInterface = theInterface == null ? getInterface( mJ2EEType ) : theInterface;
if ( mInterface == null )
{
throw new Error( "AMXImplBase: can't get interface for j2eeType " + j2eeTypeIn );
}
mCachedContainerObjectName = null;
mEmitAttributeChangeNotifications = true;
mQueryMgr = null;
mSelfProxy = null;
mSuppliedDelegate = delegate;
if ( mSuppliedDelegate instanceof DelegateBase )
{
((DelegateBase)mSuppliedDelegate).setDebugOutput( getDebugOutput() );
}
ATTRIBUTE_CLASSES.put( mJ2EEType,
Collections.synchronizedMap( new HashMap<String,Class>() ) );
// initialization of mDelegate is deferred until later; the supplied delegate
// may not be in a position to run yet
mDelegate = null;
mAttributeNameMapper = null;
mAttributeInfos = null;
mFullType = null;
mMBeanInterfaceMBeanInfo = getInterfaceMBeanInfo( mInterface );
}
public void
delegateFailed( final Throwable t )
{
// the delegate has fatally failed
}
protected String
getDebugID()
{
return ClassUtil.stripPackageName( this.getClass().getName() );
}
protected MBeanInfo
getMBeanInfoFromInterface()
{
return( mMBeanInterfaceMBeanInfo );
}
/**
Almost all MBeans don't change their MBeanInfo once it's created, making it
possible to cache it on the client side.
*/
public boolean
getMBeanInfoIsInvariant()
{
return( true );
}
protected MBeanInfo
removeUnsupportedMBeanInfo( final MBeanInfo info )
{
return( info );
}
/**
Hook for subclass to modify anything in MBeanInfo.
*/
protected MBeanInfo
modifyMBeanInfo( final MBeanInfo info )
{
return( info );
}
/**
Verify that the Delegate's interface is consistent with the MBeanInfo
for this MBean. Called by unit test code when AMX-DEBUG is enabled.
*/
public String
checkInterfaceAgainstDelegate()
{
final Delegate delegate = getDelegate();
if ( ! ( getAMXDebug() && delegate != null) )
{
return null;
}
final String NEWLINE = System.getProperty( "line.separator" );
final String result = null;
final MBeanInfo amxMBeanInfo = getMBeanInfo();
final Map<String,MBeanAttributeInfo> amxAttributeInfos =
JMXUtil.attributeInfosToMap( amxMBeanInfo.getAttributes() );
// check that every Attribute in the Delegate exists in AMX
final MBeanInfo delegateMBeanInfo = delegate.getMBeanInfo();
final MBeanAttributeInfo[] delegateAttributeInfos = delegateMBeanInfo.getAttributes();
//debug( "Delegate MBeanInfo: " + MBeanInfoStringifier.DEFAULT.stringify( delegateMBeanInfo ) );
final StringBuilder builder = new StringBuilder();
for( final MBeanAttributeInfo delegateAttrInfo : delegateAttributeInfos )
{
final String delegateAttrName = delegateAttrInfo.getName();
assert( delegateAttrName != null ) :
"delegate for " + getJ2EEType() + " supplied an MBeanAttributeInfo with a null name";
try
{
final Object value = delegateGetAttribute( delegateAttrName );
// OK, we got the value. Does AMX have it?
if ( ! amxAttributeInfos.containsKey( delegateAttrName ) )
{
builder.append( "Attribute " + quote( delegateAttrName ) +
" exists in Delegate, but not in AMX MBeanInfo" + NEWLINE );
}
}
catch( Exception e )
{
builder.append( "Getting Attribute " +
quote( delegateAttrName ) +
" threw Exception: " + e + NEWLINE );
}
}
// return empty string if nothing found
return builder.length() == 0 ?
"" : (getObjectName() + NEWLINE + builder.toString());
}
/**
Subclass may override, but should call super.addDebugMBeanInfo().
Add any additional debugging stuff to the MBeanInfo.
*/
protected MBeanInfo
addDebugMBeanInfo( final MBeanInfo origInfo )
{
final MBeanInfo debugInfo =
MBeanInfoConverter.getInstance().convert( AMXDebugStuff.class, null );
return JMXUtil.mergeMBeanInfos( origInfo, debugInfo );
}
/**
By default, the MBeanInfo is derived once from the MBean interface.
Then certain items are removed, and optional debugging is added.
This method should not cache the MBeanInfo because it may change
dynamically. The client-side proxies cache the MBeanInfo if
getMBeanInfoIsInvariant() returns true.
*/
public MBeanInfo
getMBeanInfo()
{
MBeanInfo mbeanInfo = null;
try
{
mbeanInfo = getMBeanInfoFromInterface();
final MBeanNotificationInfo[] notifs = getNotificationInfo();
if ( notifs != null && notifs.length != 0 )
{
mbeanInfo = JMXUtil.addNotificationInfos( mbeanInfo, notifs );
}
}
catch( Exception e )
{
e.printStackTrace();
throw new RuntimeException( e );
}
mbeanInfo = removeUnsupportedMBeanInfo( mbeanInfo );
mbeanInfo = modifyMBeanInfo( mbeanInfo );
return( mbeanInfo );
}
protected final boolean
shouldEmitNotifications()
{
return( mEmitAttributeChangeNotifications && getListenerCount() != 0 );
}
public Delegate
getDelegate()
{
return( mDelegate );
}
protected void
setDelegate( final Delegate delegate )
{
mDelegate = delegate;
}
protected boolean
haveDelegate()
{
return( getDelegate() != null );
}
protected Object
getDelegateProxy( final Class theInterface )
{
return( DelegateInvocationHandler.newProxyInstance( getDelegate(), theInterface ) );
}
public OldConfigProxies
getOldConfigProxies()
{
return( OldConfigProxies.getInstance( getMBeanServer() ) );
}
protected AMXAttributeNameMapper
getAttributeNameMapper()
{
return( mAttributeNameMapper );
}
private static final MBeanNotificationInfo[] EMPTY_NOTIFICATIONS = new MBeanNotificationInfo[ 0 ];
/**
Defined by NotificationBroadcaster.
@return an empty array
Subclass may wish to override this.
*/
public MBeanNotificationInfo[]
getNotificationInfo()
{
return( EMPTY_NOTIFICATIONS );
}
/**
Get the value of a property within this MBean's ObjectName.
@return the value of the specified property, or null if not found.
*/
protected String
getKeyProperty( final String key )
{
return( getObjectName().getKeyProperty( key ) );
}
public ProxyFactory
getProxyFactory()
{
assert( mConnectionSource != null );
return( ProxyFactory.getInstance( mConnectionSource, true ) );
}
public <T extends AMX> T
getProxy( final ObjectName objectName, final Class<T> theClass)
{
return getProxyFactory().getProxy( objectName, theClass );
}
protected boolean
shouldOmitObjectNameForDebug()
{
return super.shouldOmitObjectNameForDebug() ||
getObjectName().getKeyProperty( "name" ).equals( AMX.NO_NAME );
}
protected static boolean
isSingletonMBean( final Class mbeanInterface )
{
return( Singleton.class.isAssignableFrom( mbeanInterface ) );
}
protected static boolean
isUtilityMBean( final Class mbeanInterface )
{
return( Utility.class.isAssignableFrom( mbeanInterface ) );
}
protected static boolean
hasElementName( final Class mbeanInterface )
{
return( NamedConfigElement.class.isAssignableFrom( mbeanInterface ) );
}
public Container
getFactoryContainer()
{
return( Container.class.cast( getSelf() ) );
}
public final ObjectName
getContainerObjectName( )
{
return( getContainerObjectName( getObjectName() ) );
}
public final Container
getContainer( )
{
final ObjectName objectName = getContainerObjectName( getObjectName() );
return( getProxyFactory().getProxy( objectName, Container.class) );
}
protected ObjectNames
getObjectNames()
{
return ObjectNames.getInstance( getJMXDomain() );
}
protected synchronized ObjectName
getContainerObjectName( final ObjectName selfObjectName )
{
if ( mCachedContainerObjectName == null &&
! getSelfJ2EEType().equals( XTypes.DOMAIN_ROOT ) )
{
try
{
mCachedContainerObjectName =
getObjectNames().getContainerObjectName( getMBeanServer(), selfObjectName );
}
catch( InstanceNotFoundException e )
{
debug( ExceptionUtil.getStackTrace( e ) );
throw new RuntimeException( e );
}
}
return( mCachedContainerObjectName );
}
/**
Use the ObjectName with which this MBean was registered in combination with
its j2eeType and its parent keys to determine the ObjectName <i>pattern</i>
that uniquely identifies it.
*/
public final ObjectName
getObjectNamePattern()
{
final ObjectName selfObjectName = getObjectName();
final Set<String> requiredKeys = Util.getPatternKeys( getFullType() );
final String requiredProps = JMXUtil.getProps( selfObjectName, requiredKeys, true );
final ObjectName pat = Util.newObjectNamePattern( selfObjectName.getDomain(), requiredProps );
return( pat );
}
public final Class
getInterface()
{
return( mInterface );
}
public final String
getInterfaceName()
{
return( getInterface().getName() );
}
public final String[]
getAttributeNames()
{
return( GSetUtil.toStringArray( getAttributeInfos().keySet() ) );
}
/**
An operation has not been implemented. Deal with appropriately.
*/
protected final void
unimplementedOperation( final String operation )
{
final String msg = "UNIMPLEMENTED OPERATION: " + operation + " in " + getObjectName();
logInfo( msg );
throw new UnsupportedOperationException( operation );
}
/**
An Attribute has not been implemented.
*/
protected final Object
unimplementedAttribute( final String attrName )
{
final String msg = "UNIMPLEMENTED ATTRIBUTE: " + attrName + " in " + getObjectName();
logInfo( msg );
return( null );
}
/**
The impossible has happened.
*/
protected final void
impossible( final Throwable t )
{
logSevere( "AMXImplBase.impossible: " + t.getMessage() );
assert( false );
throw new RuntimeException( t );
}
private Object
convertToClass(
final Object value,
final Class theClass )
throws Exception
{
Object result = value;
if ( value instanceof String )
{
result = ClassUtil.InstantiateFromString( theClass, (String)value );
}
else
{
getMBeanLogger().info( "convertToClass: don't know how to convert: " +
value.getClass().getName() );
}
return( result );
}
/**
Called every time an Attribute is obtained via delegateGetAttribute(), so
make sure it's reasonably fast.
*/
private Class<?>
getAttributeClass( final String attributeName )
throws ClassNotFoundException
{
final Map<String,Class> mappings = ATTRIBUTE_CLASSES.get( getJ2EEType() );
Class theClass = mappings.get( attributeName );
// initialize only if the class is null and there isn't a mapping for to null
if ( theClass == null && ! mappings.containsKey( attributeName ) )
{
// no need to synchronize; the Map is already so.
// And if mappings were somehow 'put' twice, that's rare and of no importance
final MBeanAttributeInfo[] infos = getMBeanInfo().getAttributes();
// Map each Attribute to a Class
for( int i = 0; i < infos.length; ++i )
{
final String attrName = infos[ i ].getName();
final Class c = ClassUtil.getClassFromName( infos[ i ].getType() );
mappings.put( attrName, c );
}
theClass = mappings.get( attributeName );
}
return( theClass );
}
/**
*/
protected Object
delegateGetAttribute( final String name )
throws Exception
{
assert( name != null );
final Delegate delegate = getDelegate();
assert( delegate != null );
final Object value = delegate.getAttribute( name );
Object result = value;
if ( value != null )
{
Class<?> attrClass = getAttributeClass( name );
if ( attrClass != null )
{
if ( ClassUtil.IsPrimitiveClass( attrClass ) )
{
attrClass = ClassUtil.PrimitiveClassToObjectClass( attrClass );
}
if ( ! attrClass.isAssignableFrom( value.getClass() ) )
{
try
{
result = convertToClass( value, attrClass );
}
catch( Exception e )
{
// OK, there are a few exceptions
result = value;
}
}
}
else
{
getMBeanLogger().warning( "AMXImplBase.delegateGetAttribute: " +
"Can't find class for attribute: " + name + "=" + value +
" in object " + getObjectName() );
// add a null mapping
ATTRIBUTE_CLASSES.get( getJ2EEType() ).put( name, null );
}
}
return( result );
}
protected Object
delegateGetAttributeNoThrow( String name )
{
try
{
final Object value = delegateGetAttribute( name );
return( value );
}
catch( Exception e )
{
throw new RuntimeException( e );
}
}
protected void
delegateSetAttribute(
final String name,
final Object value )
throws AttributeNotFoundException, InvalidAttributeValueException
{
getDelegate().setAttribute( new Attribute( name, value ) );
}
protected void
delegateSetAttributeNoThrow( String name, Object value )
{
try
{
delegateSetAttribute( name, value );
}
catch( JMException e )
{
debug( ExceptionUtil.toString( e ) );
throw new RuntimeException( e );
}
catch( RuntimeException ee )
{
debug( ExceptionUtil.toString( ee ) );
throw ee;
}
}
protected Object
getAttributeNoThrow( String name )
{
Object result = null;
try
{
result = getAttribute( name );
}
catch( Exception e )
{
throw new RuntimeException( new ThrowableMapper( e ).map() );
}
return( result );
}
protected synchronized Map<String,MBeanAttributeInfo>
getAttributeInfos()
{
if ( mAttributeInfos == null || ! getMBeanInfoIsInvariant() )
{
mAttributeInfos = JMXUtil.attributeInfosToMap( getMBeanInfo().getAttributes() );
}
return( mAttributeInfos );
}
/**
Subclasses may need to force a refresh.
*/
protected void
clearAttributeInfos()
{
mAttributeInfos = null;
}
protected boolean
isLegalAttribute( final String name )
{
return( getAttributeInfos().keySet().contains( name ) );
}
protected MBeanAttributeInfo
getAttributeInfo( final String name )
{
return( (MBeanAttributeInfo)getAttributeInfos().get( name ) );
}
protected boolean
isReadOnlyAttribute( final String name )
{
return( ! getAttributeInfo( name ).isWritable() );
}
public Logger
getLogger()
{
return( getMBeanLogger() );
}
protected void
checkAttributeSupportedInBuild( final String attributeName )
{
}
protected void
checkOperationSupportedInBuild(
final String operationName,
Object[] args,
String[] types)
{
}
/**
Get an Attribute value, first by looking for a getter method
of the correct name and signature, then by looking for a delegate,
and finally by calling getAttributeManually(), which a subclass
is expected to override.
@param name name of the Attribute
@return value of the Attribute
*/
public Object
getAttribute( final String name )
throws AttributeNotFoundException
{
mCoverage.attributeWasRead( name );
Object result = null;
checkAttributeSupportedInBuild( name );
if ( ! isLegalAttribute( name ) )
{
mCoverage.attributeGetFailure( name );
debug( "getAttribute: unknown Attribute " + name + ", legal Attributes are: " +
toString( getAttributeInfos().keySet() ) );
throw new AttributeNotFoundException( name );
}
try
{
result = getAttributeInternal( name );
}
catch( AttributeNotFoundException e)
{
mCoverage.attributeGetFailure( name );
throw e;
}
catch( Exception e )
{
mCoverage.attributeGetFailure( name );
throw new AttributeNotFoundException( name );
}
return( result );
}
protected Object
getAttributeInternal( String name )
throws AttributeNotFoundException,
ReflectionException, MBeanException
{
Object result = null;
boolean handleManually = false;
// see if a getter exists
final Method m = findGetter( name );
if ( m != null )
{
result = getAttributeByMethod( name, m );
debug( "getAttribute: " + name + " CALLED GETTER: " + m + " = " + result);
handleManually = false;
}
else if ( haveDelegate() )
{
trace( "getAttribute: " + name + " HAVE DELEGATE " );
if ( getDelegate().supportsAttribute( name ) )
{
trace( "getAttribute: " + name + " CALLING DELEGATE " );
try
{
result = delegateGetAttribute( name );
}
catch( Exception e )
{
trace( "getAttribute: DELEGATE claims support, but fails: " + name );
handleManually = true;
}
}
else
{
trace( "getAttribute: " + name + " DELEGATE DOES NOT SUPPORT " );
handleManually = true;
}
}
else
{
handleManually = true;
}
if ( handleManually )
{
trace( "getAttribute: handle manually: " + name );
try
{
result = getAttributeManually( name );
}
catch( AttributeNotFoundException e )
{
trace( "getAttribute: " + name + " NOT FOUND " );
throw e;
}
}
return( result );
}
/**
Bulk get. Note that is is important for this implementation to
call getAttribute() for each name so that each may be processed
appropriately; some Attributes may be in this MBean itself, and some
may reside in a {@link Delegate}.
@param names array of Attribute names
@return AttributeList of Attributes successfully fetched
*/
public AttributeList
getAttributes( String[] names )
{
trace( "AMXImplBase.getAttributes: " + SmartStringifier.toString( names ) );
//trace( "AMXImplBase.getAttributes: delegate class = " + getDelegate().getClass().getName() );
final AttributeList attrs = new AttributeList();
for( int i = 0; i < names.length; ++i )
{
try
{
trace( "%%% calling getAttribute: " + names[ i ] + " on " + getObjectName() );
final Object value = getAttribute( names[ i ] );
attrs.add( new Attribute( names[ i ], value ) );
}
catch( Exception e )
{
trace( "### AttributeNotFoundException: " + names[ i ] );
// ignore, as per spec
}
}
return( attrs );
}
private final void
rethrowAttributeNotFound(
final Throwable t,
final String attrName )
throws AttributeNotFoundException
{
final Throwable rootCause = ExceptionUtil.getRootCause( t );
if ( rootCause instanceof AttributeNotFoundException )
{
throw (AttributeNotFoundException)rootCause;
}
final String msg = "Attribute not found: " + StringUtil.quote(attrName) + " [" + rootCause.getMessage() + "]";;
throw new AttributeNotFoundException( msg );
}
/**
Set an Attribute by invoking the supplied method.
*/
protected Object
getAttributeByMethod( final String attrName, final Method m)
throws AttributeNotFoundException
{
Object result = null;
try
{
//trace( "getAttributeByMethod: " + attrName );
result = m.invoke( this, (Object[])null );
}
catch( InvocationTargetException e )
{
trace( "InvocationTargetException: " + attrName + " by " + m );
rethrowAttributeNotFound( e, attrName );
}
catch( IllegalAccessException e )
{
trace( "ILLEGAL ACCESS TO: " + attrName + " by " + m );
rethrowAttributeNotFound( e, attrName );
}
catch( Exception e )
{
trace( "Exception: " + attrName + " by " + m );
rethrowAttributeNotFound( e, attrName );
}
return( result );
}
protected void
setAttributeByMethod( final Attribute attr, final Method m)
throws AttributeNotFoundException, InvalidAttributeValueException
{
try
{
// trace( "setAttributeByMethod: " + m );
m.invoke( this, new Object[] { attr.getValue() } );
}
catch( InvocationTargetException e )
{
trace( "setAttributeByMethod: InvocationTargetException: " + e );
final Throwable t = ExceptionUtil.getRootCause( e );
if ( t instanceof InvalidAttributeValueException)
{
throw (InvalidAttributeValueException)t;
}
rethrowAttributeNotFound( e, attr.getName() );
}
catch( IllegalAccessException e )
{
trace( "setAttributeByMethod: IllegalAccessException: " + e );
rethrowAttributeNotFound( e, attr.getName() );
}
catch( Exception e )
{
trace( "setAttributeByMethod: Exception: " + e );
rethrowAttributeNotFound( e, attr.getName() );
}
}
/**
Subclasses should override this to handle getAttribute( attrName ). It will
be called if no appropriate getter is found.
<p>
It generically handles all <Abc>ObjectName() and <Abc>ObjectNameMap
Attributes.
*/
protected Object
getAttributeManually( final String attributeName )
throws AttributeNotFoundException
{
Object result = null;
AMXDebug.getInstance().getOutput( "getAttributeManually" ).println(
attributeName + " on " + getObjectName() );
if ( isObjectNameAttribute( attributeName ) )
{
final String j2eeType = attributeNameToJ2EEType( attributeName );
debug( "getAttributeManually: attributeName " + attributeName + " => j2eeType " + j2eeType );
result = getContaineeObjectName( j2eeType );
}
else if ( isObjectNameMapAttribute( attributeName ) )
{
final String j2eeType = attributeNameToJ2EEType( attributeName );
debug( "invokeManually: attributeName " + attributeName + " => j2eeType " + j2eeType );
result = getContaineeObjectNameMap( j2eeType );
}
else
{
throw new AttributeNotFoundException( attributeName );
}
return result;
}
/**
Subclasses should override this to handle setAttribute( attr ). It will
be called if no appropriate setter is found.
*/
protected void
setAttributeManually( final Attribute attr )
throws AttributeNotFoundException, InvalidAttributeValueException
{
throw new AttributeNotFoundException( attr.getName() );
}
public void
setAttribute( final Attribute attr )
throws AttributeNotFoundException, InvalidAttributeValueException
{
final String name = attr.getName();
mCoverage.attributeWasWritten( name );
if ( isReadOnlyAttribute( name ) )
{
mCoverage.attributeSetFailure( name );
throw new IllegalArgumentException( "Attribute is read-only: " + attr.getName() );
}
boolean failure = true;
try
{
setAttributeInternal( attr );
failure = false;
}
catch( AttributeNotFoundException e )
{
throw e;
}
catch( InvalidAttributeValueException e )
{
throw e;
}
catch( RuntimeException e )
{
throw (RuntimeException)e;
}
catch( Exception e )
{
throw new RuntimeException( e );
}
finally
{
if ( failure )
{
mCoverage.attributeSetFailure( name );
}
}
}
/**
Set an Attribute value, first by looking for a setter method
of the correct name and signature, then by looking for a delegate,
and finally by calling setAttributeManually(), which a subclass
is expected to override.
@param attr the Attribute
*/
protected void
setAttributeInternal( final Attribute attr )
throws AttributeNotFoundException, InvalidAttributeValueException,
ReflectionException, MBeanException
{
trace( "setAttribute: " + attr.getName() + " = " + attr.getValue() );
boolean handleManually = false;
final Method m = findSetter( attr );
final boolean shouldEmitNotifications = shouldEmitNotifications();
// note that this will fail if an Attribute is write-only
final Object oldValue = shouldEmitNotifications ?
getAttribute( attr.getName() ) : null;
if ( m != null )
{
setAttributeByMethod( attr, m );
}
else if ( haveDelegate() )
{
if ( getDelegate().supportsAttribute( attr.getName() ) )
{
try
{
getDelegate().setAttribute( attr );
}
catch( JMException e )
{
handleManually = true;
}
}
else
{
handleManually = true;
}
}
else
{
handleManually = true;
}
if ( handleManually )
{
setAttributeManually( attr );
}
if ( shouldEmitNotifications )
{
final String attrType = getAttributeType( attr.getName() );
sendAttributeChangeNotification( "", attrType, oldValue, attr );
}
}
protected String
getAttributeType( final String attrName )
{
final MBeanAttributeInfo info =
JMXUtil.getMBeanAttributeInfo( getMBeanInfo(), attrName );
return( info.getType() );
}
protected synchronized void
sendAttributeChangeNotification(
final String msg,
final String attrType,
final Object oldValue,
final Attribute newAttr)
{
final AttributeChangeNotificationBuilder builder =
(AttributeChangeNotificationBuilder)
getNotificationBuilder( AttributeChangeNotification.ATTRIBUTE_CHANGE );
final AttributeChangeNotification n =
builder.buildAttributeChange( msg, newAttr.getName(), attrType, oldValue, newAttr.getValue() );
sendNotification( n );
}
/**
Bulk get. Note that is is important for this implementation to
call setAttribute() for each name so that each may be processed
appropriately; some Attributes may be in this MBean itself, and some
may reside in a {@link Delegate}.
@param attrs attributes to be set
@return AttributeList containing Attributes successfully set
*/
public AttributeList
setAttributes( final AttributeList attrs )
{
trace( "AMXImplBase.setAttributes = " + SmartStringifier.toString( attrs ) );
final int numAttrs = attrs.size();
final AttributeList successList = new AttributeList();
for( int i = 0; i < numAttrs; ++i )
{
final Attribute attr = (Attribute)attrs.get( i );
trace( "setAttributes: " + attr.getName() );
try
{
setAttribute( attr );
successList.add( attr );
}
catch( Exception e )
{
// ignore, as per spec
}
}
return( successList );
}
/**
Find a method.
@param methodName
@param sig
@return a Method or null if not found
*/
protected final Method
findMethod( String methodName, final Class[] sig )
{
return( ClassUtil.findMethod( this.getClass(), methodName, sig ) );
}
/**
Find a getXXX() method that matches the Attribute
@param name the name to which "get" will be prepended
@return a Method or null if not found
*/
static private final Class[] GETTER_SIG = new Class[0];
protected final Method
findGetter( String name )
{
final String methodName = GET + name;
Method m = findMethod( methodName, GETTER_SIG );
if ( m == null )
{
m = findMethod( "is" + name, GETTER_SIG );
}
return( m );
}
/**
Find a setXXX() method that matches the Attribute.
@param attr an Attribute for which a matching setter should be located
@return a Method or null if not found
*/
protected final Method
findSetter( final Attribute attr )
{
final Object value = attr.getValue();
Class valueClass = null;
if ( value == null )
{
final MBeanAttributeInfo info = getAttributeInfos().get( attr.getName() );
if ( info != null )
{
try
{
valueClass = ClassUtil.getClassFromName( info.getType() );
}
catch( Exception e )
{
}
}
}
else
{
valueClass = value.getClass();
}
if ( valueClass == null )
{
return null;
}
final String methodName = SET + attr.getName();
Class[] sig = new Class[] { valueClass };
Method setter = findMethod( methodName, sig );
final Class primitiveClass = ClassUtil.ObjectClassToPrimitiveClass( valueClass );
if ( setter == null && primitiveClass != valueClass )
{
//trace( "findSetter: retrying for primitive class: " + primitiveClass );
// the Attribute value is always an object. But it may be
// that the setter takes a primitive type. So for example,
// the Attribute may contain a value of type Boolean, but the setter
// may required type boolean
sig[ 0 ] = primitiveClass;
setter = findMethod( methodName, sig );
}
return( setter );
}
protected static final String GET_PREFIX = "get";
protected static final String OBJECT_NAME_SUFFIX = "ObjectName";
protected static final String OBJECT_NAME_MAP_SUFFIX = "ObjectNameMap";
protected boolean
operationNameMatches(
final String operationName,
final String prefix,
final String suffix )
{
return operationName.startsWith( prefix ) &&
operationName.endsWith( suffix );
}
protected boolean
getterNameMatches(
final String operationName,
final String suffix )
{
return operationNameMatches( operationName, GET_PREFIX, suffix );
}
protected boolean
isObjectNameGetter(
final String operationName,
final Object[] args,
final String[] types )
{
final int numArgs = args == null ? 0 : args.length;
return numArgs == 0 && isObjectNameGetter( operationName );
}
protected boolean
isObjectNameGetter( final String operationName)
{
return getterNameMatches( operationName, OBJECT_NAME_SUFFIX );
}
private static final Set<String> NO_AUTO_GET = GSetUtil.newUnmodifiableStringSet(
"ContainerObjectName",
"MonitoringPeerObjectName",
"ObjectName",
"ConfigPeerObjectName",
"ServerObjectName" );
protected boolean
isObjectNameAttribute(final String attributeName)
{
return attributeName.endsWith( OBJECT_NAME_SUFFIX ) &&
! NO_AUTO_GET.contains( attributeName );
}
protected boolean
isObjectNameMapAttribute(final String attributeName)
{
return attributeName.endsWith( OBJECT_NAME_MAP_SUFFIX ) &&
! NO_AUTO_GET.contains( attributeName );
}
protected String
attributeNameToJ2EEType( final String attributeName )
{
String j2eeType = null;
if ( isObjectNameAttribute( attributeName ) )
{
j2eeType = StringUtil.stripSuffix( attributeName, OBJECT_NAME_SUFFIX);
}
else if ( isObjectNameMapAttribute( attributeName ) )
{
j2eeType = StringUtil.stripSuffix( attributeName, OBJECT_NAME_MAP_SUFFIX);
}
if ( ! J2EETypes.ALL_STD.contains( j2eeType ) )
{
j2eeType = XTypes.PREFIX + j2eeType;
}
return j2eeType;
}
protected boolean
isObjectNameMapGetter(
final String operationName,
final Object[] args,
final String[] types )
{
final int numArgs = args == null ? 0 : args.length;
return numArgs == 0 && isObjectNameMapGetter( operationName );
}
protected boolean
isObjectNameMapGetter( final String operationName)
{
return getterNameMatches( operationName, OBJECT_NAME_MAP_SUFFIX );
}
protected String
j2eeTypeToSimpleClassname( final String j2eeType )
{
return StringUtil.stripPrefix( j2eeType, XTypes.PREFIX );
}
protected String
operationNameToJ2EEType( final String operationName )
{
String j2eeType = null;
if ( isObjectNameGetter( operationName ) )
{
j2eeType =
StringUtil.stripPrefixAndSuffix( operationName, GET_PREFIX, OBJECT_NAME_SUFFIX);
}
else if ( isObjectNameMapGetter( operationName ) )
{
j2eeType =
StringUtil.stripPrefixAndSuffix( operationName, GET_PREFIX, OBJECT_NAME_MAP_SUFFIX);
}
if ( ! J2EETypes.ALL_STD.contains( j2eeType ) )
{
j2eeType = XTypes.PREFIX + j2eeType;
}
return j2eeType;
}
/**
An operation is being invoked manually, meaning that it is missing as a method.
invokeManually() will be called only if no appropriate Method is found.
<p>
Subclasses may override this to handle invoke(), though usually it's just
easier to write the appropriate method directly, which will be found and called
if present.
*/
protected Object
invokeManually(
String operationName,
Object[] args,
String[] types )
throws MBeanException, ReflectionException, NoSuchMethodException, AttributeNotFoundException
{
final int numArgs = args == null ? 0 : args.length;
Object result = null;
boolean handled = false;
final boolean ALLOW_GETTERS = true;
if ( ALLOW_GETTERS &&
numArgs == 0 &&
operationName.startsWith( GET ) )
{
final String attributeName = StringUtil.stripPrefix( operationName, GET );
if ( getAttributeInfos().get( attributeName ) != null )
{
result = getAttribute( attributeName );
handled = true;
}
}
else if ( operationName.equals( "toString" ) && numArgs == 0 )
{
result = toString();
}
if ( ! handled )
{
debugMethod( operationName, args );
throw new NoSuchMethodException( "no operation " + operationName +
toString( types ) + " in " + getObjectName() );
}
return result;
}
protected void
handleException( final Exception e )
throws MBeanException, ReflectionException
{
final ThrowableMapper mapper = new ThrowableMapper( e );
final Throwable mapped = mapper.map();
if ( mapped instanceof ReflectionException )
{
throw (ReflectionException)mapped;
}
else if ( mapped instanceof MBeanException )
{
throw (MBeanException)mapped;
}
else if ( ! (mapped instanceof Exception) )
{
// wrap the Throwable in an Exception
final Exception wrapper = new Exception( mapped );
throw new MBeanException( wrapper );
}
else
{
throw new MBeanException( (Exception)mapped );
}
}
protected void
handleGetAttributeException( final Exception e )
throws MBeanException, ReflectionException, AttributeNotFoundException
{
if ( e instanceof AttributeNotFoundException )
{
// AttributeNotFoundException can never contain anything non-standard
throw (AttributeNotFoundException)e;
}
else
{
handleException( e );
}
}
protected void
handleInvokeThrowable( final Exception e )
throws MBeanException, ReflectionException
{
handleException( e );
}
/**
Generic handling of invoke(). Converts the types[] to a Class[], then attempts
to locate a suitable Method. If a suitable Method is found, it is invoked.
If not found the subclass is expected to handle it in invokeManually();
*/
public final Object
invoke(
String operationName,
Object[] args,
String[] types )
throws MBeanException, ReflectionException
{
mCoverage.operationWasInvoked( operationName, types );
Object result = null;
boolean unimplemented = false;
try
{
checkOperationSupportedInBuild( operationName, args, types );
final Class[] signature = ClassUtil.signatureFromClassnames( types );
final Method m = findMethod( operationName, signature );
if ( m != null )
{
debugMethod( "invoking method: " + operationName, args );
result = m.invoke( this, args );
}
else if ( haveDelegate() &&
getDelegate().supportsOperation( operationName, args, types ) )
{
debug( "AMXImplBase.invoke: calling delegate for ", operationName );
result = getDelegate().invoke( operationName, args, types );
}
else
{
result = invokeManually( operationName, args, types );
}
}
catch( Exception e )
{
mCoverage.operationFailed( operationName, types );
debug( ExceptionUtil.toString( e ) );
handleInvokeThrowable( e );
}
return( result );
}
protected TypeInfo
getTypeInfo( final String j2eeType )
{
return( TypeInfos.getInstance().getInfo( j2eeType ) );
}
protected String
getSelfJ2EEType()
{
return( mJ2EEType );
}
protected String
getSelfName()
{
return( Util.getName( getObjectName() ) );
}
protected TypeInfo
getSelfTypeInfo()
{
return( getTypeInfo( getSelfJ2EEType() ) );
}
private boolean
isContainer()
{
return( Container.class.isAssignableFrom( getInterface() ) );
}
protected Set<String>
getChildJ2EETypes()
{
return( getSelfTypeInfo().getChildJ2EETypes() );
}
protected final static Set<String> EMPTY_STRING_SET = Collections.emptySet();
/**
Certain special cases are present (eg standalone ejb/web modules) where
a parent as described by the FullType (TypeInfos) does not exist.
In this case, the actual parent must contain these items.
*/
protected Set<String>
getFauxChildTypes()
{
return( EMPTY_STRING_SET );
}
public Set<String>
getContaineeJ2EETypes()
{
if ( ! isContainer() )
{
final Exception e =
new AttributeNotFoundException( "ContaineeJ2EETypes" );
throw new RuntimeException( e );
}
final Set<String> fauxTypes = getFauxChildTypes();
final Set<String> officialTypes = getSelfTypeInfo().getContaineeJ2EETypes();
return( fauxTypes.size() == 0 ? officialTypes : GSetUtil.newSet( fauxTypes, officialTypes ) );
}
protected String
getChildJ2EEType()
{
final Set<String> types = getChildJ2EETypes();
if ( types.size() != 1 )
{
debug( "getChildJ2EEType failing on: ", getObjectName(),
", got this many children: ", types.size() );
throw new IllegalArgumentException(
SmartStringifier.toString( types ) );
}
return( GSetUtil.getSingleton( types ) );
}
/**
Our container is the one that actually holds the MBeans we
created. Ask it for the ObjectName.
*/
protected ObjectName
getProgenyObjectName(
final String j2eeType,
final String name )
{
final Container container = getContainer();
final AMX containee = container.getContainee( j2eeType, name );
if ( containee == null )
{
throw new IllegalArgumentException( "Not containee found: " + j2eeType + "=" + name );
}
return( Util.getObjectName( containee ) );
}
protected boolean
isOfflineCapable( final TypeInfo childInfo )
{
final Class c = childInfo.getInterface();
return AMXConfig.class.isAssignableFrom( c ) ||
Utility.class.isAssignableFrom( c ) ||
c == DomainRoot.class;
}
protected boolean
getOffline()
{
return BootUtil.getInstance().getOffline();
}
/**
Register a child MBean which is a manager.
*/
protected void
registerSelfMgrChild( final TypeInfo childInfo )
throws JMException, InstantiationException, IllegalAccessException
{
final String childJ2EEType = childInfo.getJ2EEType( );
if ( ( ! getOffline() ) || isOfflineCapable( childInfo ) )
{
final Class implClass = childInfo.getImplClass();
final ObjectName childObjectName =
getObjectNames().buildContaineeObjectName( getObjectName(), getFullType(), childJ2EEType );
if ( ! getMBeanServer().isRegistered ( childObjectName ) )
{
final Object impl = implClass.newInstance();
registerMBean( impl, childObjectName );
}
}
else
{
debug( "Not loading child in offline mode: " + childJ2EEType );
}
}
protected void
unregisterSelfMgrChildren()
{
final TypeInfo selfInfo = getSelfTypeInfo();
final Set<String> childTypesSet = selfInfo.getContaineeJ2EETypes();
final String[] childTypes = GSetUtil.toStringArray( childTypesSet );
final MBeanServer mbeanServer = getMBeanServer();
for( int i = 0; i < childTypes.length; ++i )
{
final String childType = childTypes[ i ];
debug( "unregisterSelfMgrChildren: processing type: ", childType);
final TypeInfo childInfo = getTypeInfo( childType );
final Class childInterface = childInfo.getInterface();
if ( //isConfigMgrMBean( childInterface ) ||
isSingletonMBean( childInterface ) ||
isUtilityMBean( childInterface )
)
{
final ObjectName containeeObjectName =
getContaineeObjectName( childType );
if ( containeeObjectName != null )
{
try
{
mbeanServer.unregisterMBean( containeeObjectName );
debug( "unregisterSelfMgrChildren: ", containeeObjectName,
" is unregistered" );
}
catch ( InstanceNotFoundException infe )
{
logWarning( "unregisterSelfMgrChildren: " + infe.getMessage() );
}
catch ( Exception e )
{
logSevere( "unregisterSelfMgrChildren: " +
ExceptionUtil.getRootCause(e).getMessage() );
}
}
}
else
{
debug( "unregisterSelfMgrChildren: skipping: ", childInterface.getName() );
}
}
}
protected void
registerSelfMgrChildren( )
{
final TypeInfo selfInfo = getSelfTypeInfo();
final Set<String> childTypesSet = selfInfo.getContaineeJ2EETypes();
debug( "registerSelfMgrChildren for ", getSelfJ2EEType(), ": ", toString( childTypesSet ) );
if ( childTypesSet.size() != 0 )
{
debug( "registerSelfMgrChildren: child types = ", toString( childTypesSet ) );
}
else
{
debug( "no child types for: ", quote( getObjectName() ) );
}
final String[] childTypes = GSetUtil.toStringArray( childTypesSet );
for( int i = 0; i < childTypes.length; ++i )
{
final String childType = childTypes[ i ];
debug( "registerSelfMgrChildren: processing type: ", childType);
final TypeInfo childInfo = getTypeInfo( childType );
final Class childInterface = childInfo.getInterface();
if ( isSingletonMBean( childInterface ) ||
isUtilityMBean( childInterface )
)
{
try
{
registerSelfMgrChild( childInfo );
}
catch( InstantiationException e )
{
trace( "InstantiationException for child of type: " + childInfo.getJ2EEType() +
" = " + e.getMessage() );
e.printStackTrace();
final Throwable t = ExceptionUtil.getRootCause( e );
if ( t != e && t != null )
{
trace( "InstantiationException: root cause msg =" + t.getMessage() );
trace( ExceptionUtil.getStackTrace( t ) );
}
else
{
trace( ExceptionUtil.getStackTrace( ExceptionUtil.getRootCause( e ) ));
}
}
catch( Exception e )
{
trace( "Can't create child, info = " + childInfo + "\n" + e + "\n\n" );
debug( ExceptionUtil.getStackTrace( e ) );
}
}
else
{
trace( "registerSelfMgrChildren: skipping: " + childInterface.getName() );
}
}
}
/**
Register special containee MBeans.
Usually this should only be done for child MBeans
that otherwise would not come into existence.
*/
protected final void
registerSpecialContainees()
{
registerSelfMgrChildren( );
registerMisc();
}
protected void
registerMisc()
{
// nothing by default
}
protected void
preDeregisterHook()
{
unregisterMisc();
unregisterSelfMgrChildren( );
}
protected void
postDeregisterHook()
{
}
protected void
unregisterMisc()
{
// nothing by default
}
/**
Classes of MBeans should override this.
*/
public String
getGroup()
{
return( GROUP_OTHER );
}
public String
getName()
{
return( Util.getName( getObjectName() ) );
}
public String
getJ2EEType()
{
return( Util.getJ2EEType( getObjectName() ) );
}
/**
@param partialSelfObjectName the ObjectName, lacking the type property
@return the fully qualified type as required by AMX.FULL_TYPE
*/
protected static String
getFullType( final ObjectName partialSelfObjectName )
{
final String selfJ2EEType = Util.getJ2EEType( partialSelfObjectName );
final TypeInfos typeInfos = TypeInfos.getInstance();
final TypeInfo info = typeInfos.getInfo( selfJ2EEType );
final String[] chain = typeInfos.getJ2EETypeChain( partialSelfObjectName );
final String fullType = ArrayStringifier.stringify( chain, "." );
return( fullType );
}
public final String
getFullType( )
{
assert( mFullType != null ) : "******************************************************";
return( mFullType );
}
/**
O the ObjectName by adding to it:
<ul>
<li>adding AMX.FULL_TYPE_KEY property</li>
<li></li>
</ul>
*/
protected ObjectName
preRegisterModifyName(
final MBeanServer server,
final ObjectName nameIn )
{
mFullType = getFullType( nameIn );
// now ensure that certain singleton ancestors have a name
String ancestorProps = "";
final String[] fullTypeArray = Util.getTypeArray( mFullType );
for( int i = 0; i < fullTypeArray.length - 1; ++i )
{
final String key = fullTypeArray[ i ];
if ( nameIn.getKeyProperty( key ) == null )
{
final String name = ObjectNames.getSingletonName( key );
final String prop = Util.makeProp( key, name );
ancestorProps = Util.concatenateProps( ancestorProps, prop );
}
}
final String props = ancestorProps;
final String newName =
Util.concatenateProps( nameIn.toString(), props );
final ObjectName nameOut = Util.newObjectName( newName );
return( nameOut );
}
/*
Note that this method is 'synchronized'--to force visibility of all fields it affects.
Since it's called only once (per instance) for an MBean Registration, it has no performance
impact on later use, but guarantees visibility of all non-final instance variables.
*/
public final synchronized ObjectName
preRegister(
final MBeanServer server,
final ObjectName nameIn)
throws Exception
{
final ObjectName nameFromSuper = super.preRegister( server, nameIn );
mConnectionSource = new MBeanServerConnectionSource( server );
if ( mSuppliedDelegate != null )
{
mDelegate = wrapSuppliedDelegate( mSuppliedDelegate );
}
mSelfObjectName = preRegisterModifyName( server, nameFromSuper );
if ( getAMXDebug() )
{
implCheck();
}
mSelfObjectName = preRegisterHook( mSelfObjectName );
registerSpecialContainees();
preRegisterDone();
return( mSelfObjectName );
}
/**
This is an opportunity for a subclass to do initialization
and optionally to modify the ObjectName one last time.
*/
protected ObjectName
preRegisterHook( final ObjectName selfObjectName)
throws Exception
{
// subclass may do something
return selfObjectName;
}
protected void
preRegisterDone()
throws Exception
{
debug( "AMXImplBase.preRegister() done for: ", getObjectName() );
mCoverage.setMBeanInfo( getMBeanInfo() );
}
static private final Set<String> AMX_NATIVE_ATTRIBUTES =
Collections.unmodifiableSet( GSetUtil.newSet(
new String[]
{
"Name",
"ObjectName", "FullType", "Group", "J2EEType", "InterfaceName",
"MBeanEmitLogNotifications", "MBeanInfoIsInvariant", "MBeanLoggerName",
"AttributeNames", "MBeanLogLevel",
"WhatsNotDone",
"DomainRootObjectName",
"ContainerObjectName", "ContaineeJ2EETypes", "ContaineeObjectNameSet",
"NotificationInfo",
"Properties", "PropertyNames",
"SystemProperties", "SystemPropertyNames",
"OpenStats", "StatsInterfaceName", "StatisticNames", "Stats",
"ConfigProvider",
}));
/**
An optimization to not bother with all the names that are
native to AMX and not mapped to a Delegate Attribute.
*/
private Set<String>
getMyAttributeMappingCandidates()
{
final Set<String> candidates = GSetUtil.newSet( getAttributeNames() );
candidates.removeAll( AMX_NATIVE_ATTRIBUTES );
// now remove all Attributes that end appropriately
final Set<String> toRemove = new HashSet<String>();
for( final String name : candidates )
{
if ( name.endsWith( "ObjectNameMap" ) ||
name.endsWith( "ObjectNameSet" ) ||
name.endsWith( "ObjectName" ) ||
name.endsWith( "Stats" )
)
{
toRemove.add( name );
}
}
candidates.removeAll( toRemove );
return candidates;
}
protected final AMXAttributeNameMapper
createAttributeNameMapper()
{
final AMXAttributeNameMapper mapper = new AMXAttributeNameMapperImpl();
//mapper.setDebugOutput( getDebugOutput() );
final Set<String> myAttributeNames = getMyAttributeMappingCandidates();
final String[] delegateAttributeNames =
JMXUtil.getAttributeNames( mSuppliedDelegate.getMBeanInfo().getAttributes() );
mapper.matchNames(
GSetUtil.toStringArray( myAttributeNames ), delegateAttributeNames );
addCustomMappings( mapper );
if ( getAMXDebug() || false )
{
// this is stuff helpful when implementing; leave here
// so it can be enabled
final Set<String> missingAttributeNames =
GSetUtil.removeSet( myAttributeNames, mapper.getDerivedNames() );
if ( missingAttributeNames.size() != 0 )
{
//handleMissingAttributeMappings( missingAttributeNames );
}
final Set<String> missingOriginals = mapper.getUnmappedOriginals();
missingOriginals.remove( "name" ); // special case, getName() always overides
missingOriginals.remove( "Name" );
if ( missingOriginals.size() != 0 )
{
handleMissingOriginals( missingOriginals );
}
}
return mapper;
}
/**
Mapping of Attributes names is done. But some of our Attributes
don't map to anything in the Delegate.
*/
protected void
handleMissingAttributeMappings( final Set<String> missing )
{
if ( missing.size() != 0 )
{
final String msg = getJ2EEType() +
": AMX Attributes have no corresponding delegate Attribute: " +
CollectionUtil.toString( missing, ", " );
AMXDebug.getInstance().getOutput( "AMXImplBase.handleMissingAttributeMappings" ).println( msg );
logInfo( msg );
}
}
/**
Mapping of Attributes names is done. But there are still names in the
Delegate which don't map to anything in this AMX MBean. This may be OK;
some delegates contain a bunch of extra stuff not used by AMX.
*/
protected void
handleMissingOriginals( final Set<String> missingOriginals )
{
if ( missingOriginals.size() != 0 )
{
final String msg = getJ2EEType() +
": Delegate Attributes have no matching AMX Attribute: " +
CollectionUtil.toString( missingOriginals, ", " ) +
"--(this may or may not be an error; if necessary override handleMissingOriginals()";
AMXDebug.getInstance().getOutput( "AMXImplBase.handleMissingOriginals" ).println( msg );
logFine( msg );
}
}
/**
Subclasses may choose to override any Attribute names by adding custom mappings
that can't be handled algorithmically.
<p>
<b>IMPORTANT: mappings for config delegates should use the same
name as found in domain.xml, not the name found in the com.sun.appserv MBean.</b>
This is because a switchover to using ConfigAPI directly will only know
the names found in domain.xml; we don't want to maintain two sets of custom
mappings. The requisite mapping is handled internally, so even though the
com.sun.appserv MBean might advertise "Id", using the domain.xml form "id"
will work correctly.
<p>
Example:
</pre>
super.addCustomMappings( mapper );
mapper.matchName( "MyName", "YourName", "your-name" );
</pre>
*/
protected void
addCustomMappings( final AMXAttributeNameMapper mapper )
{
}
/**
Wrap the supplied delegate (if one was supplied) with a MappedDelegate
which will be created by calling createAttributeNameMapper()
*/
protected final Delegate
wrapSuppliedDelegate( final Delegate delegate )
{
mAttributeNameMapper = createAttributeNameMapper();
final MappedDelegate result = new MappedDelegate( mSuppliedDelegate, mAttributeNameMapper );
result.setDebugOutput( getDebugOutput() );
return( result );
}
protected void
implCheck()
{
final boolean isContainer = isContainer();
final String j2eeType = getSelfJ2EEType();
final TypeInfo selfInfo = TypeInfos.getInstance().getInfo( j2eeType );
final Set<String> nonChildren = selfInfo.getNonChildJ2EETypes();
final Set<String> children = selfInfo.getChildJ2EETypes();
if ( isContainer )
{
assert( nonChildren.size() != 0 || children.size() != 0 ) :
"ERROR: is Container but contains no children or containees " + j2eeType;
}
else
{
assert( nonChildren.size() == 0 ) :
"ERROR: not a Container: " + j2eeType + " but contains types: " + toString( nonChildren );
assert( children.size() == 0 ) :
"ERROR: not a Container: " + j2eeType + " but contains children: " + toString( children );
}
checkSuperfluousMethods();
}
private static final Set<String> NOT_SUPERFLUOUS =
GSetUtil.newUnmodifiableStringSet(
"getProxyFactory",
"getDomainRootObjectName",
"getQueryMgrObjectName",
"getServerRootMonitorObjectName"
);
/**
@return any non-superflous methods that are the exception to the default assumptions
*/
protected Set<String>
getNotSuperfluousMethods()
{
return NOT_SUPERFLUOUS;
}
/**
@return all method names that appear superfluous
*/
protected Set<String>
getSuperfluousMethods()
{
final Set<String> items = new HashSet<String>();
final Method[] methods = this.getClass().getMethods();
for( final Method m : methods )
{
if ( JMXUtil.isGetter( m ) )
{
final String name = m.getName();
final String attributeName = StringUtil.stripPrefix( name, GET );
if ( isObjectNameAttribute( attributeName ) ||
isObjectNameMapAttribute( attributeName ) )
{
items.add( name );
}
}
}
items.removeAll( NOT_SUPERFLUOUS );
return items;
}
protected final void
checkSuperfluousMethods()
{
final Set<String> items = getSuperfluousMethods();
items.removeAll( getNotSuperfluousMethods() );
if ( items.size() != 0 )
{
final String LINE_SEP = System.getProperty( "line.separator" );
final String msg =
"The following methods in " + getJ2EEType() +
" are probably superfluous:" + LINE_SEP +
CollectionUtil.toString( items, LINE_SEP ) + LINE_SEP;
AMXDebug.getInstance().getOutput( "AMXImplBase.checkSuperfluousMethods" ).println( msg );
logFine( msg );
}
}
/*
Note that this method is 'synchronized'--to force visibility of all fields it affects.
Since it's called only once (per instance) for an MBean Registration, it has no performance
impact on later use, but guarantees visibility of all non-final instance variables, both
on this class and all subclasses, since they can only modify things via postRegisterHook().
*/
public final synchronized void
postRegister( Boolean registrationSucceeded )
{
super.postRegister( registrationSucceeded );
postRegisterHook( registrationSucceeded );
}
public void
postRegisterHook( Boolean registrationSucceeded )
{
if ( registrationSucceeded.booleanValue() )
{
enableCoverageInfo( getAMXDebug() );
}
}
public final void
preDeregister()
throws Exception
{
super.preDeregister();
preDeregisterHook();
}
public void
postDeregister()
{
super.postDeregister();
postDeregisterHook();
}
public final ObjectName
getDomainRootObjectName()
{
return( Util.getObjectName( getDomainRoot() ) );
}
/**
The QueryMgr is a special-case; all the other types rely on it.
*/
public ObjectName
getQueryMgrObjectName()
{
ObjectName objectName = null;
if ( mQueryMgr != null )
{
// do it the fast way if we already have the proxy
objectName = Util.getObjectName( mQueryMgr );
}
else
{
final MBeanServer server = getMBeanServer();
final String domainName = getObjectName().getDomain();
objectName = QueryMgrImpl.querySingletonJ2EETypeObjectName( server,
domainName, QueryMgr.J2EE_TYPE );
}
assert( objectName != null ) : "getQueryMgrObjectName failed";
return( objectName );
}
protected ConnectionSource
getMBeanServerConnectionSource()
{
return( mConnectionSource );
}
public final LoaderMBean
getLoader()
{
return( Loader.getLoader( getMBeanServer() ) );
}
public boolean
isDAS()
{
return getLoader().isDAS();
}
protected final synchronized AMX
getSelf()
{
if ( mSelfProxy == null )
{
final ObjectName selfObjectName = getObjectName();
assert( selfObjectName != null );
mSelfProxy = getProxyFactory().getProxy( selfObjectName, AMX.class );
assert( mSelfProxy != null );
}
return( mSelfProxy );
}
public final DomainRoot
getDomainRoot()
{
return( getProxyFactory().getDomainRoot() );
}
protected final QueryMgr
getQueryMgr()
{
// this relies on mQueryMgr being 'volatile'
if ( mQueryMgr != null )
return mQueryMgr;
final ObjectName objectName = getQueryMgrObjectName();
if ( objectName != null )
{
// it doesn't matter if two thread do this; the same proxy will be returned.
mQueryMgr = getProxyFactory().getProxy( objectName, QueryMgr.class);
}
return( mQueryMgr );
}
/**
Extract the value of the "name" key for each ObjectName and place
it into an array.
@return String[] containing values of "name" property, one for each ObjectName
*/
protected final String[]
getNamePropertyValues( final Set<ObjectName> objectNameSet )
{
return( JMXUtil.getKeyProperty( NAME_KEY, objectNameSet ) );
}
/**
@return String[] containing names of all children of specified type
*/
protected final String[]
getChildNames()
{
return( getContaineeNamesOfType( getChildJ2EEType() ) );
}
/**
@return String[] containing names of all children of specified type
*/
protected final String[]
getContaineeNamesOfType( final String j2eeType )
{
final Set<ObjectName> objectNames = getContaineeObjectNameSet( j2eeType );
return( getNamePropertyValues( objectNames ) );
}
/**
Get the names of all child objects, which may be of more
than one type.
@return Set containing all child ObjectNames
*/
public final Set<ObjectName>
getContaineeObjectNameSet()
{
final String selfType = getSelfJ2EEType();
final Set<ObjectName> allChildren = new HashSet<ObjectName>();
final Set<String> containedTypes =
GSetUtil.newSet( getChildJ2EETypes(), getContaineeJ2EETypes() );
for( final String childJ2eeType : containedTypes )
{
final Set<ObjectName> childrenOfType = getContaineeObjectNameSet( childJ2eeType );
allChildren.addAll( childrenOfType );
}
return( allChildren );
}
/**
Get the name of a child MBean, assuming there is only one kind,
and there is never more than one.
@return ObjectName of child, or null if not found
*/
protected ObjectName
getOnlyChildObjectName()
{
return( getContaineeObjectName( getChildJ2EEType() ) );
}
/**
@param parentType
@param subType
*/
private static String
makeType( final String parentType, final String subType )
{
String result = null;
if ( parentType == null || parentType.length() == 0 )
{
result = subType;
}
else
{
result = parentType + AMX.FULL_TYPE_DELIM + subType;
}
return( result );
}
protected Set<ObjectName>
getFauxContaineeObjectNameSet(
final String childJ2EEType,
final String nullProps )
{
assert getFauxChildTypes().contains( childJ2EEType );
final String selfProp = Util.makeProp( getJ2EEType(), getSelfName() );
final String childJ2EETypeProp = Util.makeJ2EETypeProp( childJ2EEType );
final String props = Util.concatenateProps( selfProp, nullProps, childJ2EETypeProp );
final Set<AMX> candidates = getQueryMgr().queryPropsSet( props );
final Set<ObjectName> objectNames = Util.toObjectNames( candidates );
return( objectNames );
}
public Set<ObjectName>
getContaineeObjectNameSet( final String childJ2EEType )
{
final TypeInfos infos = TypeInfos.getInstance();
final TypeInfo info = infos.getInfo( childJ2EEType );
String props = Util.makeJ2EETypeProp( childJ2EEType );
QueryExp expr = null;
if ( info.isSubType() )
{
final String selfFullType = getFullType();
final String childFullType = makeType( selfFullType, childJ2EEType );
final String selfProps = Util.getFullTypeProps( getObjectName(), getFullType() );
props = Util.concatenateProps( props, selfProps );
expr = Query.eq(Query.attr( AMXAttributes.ATTR_FULL_TYPE ), Query.value( childFullType ));
}
else
{
// not a sub-type; nothing else to add
expr = null;
}
final ObjectName pattern =
JMXUtil.newObjectNamePattern( getObjectName().getDomain(), props );
final Set<ObjectName> candidates = JMXUtil.queryNames( getMBeanServer(), pattern, expr);
return( candidates );
}
public final Set<ObjectName>
getContaineeObjectNameSet( final Set<String> j2eeTypes )
{
final Set<ObjectName> all = new HashSet<ObjectName>();
final Set<String> actualTypes =
j2eeTypes == null ? getContaineeJ2EETypes() : j2eeTypes;
for( final String j2eeType : actualTypes )
{
final Set<ObjectName> objectNames = getContaineeObjectNameSet( j2eeType );
all.addAll( objectNames );
}
return( all );
}
/**
There must be 0 or 1 children of the specified type or an exception
will be thrown.
@return ObjectName for child of specified type
*/
public final ObjectName
getContaineeObjectName( final String j2eeType )
{
final Set<ObjectName> children = getContaineeObjectNameSet( j2eeType );
ObjectName result = null;
if ( children.size() == 1 )
{
result = GSetUtil.getSingleton( children );
}
else if ( children.size() == 0 )
{
trace( "AMXImplBase.getContaineeObjectName: no children of type " + j2eeType );
result = null;
}
else
{
trace( "AMXImplBase.getContaineeObjectName: " + j2eeType + " impossible");
impossible( new UnsupportedOperationException( "getContaineeObjectName" ) );
}
return( result );
}
protected final ObjectName
getNamedChildObjectName( final String name)
{
trace( "\nAMXImplBase.getNamedContaineeObjectName: " +
"Looking for " + name + " in " + quote( getObjectName() ) );
return( getContaineeObjectName( getChildJ2EEType(), name ) );
}
public final ObjectName
getContaineeObjectName(
final String j2eeType,
final String name)
{
final Set<ObjectName> candidates = getContaineeObjectNameSet( j2eeType );
final Set<ObjectName> matching =
JMXUtil.findByProperty( candidates, NAME_KEY, name );
final ObjectName result = (matching.size() == 0) ?
null : GSetUtil.getSingleton( matching );
return( result );
}
public Map<String,Map<String,ObjectName>>
getMultiContaineeObjectNameMap( final Set<String> j2eeTypesIn )
{
// if Set is null, it means all types
final Set<String> j2eeTypes = j2eeTypesIn == null ?
getContaineeJ2EETypes() : j2eeTypesIn;
final Map<String,Map<String,ObjectName>> m =
new HashMap<String,Map<String,ObjectName>>();
for( final String j2eeType : j2eeTypes )
{
final Map<String,ObjectName> nameMap = getContaineeObjectNameMap( j2eeType );
if ( nameMap.keySet().size() != 0 )
{
m.put( j2eeType, nameMap );
}
}
return( m );
}
public final Map<String,ObjectName>
getContaineeObjectNameMap( final String j2eeType )
{
if ( ! getContaineeJ2EETypes().contains( j2eeType ) )
{
throw new IllegalArgumentException( getObjectName() +
" does not contain j2eeType: " + j2eeType );
}
final Set<ObjectName> objectNames = getContaineeObjectNameSet( j2eeType );
Map<String,ObjectName> result = Collections.emptyMap();
if ( objectNames.size() != 0 )
{
result = Util.createObjectNameMap( objectNames );
}
assert( result.keySet().size() == objectNames.size() );
return( result );
}
public Set<ObjectName>
getByNameContaineeObjectNameSet(
final Set<String> j2eeTypes,
final String name )
{
final Iterator iter = getContaineeObjectNameSet( j2eeTypes ).iterator();
final Set<ObjectName> result = new HashSet<ObjectName>();
while ( iter.hasNext() )
{
final ObjectName objectName = (ObjectName)iter.next();
if ( Util.getName( objectName ).equals( name ) )
{
result.add( objectName );
}
}
return( result );
}
//------------------------ Access to other MBeans --------------------------------
protected Object
getAttribute(
final ObjectName objectName,
String name )
throws AttributeNotFoundException, InstanceNotFoundException,
ReflectionException, MBeanException
{
return( getMBeanServer().getAttribute( objectName, name ) );
}
protected AttributeList
getAttributes(
final ObjectName objectName,
String[] names )
throws AttributeNotFoundException, InstanceNotFoundException,
ReflectionException, MBeanException
{
return( getMBeanServer().getAttributes( objectName, names ) );
}
protected void
setAttribute(
final ObjectName objectName,
Attribute attr )
throws AttributeNotFoundException, InvalidAttributeValueException,
InstanceNotFoundException,
ReflectionException, MBeanException
{
getMBeanServer().setAttribute( objectName, attr );
}
protected AttributeList
setAttributes(
final ObjectName objectName,
AttributeList attrs )
throws AttributeNotFoundException, InvalidAttributeValueException,
InstanceNotFoundException,
ReflectionException, MBeanException
{
return( getMBeanServer().setAttributes( objectName, attrs ) );
}
//-------------------------------------------------------------------------------
protected ObjectName
registerMBean( Object mbean, ObjectName name )
throws MalformedObjectNameException, InstanceAlreadyExistsException,
NotCompliantMBeanException, MBeanRegistrationException
{
return getMBeanServer().registerMBean( mbean, name ).getObjectName();
}
protected String
stringify( Object o )
{
return( SmartStringifier.toString( o ) );
}
public String
toString()
{
return getImplString( false );
}
public String
getImplString( final boolean verbose )
{
final String NEWLINE = System.getProperty( "line.separator" );
String s = this.getClass().getName() + NEWLINE +
MBeanInfoStringifier.DEFAULT.stringify( getMBeanInfo() ) + NEWLINE;
if ( verbose )
{
final AttributeList attrs = getAttributes( getAttributeNames() );
final Map<String,Object> m = JMXUtil.attributeListToValueMap( attrs );
s = NEWLINE + s + MapUtil.toString( m, NEWLINE + NEWLINE ) + NEWLINE;
}
return s;
}
}
|