/*
* (c) Rob Gordon 2005.
*/
package org.oddjob.jmx;
import java.util.HashSet;
import java.util.Set;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import junit.framework.TestCase;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.oddjob.FailedToStopException;
import org.oddjob.Oddjob;
import org.oddjob.OddjobConsole;
import org.oddjob.OddjobLookup;
import org.oddjob.Resetable;
import org.oddjob.Stateful;
import org.oddjob.Stoppable;
import org.oddjob.Structural;
import org.oddjob.arooa.convert.ArooaConversionException;
import org.oddjob.arooa.registry.BeanDirectory;
import org.oddjob.arooa.registry.BeanRegistry;
import org.oddjob.arooa.registry.MockBeanDirectoryOwner;
import org.oddjob.arooa.registry.SimpleBeanRegistry;
import org.oddjob.arooa.standard.StandardArooaSession;
import org.oddjob.arooa.types.ArooaObject;
import org.oddjob.arooa.xml.XMLConfiguration;
import org.oddjob.jmx.client.ComponentTransportable;
import org.oddjob.jmx.server.OddjobMBeanFactory;
import org.oddjob.jobs.structural.ForEachJobTest;
import org.oddjob.jobs.structural.JobFolder;
import org.oddjob.logging.LogEnabled;
import org.oddjob.logging.LogEvent;
import org.oddjob.logging.LogHelper;
import org.oddjob.logging.LogLevel;
import org.oddjob.logging.LogListener;
import org.oddjob.monitor.context.ExplorerContext;
import org.oddjob.monitor.model.EventThreadLaterExecutor;
import org.oddjob.monitor.model.ExplorerContextFactory;
import org.oddjob.monitor.model.ExplorerModel;
import org.oddjob.monitor.model.JobTreeModel;
import org.oddjob.monitor.model.JobTreeNode;
import org.oddjob.monitor.model.MockExplorerContext;
import org.oddjob.monitor.model.MockExplorerModel;
import org.oddjob.state.ParentState;
import org.oddjob.state.ServiceState;
import org.oddjob.structural.ChildHelper;
import org.oddjob.structural.StructuralEvent;
import org.oddjob.structural.StructuralListener;
import org.oddjob.tools.OddjobTestHelper;
import org.oddjob.tools.StateSteps;
import org.oddjob.tools.WaitForChildren;
/**
* Test the JMXClientJob
*/
public class JMXClientJobTest extends TestCase {
static final Logger logger = Logger.getLogger(JMXClientJobTest.class);
/**
* Fixture
*/
public class ServerChild implements Structural {
ChildHelper<Object> childHelper = new ChildHelper<Object>(this);
String name;
ServerChild(String name) {
this.name = name;
}
public String toString() {
return name;
}
public void addStructuralListener(StructuralListener listener) {
childHelper.addStructuralListener(listener);
}
public void removeStructuralListener(StructuralListener listener) {
childHelper.removeStructuralListener(listener);
}
}
public void setUp() {
logger.debug("================== Running " + getName() + "================");
// System.setProperty("mx4j.log.priority", "trace");
}
private class OurSession extends StandardArooaSession {
SimpleBeanRegistry registry = new SimpleBeanRegistry();
@Override
public BeanRegistry getBeanRegistry() {
return registry;
}
}
/**
* Fixture to create a server job.
*
* @return A server job.
*/
JMXServerJob createServer() {
OurSession session = new OurSession();
ServerChild c1 = new ServerChild("test");
session.registry.register("c1", c1);
ServerChild test1 = new ServerChild("test1");
session.registry.register("test1", test1);
ServerChild test2 = new ServerChild("test2");
session.registry.register("test2", test2);
ServerChild test3 = new ServerChild("test3");
session.registry.register("test3", test3);
c1.childHelper.insertChild(0, test1);
c1.childHelper.insertChild(1, test2);
c1.childHelper.insertChild(2, test3);
session.registry.register("dummy", new Object());
JMXServerJob j = new JMXServerJob();
j.setRoot(c1);
j.setArooaSession(session);
j.setUrl("service:jmx:rmi://");
return j;
}
/**
* Thest running and stopping the client job.
*
* @throws Exception
*/
public void testRun() throws Exception {
try (OddjobConsole.Close close = OddjobConsole.initialise()) {
JMXServerJob server = createServer();
server.start();
JMXClientJob client = new JMXClientJob();
client.setArooaSession(new StandardArooaSession());
client.setConnection(server.getAddress());
client.run();
Object[] children = OddjobTestHelper.getChildren(client);
assertEquals("child", "test", children[0].toString());
Object[] children2 = OddjobTestHelper.getChildren((Structural) children[0]);
assertEquals(3, children2.length);
// got this far - must have worked.
client.stop();
server.stop();
assertEquals(ServiceState.STOPPED, client.lastStateEvent().getState());
}
}
/**
* Tracking down a problem with the next test.
* @throws ArooaConversionException
* @throws FailedToStopException
*/
public void testPrinciplesOfNextTest() throws ArooaConversionException, FailedToStopException {
String xml =
"<oddjob id='this' xmlns:jmx='http://rgordon.co.uk/oddjob/jmx'>" +
"<job>" +
" <jmx:server id='server' " +
" name='X'" +
" root='${server}' " +
" url='service:jmx:rmi://'/>" +
"</job></oddjob>";
Oddjob oj = new Oddjob();
oj.setConfiguration(new XMLConfiguration("XML", xml));
oj.run();
String address = new OddjobLookup(oj).lookup(
"server.address", String.class);
assertNotNull(address);
JMXClientJob client = new JMXClientJob();
client.setArooaSession(new StandardArooaSession());
client.setConnection(address);
client.run();
Object[] children = OddjobTestHelper.getChildren(client);
assertEquals(1, children.length);
assertEquals("X", children[0].toString());
client.stop();
client.hardReset();
client.run();
children = OddjobTestHelper.getChildren(client);
assertEquals(1, children.length);
assertEquals("X", children[0].toString());
client.destroy();
oj.destroy();
}
/**
* This test creates a server and then attempts to
* connect and disconnect multiple times
*/
public void testRunLotsOfClients() throws Exception {
String xml =
"<oddjob id='this' xmlns:jmx='http://rgordon.co.uk/oddjob/jmx'>" +
" <job>" +
" <jmx:server id='server' root='${this}' url='service:jmx:rmi://'/>" +
" </job>" +
"</oddjob>";
Oddjob oj = new Oddjob();
oj.setConfiguration(new XMLConfiguration("XML", xml));
oj.run();
String address = new OddjobLookup(oj).lookup(
"server.address", String.class);
assertNotNull(address);
Thread[] threads = new Thread[10];
final boolean[] ok = new boolean[10];
for (int i = 0; i < 10; i++) {
logger.debug("************ " + i + " *************");
final JMXClientJob client = new JMXClientJob();
client.setArooaSession(new StandardArooaSession());
client.setConnection(address);
final int index = i;
Thread t2 = new Thread(
new Runnable() {
public void run() {
client.run();
WaitForChildren wait = new WaitForChildren(client);
wait.waitFor(1);
try {
client.stop();
} catch (FailedToStopException e) {
throw new RuntimeException(e);
}
if (OddjobTestHelper.getJobState(client) == ServiceState.STOPPED) {
ok[index] = true;
}
}
});
threads[i] = t2;
t2.start();
}
for (int i = 0; i < 10; ++i) {
threads[i].join();
}
logger.debug("stopping server");
oj.stop();
for (int i = 0; i < 10; ++i) {
if (!ok[i]) {
fail("" + i + "failed.");
}
}
}
// test looking up a property on a server using a path.
public void testLookup() throws Exception {
Oddjob server = new Oddjob();
server.setConfiguration(
new XMLConfiguration("Resource",
this.getClass().getResourceAsStream("server1.xml")));
server.run();
String address = new OddjobLookup(server).lookup(
"server.address", String.class);
assertNotNull(address);
Oddjob client = new Oddjob();
client.setConfiguration(
new XMLConfiguration("Resource",
this.getClass().getResourceAsStream("client1.xml")));
client.setArgs(new String[] { address });
client.run();
client.stop();
server.stop();
assertEquals(ParentState.COMPLETE, client.lastStateEvent().getState());
assertEquals(ParentState.COMPLETE, server.lastStateEvent().getState());
}
public static class Echo {
Object echo;
public Object getEchoWrapped() {
logger.debug("returning [" + echo + "]");
return new ComponentTransportable(
OddjobMBeanFactory.objectName(1));
}
public void setEcho(Object echo) {
logger.debug("setting [" + echo + "]");
this.echo = echo;
}
}
public void testHostRelative() throws Exception {
try (OddjobConsole.Close close = OddjobConsole.initialise()) {
OurSession serverSession = new OurSession();
Echo e = new Echo();
serverSession.registry.register("echo", e);
final JMXServerJob server = new JMXServerJob();
server.setRoot(e);
server.setArooaSession(serverSession);
server.setUrl("service:jmx:rmi://");
OurSession clientSession = new OurSession();
JMXClientJob client = new JMXClientJob();
clientSession.registry.register("client", client);
client.setArooaSession(clientSession);
server.start();
client.setConnection(server.getAddress());
client.run();
DynaBean bean = (DynaBean) clientSession.registry.lookup("client/echo");
assertNotNull(bean);
bean.set("echo", bean);
Object echo = bean.get("echoWrapped");
assertEquals(bean, echo);
client.stop();
server.stop();
assertEquals(ServiceState.STOPPED, client.lastStateEvent().getState());
}
}
class Owner extends MockBeanDirectoryOwner implements Structural {
SimpleBeanRegistry beanRegistry = new SimpleBeanRegistry();
ChildHelper<Object> helper = new ChildHelper<Object>(this);
public BeanDirectory provideBeanDirectory() {
return beanRegistry;
}
public String toString() {
return "comp1";
}
public void addStructuralListener(StructuralListener listener) {
helper.addStructuralListener(listener);
}
public void removeStructuralListener(StructuralListener listener) {
helper.removeStructuralListener(listener);
}
}
// test a remote nested registry.
public void testRemoteNestedRegistry() throws Exception {
OurSession serverSession = new OurSession();
Owner comp1 = new Owner();
serverSession.registry.register("comp1", comp1);
Object comp2 = new Object() {
@Override
public String toString() {
return "comp2";
}
};
comp1.helper.insertChild(0, comp2);
comp1.beanRegistry.register("comp2", comp2);
JobFolder folder = new JobFolder();
folder.setJobs(0, comp1);
JMXServerJob server = new JMXServerJob();
server.setRoot(folder);
server.setArooaSession(serverSession);
server.setUrl("service:jmx:rmi://");
try (OddjobConsole.Close close = OddjobConsole.initialise()) {
server.start();
OurSession localSession = new OurSession();
JMXClientJob client = new JMXClientJob();
client.setArooaSession(localSession);
client.setConnection(server.getAddress());
client.run();
// test we can look up a component in a nested registry.
BeanDirectory mirrorCR1 = client.provideBeanDirectory();
RemoteDirectoryOwner comp1Proxy = (RemoteDirectoryOwner) mirrorCR1.lookup("comp1");
assertNotNull(comp1Proxy);
assertEquals("comp1", comp1Proxy.toString());
Object comp2Proxy = mirrorCR1.lookup("comp1/comp2");
assertNotNull(comp2Proxy);
assertEquals("comp2", comp2Proxy.toString());
RemoteDirectory mirrorCR2 = comp1Proxy.provideBeanDirectory();
assertNotNull(mirrorCR2);
assertEquals(comp2Proxy, mirrorCR2.lookup("comp2"));
client.stop();
server.stop();
}
}
class ResultHolder {
Object result;
}
/**
* When a client node such as Oddjob resets, check that the client
* removes it's registry.
*/
public void testRegistryManagement() throws Exception {
Oddjob oddjob = new Oddjob();
oddjob.setName("Top OJ");
oddjob.setConfiguration(
new XMLConfiguration("Resource",
this.getClass().getResourceAsStream(
"JMXClientJobTest1.xml")));
oddjob.setExport("config-2",
new ArooaObject(JMXClientJobTest.class.getResourceAsStream(
"JMXClientJobTest2.xml")));
oddjob.setExport("config-3",
new ArooaObject(JMXClientJobTest.class.getResourceAsStream(
"JMXClientJobTest3.xml")));
oddjob.run();
JMXClientJob client = new JMXClientJob();
client.setArooaSession(new StandardArooaSession());
Object server = new OddjobLookup(oddjob).lookup("server");
client.setConnection((String) PropertyUtils.getProperty(
server, "address"));
client.run();
Object firstoj = new OddjobLookup(client).lookup("oj");
assertNotNull(firstoj);
Object seq = new OddjobLookup(client).lookup("oj/seq");
assertNotNull(seq);
Resetable nested = (Resetable) new OddjobLookup(client).lookup("oj/oj");
assertNotNull(nested);
assertEquals(ParentState.COMPLETE, OddjobTestHelper.getJobState(nested));
Object echoJob = new OddjobLookup(client).lookup("oj/oj/fruit");
assertNotNull(echoJob);
StateSteps steps = new StateSteps((Stateful) nested);
steps.startCheck(ParentState.COMPLETE, ParentState.READY);
nested.hardReset();
// takes a while for the notifications...
steps.checkWait();
assertNull(new OddjobLookup(client).lookup("oj/oj/fruit"));
logger.info("Re-running Once...");
steps.startCheck(ParentState.READY, ParentState.EXECUTING,
ParentState.COMPLETE);
((Runnable) nested).run();
// We might still be waiting for notifications. This is where
// we need to wait for a JobState.DESTROYED on echoJob.
steps.checkWait();
while (new OddjobLookup(client).lookup("oj/oj/fruit") == null) {
Thread.sleep(1000);
Thread.yield();
}
steps.startCheck(ParentState.COMPLETE, ParentState.READY);
nested.hardReset();
steps.checkWait();
assertNull(new OddjobLookup(client).lookup("oj/oj/fruit"));
logger.info("Re-running Twice...");
steps.startCheck(ParentState.READY, ParentState.EXECUTING,
ParentState.COMPLETE);
((Runnable) nested).run();
while (new OddjobLookup(client).lookup("oj/oj/fruit") == null) {
Thread.sleep(1000);
Thread.yield();
}
steps.checkWait();
client.stop();
((Stoppable) server).stop();
oddjob.destroy();
}
/**
* Fixture for logger testing.
*
*/
public static class ThingWithLogger implements LogEnabled {
public String loggerName() {
return "org.oddjob.TestLogger";
}
}
class MockLogListener implements LogListener {
LogEvent e;
synchronized public void logEvent(LogEvent logEvent) {
this.e = logEvent;
notifyAll();
}
}
/**
* Test the client job as a log archiver.
*
*/
public void testLogArchiver() throws Exception {
try (OddjobConsole.Close close = OddjobConsole.initialise()) {
OurSession session = new OurSession();
ThingWithLogger serverNode = new ThingWithLogger();
session.registry.register("thing", serverNode);
JMXServerJob server = new JMXServerJob();
server.setArooaSession(session);
server.setRoot(serverNode);
server.setLogFormat("%m");
server.setUrl("service:jmx:rmi://");
server.start();
Logger ourLogger = Logger.getLogger(serverNode.loggerName());
ourLogger.setLevel(Level.DEBUG);
ourLogger.info("Test");
JMXClientJob client = new JMXClientJob();
client.setArooaSession(new StandardArooaSession());
client.setConnection(server.getAddress());
client.run();
Object[] children = OddjobTestHelper.getChildren(client);
Object proxy = children[0];
assertEquals("logger name", "org.oddjob.TestLogger",
LogHelper.getLogger(proxy));
MockLogListener ll = new MockLogListener();
client.addLogListener(ll, proxy, LogLevel.DEBUG, -1, 10);
// log poller runs on separate thread, so need to wait for event
while (ll.e == null) {
synchronized (ll) {
ll.wait();
}
}
assertNotNull("event", ll.e);
assertEquals("message", "Test", ll.e.getMessage());
client.stop();
server.stop();
}
}
// Make sure the bug fix doesn't leave Old Jobs lying around
private static class JobCounter implements StructuralListener {
private Set<Object> jobs = new HashSet<Object>();
@Override
public void childAdded(StructuralEvent event) {
Object child = event.getChild();
jobs.add(child);
logger.info("JobCounter added " + child);
if (child instanceof Structural) {
((Structural) child).addStructuralListener(this);
}
}
@Override
public void childRemoved(StructuralEvent event) {
Object child = event.getChild();
jobs.remove(child);
logger.info("JobCounter removed " + child);
}
}
// Ditto the JobTreeModel.
private static class NodeCounter implements TreeModelListener {
private Set<Object> jobs = new HashSet<Object>();
@Override
public void treeNodesChanged(TreeModelEvent e) {
// Don't care about Icon notifications.
}
@Override
public void treeStructureChanged(TreeModelEvent e) {
throw new RuntimeException("Unexpected!");
}
@Override
public synchronized void treeNodesInserted(TreeModelEvent e) {
assertEquals(1, e.getChildren().length);
JobTreeNode child = (JobTreeNode) e.getChildren()[0];
child.setVisible(true);
jobs.add(child);
logger.info("NodeCounter added " + child);
}
@Override
public synchronized void treeNodesRemoved(TreeModelEvent e) {
assertEquals(1, e.getChildren().length);
Object child = e.getChildren()[0];
jobs.remove(child);
logger.info("NodeCounter removed " + child);
}
}
/**
* Tracking down a bug where complicated structures cause an exception in explorer.
* <p>
* This is the flip side of fixing the bug {@link ForEachJobTest#testDestroyWithComplicateStructure()}
* because fixing that caused a bug in the JMXClient.
*
* @throws Exception
*/
public void testDestroyWithComplicatedStructure() throws Exception {
String serverConfig =
"<oddjob>" +
" <job>" +
" <sequential>" +
" <jobs>" +
" <echo/>" +
" <echo/>" +
" <echo/>" +
" </jobs>" +
" </sequential>" +
" </job>" +
"</oddjob>";
Oddjob serverOddjob = new Oddjob();
serverOddjob.setConfiguration(new XMLConfiguration("XML" , serverConfig));
JMXServerJob server = new JMXServerJob();
server.setRoot(serverOddjob);
server.setArooaSession(new OurSession());
server.setUrl("service:jmx:rmi://");
server.start();
serverOddjob.run();
String clientConfig =
"<oddjob>" +
" <job>" +
" <jmx:client xmlns:jmx='http://rgordon.co.uk/oddjob/jmx' " +
" id='client' connection='${server-address}'/>" +
" </job>" +
"</oddjob>";
final Oddjob clientOddjob = new Oddjob();
clientOddjob.setConfiguration(new XMLConfiguration("XML" , clientConfig));
clientOddjob.setExport("server-address", new ArooaObject(server.getAddress()));
clientOddjob.run();
assertEquals(ParentState.STARTED, clientOddjob.lastStateEvent().getState());
JobTreeModel model = new JobTreeModel();
NodeCounter nodeCounter = new NodeCounter();
model.addTreeModelListener(nodeCounter);
JobTreeNode root = new JobTreeNode(
new MockExplorerModel() {
@Override
public Oddjob getOddjob() {
return clientOddjob;
}
},
model,
new EventThreadLaterExecutor(),
new ExplorerContextFactory() {
@Override
public ExplorerContext createFrom(ExplorerModel explorerModel) {
return new MockExplorerContext() {
@Override
public ExplorerContext addChild(Object child) {
return this;
}
};
}
});
root.setVisible(true);
JobCounter jobCounter = new JobCounter();
clientOddjob.addStructuralListener(jobCounter);
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
logger.info("Tree built and event queue should be clear.");
}
});
logger.info("Checking node counts.");
assertEquals(6, jobCounter.jobs.size());
assertEquals(6, nodeCounter.jobs.size());
logger.info("Stopping client.");
JMXClientJob client = new OddjobLookup(
clientOddjob).lookup("client", JMXClientJob.class);
client.stop();
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
logger.info("Tree destroyed and event queue should be clear.");
}
});
// Only the top node is removed.
assertEquals(5, jobCounter.jobs.size());
// But all Job Nodes must be destroyed.
assertEquals(1, nodeCounter.jobs.size());
serverOddjob.destroy();
server.stop();
clientOddjob.destroy();
}
}