FileDocCategorySizeDatePackage
ProxyFactory.javaAPI DocGlassfish v2 API21677Fri May 04 22:30:32 BST 2007com.sun.appserv.management.client

ProxyFactory.java

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

import java.util.Collection;
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Collections;
import java.io.IOException;
import java.lang.reflect.Proxy;

import javax.management.MBeanServerConnection;
import javax.management.InstanceNotFoundException;
import javax.management.ObjectName;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.JMException;
import javax.management.relation.MBeanServerNotificationFilter;
import javax.management.MBeanServerNotification;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectionNotification;

import com.sun.appserv.management.DomainRoot;
import com.sun.appserv.management.base.AMX;
import com.sun.appserv.management.base.AMXAttributes;
import com.sun.appserv.management.base.XTypes;
import com.sun.appserv.management.base.Util;
import com.sun.appserv.management.util.jmx.MBeanProxyHandler;
import com.sun.appserv.management.util.jmx.MBeanServerConnectionSource;
import com.sun.appserv.management.util.jmx.MBeanServerConnectionConnectionSource;
import com.sun.appserv.management.util.misc.ClassUtil;
import com.sun.appserv.management.util.misc.TypeCast;

import com.sun.appserv.management.client.ConnectionSource;
import com.sun.appserv.management.client.handler.ConverterHandlerFactory;
import com.sun.appserv.management.client.handler.ConverterHandlerUtil;
import com.sun.appserv.management.client.handler.ProxyCache;

import com.sun.appserv.management.util.misc.GSetUtil;
import com.sun.appserv.management.util.misc.ExceptionUtil;
import com.sun.appserv.management.util.jmx.JMXUtil;

import com.sun.appserv.management.helper.AMXDebugHelper;

/**
	Factory for {@link AMX} proxies.
	Usually proxies are obtained by starting with the DomainRoot obtained via
	{@link AppserverConnectionSource#getDomainRoot}.
	
	@see com.sun.appserv.management.client.AppserverConnectionSource
 */
public final class ProxyFactory implements NotificationListener
{
	private final ProxyCache		mProxyCache;
	private final ConnectionSource	mConnectionSource;
	private final ObjectName		mDomainRootObjectName;
	private final DomainRoot		mDomainRoot;
	private final String			mMBeanServerID;
    
    private static final AMXDebugHelper mDebug  =
        new AMXDebugHelper( "com.sun.appserv.management.client.ProxyFactory" );
    private static void debug( final Object... args ) { mDebug.println( args ); }
	
	private static final Map<MBeanServerConnection,ProxyFactory> INSTANCES	=
	    Collections.synchronizedMap( new HashMap<MBeanServerConnection,ProxyFactory>() );
    
    /**
        Because ProxyFactory is used on both client and server, emitting anything to stdout
        or to the log is unacceptable in some circumstances.  Warnings remain available
        if the AMX-DEBUG system property allows it.
     */
        private static void
    warning( final Object... args )
    {
        debug( args );
    }
	
		private
	ProxyFactory( final ConnectionSource connSource )
	{
        mDebug.setEchoToStdOut( true );
        
		assert( connSource != null );
		
		mConnectionSource	= connSource;
		mProxyCache			= new ProxyCache();
		
		try
		{
			final MBeanServerConnection	conn	= getConnection();
			
			mMBeanServerID		= JMXUtil.getMBeanServerID( conn );
				
			mDomainRootObjectName	= initDomainRootObjectName();
			mDomainRoot				= initDomainRoot();
			
			// we should always be able to listen to MBeans--
			// but the http connector does not support listeners
			try
			{
				final MBeanServerNotificationFilter	filter	=
					new MBeanServerNotificationFilter();
				filter.enableAllObjectNames();
				filter.disableAllTypes();
				filter.enableType( MBeanServerNotification.UNREGISTRATION_NOTIFICATION );
				
				JMXUtil.listenToMBeanServerDelegate( conn, this, filter, null );
			}
			catch( Exception e )
			{
				warning( "ProxyFactory: connection does not support notifications: ",
                    mMBeanServerID, connSource);
			}
			
			// same idea as above, this time we want to listen to connection died
			// plus there may not be a JMXConnector involved
			final JMXConnector	connector	= connSource.getJMXConnector( false );
			if ( connector != null )
			{
				try
				{
					connector.addConnectionNotificationListener( this, null, null );
				}
				catch( Exception e )
				{
					warning("addConnectionNotificationListener failed: ",
                        mMBeanServerID, connSource, e);
				}
			}
		}
		catch( Exception e )
		{
			warning( "ProxyFactory.ProxyFactory:\n", e );
			throw new RuntimeException( e );
		}
	}
	
	
	/**
		The connection is bad.  Tell each proxy its gone and remove it.
	 */
		private void
	connectionBad()
	{
        final Set<AMX>   proxies  = new HashSet<AMX>();
        proxies.addAll( mProxyCache.values() );
        mProxyCache.clear();
        
        for( final AMX proxy : proxies )
        {
            ConverterHandlerUtil.connectionBad( proxy );
        }
	}
	
