/*
* 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;
import java.io.IOException;
import java.util.Set;
import java.util.Map;
import java.util.Collection;
import java.util.HashSet;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.Arrays;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.Attribute;
import javax.management.JMException;
import javax.management.Notification;
import javax.management.NotificationListener;
import com.sun.appserv.management.base.AMX;
import com.sun.appserv.management.base.XTypes;
import com.sun.appserv.management.base.QueryMgr;
import com.sun.appserv.management.base.SystemInfo;
import com.sun.appserv.management.base.BulkAccess;
import com.sun.appserv.management.base.NotificationServiceMgr;
import com.sun.appserv.management.base.Util;
import com.sun.enterprise.management.support.XTypesMapper;
import com.sun.enterprise.management.support.J2EETypesMapper;
import com.sun.appserv.management.DomainRoot;
import com.sun.appserv.management.config.AMXConfig;
import com.sun.appserv.management.config.DomainConfig;
import com.sun.appserv.management.config.ConfigConfig;
import com.sun.appserv.management.config.ModuleMonitoringLevelsConfig;
import com.sun.appserv.management.config.ModuleMonitoringLevelValues;
import com.sun.appserv.management.config.NodeAgentConfig;
import com.sun.appserv.management.client.ProxyFactory;
import com.sun.appserv.management.client.ConnectionSource;
import com.sun.appserv.management.client.AppserverConnectionSource;
import com.sun.appserv.management.j2ee.J2EEDomain;
import com.sun.appserv.management.util.jmx.MBeanServerConnectionConnectionSource;
import com.sun.appserv.management.util.jmx.JMXUtil;
import com.sun.appserv.management.util.stringifier.SmartStringifier;
import com.sun.appserv.management.util.jmx.stringifier.AttributeStringifier;
import com.sun.appserv.management.util.misc.StringUtil;
import com.sun.appserv.management.util.misc.ClassUtil;
import com.sun.appserv.management.util.misc.GSetUtil;
import com.sun.appserv.management.util.misc.CollectionUtil;
import com.sun.appserv.management.util.misc.ExceptionUtil;
import com.sun.appserv.management.util.misc.TypeCast;
import com.sun.enterprise.management.PropertyKeys;
import com.sun.enterprise.management.util.jmx.JMXTestBase;
import com.sun.enterprise.management.support.AMXDebugStuff;
import com.sun.enterprise.management.support.AMXDebugSupportMBean;
/**
Base class for testing the AMX API
*/
public class AMXTestBase extends JMXTestBase
{
private ConnectionSource mConnectionSource;
private final DomainRoot mDomainRoot;
private final ProxyFactory mProxyFactory;
private final Set<ObjectName> mNotTested;
private final TestUtil mTestUtil;
/** config name for PE (non-EE) configuration */
protected static final String PE_CONFIG_NAME = "server-config";
private final static boolean WEB_MODULE_MONITOR_BROKEN = true;
private static boolean MONITORING_ENABLED = false;
protected static final String NEWLINE = System.getProperty( "line.separator" );
public
AMXTestBase( )
{
checkConnection( getConnection() );
mNotTested = new HashSet<ObjectName>();
try
{
mConnectionSource =
new MBeanServerConnectionConnectionSource( getConnection() );
mProxyFactory = ProxyFactory.getInstance( mConnectionSource, true );
mDomainRoot = mProxyFactory.getDomainRoot();
}
catch( Exception e )
{
throw new RuntimeException( e.toString(), e );
}
mTestUtil = new TestUtil( getDomainRoot() );
}
protected Set<AMX>
getAllAMX()
{
return getTestUtil().getAllAMX();
}
protected ModuleMonitoringLevelsConfig
getModuleMonitoringLevelsConfig( final String configName )
{
final ConfigConfig cc = configName == null ? getConfigConfig() : getConfigConfig( configName );
assert( cc != null );
final ModuleMonitoringLevelsConfig mon =
cc.getMonitoringServiceConfig().getModuleMonitoringLevelsConfig();
return( mon );
}
protected ModuleMonitoringLevelsConfig
getModuleMonitoringLevelsConfig( )
{
return getModuleMonitoringLevelsConfig( null );
}
/**
Ensure that monitoring is enabled so that unit tests don't miss anything
*/
protected synchronized void
turnOnMonitoring()
{
if ( ! MONITORING_ENABLED )
{
synchronized( AMXTestBase.class )
{
final String[] configNames = getConfigNames();
for( int i = 0; i < configNames.length; ++i )
{
getModuleMonitoringLevelsConfig( configNames[ i ] ).changeAll( ModuleMonitoringLevelValues.HIGH );
}
MONITORING_ENABLED = true;
}
}
}
protected synchronized void
turnOffMonitoring()
{
synchronized( AMXTestBase.class )
{
getModuleMonitoringLevelsConfig().changeAll( ModuleMonitoringLevelValues.OFF );
MONITORING_ENABLED = false;
}
}
private static final Set<String> EXPECTED_REMOTE_INCOMPLETE_TYPES =
GSetUtil.newUnmodifiableStringSet( XTypes.CALL_FLOW_MONITOR, XTypes.LOGGING );
protected boolean
isRemoteIncomplete( final ObjectName objectName )
{
final AMX amx = getProxyFactory().getProxy( objectName, AMX.class );
final String j2eeType = amx.getJ2EEType();
final boolean isRemote = ! amx.isDAS();
if ( isRemote &&
! EXPECTED_REMOTE_INCOMPLETE_TYPES.contains( j2eeType ) )
{
warning( "isRemoteIncomplete: not expecting j2eeType=" + j2eeType +
", has the implementation changed?" );
}
return isRemote;
}
protected boolean
shouldTest( final ObjectName objectName )
{
boolean shouldTest = ! isRemoteIncomplete( objectName );
return( shouldTest );
}
protected void
waitUnregistered( final ObjectName objectName )
throws IOException
{
final MBeanServerConnection conn = getConnection();
if ( conn.isRegistered( objectName ) )
{
mySleep( 100 );
}
while ( conn.isRegistered( objectName ) )
{
trace( "waitUnregistered: " + objectName);
mySleep( 100 );
}
}
/**
Because proxies are cleaned up asynchronously, it's possible for one
to remain in the factory until the factory processes the Notification
that it's MBean has been unregistered.
*/
protected void
waitProxyGone(
final ProxyFactory factory,
final ObjectName objectName )
{
long millis = 1;
while ( factory.getProxy( objectName, AMX.class, false ) != null )
{
mySleep( millis );
millis *= 2;
trace( "waitProxyGone: waiting for proxy to disappear: " + objectName );
}
}
protected final void
notTested( final ObjectName objectName )
{
if ( isRemoteIncomplete( objectName ) )
{
trace( "remoteIncomplete (this is OK): " + objectName );
}
else
{
mNotTested.add( objectName );
}
}
protected final Set<ObjectName>
getNotTestedSet( )
{
return( mNotTested );
}
public static void
mySleep( final long millis )
{
try
{
Thread.sleep( millis );
}
catch( InterruptedException e )
{
}
}
protected final void
warnNotTested( )
{
final Set<ObjectName> notTested = getNotTestedSet();
if ( notTested.size() != 0 )
{
final Set j2eeTypes =
JMXUtil.getKeyPropertySet( AMX.J2EE_TYPE_KEY, notTested );
trace( "WARNING: DID NOT TEST: " + notTested.size() + " MBeans of types {" +
toString( j2eeTypes ) + "}" );
}
}
protected void
checkConnection( final MBeanServerConnection conn )
{
assert( getConnection() != null );
try
{
conn.isRegistered( JMXUtil.getMBeanServerDelegateObjectName() );
}
catch( Exception e )
{
fail( "Connection failed:\n" +
ExceptionUtil.getStackTrace( getRootCause( e ) ) );
}
}
protected void
checkConnection( )
{
checkConnection( getConnection( ) );
}
protected TestUtil
getTestUtil()
{
return mTestUtil;
}
protected final AMX
getProxy( final ObjectName objectName )
{
final ProxyFactory factory = ProxyFactory.getInstance( getConnectionSource(), true);
final AMX proxy = factory.getProxy( objectName, AMX.class );
return( proxy );
}
/**
We don't have T extend AMX because not all mixin interfaces extend AMX.
*/
protected final <T> T
getProxy(
final ObjectName objectName,
final Class<T> theClass)
{
return( theClass.cast( getProxy(objectName) ) );
}
protected final DomainRoot
getDomainRoot()
{
assert( mDomainRoot != null ) : "mDomainRoot is null";
return( mDomainRoot );
}
protected final DomainConfig
getDomainConfig()
{
return( getDomainRoot().getDomainConfig() );
}
protected final J2EEDomain
getJ2EEDomain()
{
return( getDomainRoot().getJ2EEDomain() );
}
protected String[]
getConfigNames()
{
final Map<String,ConfigConfig> configMap =
getDomainConfig().getConfigConfigMap();
return( GSetUtil.toStringArray( configMap.keySet() ) );
}
protected ConfigConfig
getConfigConfig( final String name )
{
final Map<String,ConfigConfig> configs = getDomainConfig().getConfigConfigMap();
return configs.get( name == null ? PE_CONFIG_NAME : name );
}
protected static ConfigConfig
getConfigConfig( final AMXConfig any )
{
final ObjectName objectName = Util.getObjectName( any );
final String configName = objectName.getKeyProperty( XTypes.CONFIG_CONFIG );
return any.getDomainRoot().getDomainConfig().getConfigConfigMap().get( configName );
}
protected ConfigConfig
getConfigConfig( )
{
return( getConfigConfig( PE_CONFIG_NAME ) );
}
protected QueryMgr
getQueryMgr()
{
assert( mDomainRoot != null );
final QueryMgr proxy = getDomainRoot().getQueryMgr();
assert( proxy != null );
return( proxy );
}
protected NotificationServiceMgr
getNotificationServiceMgr()
{
return( getDomainRoot().getNotificationServiceMgr() );
}
protected BulkAccess
getBulkAccess()
{
return( getDomainRoot().getBulkAccess() );
}
protected ConnectionSource
getConnectionSource()
{
assert( mConnectionSource != null );
return( mConnectionSource );
}
protected MBeanServerConnection
getConnection()
{
return( getGlobalConnection() );
}
protected ProxyFactory
getProxyFactory()
{
return( mProxyFactory );
}
protected Class
getInterfaceClass( AMX proxy )
throws ClassNotFoundException
{
final String name = Util.getExtra( proxy ).getInterfaceName();
return( ClassUtil.getClassFromName( name ) );
}
protected <T extends AMX> boolean
testOnProxies(
final Collection<T> proxies,
final Method method )
throws Exception
{
final long start = now();
boolean failed = false;
int testCount = 0;
final Object[] args = new Object[ 1 ];
for( final T proxy : proxies )
{
final ObjectName objectName = Util.getExtra( proxy ).getObjectName();
if ( ! shouldTest( objectName ) )
{
notTested( objectName );
continue;
}
++testCount;
try
{
args[ 0 ] = proxy;
method.invoke( this, args );
}
catch( Exception e )
{
trace( method.getName() + " failed for proxy: " +
quote( JMXUtil.toString( objectName ) ) );
failed = true;
trace( ExceptionUtil.toString( e ) );
}
}
final long elapsed = now() - start;
printVerbose( "Ran test method " + method.getName() + " on " + testCount +
" candidates in " + elapsed + "ms");
warnNotTested();
warnNotTested();
return( ! failed );
}
protected boolean
testOnObjectNames(
final Collection<ObjectName> objectNames,
final Method method )
throws Exception
{
boolean failed = false;
final Object[] args = new Object[ 1 ];
int testCount = 0;
final long start = now();
for( final ObjectName objectName : objectNames )
{
if ( ! shouldTest( objectName ) )
{
notTested( objectName );
continue;
}
++testCount;
try
{
args[ 0 ] = objectName;
method.invoke( this, args );
}
catch( Exception e )
{
final Throwable rootCause = getRootCause( e );
trace( method.getName() + " failed for: " +
quote( JMXUtil.toString( objectName ) ) + " with Exception of type " +
rootCause.getClass().getName() + ", msg = " + rootCause.getMessage() );
failed = true;
}
}
final long elapsed = now() - start;
printVerbose( "Ran test method " + method.getName() + " on " + testCount +
" candidates in " + elapsed + "ms");
warnNotTested();
return( ! failed );
}
protected final static Class[] OBJECTNAME_SIG = new Class[] { ObjectName.class };
protected final static Class[] PROXY_SIG = new Class[] { AMX.class };
protected void
testAll( final Collection<ObjectName> objectNames, final String methodName )
throws Exception
{
final boolean success = testOnObjectNames( objectNames,
this.getClass().getMethod( methodName, OBJECTNAME_SIG ) );
assert( success );
}
protected <T extends AMX> void
testAllProxies( final Collection<T> proxies, final String methodName )
throws Exception
{
final boolean success = testOnProxies( proxies,
this.getClass().getMethod( methodName, PROXY_SIG ) );
assert( success );
}
protected void
testAll( String methodName )
throws Exception
{
final Set<ObjectName> names = getTestUtil().getAllObjectNames();
testAll( names, methodName );
}
public void
setUp() throws Exception
{
super.setUp();
turnOnMonitoring();
}
public void
testAssertsOn()
{
checkAssertsOn();
}
private static final String DEFAULT_INSTANCE_NAME = "test";
protected static String
getDefaultInstanceName( final String qualifier )
{
String name = null;
if ( qualifier == null )
{
name = DEFAULT_INSTANCE_NAME;
}
else
{
name = qualifier + "." + DEFAULT_INSTANCE_NAME;
}
return name;
}
protected Throwable
getRootCause( Throwable t )
{
return ExceptionUtil.getRootCause( t );
}
protected String
getStackTrace( Throwable t )
{
return ExceptionUtil.getStackTrace( t );
}
protected String
getRootCauseStackTrace( Throwable t )
{
return getStackTrace( getRootCause( t ) );
}
protected Map<String,AppserverConnectionSource>
getNodeAgents()
{
final Map<?,?> m = Map.class.cast( getEnvValue( PropertyKeys.NODE_AGENTS_KEY ) );
return TypeCast.checkedMap( m,
String.class, AppserverConnectionSource.class);
}
protected String
getDASNodeAgentName()
{
return getEnvString( PropertyKeys.DAS_NODE_AGENT_NAME, null);
}
protected NodeAgentConfig
getDASNodeAgentConfig()
{
final String name = getDASNodeAgentName();
NodeAgentConfig config = null;
if ( name != null )
{
config = getDomainConfig().getNodeAgentConfigMap().get( name );
}
return config;
}
protected boolean
getTestOffline()
{
return getEnvBoolean( PropertyKeys.TEST_OFFLINE_KEY, false );
}
/**
Check if we're testing in Offline mode, which means that
Config MBeans are loaded in-process. If so, issue a warning.
@return true if test should be run, false if in offline mode
*/
protected boolean
checkNotOffline( final String testName )
{
boolean offline = getTestOffline();
if ( offline )
{
//warning( "amxtest.testOffline=true, skipping test " + testName + "()" );
}
return ! offline;
}
public static Capabilities
getDefaultCapabilities()
{
return getOfflineCapableCapabilities( true );
}
protected static Capabilities
getOfflineCapableCapabilities( boolean offlineCapable )
{
final Capabilities c = new Capabilities( );
c.setOfflineCapable( offlineCapable );
return c;
}
AMXDebugSupportMBean
getAMXDebugSupportMBean()
{
final ObjectName objectName = Util.newObjectName( AMXDebugSupportMBean.OBJECT_NAME );
try
{
return (AMXDebugSupportMBean)newProxy( objectName, AMXDebugSupportMBean.class );
}
catch( Exception e )
{
assert false : "Can't get proxy to " + objectName;
}
return null;
}
protected boolean
supportsMultipleServers()
{
return supportsMultipleServers( getDomainRoot() );
}
public static boolean
supportsMultipleServers( final DomainRoot domainRoot )
{
return domainRoot.getSystemInfo().supportsFeature( SystemInfo.MULTIPLE_SERVERS_FEATURE );
}
protected boolean
supportsClusters()
{
return getDomainRoot().getSystemInfo().supportsFeature( SystemInfo.CLUSTERS_FEATURE );
}
}