/*
 * 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/tests/com/sun/enterprise/management/base/GenericTest.java,v 1.8.6.1 2007/11/30 23:16:52 llc Exp $
 * $Revision: 1.8.6.1 $
 * $Date: 2007/11/30 23:16:52 $
 */
package com.sun.enterprise.management.base;

import java.util.Set;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.io.IOException;
import java.io.NotSerializableException;

import javax.management.ObjectName;
import javax.management.MBeanServerConnection;
import javax.management.MBeanOperationInfo;
import javax.management.InstanceNotFoundException;
import javax.management.JMException;
import javax.management.ReflectionException;
import javax.management.AttributeNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanAttributeInfo;
import javax.management.Attribute;
import javax.management.AttributeList;


import com.sun.appserv.management.j2ee.J2EEDomain;

import com.sun.appserv.management.client.ProxyFactory;
import com.sun.appserv.management.base.AMX;
import com.sun.appserv.management.config.ResourceRefConfig;
import com.sun.appserv.management.config.DeployedItemRefConfig;
import com.sun.appserv.management.config.ResourceConfig;

import com.sun.appserv.management.util.misc.ExceptionUtil;
import com.sun.appserv.management.util.misc.ArrayUtil;
import com.sun.appserv.management.util.misc.ArrayConversion;
import com.sun.appserv.management.util.misc.StringUtil;

import com.sun.appserv.management.util.misc.ClassUtil;
import com.sun.appserv.management.util.jmx.JMXUtil;
import com.sun.appserv.management.util.jmx.ReadWriteAttributeFilter;

import com.sun.appserv.management.util.stringifier.ArrayStringifier;



import com.sun.enterprise.management.AMXTestBase;
import com.sun.enterprise.management.Capabilities;

/**
 */
public final class GenericTest extends AMXTestBase
{
	final boolean		mDoInfo;
	final boolean		mDoAttributes;
	final boolean		mDoOperations;
	final boolean		mwarnings;

		public
	GenericTest()
		throws IOException
	{
		mDoInfo				= true;
		mDoAttributes		= true;
		mDoOperations		= true;
		mwarnings		= true;
	}
	
	    public static Capabilities
	getCapabilities()
	{
	    return getOfflineCapableCapabilities( true );
	}
		private Map<String,Throwable>
	validateAttributesSingly(
		final ObjectName		objectName,
		final String[]			attrNames,
		Map<String,Throwable>		failures,
		Map<String,Throwable>		warnings )
		throws Exception
	{
		MBeanServerConnection conn	= getConnection();
			
		for( int i = 0; i < attrNames.length; ++i )
		{
			final String	attrName	= attrNames[ i ];
			
			try
			{
				final Object	a	= conn.getAttribute( objectName, attrName );
				
				if ( a == null )
				{
					// null is legal, apparently
				}
			}
			catch( NotSerializableException e )
			{
				warnings.put( attrName, e );
			}
			catch( IOException e )
			{
				failures.put( attrName, e );
			}
			catch( Exception e )
			{
				failures.put( attrName, e );
			}
		}
		
		return( failures );
	}


		private String
	getExceptionMsg( final Throwable e )
	{
		String	msg	= null;
		
		if ( e instanceof IOException )
		{
			msg	= "received an exception of class " + e.getClass().getName();
			
			if ( shouldPrintStackTraces() )
			{
				msg	= msg + "Stack trace = \n" + 
				ExceptionUtil.getStackTrace( ExceptionUtil.getRootCause( e ) );
			}
		}
		else
		{
			msg	= "threw an Exception of type " +
				e.getClass().getName() + ", message =  " + e.getMessage();
			
			if ( shouldPrintStackTraces() )
			{
				msg	= msg + "\n" + ExceptionUtil.getStackTrace( e );
			}
		}
		
		final Throwable rootCause	= ExceptionUtil.getRootCause( e );
		
		if ( rootCause != e )
		{
			msg	= msg + "...\nRoot cause was exception of type " + e.getClass().getName() + ", message = " +
				rootCause.getMessage();
				
			
			if ( shouldPrintStackTraces() )
			{
				msg	= msg + "\n" + ExceptionUtil.getStackTrace( rootCause );
			}
		}
		
		return( msg );
	}

