package org.neo4j.kernel.impl.management;
import static java.lang.management.ManagementFactory.getPlatformMBeanServer;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.logging.Logger;
import javax.management.DynamicMBean;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanFeatureInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.StandardMBean;
import org.neo4j.kernel.impl.core.NodeManager;
import org.neo4j.kernel.impl.nioneo.xa.NeoStoreXaDataSource;
import org.neo4j.kernel.impl.transaction.LockManager;
import org.neo4j.kernel.impl.transaction.TxModule;
import org.neo4j.kernel.impl.transaction.XaDataSourceManager;
public class Neo4jMBean extends StandardMBean
{
private static final Logger log = Logger.getLogger( Neo4jMBean.class.getName() );
public static Runnable initMBeans( Creator creator )
{
Factory jmx = new Factory( getPlatformMBeanServer(), creator );
creator.create( jmx );
jmx.createKernelMBean( creator.kernelVersion );
return new JmxShutdown( jmx.beans );
}
public static <T> T getBean( int instanceId, Class<T> beanType )
{
if ( beanType.isInterface()
&& ( beanType.getPackage().getName().equals( "org.neo4j.kernel.management" )
|| beanType == DynamicMBean.class ) )
{
if ( PROXY_MAKER == null )
{
throw new UnsupportedOperationException(
"Creating Management Bean proxies requires Java 1.6" );
}
else
{
ObjectName name = getObjectName( instanceId, beanType, Configuration.class );
return PROXY_MAKER.makeProxy( name, beanType );
}
}
throw new IllegalArgumentException( "Not a Neo4j management bean: " + beanType );
}
public static abstract class Creator
{
private final int id;
private final String kernelVersion;
private final NeoStoreXaDataSource datasource;
protected Creator( int instanceId, String kernelVersion, NeoStoreXaDataSource datasource )
{
if ( kernelVersion == null || datasource == null )
{
throw new IllegalArgumentException( "null valued argument" );
}
this.id = instanceId;
this.kernelVersion = kernelVersion;
this.datasource = datasource;
}
protected abstract void create( Neo4jMBean.Factory jmx );
}
public static final class Factory
{
private final MBeanServer mbs;
private final Creator instance;
private final List<Neo4jMBean> beans = new ArrayList<Neo4jMBean>();
private Factory( MBeanServer mbs, Creator creator )
{
this.mbs = mbs;
this.instance = creator;
}
private void createKernelMBean( final String kernelVersion )
{
if ( !register( new Callable<KernelBean>()
{
public KernelBean call() throws Exception
{
return new KernelBean( instance.id, kernelVersion, instance.datasource,
getObjectName( instance.id, null, null ) );
}
} ) ) failedToRegister( "KernelBean" );
}
public void createPrimitiveMBean( final NodeManager nodeManager )
{
if ( !register( new Callable<PrimitivesBean>()
{
public PrimitivesBean call() throws NotCompliantMBeanException
{
return new PrimitivesBean( instance.id, nodeManager );
}
} ) ) failedToRegister( "PrimitiveMBean" );
}
public void createCacheMBean( final NodeManager nodeManager )
{
if ( !register( new Callable<CacheBean>()
{
public CacheBean call() throws NotCompliantMBeanException
{
return new CacheBean( instance.id, nodeManager );
}
} ) ) failedToRegister( "CacheMBean" );
}
public void createDynamicConfigurationMBean( final Map<Object, Object> params )
{
if ( !register( new Callable<Neo4jMBean>()
{
public Neo4jMBean call() throws NotCompliantMBeanException
{
return new Configuration( instance.id, params );
}
} ) ) failedToRegister( "ConfigurationMBean" );
}
public void createMemoryMappingMBean( XaDataSourceManager datasourceMananger )
{
final NeoStoreXaDataSource datasource = (NeoStoreXaDataSource) datasourceMananger.getXaDataSource( "nioneodb" );
if ( !register( new Callable<Neo4jMBean>()
{
public Neo4jMBean call()
{
return MemoryMappingBean.create( instance.id, datasource );
}
} ) ) failedToRegister( "MemoryMappingMBean" );
}
public void createXaManagerMBean( final XaDataSourceManager datasourceMananger )
{
if ( !register( new Callable<Neo4jMBean>()
{
public Neo4jMBean call()
{
return XaManagerBean.create( instance.id, datasourceMananger );
}
} ) ) failedToRegister( "XaManagerMBean" );
}
public void createTransactionManagerMBean( final TxModule txModule )
{
if ( !register( new Callable<TransactionManagerBean>()
{
public TransactionManagerBean call() throws NotCompliantMBeanException
{
return new TransactionManagerBean( instance.id, txModule );
}
} ) ) failedToRegister( "TransactionManagerMBean" );
}
public void createLockManagerMBean( final LockManager lockManager )
{
if ( !register( new Callable<LockManagerBean>()
{
public LockManagerBean call() throws NotCompliantMBeanException
{
return new LockManagerBean( instance.id, lockManager );
}
} ) ) failedToRegister( "LockManagerMBean" );
}
public void createStoreFileMBean()
{
File path;
try
{
path = new File( instance.datasource.getStoreDir() ).getCanonicalFile().getAbsoluteFile();
}
catch ( IOException e )
{
path = new File( instance.datasource.getStoreDir() ).getAbsoluteFile();
}
final File storePath = path;
if ( !register( new Callable<StoreFileBean>()
{
public StoreFileBean call() throws NotCompliantMBeanException
{
return new StoreFileBean( instance.id, storePath );
}
} ) ) failedToRegister( "StoreFileMBean" );
}
private boolean register( Callable<? extends Neo4jMBean> beanFactory )
{
Neo4jMBean bean;
try
{
bean = beanFactory.call();
}
catch ( Exception e )
{
return false;
}
bean = registerBean( mbs, bean );
if ( bean != null )
{
beans.add( bean );
return true;
}
return false;
}
}
private static Neo4jMBean registerBean( MBeanServer mbs, Neo4jMBean bean )
{
try
{
mbs.registerMBean( bean, bean.objectName );
return bean;
}
catch ( Exception e )
{
return null;
}
}
private static void failedToRegister( String mBean )
{
log.info( "Failed to register " + mBean );
}
private static final class JmxShutdown implements Runnable
{
private final Neo4jMBean[] beans;
public JmxShutdown( List<Neo4jMBean> beans )
{
this.beans = beans.toArray( new Neo4jMBean[beans.size()] );
}
public void run()
{
MBeanServer mbs = getPlatformMBeanServer();
for ( Neo4jMBean bean : beans )
{
unregisterBean( mbs, bean );
}
}
}
private static void unregisterBean( MBeanServer mbs, Neo4jMBean bean )
{
try
{
mbs.unregisterMBean( bean.objectName );
}
catch ( Exception e )
{
log.warning( "Failed to unregister JMX Bean " + bean );
e.printStackTrace();
}
}
private static final ProxyMaker PROXY_MAKER;
private static class ProxyMaker
{
private final Method isMXBeanInterface;
private final Method newMBeanProxy;
private final Method newMXBeanProxy;
ProxyMaker() throws Exception
{
Class<?> JMX = Class.forName( "javax.management.JMX" );
this.isMXBeanInterface = JMX.getMethod( "isMXBeanInterface", Class.class );
this.newMBeanProxy = JMX.getMethod( "newMBeanProxy", MBeanServerConnection.class,
ObjectName.class, Class.class );
this.newMXBeanProxy = JMX.getMethod( "newMXBeanProxy", MBeanServerConnection.class,
ObjectName.class, Class.class );
}
<T> T makeProxy( ObjectName name, Class<T> beanType )
{
try
{
final Method factoryMethod;
if ( isMXBeanInterface( beanType ) )
{
factoryMethod = newMXBeanProxy;
}
else
{
factoryMethod = newMBeanProxy;
}
return beanType.cast( factoryMethod.invoke( null, getPlatformMBeanServer(), name,
beanType ) );
}
catch ( InvocationTargetException exception )
{
throw launderRuntimeException( exception.getTargetException() );
}
catch ( Exception exception )
{
throw new UnsupportedOperationException(
"Creating Management Bean proxies requires Java 1.6", exception );
}
}
private boolean isMXBeanInterface( Class<?> interfaceClass ) throws Exception
{
return (Boolean) isMXBeanInterface.invoke( null, interfaceClass );
}
static RuntimeException launderRuntimeException( Throwable exception )
{
if ( exception instanceof RuntimeException )
{
return (RuntimeException) exception;
}
else if ( exception instanceof Error )
{
throw (Error) exception;
}
else
{
throw new RuntimeException( "Unexpected Exception!", exception );
}
}
}
private static final boolean SUPPORT_MX_BEAN;
static
{
ProxyMaker proxyMaker;
try
{
proxyMaker = new ProxyMaker();
}
catch ( Throwable t )
{
proxyMaker = null;
}
PROXY_MAKER = proxyMaker;
SUPPORT_MX_BEAN = proxyMaker != null;
}
static abstract class MXFactory<T extends Neo4jMBean>
{
abstract T createStandardMBean() throws NotCompliantMBeanException;
abstract T createMXBean();
final T createMBean()
{
try
{
return createStandardMBean();
}
catch ( NotCompliantMBeanException cause )
{
throw new IllegalArgumentException( cause );
}
}
}
static <T extends Neo4jMBean> T createMX( MXFactory<T> factory )
{
if ( SUPPORT_MX_BEAN )
{
return factory.createMXBean();
}
else
{
return factory.createMBean();
}
}
private final ObjectName objectName;
Neo4jMBean( int instanceId, Class<?> mbeanIface, boolean isMXBean )
{
super( mbeanIface, isMXBean );
objectName = getObjectName( instanceId, mbeanIface, getClass() );
if ( objectName == null )
{
throw new IllegalArgumentException( "" );
}
}
Neo4jMBean( int instanceId, Class<?> mbeanIface ) throws NotCompliantMBeanException
{
super( mbeanIface );
objectName = getObjectName( instanceId, mbeanIface, getClass() );
if ( objectName == null )
{
throw new IllegalArgumentException( "" );
}
}
Neo4jMBean( int instanceId ) throws NotCompliantMBeanException
{
super( DynamicMBean.class );
objectName = getObjectName( instanceId, DynamicMBean.class, getClass() );
}
private static ObjectName getObjectName( int instanceId, Class<?> iface, Class<?> clazz )
{
final String name;
if ( iface == null )
{
name = "*";
}
else if ( iface == DynamicMBean.class )
{
name = clazz.getSimpleName();
}
else
{
try
{
name = (String) iface.getField( "NAME" ).get( null );
}
catch ( Exception e )
{
return null;
}
}
StringBuilder identifier = new StringBuilder( "org.neo4j:" );
identifier.append( "instance=kernel#" );
identifier.append( instanceId );
identifier.append( ",name=" );
identifier.append( name );
try
{
return new ObjectName( identifier.toString() );
}
catch ( MalformedObjectNameException e )
{
return null;
}
}
@Override
protected String getDescription( MBeanInfo info )
{
Description description = getClass().getAnnotation( Description.class );
if ( description != null ) return description.value();
return super.getDescription( info );
}
@Override
protected String getDescription( MBeanAttributeInfo info )
{
Description description = describeMethod( info, "get", "is" );
if ( description != null ) return description.value();
return super.getDescription( info );
}
@Override
protected String getDescription( MBeanOperationInfo info )
{
Description description = describeMethod( info );
if ( description != null ) return description.value();
return super.getDescription( info );
}
@Override
protected int getImpact( MBeanOperationInfo info )
{
Description description = describeMethod( info );
if ( description != null ) return description.impact();
return super.getImpact( info );
}
private Description describeMethod( MBeanFeatureInfo info, String... prefixes )
{
if ( prefixes == null || prefixes.length == 0 )
{
try
{
return getClass().getMethod( info.getName() ).getAnnotation( Description.class );
}
catch ( Exception e )
{
return null;
}
}
else
{
for ( String prefix : prefixes )
{
try
{
return getClass().getMethod( prefix + info.getName() ).getAnnotation(
Description.class );
}
catch ( Exception e )
{
}
}
return null;
}
}
}