	/**
		Verify that the connection is still alive.
	 */
		public boolean
	checkConnection()
	{
		boolean	connectionGood	= true;
		
		try
		{
			getConnection().isRegistered( JMXUtil.getMBeanServerDelegateObjectName() );
			connectionGood	= true;
		}
		catch( Exception e )
		{
			connectionBad();
		}
		
		return( connectionGood );
	}
	

		void
	notifsLost()
	{
		// should probably check each proxy for validity, but not clear if it's important...
	}
	
	/**
		Listens for MBeanServerNotification.UNREGISTRATION_NOTIFICATION and
		JMXConnectionNotification and takes appropriate action.
		<br>
	    Used internally as callback for {@link javax.management.NotificationListener}.
	    <b>DO NOT CALL THIS METHOD</b>.
	 */
		public void
	handleNotification(
		final Notification	notifIn, 
		final Object		handback) 
	{
		final String	type	= notifIn.getType();
		
		if ( type.equals( MBeanServerNotification.UNREGISTRATION_NOTIFICATION)  )
		{
			final MBeanServerNotification	notif	= (MBeanServerNotification)notifIn;
			final ObjectName	objectName	= notif.getMBeanName();
			final AMX proxy	= getProxy( objectName, AMX.class, false );
			mProxyCache.remove( objectName );
			ConverterHandlerUtil.targetUnregistered(proxy);
			
			//debug( "ProxyFactory.handleNotification: UNREGISTERED: ", objectName );
		}
		else if ( notifIn instanceof JMXConnectionNotification )
		{
			if ( type.equals( JMXConnectionNotification.CLOSED ) ||
				type.equals( JMXConnectionNotification.FAILED ) )
			{
                debug( "ProxyFactory.handleNotification: connection closed or failed: ", notifIn);
				connectionBad();
			}
			else if ( type.equals( JMXConnectionNotification.NOTIFS_LOST ) )
			{
                debug( "ProxyFactory.handleNotification: notifications lost: ", notifIn);
				notifsLost();
			}
		}
		else
		{
			debug( "ProxyFactory.handleNotification: UNKNOWN notification: ", notifIn );
		}
	}
	
		private ObjectName
	initDomainRootObjectName()
		throws IOException
	{
		final MBeanServerConnection	conn	= mConnectionSource.getMBeanServerConnection( false );
		
		final String	patternString	= "*:" +
							AMX.J2EE_TYPE_KEY + "=" + XTypes.DOMAIN_ROOT + ",*";
		
		ObjectName	pattern	= null;
		try
		{
			pattern	= new ObjectName( patternString );
		}
		catch ( MalformedObjectNameException e )
		{
            warning( "initDomainRootObjectName: impossible failure", e );
			assert( false );	// can't happen
			throw new RuntimeException( e );
		}
		
		final Set<ObjectName>	objectNames	= JMXUtil.queryNames( conn, pattern, null );
		if ( objectNames.size() != 1 )
		{
            warning( "Can't find DomainRoot MBean using pattern: ", pattern);
			throw new IllegalArgumentException( "Can't find DomainRoot MBean using pattern " + pattern );
		}

		final ObjectName	objectName	= GSetUtil.getSingleton( objectNames );
		
		return( objectName );
	}
	
	private final static String	DOMAIN_ROOT_KEY	= "DomainRoot";
	
