package org.dcache.tests.cells;
import com.google.common.primitives.Ints;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import dmg.cells.nucleus.CellMessage;
import dmg.cells.nucleus.CellPath;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Abstract helper class for creating cell stubs for testing.
*
* The intention is that this class is subclassed for each test case.
* The test case is implemented in the <code>run</code> method, which
* is executed as part of the stub constructor.
*
* The subclass implements a number of message handlers. A message
* handler is a method annotated with <code>@Message</code>. The stub
* will send messages send to a CellEndpointHelper and forward them to
* the appropriate handler. A handler is used exactly once. If the same
* message is expected several times, several message handlers must be
* defined.
*
* To precisely test the cell interaction, the test is split into a
* number of steps. The test starts at step 0. Each message handler is
* bound to a step and the test is automatically advanced to the next
* step matching the message handler used. In case several message
* handlers apply, the one with the lowest step is used. The
* <code>run</code> method can assert the progress of the test by
* calling <code>assertStep</code>.
*
*/
public abstract class CellStubHelper
{
/**
* This class wraps methods for handling message. Handlers
* implement the chain of command pattern.
*/
class Handler implements Comparable<Handler>
{
/** The method this handler wraps. */
private final Method _method;
/** The annotation of the method. */
private final Message _annotation;
/** Whether the handler has been called. */
private boolean _used;
public Handler(Method method, Message annotation)
{
_method = method;
_method.setAccessible(true);
_annotation = annotation;
_used = false;
}
public boolean isRequired()
{
return _annotation.required();
}
public boolean isUsed()
{
return _used;
}
public int getStep()
{
return _annotation.step();
}
public String getCellName()
{
return _annotation.cell();
}
/** Handlers are ordered according to the step annotation. */
@Override
public int compareTo(Handler handler)
{
return Ints.compare(getStep(), handler.getStep());
}
public CellMessage call(CellMessage msg)
throws Throwable
{
/* Cannot use handler belonging to a previous step.
*/
if (_step > getStep()) {
return null;
}
/* Can only use handler once.
*/
if (_used) {
return null;
}
/* Only accept messages sent to the cell for this messages
* was written.
*/
CellPath dest = msg.getDestinationPath();
String cell = getCellName();
if (!cell.equals(dest.getCellName())) {
return null;
}
/* Deliver message.
*/
try {
msg.setMessageObject((Serializable) _method.invoke(CellStubHelper.this, msg.getMessageObject()));
} catch (IllegalAccessException e) {
fail("Unexpected failure while invoking message handler: " +
e.getMessage());
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (IllegalArgumentException e) {
/* Handler parameters didn't match message object.
*/
return null;
}
/* Advance step. Will fail if required handlers belonging
* to earlier steps have not been called.
*/
_used = true;
assertStep("Required messages missing", getStep(), 0);
msg.revertDirection();
return msg;
}
public String toString()
{
return _method.toString();
}
}
protected final List<Handler> _handlers = new ArrayList<>();
protected int _step;
protected Throwable _failed;
public CellStubHelper(CellEndpointHelper endpoint)
throws Throwable
{
_step = 0;
try {
/* Collect message handlers.
*/
for (Method method : getClass().getDeclaredMethods()) {
Message annotation = method.getAnnotation(Message.class);
if (annotation != null) {
_handlers.add(new Handler(method, annotation));
}
}
Collections.sort(_handlers);
/* Execute the test.
*/
endpoint.execute(this);
assertStep("Required messages missing", Integer.MAX_VALUE);
} finally {
if (_failed != null) {
throw _failed;
}
}
}
synchronized public CellMessage messageArrived(CellMessage msg)
{
try {
for (Handler handler : _handlers) {
CellMessage reply = handler.call(msg);
if (reply != null) {
return reply;
}
}
throw new AssertionError("Unexpected message: " + msg);
} catch (AssertionError e) {
_failed = e;
throw e;
} catch (Throwable e) {
_failed = e;
msg.revertDirection();
msg.setMessageObject(e);
return msg;
}
}
protected void assertStep(String message, int step)
{
assertStep(message, step, 100);
}
protected void assertStep(String message, int step, long timeout)
{
/* We need to make sure that messages have actually been
* delivered. Currently I cannot find a better way than to
* sleep for a moment... (REVISIT).
*/
try {
Thread.currentThread().sleep(timeout);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
assertTrue(message, _step <= step);
for (Handler handler: _handlers) {
assertTrue("Required message missing: " + handler,
!handler.isRequired()
|| handler.isUsed()
|| handler.getStep() >= step);
}
_step = step;
}
protected abstract void run() throws Exception;
}