		MBeanAttributeInfo
	findAttributeInfo(
		final MBeanAttributeInfo[]	infos,
		String 						attrName )
	{
		MBeanAttributeInfo	info	= null;
		
		for( int i = 0; i < infos.length; ++i )
		{
			if ( infos[ i ] != null && infos[ i ].getName().equals( attrName ) )
			{
				info	= infos[ i ];
				break;
			}
		}
		
		assert( info != null );
		return( info );
	}
	
		private void
	displayAttributeFailuresOrWarnings(
		final boolean				failure,
		final ObjectName			objectName,
		final MBeanAttributeInfo[]	infos,
		final Map<String,Throwable>    problems )
		throws Exception
	{
		trace( "" );
		trace( problems.keySet().size() + (failure ? " Failures: " : " Warnings: ") + objectName  );
		
		int	i = 0;
		for( final String attrName : problems.keySet() )
		{
			final Throwable	t = problems.get( attrName );
			
			final MBeanAttributeInfo	info	= findAttributeInfo( infos, attrName );
			
			final String prefix	= "(" + (i+1) + ")" + " getting Attribute \"" + attrName + "\" of type " +
				info.getType() + " ";
				
			if ( t == null )
			{
				trace( prefix + "returned null" );
			}
			else
			{
				trace( prefix + getExceptionMsg( t ) );
			}
			++i;
		}
	}
	
	
	
		private boolean
	validateMBeanInfo( final ObjectName objectName, final MBeanInfo info)
	{
		boolean	valid	= true;
		
		if ( ArrayUtil.arrayContainsNulls( info.getAttributes() ) )
		{
			warning( "MBean has nulls in its MBeanAttributeInfo[]: " + objectName );
			valid	= false;
		}
		
		if ( ArrayUtil.arrayContainsNulls( info.getConstructors() ) )
		{
			warning( "MBean has nulls in its MBeanConstructorInfo[]: " + objectName );
			valid	= false;
		}
		
		if ( ArrayUtil.arrayContainsNulls( info.getOperations() ) )
		{
			warning( "MBean has nulls in its MBeanOperationInfo[]: " + objectName );
			valid	= false;
		}
		
		if ( ArrayUtil.arrayContainsNulls( info.getNotifications() ) )
		{
			warning( "MBean has nulls in its MBeanNotificationInfo[]: " + objectName );
			valid	= false;
		}
		
		return( valid );
	}
	
	static final private String	SECTION_LINE	=
	"--------------------------------------------------------------------------------";
	
	
	
		private void
	printDuplicateAttributes( final ObjectName objectName, MBeanAttributeInfo[] attrInfos, String name)
	{
		String	msg	= "MBean " + quote( objectName ) + " has the same Attribute listed more than once:\n";
		
		for( int i = 0; i < attrInfos.length; ++i )
		{
			final MBeanAttributeInfo	a	= attrInfos[ i ];
				
			if ( a.getName().equals( name ) )
			{
				msg	= msg + quote(name) + ": " + a.getType() + ", " + quote( a.getDescription() )  + StringUtil.NEWLINE();
			}
		}
		
		warning( msg );
	}
	
		private boolean
	validateUniqueAttributeNames( final ObjectName objectName, MBeanAttributeInfo[] attrInfos )
	{
		boolean	valid	= true;
		final MBeanAttributeInfo[]	infos	=
			JMXUtil.filterAttributeInfos( attrInfos, ReadWriteAttributeFilter.READABLE_FILTER );
		final String[]	names	= JMXUtil.getAttributeNames( infos );
		
		if ( ArrayConversion.arrayToSet( names ).size() != attrInfos.length )
		{
			final Set<String>	set		= new HashSet<String>();
			
			for( int i = 0; i < names.length; ++i )
			{
				final String	name	= names[ i ];
			
				if ( set.contains( name ) )
				{
					valid	= false;
					
					printDuplicateAttributes( objectName, attrInfos, name );
				}
				else
				{
					set.add( name );
				}
			}
			set.clear();
		}
		
		return( valid );
	}
	