		public DomainRoot
	createDomainRoot( )
		throws IOException
	{
		return( mDomainRoot );
	}
	
		public DomainRoot
	initDomainRoot( )
		throws IOException
	{
		final ObjectName	domainRootObjectName	= getDomainRootObjectName( );
		
		final DomainRoot	domainRoot	= (DomainRoot)
			newProxyInstance(domainRootObjectName, new Class[] { DomainRoot.class });
		
		return( domainRoot );
	}
	
	/**
	    Return the DomainRoot.  AMX may not yet be fully 
	    initialized; call getDomainRoot( true ) if AMX
	    must be initialized upon return.
	    
		@return the DomainRoot for this factory.
	 */
		public DomainRoot
	getDomainRoot( )
	{
		return getDomainRoot( false );
	}
	
	/**
	    If 'waitReady' is true, then upon return AMX
	    is guaranteed to be fully loaded.  Otherwise
	    AMX MBeans may continue to initialize asynchronously.
	    
	    @param waitReady
		@return the DomainRoot for this factory.
	 */
		public DomainRoot
	getDomainRoot( boolean waitReady )
	{
	    if ( waitReady )
	    {
	        mDomainRoot.waitAMXReady();
	    }
	    
		return( mDomainRoot );
	}
	
	
	/**
		@return the ConnectionSource used by this factory
	 */
		public ConnectionSource
	getConnectionSource()
	{
		return( mConnectionSource );
	}
	
	/**
		@return the JMX MBeanServerID for the MBeanServer in which MBeans reside.
	 */
		public String
	getMBeanServerID()
	{
		return( mMBeanServerID );
	}
	
	/**
		Return the ObjectName for the DomainMBean.
	 */
		public ObjectName
	getDomainRootObjectName()
		throws IOException
	{
		return( mDomainRootObjectName );
	}
	
	/**
		Get an instance of the ProxyFactory for the MBeanServer.  Generally
		not applicable for remote clients.
		
		@param server
	 */
		public static ProxyFactory
	getInstance( final MBeanServer server )
	{
		return( getInstance( new MBeanServerConnectionSource( server ), true ) );
	}
	
	/**
		Get an instance of the ProxyFactory for the MBeanServerConnection.
		Creates a ConnectionSource for it and calls getInstance( connSource, true ).
	 */
		public static ProxyFactory
	getInstance( final MBeanServerConnection conn )
	{
		return( getInstance( new MBeanServerConnectionConnectionSource( conn ), true ) );
	}
	
	/**
		Calls getInstance( connSource, true ).
	 */
		public static ProxyFactory
	getInstance( final ConnectionSource conn )
	{	
		return( getInstance( conn, true ) );
	}
	
	/**
		Get an instance.  If 'useMBeanServerID' is false, and
		the ConnectionSource is not one that has been passed before, a new ProxyFactory
		is instantiated which will not share its proxies with any previously-instantiated
		ones.  Such usage is discouraged, as it duplicates proxies.  Pass 'true' unless
		there is an excellent reason to pass 'false'.
		
		@param connSource			the ConnectionSource
		@param useMBeanServerID		use the MBeanServerID to determine if it's the same server
	 */
		public static synchronized ProxyFactory
	getInstance(
		final ConnectionSource	connSource,
		final boolean			useMBeanServerID )
	{
		ProxyFactory	instance	= findInstance( connSource );
		
		if ( instance == null )
		{
			try
			{
				// match based on the MBeanServerConnection; different
				// ConnectionSource instances could wrap the same connection
				final MBeanServerConnection	conn =
					connSource.getMBeanServerConnection( false );
				
				instance	= findInstance( conn );
				
				// if not found, match based on MBeanServerID as requested, or if this
				// is an in-process MBeanServer
				if ( instance == null &&
					( useMBeanServerID  || connSource instanceof MBeanServerConnectionSource ) )
				{
					final String	id	= JMXUtil.getMBeanServerID( conn );
					instance	= findInstanceByID( id );
				}
			
				if ( instance == null )
				{
                    debug( "Creating new ProxyFactory for ConnectionSource / conn", connSource, conn );
					instance	= new ProxyFactory( connSource );
					INSTANCES.put( conn, instance );
				}
			}
			catch( Exception e )
			{
				warning( "ProxyFactory.getInstance: failure creating ProxyFactory: ", e );
				throw new RuntimeException( e );
			}
		}
		
		return( instance );
	}
	
