/*
* Copyright (c) 2005, Rob Gordon.
*/
package org.oddjob.framework;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
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.Describeable;
import org.oddjob.FailedToStopException;
import org.oddjob.Forceable;
import org.oddjob.Oddjob;
import org.oddjob.OddjobLookup;
import org.oddjob.Resetable;
import org.oddjob.Stateful;
import org.oddjob.Stoppable;
import org.oddjob.arooa.ArooaDescriptor;
import org.oddjob.arooa.ArooaSession;
import org.oddjob.arooa.ArooaTools;
import org.oddjob.arooa.MockArooaSession;
import org.oddjob.arooa.life.ArooaContextAware;
import org.oddjob.arooa.life.ArooaLifeAware;
import org.oddjob.arooa.life.ArooaSessionAware;
import org.oddjob.arooa.parsing.MockArooaContext;
import org.oddjob.arooa.registry.ComponentPool;
import org.oddjob.arooa.registry.MockComponentPool;
import org.oddjob.arooa.runtime.MockRuntimeConfiguration;
import org.oddjob.arooa.runtime.RuntimeConfiguration;
import org.oddjob.arooa.runtime.RuntimeListener;
import org.oddjob.arooa.standard.StandardArooaDescriptor;
import org.oddjob.arooa.standard.StandardArooaSession;
import org.oddjob.arooa.standard.StandardTools;
import org.oddjob.arooa.xml.XMLConfiguration;
import org.oddjob.describe.UniversalDescriber;
import org.oddjob.logging.LogEnabled;
import org.oddjob.logging.LogEvent;
import org.oddjob.logging.LogLevel;
import org.oddjob.logging.LogListener;
import org.oddjob.logging.log4j.Log4jArchiver;
import org.oddjob.state.JobState;
import org.oddjob.state.ParentState;
import org.oddjob.state.StateEvent;
import org.oddjob.state.StateListener;
import org.oddjob.tools.OddjobTestHelper;
import org.oddjob.tools.StateSteps;
/**
*
* @author Rob Gordon.
*/
public class RunnableWrapperTest extends TestCase {
private class OurContext extends MockArooaContext {
OurSession session;
@Override
public ArooaSession getSession() {
return session;
}
@Override
public RuntimeConfiguration getRuntime() {
return new MockRuntimeConfiguration() {
@Override
public void addRuntimeListener(RuntimeListener listener) {
}
};
}
}
private class OurSession extends MockArooaSession {
Object configured;
Object saved;
ArooaDescriptor descriptor = new StandardArooaDescriptor();
@Override
public ArooaDescriptor getArooaDescriptor() {
return descriptor;
}
@Override
public ComponentPool getComponentPool() {
return new MockComponentPool() {
@Override
public void configure(Object component) {
configured = component;
}
@Override
public void save(Object component) {
saved = component;
}
};
}
@Override
public ArooaTools getTools() {
return new StandardTools();
}
}
/** Runnable fixture. */
public static class OurRunnable implements Runnable {
boolean ran;
public void run() {
ran = true;
}
public boolean isRan() {
return ran;
}
public String toString() {
return "OurRunnable";
}
}
/**
* Test a runable that runs without exception. Test the
* state of the wrapper.
*
*/
public void testGoodRunnable() {
OurSession session = new OurSession();
OurContext context = new OurContext();
context.session = session;
OurRunnable test = new OurRunnable();
Object proxy = new RunnableProxyGenerator().generate(
(Runnable) test,
getClass().getClassLoader());
((ArooaSessionAware) proxy).setArooaSession(session);
((ArooaContextAware) proxy).setArooaContext(context);
MyStateListener stateListener = new MyStateListener();
((Stateful) proxy).addStateListener(stateListener);
assertSame(proxy, stateListener.lastEvent.getSource());
((Runnable) proxy).run();
assertEquals(proxy, session.configured);
assertEquals(proxy, session.saved);
assertTrue(test.ran);
assertEquals("JobState", JobState.COMPLETE,
stateListener.lastEvent.getState());
session.saved = null;
((Resetable) proxy).hardReset();
assertEquals("JobState", JobState.READY,
stateListener.lastEvent.getState());
assertEquals(proxy, session.saved);
((Forceable) proxy).force();
assertEquals("JobState", JobState.COMPLETE,
stateListener.lastEvent.getState());
}
/**
* Test a runnable that throws an exception. Test the
* state of the wrapper.
*
*/
public void testBadRunnable() {
Runnable test = new Runnable() {
public void run() {
throw new RuntimeException("How bad is this!");
}
};
Object proxy = new RunnableProxyGenerator().generate(
(Runnable) test,
getClass().getClassLoader());
ArooaSession session = new StandardArooaSession();
((ArooaSessionAware) proxy).setArooaSession(session);
MyStateListener l = new MyStateListener();
((Stateful) proxy).addStateListener(l);
((Runnable) proxy).run();
assertEquals("JobState", JobState.EXCEPTION,
l.lastEvent.getState());
((Resetable) proxy).softReset();
assertEquals("JobState", JobState.READY,
l.lastEvent.getState());
}
/** Listener fixture. */
private class MyStateListener implements StateListener {
StateEvent lastEvent;
public void jobStateChange(StateEvent event) {
lastEvent = event;
}
}
public void testStop() throws InterruptedException, FailedToStopException {
final CountDownLatch latch = new CountDownLatch(1);
Runnable test = new Runnable() {
@Override
public void run() {
latch.countDown();
synchronized(this) {
try {
wait();
} catch (InterruptedException e){
Thread.currentThread().interrupt();
}
}
}
@Override
public String toString() {
return "StopTestJob";
}
};
Runnable wrapper = (Runnable) new RunnableProxyGenerator().generate(
(Runnable) test,
getClass().getClassLoader());
Stateful stateful = (Stateful) wrapper;
StateSteps states = new StateSteps(stateful);
states.startCheck(JobState.READY, JobState.EXECUTING);
Thread t = new Thread(wrapper);
t.start();
states.checkWait();
latch.await();
Stoppable stoppable = (Stoppable) wrapper;
states.startCheck(JobState.EXECUTING, JobState.COMPLETE);
stoppable.stop();
states.checkWait();
}
/**
* Test default Object implementation of hash code is sufficient
* to store a wrapper in a hashmap.
*
* This test is a bit weak.
*
*/
public void testHashCode() {
Runnable wrapped = new Runnable() {
public void run() {}
};
Runnable test = (Runnable) new RunnableProxyGenerator().generate(
(Runnable) wrapped,
getClass().getClassLoader());
HashMap<Object, String> hashMap = new HashMap<Object, String>();
hashMap.put(test, "Hello");
String result = hashMap.get(test);
assertEquals("Hello", result);
}
/**
* Test the equals method.
*
*/
public void testEquals() {
Runnable wrapped = new Runnable() {
public void run() {}
};
Runnable test = (Runnable) new RunnableProxyGenerator().generate(
(Runnable) wrapped,
getClass().getClassLoader());
assertTrue(test.equals(test));
assertEquals(test, test);
}
/** Bean fixture. */
public static class Bean {
String greeting;
public void setGreeting(String greeting) {
this.greeting = greeting;
}
public String getGreeting() {
return greeting;
}
}
/** Job with result fixture. */
public static class Job implements Runnable {
public String result;
public void run() {
}
public void setResult(String result) {
this.result = result;
}
public String getResult() {
return result;
}
}
/**
* A simple test in oddjob. Ensures the wrapped job runs and the
* state is OK.
*/
public void testInOddjob() throws Exception {
String xml =
"<oddjob>" +
" <job>" +
" <bean class='" + OurRunnable.class.getName() + "' id='r' />" +
" </job>" +
"</oddjob>";
Oddjob oddjob = new Oddjob();
oddjob.setConfiguration(new XMLConfiguration("XML", xml));
oddjob.run();
Object r = new OddjobLookup(oddjob).lookup("r");
assertEquals(JobState.COMPLETE, OddjobTestHelper.getJobState(r));
Object ran = PropertyUtils.getProperty(r, "ran");
assertEquals(Boolean.class, ran.getClass());
assertEquals(new Boolean(true), ran);
Map<String, String> description = ((Describeable) r).describe();
assertEquals("true", description.get("ran"));
oddjob.destroy();
}
/**
* A mock class with all the properties. Implements
* Runnable so that a proxy wrapper will be created.
*/
public static class LotsOfProperties implements Runnable {
private Map<String, Object> map = new HashMap<String, Object>();
private String[] indexed = new String[1];
private String simple;
public void run() {}
public void setMapped(String name, Object value) {
map.put(name, value);
}
public Object getMapped(String name) {
return map.get(name);
}
public void setIndexed(int i, String value) {
this.indexed[i] = value;
}
public String[] getIndexed() {
return this.indexed;
}
public void setSimple(String simple) {
this.simple = simple;
}
public String getSimple() {
return this.simple;
}
}
/**
* Test that all property setting and getting works
* for a wrapped job by testing the proxy created.
*/
public void testPropertiesInProxy() throws Exception {
LotsOfProperties bean = new LotsOfProperties();
Runnable test = (Runnable) new RunnableProxyGenerator().generate(
(Runnable) bean,
getClass().getClassLoader());
DynaBean db = (DynaBean) test;
db.set("simple", "test");
assertEquals("test", db.get("simple"));
db.set("mapped", "simple", "test");
assertEquals("test", db.get("mapped", "simple"));
db.set("indexed", 0, "test");
assertEquals("test", db.get("indexed", 0));
// check via property utils which exercises the underlying DynaClass
PropertyUtils.setProperty(db, "simple", "test");
assertEquals("test", PropertyUtils.getProperty(db, "simple"));
PropertyUtils.setProperty(db, "mapped(simple)", "test");
assertEquals("test", PropertyUtils.getProperty(db, "mapped(simple)"));
PropertyUtils.setProperty(db, "indexed[0]", "test");
assertEquals("test", PropertyUtils.getProperty(db, "indexed[0]"));
}
/**
* Test that all property setting and getting works
* for a wrapped job when in Oddjob.
*/
public void testProperitesInOddjob() throws Exception{
String xml =
"<oddjob>" +
" <job>" +
" <sequential>" +
" <jobs>" +
" <set>" +
" <values>" +
" <value key='test.simple' value='test'/>" +
" <value key='test.mapped(akey)' value='test'/>" +
" <value key='test.indexed[0]' value='test'/>" +
" </values>" +
" </set>" +
" <bean class='" + LotsOfProperties.class.getName() + "' id='test' />" +
" <variables id='v'>" +
" <simple>" +
" <value value='${test.simple}'/>" +
" </simple>" +
" <mapped>" +
" <properties>" +
" <values>" +
" <value key='akey' value='${test.mapped(akey)}'/>" +
" </values>" +
" </properties>" +
" </mapped>" +
" <indexed>" +
" <list>" +
" <values>" +
" <value value='${test.indexed[0]}'/>" +
" </values>" +
" </list>" +
" </indexed>" +
" </variables>" +
" </jobs>" +
" </sequential>" +
" </job>" +
"</oddjob>";
Oddjob oj = new Oddjob();
oj.setConfiguration(new XMLConfiguration("TEST", xml));
oj.run();
OddjobLookup lookup = new OddjobLookup(oj);
assertEquals("test", lookup.lookup("v.simple", String.class));
Map<?, ?> map = lookup.lookup("v.mapped", Properties.class);
assertEquals(map.get("akey"), "test");
Object[] array = lookup.lookup("v.indexed", Object[].class);
assertEquals(array[0], "test");
oj.destroy();
}
/** A Logger fixture. */
public static class AnyLogger implements Runnable {
public void run() {
Logger.getLogger("AnyLogger").error("FINDME");
}
}
/**
* Test logging.
*
* @throws Exception
*/
public void testDefaultLogger() throws Exception {
class MyL implements LogListener {
StringBuffer messages = new StringBuffer();
public void logEvent(LogEvent logEvent) {
messages.append(logEvent.getMessage());
}
}
AnyLogger l = new AnyLogger();
Runnable proxy = (Runnable) new RunnableProxyGenerator().generate(
(Runnable) l,
getClass().getClassLoader());
Logger.getLogger("AnyLogger").setLevel(Level.DEBUG);
String proxyLoggerName = ((LogEnabled) proxy).loggerName();
assertEquals(AnyLogger.class.getName(),
proxyLoggerName.substring(0, AnyLogger.class.getName().length()));
Logger.getLogger(proxyLoggerName).setLevel(Level.DEBUG);
Log4jArchiver archiver = new Log4jArchiver(proxy, "%m%n");
MyL ll = new MyL();
archiver.addLogListener(ll, proxy, LogLevel.DEBUG, 0, 1000);
proxy.run();
assertTrue(ll.messages.indexOf("FINDME") > 0);
}
public static class MyLogger implements Runnable, LogEnabled {
public String loggerName() {
return "MyLogger";
}
public void run() {
Logger.getLogger(loggerName()).error("FINDME");
}
}
public void testSpecificLogger() throws Exception {
class MyL implements LogListener {
StringBuffer messages = new StringBuffer();
public void logEvent(LogEvent logEvent) {
messages.append(logEvent.getMessage());
}
}
MyLogger l = new MyLogger();
Runnable proxy = (Runnable) new RunnableProxyGenerator().generate(
(Runnable) l,
getClass().getClassLoader());
assertEquals("MyLogger", ((LogEnabled) proxy).loggerName());
Logger.getLogger(((LogEnabled) proxy).loggerName()).setLevel(Level.DEBUG);
Log4jArchiver archiver = new Log4jArchiver(proxy, "%m%n");
MyL ll = new MyL();
archiver.addLogListener(ll, proxy, LogLevel.DEBUG, 0, 1000);
proxy.run();
assertTrue(ll.messages.indexOf("FINDME") > 0);
}
/**
* Test describing a component via a RunnableWrapper.
*
*/
public void testDescribe() {
ArooaSession session = new StandardArooaSession();
Job j = new Job();
j.setResult("Hello");
Runnable wrapper = (Runnable) new RunnableProxyGenerator().generate(
(Runnable) j,
getClass().getClassLoader());
((ArooaSessionAware) wrapper).setArooaSession(session);
Map<String, String> m = new UniversalDescriber(
session).describe(wrapper);
assertEquals("Hello", m.get("result"));
}
public static class Stubbon implements ArooaLifeAware {
boolean ouch;
public void configured() {
// TODO Auto-generated method stub
}
public void destroy() {
if (!ouch) {
ouch = true;
throw new RuntimeException("Ouch!");
}
}
public void initialised() {
// TODO Auto-generated method stub
}
public String toString() {
return "Stubbon";
}
}
/**
* Test when destroy fails.
*
* @throws Exception
*/
public void testDestroyInOddjob() throws Exception {
String xml =
"<oddjob>" +
" <job>" +
" <folder>" +
" <jobs>" +
" <bean class='" + OurRunnable.class.getName() + "'/>" +
" <bean class='" + Stubbon.class.getName() + "'/>" +
" </jobs>" +
" </folder>" +
" </job>" +
"</oddjob>";
Oddjob oj = new Oddjob();
oj.setConfiguration(new XMLConfiguration("XML", xml));
oj.run();
assertEquals(ParentState.COMPLETE, OddjobTestHelper.getJobState(oj));
try {
oj.destroy();
fail("Should fail!");
} catch (RuntimeException e) {
assertEquals("Ouch!", e.getMessage());
}
oj.destroy();
}
}