		private boolean
	validateMissingAndEmptyAttributeNames( final ObjectName objectName  )
	{
		boolean						valid	= true;
		final MBeanServerConnection	conn	= getConnection();
		
		AttributeList	attrs	= null;
		try
		{
			attrs	= conn.getAttributes( objectName, new String[0] );
			if ( attrs == null )
			{
				warning( "MBean " + quote( objectName ) +
					" returned NULL for an empty AttributeList" );
				valid	= false;
			}
			else if ( attrs.size() != 0 )
			{
				warning( "MBean " + quote( objectName ) +
					" returned attributes for an empty AttributeList" );
				valid	= false;
			}
		}
		catch( Exception e )
		{
			valid	= false;
			
			warning( "MBean " + quote( objectName ) +
				" threw an exception getting an empty attribute list" );
		}
		
		try
		{
			final String	notFoundName	= "bogus." + System.currentTimeMillis();
			attrs	= conn.getAttributes( objectName, new String[] { notFoundName });
			if ( attrs == null )
			{
				warning( "MBean " + quote( objectName ) +
					" returned NULL for a missing Attribute" );
				valid	= false;
			}
			else if ( attrs.size() != 0 )
			{
				warning( "MBean " + quote( objectName ) +
					" returned attributes for a non-existent name" );
				valid	= false;
			}
		}
		catch( Exception e )
		{
			valid	= false;
			
			warning( "MBean " + quote( objectName ) +
				" threw an exception when getAttributes() was called with a " +
				"non-existent Attribute, exception class = " +
				e.getClass().getName() );
		}
		
		return( valid );
	}
	
		private boolean
	validateAttributeTypes(
		final ObjectName			objectName,
		final AttributeList			attrs,
		final MBeanAttributeInfo[]	attrInfos)
		throws Exception
	{
		boolean	valid	= true;
		
		final Map<String,MBeanAttributeInfo>	attrInfosMap	= JMXUtil.attributeInfosToMap( attrInfos );
		
		final Iterator	iter	= attrs.iterator();
		while ( iter.hasNext() )
		{
			final Attribute	attr	= (Attribute)iter.next();
			
			final String	name	= attr.getName();
			final Object	value	= attr.getValue();
			final MBeanAttributeInfo	attrInfo = (MBeanAttributeInfo)attrInfosMap.get( name );
			if ( attrInfo == null )
			{
				valid	= false;
				warning( "MBean " + objectName + " returned an Attribute not " +
					"declared in its MBeanInfo: " + name );
			}
			else if ( value != null )
			{
				final String	typeName	= attrInfo.getType();
				final Class<?>		infoClass	= ClassUtil.getClassFromName( typeName );
				final Class<?>		valueClass	= value.getClass();
				
				if ( infoClass == null )
				{
					valid	= false;
					warning( "Can't find class for: " + typeName );
				}
				else if ( ! infoClass.isAssignableFrom( valueClass ) )
				{
					final Class<?> objectClass	= ClassUtil.PrimitiveClassToObjectClass( infoClass );
					
					if ( ! objectClass.isAssignableFrom( valueClass ) )
					{
						valid	= false;
						warning( "MBean " + objectName + " returned Attribute " +
							name + "=" + value +
							" of class " + value.getClass().getName() +
							" not matching its MBeanInfo: " + infoClass.getName() );
					}
				}
			}
		}
		
		return( valid );
	}
	