	/**
		@return ProxyFactory corresponding to the ConnectionSource
	 */
		public static synchronized ProxyFactory
	findInstance( final ConnectionSource conn )
	{
		return( INSTANCES.get( conn ) );
	}
	
	/**
		@return ProxyFactory corresponding to the MBeanServerConnection
	 */
		public static synchronized ProxyFactory
	findInstance( final MBeanServerConnection conn )
	{
		ProxyFactory	instance	= null;
		
		final Collection<ProxyFactory> values	= INSTANCES.values();
		for( final ProxyFactory factory : values )
		{
			if ( factory.getConnectionSource().getExistingMBeanServerConnection( ) == conn )
			{
				instance	= factory;
				break;
			}
		}
		return( instance );
	}
	
	
	/**
		@return ProxyFactory corresponding to the MBeanServerID
	 */
		public static synchronized ProxyFactory
	findInstanceByID( final String mbeanServerID )
	{
		ProxyFactory	instance	= null;
		
		final Collection<ProxyFactory> values	= INSTANCES.values();
		for( final ProxyFactory factory : values )
		{
			if ( factory.getMBeanServerID().equals( mbeanServerID ) )
			{
				instance	= factory;
				break;
			}
		}
		
		return( instance );
	}
	
    
	/**
		@return an appropriate {@link AMX} interface for the ObjectName
		@deprecated use versions that take a class as a parameter
	 */
		public AMX
	getProxy( final ObjectName	objectName )
	{
	    return getProxy( objectName, true );
	}
		
	/**
		Get any existing proxy, returning null if none exists and 'create' is false.
		
		@param objectName	ObjectName for which a proxy should be created
		@param create		true to create the proxy, false to return existing value
		@return an appropriate {@link AMX} interface for the ObjectName
		@deprecated use versions that take a class as a parameter
	 */
		public AMX
	getProxy( final ObjectName	objectName, boolean create )
	{
	    return getProxy( objectName, AMX.class, create );
	}
	
	/**
	    The actual interface(s) that the proxy implements are predetermined.
	    Specifying the interface ties the return type to the interface at compile-time
	    but has no effect on the actual interfaces that are implemented by
	    the proxy.
		@return an appropriate {@link AMX} interface for the ObjectName
	 */
		public synchronized <T extends AMX> T
	getProxy(
	    final ObjectName	objectName,
	    final Class<T>      theInterface )
	{
		return getProxy( objectName, theInterface, true );
	}
	
	/**
		Get any existing proxy, returning null if none exists and 'create' is false.
		
		@param objectName	ObjectName for which a proxy should be created
		@param create		true to create the proxy, false to return existing value
		@param theClass     class of returned proxy, avoids casts and compiler warnings
		@return an appropriate {@link AMX} interface for the ObjectName
	 */
		public synchronized <T extends AMX> T
	getProxy(
	    final ObjectName	objectName,
	    Class<T>            theClass,
	    boolean             create )
	{
		AMX	proxy	= mProxyCache.getCachedProxy( objectName );
		
		if ( proxy == null && create )
		{
			proxy	= createProxy( objectName );
		}
		return theClass.cast( proxy );
	}
	
	/**
		@return MBeanServerConnection used by this factory
	 */
		protected MBeanServerConnection
	getConnection()
		throws IOException
	{
		return( getConnectionSource().getMBeanServerConnection( false ) );
	}
	
	/**
		Create a new proxy.  When a new proxy is created, its parent is
		required, which could cause a chain of proxies to be created.
		@param ObjectName
	 */
		private AMX
	createProxy( final ObjectName	objectName  )
	{
		AMX proxy				= null;
		
		try
		{
			String		proxyInterfaceName	= null;
			Class	proxyInterface		= null;
			
			proxyInterfaceName	= (String)
				getConnection().getAttribute( objectName, AMXAttributes.ATTR_INTERFACE_NAME );
			
			proxyInterface	= ClassUtil.getClassFromName( proxyInterfaceName );
					
			proxy	=  newProxyInstance( objectName, new Class[] { proxyInterface } );
		}
		catch( IllegalArgumentException e )
		{
            debug( "createProxy", e );
			throw e;
		}
		catch( Exception e )
		{
            debug( "createProxy", e );
			throw new RuntimeException( e );
		}
				
		return( proxy );
	}
	
