/* * (c) Rob Gordon 2005. */ package org.oddjob.jmx.client; import java.io.Serializable; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.AttributeNotFoundException; import javax.management.DynamicMBean; import javax.management.InstanceNotFoundException; import javax.management.IntrospectionException; import javax.management.InvalidAttributeValueException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanConstructorInfo; import javax.management.MBeanException; import javax.management.MBeanInfo; import javax.management.MBeanNotificationInfo; import javax.management.MBeanOperationInfo; import javax.management.MBeanServer; import javax.management.MBeanServerFactory; import javax.management.NotificationBroadcasterSupport; import javax.management.ObjectName; import javax.management.ReflectionException; import junit.framework.TestCase; import org.apache.commons.beanutils.DynaBean; import org.apache.commons.beanutils.DynaClass; import org.apache.commons.beanutils.DynaProperty; import org.apache.log4j.Logger; import org.oddjob.OddjobConsole; import org.oddjob.arooa.ArooaDescriptor; import org.oddjob.arooa.ArooaSession; import org.oddjob.arooa.ClassResolver; import org.oddjob.arooa.MockArooaDescriptor; import org.oddjob.arooa.MockArooaSession; import org.oddjob.arooa.MockClassResolver; import org.oddjob.arooa.beanutils.BeanUtilsPropertyAccessor; import org.oddjob.arooa.registry.Address; import org.oddjob.arooa.registry.BeanRegistry; import org.oddjob.arooa.registry.MockBeanRegistry; import org.oddjob.arooa.registry.Path; import org.oddjob.arooa.registry.ServerId; import org.oddjob.arooa.registry.SimpleBeanRegistry; import org.oddjob.arooa.standard.StandardArooaSession; import org.oddjob.describe.UniversalDescriber; import org.oddjob.jmx.RemoteOddjobBean; import org.oddjob.jmx.handlers.DynaBeanHandlerFactory; import org.oddjob.jmx.handlers.LogPollableHandlerFactory; import org.oddjob.jmx.handlers.ObjectInterfaceHandlerFactory; import org.oddjob.jmx.handlers.RemoteOddjobHandlerFactory; import org.oddjob.jmx.handlers.RunnableHandlerFactory; import org.oddjob.jmx.server.MockServerSession; import org.oddjob.jmx.server.OddjobMBean; import org.oddjob.jmx.server.OddjobMBeanFactory; import org.oddjob.jmx.server.ServerContext; import org.oddjob.jmx.server.ServerContextImpl; import org.oddjob.jmx.server.ServerInfo; import org.oddjob.jmx.server.ServerInterfaceManagerFactoryImpl; import org.oddjob.jmx.server.ServerModel; import org.oddjob.jmx.server.ServerModelImpl; import org.oddjob.logging.LogEnabled; import org.oddjob.logging.LogEvent; import org.oddjob.logging.LogLevel; import org.oddjob.util.MockThreadManager; /** * Test a ClientNode. */ public class ClientNodeTest extends TestCase { public static final Logger logger = Logger.getLogger(ClientNodeTest.class); /** * Fixture interface which contains the minimum * set of operations a server side MBean must implement. * */ public interface OJMBeanInternals extends RemoteOddjobBean { public String toString(); } int unique; /** * Fixture for the base class of an MBean which provides * a minimal implementation of an OddjobMBean. * */ public abstract class BaseMockOJMBean extends NotificationBroadcasterSupport implements OJMBeanInternals { int instance = unique++; protected final Set<ClientHandlerResolver<?>> handlerFactories = new HashSet<ClientHandlerResolver<?>>(); { handlerFactories.add(new RemoteOddjobHandlerFactory().clientHandlerFactory()); handlerFactories.add(new ObjectInterfaceHandlerFactory().clientHandlerFactory()); } public ServerInfo serverInfo() { return new ServerInfo( new Address(new ServerId(url()), new Path(id())), (ClientHandlerResolver[]) handlerFactories.toArray( new ClientHandlerResolver[0]) ); } public void noop() { } protected String url() { return "//test"; } protected String id() { return "x" + instance; } public String toString() { return "MockOJMBean."; } public String loggerName() { return "org.oddjob.TestLogger"; } } ///////////////////// simplest bean possible ///////////////// public interface SimpleMBean extends OJMBeanInternals { } public class Simple extends BaseMockOJMBean implements SimpleMBean { public String toString() { return "test"; } } private class OurArooaSession extends MockArooaSession { @Override public ArooaDescriptor getArooaDescriptor() { return new MockArooaDescriptor() { @Override public ClassResolver getClassResolver() { return new MockClassResolver() { @Override public Class<?> findClass(String className) { try { return Class.forName(className); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } }; } }; } } /** * Test the client can create a proxy for the simplest bean * possible. * * @throws Exception */ public void testSimple() throws Exception { Simple mb = new Simple(); MBeanServer mbs = MBeanServerFactory.createMBeanServer(); ObjectName on = new ObjectName("oddjob:name=whatever"); mbs.registerMBean(mb, on); ClientSessionImpl clientSession = new ClientSessionImpl( mbs, new DummyNotificationProcessor(), new OurArooaSession(), logger); Object proxy = clientSession.create(on); assertEquals("test", proxy.toString()); } /** * Test the same proxy instances are equal * * @throws Exception */ public void testEquals() throws Exception { Simple mb = new Simple(); MBeanServer mbs = MBeanServerFactory.createMBeanServer(); ObjectName on = new ObjectName("oddjob:name=whatever"); mbs.registerMBean(mb, on); ClientSessionImpl clientSession = new ClientSessionImpl( mbs, new DummyNotificationProcessor(), new OurArooaSession(), logger); Object proxy = clientSession.create(on); assertEquals(proxy, proxy); assertEquals(proxy.hashCode(), proxy.hashCode()); } //////////////////////// interfaces //////////////////////////// public interface MockRunnableMBean extends Runnable, OJMBeanInternals { } public class MockRunnable extends BaseMockOJMBean implements MockRunnableMBean { boolean ran; public MockRunnable() { handlerFactories.add(new RunnableHandlerFactory().clientHandlerFactory()); } public void run() { ran = true; } } private BeanRegistry cr; public void setUp() { logger.debug("================== Running " + getName() + "================"); System.setProperty("mx4j.log.priority", "trace"); cr = new SimpleBeanRegistry(); cr.register("this", this); } /** * Test running a proxy runs it's server side equivelant. * * @throws Exception */ public void testRunnable() throws Exception { MockRunnable mb = new MockRunnable(); MBeanServer mbs = MBeanServerFactory.createMBeanServer(); ObjectName on = new ObjectName("oddjob:name=whatever"); mbs.registerMBean(mb, on); ClientSessionImpl clientSession = new ClientSessionImpl( mbs, new DummyNotificationProcessor(), new OurArooaSession(), logger); Object proxy = clientSession.create(on); assertTrue("Runnable", proxy instanceof Runnable); ((Runnable) proxy).run(); assertTrue("Ran", mb.ran); } ///////////////// MBean getter setter tests ////////////////// // a bean with a property. public static class Fred implements Serializable { private static final long serialVersionUID = 20051117; public String getFruit() { return "apples"; } } // a dyna class with one property but supports adding any. public class MyDC implements DynaClass, Serializable { private static final long serialVersionUID = 20051117; public DynaProperty[] getDynaProperties() { return new DynaProperty[] { new DynaProperty("fred", Fred.class), // new DynaProperty("description", Map.class) }; } public DynaProperty getDynaProperty(String arg0) { return new DynaProperty(arg0); } public String getName() { return "MyDynaClass"; } public DynaBean newInstance() throws IllegalAccessException, InstantiationException { throw new UnsupportedOperationException("newInstance"); } } public class Bean implements DynaBean, LogEnabled { public boolean contains(String name, String key) { logger.debug("contains(" + name + ", " + key + ")"); return false; } public Object get(String name) { logger.debug("get(" + name + ")"); if ("fred".equals(name)) { return new Fred(); } else if ("description".equals(name)) { Map<Object, Object> m = new HashMap<Object, Object>(); m.put("fooled", "you"); return m; } return null; } public Object get(String name, int index) { logger.debug("get(" + name + ", " + index + ")"); return null; } public Object get(String name, String key) { logger.debug("get(" + name + ", " + key + ")"); return null; } public DynaClass getDynaClass() { logger.debug("getDynaClass"); return new MyDC(); } public void remove(String name, String key) { logger.debug("remove(" + name + ", " + key + ")"); } public void set(String name, int index, Object value) { logger.debug("set(" + name + ", " + index + ", " + value + ")"); } public void set(String name, Object value) { logger.debug("set(" + name + ", " + value + ")"); } public void set(String name, String key, Object value) { logger.debug("set(" + name + ", " + key + ", " + value + ")"); } public String loggerName() { return "org.oddjob.TestLogger"; } } public class MyDynamicMBean extends NotificationBroadcasterSupport implements DynamicMBean { public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException { logger.debug("MyDynamicMBean getting attribute [" + attribute + "]"); throw new UnsupportedOperationException(); } public AttributeList getAttributes(String[] attributes) { return null; } public MBeanInfo getMBeanInfo() { return new MBeanInfo(this.getClass().getName(), "Test MBean", new MBeanAttributeInfo[0], new MBeanConstructorInfo[0], new MBeanOperationInfo[0], new MBeanNotificationInfo[0]); } public Object invoke(String actionName, Object[] arguments, String[] signature) throws MBeanException, ReflectionException { logger.debug("MyDynamicMBean invoking [" + actionName + "]"); if ("toString".equals(actionName)) { return "MyDynamicMBean"; } else if ("serverInfo".equals(actionName)){ return new ServerInfo( new Address(new ServerId("//foo/"), new Path("whatever")), new ClientHandlerResolver[] { new ObjectInterfaceHandlerFactory().clientHandlerFactory(), new RemoteOddjobHandlerFactory().clientHandlerFactory(), new DynaBeanHandlerFactory().clientHandlerFactory() } ); } else if ("toString".equals(actionName)){ return "MyDynamicMBean"; } else if ("loggerName".equals(actionName)){ return "org.oddjob.TestLogger"; } else if ("getDynaClass".equals(actionName)) { return new MyDC(); } else if ("get".equals(actionName)) { return new Fred(); } else { throw new MBeanException( new UnsupportedOperationException("Unsupported Method [" + actionName + "]")); } } public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { throw new UnsupportedOperationException(); } public AttributeList setAttributes(AttributeList attributes) { throw new UnsupportedOperationException(); } } // this test uses a dyna bean get public void testSimpleGet() throws Exception { MyDynamicMBean firstBean = new MyDynamicMBean(); MBeanServer mbs = MBeanServerFactory.createMBeanServer(); ObjectName on = new ObjectName("oddjob:name=whatever"); mbs.registerMBean(firstBean, on); ClientSessionImpl clientSession = new ClientSessionImpl( mbs, new DummyNotificationProcessor(), new OurArooaSession(), logger); Object proxy = clientSession.create(on); assertNotNull(proxy); BeanUtilsPropertyAccessor propertyAccessor = new BeanUtilsPropertyAccessor(); String fruit = (String) propertyAccessor.getProperty(proxy, "fred.fruit"); assertEquals("apples", fruit); // test a component registry nested lookup // need to add a new level to create the new registry MyDynamicMBean nestedBean = new MyDynamicMBean(); ObjectName on2 = new ObjectName("oddjob:name=whatever2"); mbs.registerMBean(nestedBean, on2); } private class OurHierarchicalRegistry extends MockBeanRegistry { @Override public String getIdFor(Object component) { assertNotNull(component); return "x"; } } private class OurServerSession extends MockServerSession { ArooaSession session = new StandardArooaSession(); @Override public ArooaSession getArooaSession() { return session; } } // and this test uses an OddjobMBean get. public void testBean() throws Exception { try (OddjobConsole.Close close = OddjobConsole.initialise()) { Object o = new Bean(); ServerModel sm = new ServerModelImpl( new ServerId("//whatever"), new MockThreadManager(), new ServerInterfaceManagerFactoryImpl() ); ServerContext srvcon = new ServerContextImpl(o, sm, new OurHierarchicalRegistry()); Object mb = new OddjobMBean(o, OddjobMBeanFactory.objectName(0), new OurServerSession(), srvcon); MBeanServer mbs = MBeanServerFactory.createMBeanServer(); ObjectName on = new ObjectName("oddjob:name=whatever"); mbs.registerMBean(mb, on); ClientSessionImpl clientSession = new ClientSessionImpl( mbs, new DummyNotificationProcessor(), new OurArooaSession(), logger); Object proxy = clientSession.create(on); assertNotNull(proxy); ArooaSession session = new StandardArooaSession(); Map<String, String> map = new UniversalDescriber( session).describe(proxy); assertNotNull(map); BeanUtilsPropertyAccessor bubh = new BeanUtilsPropertyAccessor(); Object gotten = bubh.getProperty(proxy, "fred.fruit"); assertEquals("apples", gotten); } } ////// Logging Test public interface MockLoggingMBean extends OJMBeanInternals, LogPollable { } public class MockLogging extends BaseMockOJMBean implements MockLoggingMBean { public MockLogging() { handlerFactories.add(new LogPollableHandlerFactory().clientHandlerFactory()); } public LogEvent[] retrieveLogEvents(long from, int max) { return new LogEvent[] { new LogEvent("foo", 0, LogLevel.DEBUG, "Test") }; } public LogEvent[] retrieveConsoleEvents(long from, int max) { throw new RuntimeException("Unexpected."); } public String consoleId() { return "foo"; } public String url() { return super.url(); } } /** * Test retrieving log events. * * @throws Exception */ public void testLogging() throws Exception { MockLogging mb = new MockLogging(); MBeanServer mbs = MBeanServerFactory.createMBeanServer(); ObjectName on = new ObjectName("oddjob:name=whatever"); mbs.registerMBean(mb, on); beanDump(mbs, on); ClientSessionImpl clientSession = new ClientSessionImpl( mbs, new DummyNotificationProcessor(), new OurArooaSession(), logger); Object proxy = clientSession.create(on); assertTrue("Log Pollable", proxy instanceof LogPollable); LogPollable test = (LogPollable) proxy; assertEquals("url", "//test", test.url()); LogEvent[] events = test.retrieveLogEvents(-1L, 10); assertEquals("num events", 1, events.length); assertEquals("event", "Test", events[0].getMessage()); } /////////////////////////////////////////// static void beanDump(MBeanServer mbs, ObjectName on) throws ReflectionException, InstanceNotFoundException, IntrospectionException { MBeanInfo info = mbs.getMBeanInfo(on); MBeanOperationInfo[] opInfo = info.getOperations(); for (int i = 0; i < opInfo.length; ++i) { logger.debug("Op: " + opInfo[i].getName()); } MBeanAttributeInfo[] atInfo = info.getAttributes(); for (int i = 0; i < atInfo.length; ++i) { logger.debug("At: " + atInfo[i].getName()); } } }