		private boolean
	validateAttributes(
		final ObjectName			objectName,
		final MBeanAttributeInfo[]	attrInfos)
		throws Exception
	{
		boolean	valid	= true;
		
		final MBeanAttributeInfo[]	readableInfos	= JMXUtil.filterAttributeInfos( attrInfos,
				ReadWriteAttributeFilter.READABLE_FILTER );
		final String[]	attrNames	= JMXUtil.getAttributeNames( readableInfos );
		Arrays.sort( attrNames  );
		
		if ( attrNames.length != 0 )
		{
			// if we can fetch all the attributes, then the MBean is OK;
			// try this first for efficiency
			try
			{
                //trace( objectName.getKeyProperty( "j2eeType" ) + ": " + attrNames.length );
				final AttributeList	attrs	= getConnection().getAttributes( objectName, attrNames );
				
				if ( attrs == null )
				{
					warning( "MBean " + quote( objectName ) + " returned NULL for its AttributeList" );
					valid	= false;
				}
				else if ( attrs.size() != readableInfos.length )
				{
					// mismatch between claimed number of attributes and actual
					final ArrayStringifier	as	= new ArrayStringifier( ", ", true );
					final String claimedString	= as.stringify( attrNames );
					
					final Set<String>	actualSet	= JMXUtil.attributeListToValueMap( attrs ).keySet();
					final Set<String>	missingSet	= ArrayConversion.arrayToSet( attrNames );
					missingSet.removeAll( actualSet );
					
					final String[]	missingNames	= (String[])ArrayConversion.setToArray( missingSet, true );
					Arrays.sort( missingNames  );
					final String	missingString	= as.stringify( missingNames );
					
					warning( "MBean " + quote( objectName ) +
						" did not supply the " +
						missingNames.length + " attributes " + missingString );
				}
				
				valid	= validateAttributeTypes( objectName, attrs, readableInfos  );
			}
			catch( Exception e )
			{
				trace( SECTION_LINE );
				final String	msg	= "getAttributes() failed on " + quote( objectName ) + ", exception =\n" + e;
				
				if ( e instanceof NotSerializableException )
				{
					warning( msg );
				}
				else
				{
					warning( msg );
					valid	= false;
				}
				// do them one-at-a time to see where failure occurs
				final Map<String,Throwable>	failures	= new HashMap<String,Throwable>();
				final Map<String,Throwable>	warnings	= new HashMap<String,Throwable>();
				
				validateAttributesSingly( objectName, attrNames, failures, warnings );
				
				trace( "Validating attributes one-at-a-time using getAttribute() for " + quote( objectName ));
				if ( failures.size() == 0 && warnings.size() == 0 )
				{
					warning( " during getAttributes(" +
						ArrayStringifier.stringify( attrNames, "," ) + ") for: " + objectName +
						" (but Attributes work when queried one-at-a-time).\nIt " +
						getExceptionMsg( e ) );
				}
				
				if ( failures.size() != 0 )
				{
					displayAttributeFailuresOrWarnings( true, objectName, readableInfos, failures );
				}
				
				if ( warnings.size() != 0 )
				{
					displayAttributeFailuresOrWarnings( false, objectName, readableInfos, warnings );
				}
				
				trace( SECTION_LINE );
			}
		}
		else
		{
			valid	= true;
		}
		
		if ( ! validateUniqueAttributeNames( objectName, attrInfos ) )
		{
			valid	= false;
		}
		
		if ( ! validateMissingAndEmptyAttributeNames( objectName ) )
		{
			valid	= false;
		}
		
		return( valid );
	}
	
		void
	checkObjectNameReturnValue(
		MBeanServerConnection	conn,
		ObjectName				callee,
		MBeanOperationInfo		operationInfo,
		ObjectName				resultOfCall )
		throws Exception
	{
		try
		{
			printVerbose( "checking MBean info for: " + resultOfCall );
			final MBeanInfo	mbeanInfo	= conn.getMBeanInfo( resultOfCall );
		}
		catch( InstanceNotFoundException e )
		{
			trace( "WARNING: MBean " + resultOfCall + " returned from " +
				operationInfo.getReturnType() + " " + operationInfo.getName() + "() does not exist" );
				
		}
		catch( Exception e )
		{
			trace( "WARNING: MBean " + resultOfCall + " returned from " +
				operationInfo.getReturnType() + " " + operationInfo.getName() +
				"() can't supply MBeanInfo: " + getExceptionMsg( e )
				);
			
			if ( e instanceof IOException )
			{
				throw (IOException)e;
			}
		}
	}
	
