package net.codjo.segmentation.server.blackboard;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import net.codjo.segmentation.server.blackboard.JdbcBlackboardParticipantTest.Exceptions.RootCause;
import net.codjo.segmentation.server.blackboard.message.Level;
import net.codjo.segmentation.server.blackboard.message.Todo;
import net.codjo.sql.server.JdbcServiceUtilMock;
import net.codjo.test.common.ListAppender.LogEntryMatcher;
import net.codjo.test.common.LogString;
import net.codjo.test.common.LoggerRule;
import net.codjo.util.time.MockTimeSource;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.theories.DataPoint;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
import static net.codjo.segmentation.server.blackboard.JdbcBlackboardParticipantTest.Exceptions.exceptions;
import static net.codjo.segmentation.server.blackboard.JdbcBlackboardParticipantTest.ExceptionsInTwoTimeWindows.twoExceptions;
import static net.codjo.test.common.ListAppender.Count.count;
import static org.apache.commons.lang.exception.ExceptionUtils.getRootCause;
/**
*
*/
@RunWith(Theories.class)
public class JdbcBlackboardParticipantTest {
private static final long TIME_WINDOW_VALUE = 60;
private static final TimeUnit TIME_WINDOW_UNIT = TimeUnit.SECONDS;
private static final String ERROR1 = generateUniqueErrorMessage();
private static final String ERROR2 = generateUniqueErrorMessage();
private static final SQLException ERROR1_LEVEL1;
private static final SQLException ERROR1_LEVEL2;
private static final SQLException ERROR1_LEVEL3;
private static final SQLException ERROR2_LEVEL2;
static {
ERROR1_LEVEL1 = exception(ERROR1);
ERROR1_LEVEL2 = exception(generateUniqueErrorMessage(), ERROR1);
ERROR1_LEVEL3 = exception(generateUniqueErrorMessage(), generateUniqueErrorMessage(), ERROR1);
ERROR2_LEVEL2 = exception(generateUniqueErrorMessage(), ERROR2);
}
@DataPoint
public static final Exceptions NO_ERROR = exceptions();
@DataPoint
public static final Exceptions ERRORS_1xE1L1 = exceptions(ERROR1_LEVEL1);
@DataPoint
public static final Exceptions ERRORS_1xE1L2 = exceptions(ERROR1_LEVEL2);
@DataPoint
public static final Exceptions ERRORS_1xE1L3 = exceptions(ERROR1_LEVEL3);
@DataPoint
public static final Exceptions ERRORS_2xE1L1 = exceptions(ERROR1_LEVEL1, ERROR1_LEVEL1);
@DataPoint
public static final Exceptions ERRORS_2xE1L2 = exceptions(ERROR1_LEVEL2, ERROR1_LEVEL2);
@DataPoint
public static final Exceptions ERRORS_2xE1L3 = exceptions(ERROR1_LEVEL3, ERROR1_LEVEL3);
@DataPoint
public static final Exceptions ERRORS_1xE1L1_2xE2L2 = exceptions(ERROR2_LEVEL2, ERROR1_LEVEL1, ERROR2_LEVEL2);
@DataPoint
public static final Exceptions ERRORS_2xE1L3_1xE2L2 = exceptions(ERROR1_LEVEL3, ERROR2_LEVEL2, ERROR1_LEVEL3);
@DataPoint
public static final ExceptionsInTwoTimeWindows ERRORS_2TW = twoExceptions(ERROR1_LEVEL1, ERROR1_LEVEL1);
@DataPoint
public static final long TIME0 = 0;
@DataPoint
public static final long TIME1 = TIME_WINDOW_VALUE / 2;
private LogString log = new LogString();
@Rule
public LoggerRule rule = new LoggerRule();
@Test
public void test_handleTodo() throws Exception {
JdbcServiceUtilMock mock = new JdbcServiceUtilMock(log);
JdbcBlackboardParticipant participant =
new JdbcBlackboardParticipant(mock,
JdbcBlackboardParticipant.TransactionType.AUTO_COMMIT,
new Level("my-level")) {
@Override
protected void handleTodo(Todo todo, Level fromLevel, Connection connection) {
log.call("handleTodo", fromLevel.getName(), todo.getId(),
(connection == null ? "Null connection" : "good connection"));
}
@Override
protected ErrorLogLimiter getErrorLogLimiter(Todo todo) {
return ErrorLogLimiter.NONE;
}
};
//noinspection unchecked
participant.handleTodo(new Todo(1), new Level("level"));
log.assertContent("getConnectionPool(null, message:null), handleTodo(level, 1, good connection)");
}
@Theory
public void testSameSQLExceptionReportedOnlyOnce_sameTimeWindow(Exceptions exceptions, long startTime)
throws Exception {
testSameSQLExceptionReportedOnlyOnce(exceptions, true, startTime, true);
}
@Theory
public void testSameSQLExceptionReportedOnlyOnce_twoTimeWindows(ExceptionsInTwoTimeWindows exceptions,
long startTime) throws Exception {
testSameSQLExceptionReportedOnlyOnce(exceptions, false, startTime, true);
}
@Theory
public void testSameSQLExceptionReportedManyTimes_noErrorLogLimiter(Exceptions exceptions) throws Exception {
testSameSQLExceptionReportedOnlyOnce(exceptions, false, 0, false);
}
private void testSameSQLExceptionReportedOnlyOnce(Exceptions exceptions,
boolean sameTimePeriod,
long startTime,
boolean enableLimiter)
throws Exception {
final DefaultErrorLogLimiter limiter;
final MockTimeSource timeSource;
if (enableLimiter) {
timeSource = new MockTimeSource();
timeSource.inc(startTime);
limiter = new DefaultErrorLogLimiter(timeSource, TIME_WINDOW_VALUE, TIME_WINDOW_UNIT);
}
else {
timeSource = null;
limiter = null;
}
for (SQLException exception : exceptions.exceptions()) {
JdbcServiceUtilMock mock = new JdbcServiceUtilMock(log);
mock.mockGetConnectionPoolFailure(exception);
Level level = new Level("my-level");
JdbcBlackboardParticipant participant =
new JdbcBlackboardParticipant(mock,
JdbcBlackboardParticipant.TransactionType.AUTO_COMMIT, level) {
@Override
protected void handleTodo(Todo todo, Level fromLevel, Connection connection) {
}
@Override
protected ErrorLogLimiter getErrorLogLimiter(Todo todo) {
return limiter;
}
};
try {
//noinspection unchecked
participant.handleTodo(new Todo(1), level);
}
catch (NullPointerException npe) {
// it's expected
}
if (!sameTimePeriod && (timeSource != null)) {
// advance mocked time to the next window
timeSource.inc(TIME_WINDOW_UNIT.toMillis(TIME_WINDOW_VALUE));
}
}
for (Iterator<RootCause> itEntry = exceptions.rootCauses(); itEntry.hasNext(); ) {
RootCause expectedResult = itEntry.next();
SQLException expectedRootCause = expectedResult.rootCause;
final int expectedCount;
if (enableLimiter) {
if (sameTimePeriod) {
expectedCount = 1;
}
else {
// we assume here that there is only one common root cause
// for all exceptions returned by exceptions()
expectedCount = exceptions.exceptions().length;
}
}
else {
expectedCount = expectedResult.count;
}
rule.getAppender().assertHasLog(LogEntryMatcher.exception(expectedRootCause.getClass(),
expectedRootCause.getMessage()),
count(expectedCount));
}
}
static class Exceptions {
private final SQLException[] exceptions;
public static Exceptions exceptions(SQLException... exceptions) {
return new Exceptions(exceptions);
}
private Exceptions(SQLException... exceptions) {
this.exceptions = exceptions;
}
public Iterator<RootCause> rootCauses() {
Map<String, RootCause> rootCauses = new HashMap<String, RootCause>();
for (Throwable t : exceptions) {
SQLException rootCause = (SQLException)getRootCause(t);
if (rootCause == null) {
rootCause = (SQLException)t;
}
String key = rootCause.getClass().getName() + '#' + rootCause.getMessage();
RootCause r = rootCauses.get(key);
if (r == null) {
r = new RootCause(rootCause);
rootCauses.put(key, r);
}
r.count++;
}
return rootCauses.values().iterator();
}
public static class RootCause {
private final SQLException rootCause;
private int count;
public RootCause(SQLException rootCause) {
this.rootCause = rootCause;
}
}
public SQLException[] exceptions() {
return exceptions;
}
}
static class ExceptionsInTwoTimeWindows extends Exceptions {
public static ExceptionsInTwoTimeWindows twoExceptions(SQLException exception1, SQLException exception2) {
return new ExceptionsInTwoTimeWindows(exception1, exception2);
}
private ExceptionsInTwoTimeWindows(SQLException exception1, SQLException exception2) {
super(exception1, exception2);
}
}
private static int errorNumber = 1;
private static String generateUniqueErrorMessage() {
String result = "Error" + errorNumber;
errorNumber++;
return result;
}
public static SQLException exception(String... messages) {
SQLException result = null;
for (int i = messages.length - 1; i >= 0; i--) {
SQLException e = new SQLException(messages[i]);
if (result != null) {
e.initCause(result);
}
result = e;
}
return result;
}
}