/*
 * 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 javax.management.ObjectName;

import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Arrays;

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

import javax.management.j2ee.statistics.Statistic;
import javax.management.j2ee.statistics.Stats;
import javax.management.j2ee.statistics.CountStatistic;
import javax.management.j2ee.statistics.BoundaryStatistic;
import javax.management.j2ee.statistics.RangeStatistic;
import javax.management.j2ee.statistics.BoundedRangeStatistic;
import javax.management.j2ee.statistics.TimeStatistic;

import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
			
import com.sun.appserv.management.base.AMX;
import com.sun.appserv.management.base.Util;

import com.sun.appserv.management.monitor.MonitoringStats;
import com.sun.appserv.management.monitor.MonitoringRoot;
import com.sun.appserv.management.monitor.ServerRootMonitor;
import com.sun.appserv.management.monitor.TransactionServiceMonitor;

import com.sun.appserv.management.util.misc.GSetUtil;
import com.sun.appserv.management.util.misc.ExceptionUtil;
import com.sun.appserv.management.util.misc.CollectionUtil;
import com.sun.appserv.management.util.misc.StringUtil;
import com.sun.appserv.management.util.jmx.JMXUtil;
import com.sun.appserv.management.util.j2ee.J2EEUtil;

import com.sun.appserv.management.config.ModuleMonitoringLevelsConfig;

import com.sun.appserv.management.j2ee.statistics.*;


public final class MonitorTest
	extends AMXMonitorTestBase
{
    	public
    MonitorTest()
    {
    }

	

		public void
	checkStatisticNames( final MonitoringStats mon )
	{
		final Stats	stats	= mon.getStats();
		
		final Set<String>	namesFromMon	= GSetUtil.newStringSet( mon.getStatisticNames() );
		final Set<String>	namesFromStats	= GSetUtil.newStringSet( stats.getStatisticNames() );
		
		assert( namesFromStats.equals( namesFromMon ) ):
			"statistic names from stats.getStatisticNames() != mon.getStatisticNames(): " +
			namesFromStats + " != " + namesFromMon;
	}
	
		public void
	checkNumStatistics( final MonitoringStats mon )
	{
		final Stats	stats	= mon.getStats();
		assert( stats != null ) : "null Stats from: " + Util.getObjectName( mon );
		final String[]	allNames	= mon.getStatisticNames();
		
		final Statistic[]	statistics	= mon.getStatistics( allNames );
		assert( statistics.length == allNames.length ) :
			"wrong number of statistics from: " + Util.getObjectName( mon ) +
			", got " + statistics.length + ", should be " + allNames.length;
	}
	
		public void
	checkOpenStats( final MonitoringStats mon )
	{
		final CompositeDataSupport	openStats	= mon.getOpenStats();
		assert( openStats != null ) : "null OpenStats from: " + Util.getObjectName( mon );
		
		final StatsImpl	stats	= new StatsImpl( openStats );
		
		final Set<String>	fromOpenStats	= GSetUtil.newStringSet( stats.getStatisticNames() );
		final Set<String>	fromStats		= GSetUtil.newStringSet( mon.getStats().getStatisticNames() );
		assert( fromOpenStats.equals( fromStats ) ) :
			"openStats Statistic names don't match Stats Statistic names: " +
			fromOpenStats + " != " + fromStats;
	}
	
		private final boolean
	isLegalStatistic( final Statistic s)
	{
		// current, we do not allow MapStatistic as these types cover all
		return( (s instanceof CountStatistic) ||
			(s instanceof BoundaryStatistic) ||
			(s instanceof RangeStatistic) ||
			(s instanceof BoundedRangeStatistic) ||
			(s instanceof TimeStatistic ) ||
			(s instanceof StringStatistic ) );
	}
	
		private final boolean
	isLegalStatisticImpl( final Statistic s)
	{
		boolean	isLegal	= isLegalStatistic( s );
		if ( isLegal )
		{
			final Class	theClass	= s.getClass();
			
			if( (theClass == CountStatisticImpl.class) ||
				(theClass == BoundaryStatisticImpl.class) ||
				(theClass == RangeStatisticImpl.class) ||
				(theClass == BoundedRangeStatisticImpl.class) ||
				(theClass == TimeStatisticImpl.class ) ||
				(theClass == StringStatisticImpl.class ) );
		}
		return( isLegal );
	}
	
	
		private void
	checkLegalStatistic(
		final ObjectName objectName,
		final Statistic s )
	{
		assert( isLegalStatistic( s ) ) : "Statistic " + s.getName() +
			" in \"" + objectName +
			"\" is not a known type of Statistic";
			
		assert( isLegalStatisticImpl( s ) ) : "Statistic " + s.getName() +
			" in \"" + objectName +
			"\" uses an implementation not intended by the API: " +
			s.getClass();
	}
	
		public void
	checkGetStatistic( final MonitoringStats mon )
	{
		final Stats	stats	= mon.getStats();
		
		final ObjectName	objectName	= Util.getObjectName( mon );
		
		final String[]	names	= mon.getStatisticNames();
		for( int i = 0; i < names.length; ++i )
		{
			final String	name	= names[ i ];
			final Statistic	s	= mon.getStatistic( name );
			assert( s != null );
			assert( s.getName().equals( name ) );
			
			checkLegalStatistic( objectName, s );
		}
	}
	
		public void
	checkGetStats( final MonitoringStats mon )
	{
		final Stats	stats	= mon.getStats();
		
		final ObjectName	objectName	= Util.getObjectName( mon );
		
		final String[]	names	= stats.getStatisticNames();
		for( int i = 0; i < names.length; ++i )
		{
			final Statistic	s	= stats.getStatistic( names[ i ] );
			assert( s != null );
			assert( s.getName().equals( names[ i ] ) );
			
			checkLegalStatistic( objectName, s );
		}
	}
	
		public void
	checkGetOpenStatistic( final MonitoringStats mon )
	{
		final Stats	stats	= mon.getStats();
		
		final String[]	names	= mon.getStatisticNames();
		for( int i = 0; i < names.length; ++i )
		{
			final String	name	= names[ i ];
			
			final CompositeData	d	= mon.getOpenStatistic( name );
			final Statistic	s		= StatisticFactory.create( d );
			final Statistic	s2		= mon.getStatistic( name );
			
			assert( s.getName().equals( name ) );
			// values may have changed, but check the static fields
		}
		
		final CompositeDataSupport[]	all	= mon.getOpenStatistics( names );
		assert( all != null );
		assert( all.length == names.length );
	}
	
		public void
	checkMonitoringStats( final ObjectName objectName )
		throws Exception
	{
		final MonitoringStats	mon	= getProxyFactory().getProxy( objectName, MonitoringStats.class);
		
		mon.refresh();
		mon.getStatsInterfaceName();
		
		checkNumStatistics( mon );
		
		checkStatisticNames( mon );
		
		checkGetStatistic( mon );
		
		checkGetStats( mon );
		
		checkGetOpenStatistic( mon );
		
		checkOpenStats( mon );
	}
	
		private Set<MonitoringStats>
	getAllMonitoringStats()
		throws ClassNotFoundException
	{
		final long	start	= now();
		
		final Set<MonitoringStats>	all	= 
			getQueryMgr().queryInterfaceSet( MonitoringStats.class.getName(), null );
			
		for( final MonitoringStats stats : all )
		{
		}
		
		printElapsed( "getAllMonitoringStats", all.size(), start );
		
		return( all );
	}
	
	
	
		private String
	getterToName( final String getterName )
	{
		return StringUtil.stripPrefix( getterName, JMXUtil.GET );
	}
	
		private String[]
	methodsToNames( final Method[] methods )
	{
		final String[]	result	= new String[ methods.length ];
		
		for( int i = 0; i < methods.length; ++i )
		{
			result[ i ]	= getterToName( methods[ i ].getName() );
		}
		
		Arrays.sort( result );
		return( result );
	}
	
		public void
	checkStats(
		final MonitoringStats	mon,
		final Stats				stats )
		throws InvocationTargetException, IllegalAccessException, NoSuchMethodException
	{
		final ObjectName	objectName	= Util.getObjectName( mon );
		
	trace( "checkStats: " + objectName );
		
		final Method[]	methodsViaNames	= J2EEUtil.getStatisticGetterMethodsUsingNames( stats );
		
		final Method[]		methods		= stats.getClass().getMethods();
		
		final Set<String>	statisticNames	= GSetUtil.newSet( stats.getStatisticNames() );
		
		for( int methodIdx = 0; methodIdx < methodsViaNames.length; ++methodIdx )
		{
			final Method	method	= methodsViaNames[ methodIdx ];
			final String	methodName	= method.getName();
			
			final Class<?>	returnType	= method.getReturnType();
			
			final String statisticName	= getterToName( methodName );
			if ( ! statisticNames.contains( statisticName ) )
			{
				warning( "Statistic " + quote( statisticName ) + " as derived from " + method +
					" missing from " + quote( objectName ) +
					" available names = " + toString( statisticNames ) );
			}
			
			try
			{
				final Object	o	= method.invoke( stats, (Object[])null );
				assert( o != null );
				assert( Statistic.class.isAssignableFrom( o.getClass() ) );
				
				assert( returnType.isAssignableFrom( o.getClass() ) ) :
					"Method " + methodName + " of MBean " + objectName +
					" returned object not assignable to " + returnType.getName();

				final Statistic	stat	= (Statistic)method.invoke( stats, (Object[])null );
				assert( method.getReturnType().isAssignableFrom( stat.getClass() ) );
				
				final Statistic s	= mon.getStatistic( stat.getName() );
				assert( returnType.isAssignableFrom( s.getClass() ) ) :
					"getStatistic() of MBean " + objectName +
					" returned Statistic not assignable to " + returnType.getName();
					
				//printVerbose( "Verified " + stat.getClass().getName()  + " " + stat.getName() );
			}
			catch( Exception e )
			{
				final Throwable rootCause	= ExceptionUtil.getRootCause( e );
				
				warning(
				"Failure calling " + method + " on Stats for " + objectName + " = " +
					rootCause.getClass().getName() + "\n" + 
					"Statistic names = " + toString( stats.getStatisticNames() ) );
			}
		}
	}
	
		public void
	checkAllStats( final ObjectName objectName )
		throws InvocationTargetException, IllegalAccessException, NoSuchMethodException
	{
	    trace( "checkAllStats: " + objectName );
	
		final MonitoringStats	mon	=
		    getProxyFactory().getProxy( objectName, MonitoringStats.class);
		
		final Method[]	methods		= mon.getClass().getMethods();
		
		final Method	specificStatsMethod	= getSpecificStatsGetterMethod( mon );


		// the type of Stats returned from getStats() should be the same as the type
		// returned from the (only) specific getAbcStats()
		final Stats	plainStats	= mon.getStats();
		assert( specificStatsMethod.getReturnType().isAssignableFrom( plainStats.getClass() ) ) :
			"Stats returned from " + objectName + " getStats() should be assignable to " +
				specificStatsMethod.getReturnType().getName();
		checkStats( mon, plainStats );
		
		Stats	stats	= null;
		try
		{
			// verify that we can get it
			stats	= (Stats)specificStatsMethod.invoke( mon, (Object[])null );
		}
		catch( Exception e )
		{
			final Throwable rootCause	= ExceptionUtil.getRootCause( e );
			
			failure(
				"Failure calling " + specificStatsMethod.getName() + "() on " + objectName + " = " +
				rootCause.getClass().getName() );
		}
			
		assert( plainStats.getClass() == stats.getClass() );
		checkStats( mon, stats );
	}
	
	/**
		Test the MonitoringStats interface.
	 */
		public void
	testMonitoringStats()
		throws Exception
	{
		final long	start	= now();
		
		final Set<MonitoringStats>	all	= getAllMonitoringStats();
		
		testAll( Util.toObjectNames( all ), "checkMonitoringStats" );
		
		printElapsed( "testMonitoringStats", all.size(), start );
	}
	
		public void
	xtestStats()
		throws Exception
	{
	    trace( "testStats: ");
		final long	start	= now();
		
		final Set<MonitoringStats>	all	= getAllMonitoringStats();
		assert( all.size() >= 10 ) : "Monitoring is not turned on";

		//final Set	all	= getQueryMgr().queryInterfaceSet( com.sun.appserv.management.monitor.HTTPServiceVirtualServerMonitor.class.getName(), null );
		
		testAll( Util.toObjectNames( all ), "checkAllStats" );
		
		printElapsed( "testStats", all.size(), start );
	}
	
	
	/**
		Get the specific (non-generic) Stats getter. Example:
		getJVMStats() versus plain getStats().
	 */
		public Method
	getSpecificStatsGetterMethod( final MonitoringStats mon )
		throws InvocationTargetException, IllegalAccessException, NoSuchMethodException
	{
		final Method[]	methods		= mon.getClass().getMethods();
		
		Method	result	= null;
		
		for( int methodIdx = 0; methodIdx < methods.length; ++methodIdx )
		{
			final Method	method	= methods[ methodIdx ];
			final String	methodName	= method.getName();

			if ( JMXUtil.isGetter( method ) && ! methodName.equals( "getStats" ) &&
				Stats.class.isAssignableFrom( method.getReturnType() ) &&
				method.getParameterTypes().length == 0 )
			{
				result	= method;
				break;
			}
		}
		
		if ( result == null )
		{
			throw new NoSuchMethodException( "Can't find specific Stats getter in " +
				quote( Util.getObjectName( mon ) ) );
		}
		
		
		return( result );
	}
	
	  
	
		public void
	checkStatsClassSuppliesAllStatistics( final ObjectName objectName )
		throws InvocationTargetException, IllegalAccessException, NoSuchMethodException
	{
	    //trace( "testStatsClassSuppliesAllStatistics: " + objectName);
	    
	    try
	    {
		final MonitoringStats	mon	= getProxyFactory().getProxy( objectName, MonitoringStats.class);
		
		final Method	m	= getSpecificStatsGetterMethod( mon );
		final Stats		stats	= (Stats)m.invoke( mon, (Object[])null );
		final Method[]	methodsViaIntrospection	= J2EEUtil.getStatisticGetterMethodsUsingIntrospection( stats );
		final Method[]	methodsViaNames	= J2EEUtil.getStatisticGetterMethodsUsingNames( stats );
		
		assert GSetUtil.newSet( methodsViaNames ).equals( GSetUtil.newSet( methodsViaIntrospection ) ) :
			"Statistic names for " + quote( objectName ) + 
				" obtained via Statistic names do not match those obtained via introspection: \n" +
				"via names:" + toString( methodsToNames(methodsViaNames) ) + 
				"\nvia introspection: " + toString( methodsToNames(methodsViaIntrospection) );
				
		final String[]	namesFromMethods	= methodsToNames( methodsViaNames );
		
		assert GSetUtil.newSet( namesFromMethods ).equals( GSetUtil.newSet( stats.getStatisticNames() ) ) :
			"MBean " + quote( objectName ) + " Stats object of class " + stats.getClass().getName() +
				" has Statistic methods that don't match getStatisticNames() =>\n" +
				toString( namesFromMethods ) + " != " +
				    toString( stats.getStatisticNames() );
	    }
	    catch( Exception e )
	    {
	        trace( "Caught exception for " + StringUtil.quote(JMXUtil.toString(objectName)) +
	            " = " +  e.getClass().getName() + ": " + StringUtil.quote(e.getMessage()) + "\n" + 
	            ExceptionUtil.getStackTrace( ExceptionUtil.getRootCause(e) ) );
	    }
	}
	
	/**
		Verify that the Stats class for each MonitoringStats supplies all Statistics
		found in itself, and that this matches those advertised by MonitoringStats.
	 */
		public void
	testStatsClassSuppliesAllStatistics()
		throws Exception
	{
	    //trace( "testStatsClassSuppliesAllStatistics: ");
		final long	start	= now();
		
		final Set<MonitoringStats>	all	= getAllMonitoringStats();
		
		testAll( Util.toObjectNames( all ), "checkStatsClassSuppliesAllStatistics" );
		
		printElapsed( "testStatsClassSuppliesAllStatistics", all.size(), start );
	}
}