	    private MBeanProxyHandler
	createProxyHandler( final ObjectName objectName )
	    throws IOException
	{
	    return ConverterHandlerFactory.createHandler( mConnectionSource, objectName );
	}

		
	/**
		Instantiates a new proxy using the default AttributeNameMangler and with any desired number
		of interfaces.  If you want NotificationBroadcaster as one of the interfaces, you must
		supply it in the list.
		Use of this routine is discouraged in favor of
		{@link #getProxy}
		
		@param objectName			the target MBean which will be invoked by the proxy
		@param interfaceClasses	all interfaces the proxy should implement
		
		@return the new Proxy implementing the specified interface
	 */
		public AMX
	newProxyInstance(
		final ObjectName			objectName,
		final Class<?>[]			interfaceClasses )
		throws IOException
	{
		final MBeanProxyHandler	handler	= createProxyHandler( objectName );
		
		final ClassLoader		classLoader	= interfaceClasses[ 0 ].getClassLoader();
		
		final AMX proxy	= Util.asAMX(Proxy.newProxyInstance( classLoader, interfaceClasses, handler));
		if ( proxy != null )
		{
		    mProxyCache.remove( objectName );
			mProxyCache.cacheProxy( proxy );
		}
		
		return( proxy );
	}
	
		protected static String
	toString( final Object o )
	{
		return( com.sun.appserv.management.util.stringifier.SmartStringifier.toString( o ) );
	}
    
	/**
		Convert a Set of ObjectName to a Set of AMX.
		
		@return a Set of AMX from a Set of ObjectName.
	 */
		public Set<AMX>
	toProxySet( final Set<ObjectName> objectNames )
	{
		final Set<AMX>	s	= new HashSet<AMX>();
		
		for( final ObjectName objectName : objectNames )
		{
			try
			{
				final AMX	proxy	= getProxy( objectName, AMX.class, true );
				assert( ! s.contains( proxy ) );
				s.add( proxy );
			}
			catch( final Exception e )
			{
			    debug( "ProxyFactory.toProxySet: exception for MBean ",
                    objectName, " = ", ExceptionUtil.getRootCause( e ) );
			}
		}
		
		return( s );
	}
	
	/**
		Convert a Collection of ObjectName to a List of AMX.
		
		@return a List of AMX from a List of ObjectName.
	 */
		public List<AMX>
	toProxyList( final Collection<ObjectName> objectNames )
	{
		final List<AMX>	list	= new ArrayList<AMX>();
		
		for( final ObjectName objectName : objectNames )
		{
			try
			{
				final AMX	proxy	= getProxy( objectName, AMX.class, true );
				list.add( proxy );
			}
			catch( final Exception e )
			{
			    debug( "ProxyFactory.toProxySet: exception for MBean ",
                    objectName, " = ", ExceptionUtil.getRootCause( e ) );
			}
		}
		
		return( list );
	}
	
	/**
		Convert a Map of ObjectName, and convert it to a Map
		of AMX, with the same keys.
		
		@return a Map of AMX from a Map of ObjectName.
	 */
		public Map<String,AMX>
	toProxyMap(
		final Map<String,ObjectName>	objectNameMap )
	{
		final Map<String,AMX> resultMap	= new HashMap<String,AMX>();
		
		final Set<String>   keys    = objectNameMap.keySet();
		
		for( final String key : keys )
		{
			final ObjectName	objectName	= objectNameMap.get( key );
			
			try
			{
				final AMX	proxy	= getProxy( objectName, AMX.class, true );
				resultMap.put( key, proxy );
			}
			catch( final Exception e )
			{
			    debug( "ProxyFactory.toProxySet: exception for MBean ",
                    objectName, " = ", ExceptionUtil.getRootCause( e ) );
			}
		}
		
		return( resultMap );
	}
	
}