		void
	checkGetterResult(
		MBeanServerConnection	conn,
		ObjectName				callee,
		MBeanOperationInfo		operationInfo,
		Object					resultOfCall )
		throws Exception
	{
		if ( resultOfCall instanceof ObjectName )
		{
			final ObjectName	name	= (ObjectName)resultOfCall;
			
			checkObjectNameReturnValue( conn, callee, operationInfo, name );
		}
		else if ( resultOfCall instanceof ObjectName[] )
		{
			final ObjectName[]	names	= (ObjectName[])resultOfCall;
			
			for( int i = 0; i < names.length; ++i )
			{
				checkObjectNameReturnValue( conn, callee, operationInfo, names[ i ]);
			}
		}
	}
	
		private boolean
	validateGetters(
		final ObjectName			objectName,
		final MBeanOperationInfo[]	operationInfos )
		throws Exception
	{
		boolean	valid	= true;
		MBeanServerConnection conn	= getConnection();
		
		for( int i = 0; i < operationInfos.length; ++i )
		{
			final MBeanOperationInfo	info	= operationInfos[ i ];
			
			if ( JMXUtil.isGetter( info ) )
			{
				boolean	opValid	= false;
					
				try
				{
					printVerbose( "invoking getter: " + info.getName() + "()" );
					final Object	result	= conn.invoke( objectName, info.getName(), null, null );
					
					checkGetterResult( conn, 
					objectName, info, result );
				}
				catch( Exception e )
				{
					warning( "Failure: calling " + info.getName() + "() on " + objectName +
						": " + getExceptionMsg( e ) );
						
					if ( e instanceof IOException)
					{
						throw( (IOException)e );
					}
					valid	= false;
				}
			}
		}
		
		return( valid );
	}
	

	
		boolean
	shouldPrintStackTraces()
	{
		return( true );
	}
	
	
		private boolean
	validate( final ObjectName objectName )
		throws Exception
	{
		boolean	valid	= true;

		MBeanServerConnection conn	= getConnection();
		
		MBeanInfo	info	= null;
		try
		{
			info	= conn.getMBeanInfo( objectName );
		}
		catch( Exception e )
		{
			valid	= false;
			warning( " during getMBeanInfo() for: " + objectName + "\n" +
				" message = " + e.getMessage() );
			// abort--the connection has died
			throw e;
		}
		
		if ( mDoInfo && ! validateMBeanInfo( objectName, info ) )
		{
			trace( "validateMBeanInfo failed for: " + objectName );
			valid	= false;
		}
		
		
		if ( mDoAttributes &&
			! validateAttributes( objectName, info.getAttributes() ) )
		{
			trace( "validateAttributes failed for: " + objectName );
			valid	= false;
		}
		
		if ( mDoOperations &&
			! validateGetters( objectName, info.getOperations() ) )
		{
			trace( "validateGetters failed for: " + objectName );
			valid	= false;
		}
		
		return( valid );
	}
	
	
		private void
	validate( final ObjectName[] objectNames )
		throws Exception
	{
		int	failureCount	= 0;
		
		trace( "Validating: " );
		if ( mDoInfo )
		{
			trace( "- MBeanInfo" );
		}
		if ( mDoAttributes )
		{
			trace( "- Attributes" );
		}
		if ( mDoOperations )
		{
			trace( "- Operations (getters)" );
		}
		
		trace( "" );
		
		for( int i = 0; i < objectNames.length; ++i )
		{
			final ObjectName	objectName	= objectNames[ i ];
			
			printVerbose( "Validating: " + objectName );
			
			if ( ! shouldTest( objectName ) )
			{
				notTested( objectName );
				continue;
			}
			
			final boolean	valid	= validate( objectName );
			if ( ! valid )
			{
				++failureCount;
			}
		}
		
		trace( "Total mbeans failing: " + failureCount );
	}
	
	
	
		public void
	testGenerically()
		throws Exception
	{
		final Set<ObjectName>	all	= getTestUtil().getAllObjectNames();
		
		final ObjectName[]	allObjectNames	= new ObjectName[ all.size() ];
		all.toArray( allObjectNames );
		validate( allObjectNames );
	}
	
}


