package org.nuxeo.ecm.core.management.jtajca;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import javax.inject.Named;
import javax.transaction.TransactionManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Level;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.nuxeo.runtime.test.runner.Features;
import org.nuxeo.runtime.test.runner.FeaturesRunner;
import org.nuxeo.runtime.test.runner.LogCaptureFeature;
import org.nuxeo.runtime.test.runner.LogCaptureFeature.NoLogCaptureFilterException;
import org.slf4j.MDC;
import com.google.inject.Inject;
/*
* (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* Contributors:
* matic
*/
/**
* @author matic
*/
@RunWith(FeaturesRunner.class)
@Features({ JtajcaManagementFeature.class, LogCaptureFeature.class})
public class CanMonitorTransactionsTest {
@Inject @Named("default") protected TransactionMonitor monitor;
@Inject protected TransactionManager tm;
protected ExecutorService executor;
@Before
public void injectExectutor() {
executor = Executors.newSingleThreadExecutor();
}
@After
public void shudownExecutor() {
executor.shutdownNow();
}
@Before
public void enableMonitoring() {
monitor.toggle();
}
@After
public void disableMonitoring() {
monitor.toggle();
}
protected void begin() {
try {
tm.begin();
} catch (Exception cause) {
throw new RuntimeException("Cannot start new transacton", cause);
}
}
protected void rollback() {
try {
tm.rollback();
} catch (Exception cause) {
throw new RuntimeException("Cannot rollback transaction", cause);
}
}
protected void commit() {
try {
tm.commit();
} catch (Exception cause) {
throw new RuntimeException("Cannot commit transaction", cause);
}
}
@Test
public void isMonitorInstalled() {
assertThat(monitor, notNullValue());
monitor.getTotalCommits(); // throw exception is monitor not present
}
protected class TestTotalRollbacks implements Callable<Boolean> {
@Override
public Boolean call() {
long activeCount = monitor.getActiveCount();
long totalRollbacks = monitor.getTotalRollbacks();
begin();
assertThat(monitor.getActiveCount(), is(activeCount + 1));
rollback();
assertThat(monitor.getActiveCount(), is(activeCount));
assertThat(monitor.getTotalRollbacks(), is(totalRollbacks + 1));
return Boolean.TRUE;
}
}
@Test
public void isTotalRollbacksCorrect() throws InterruptedException,
ExecutionException {
assertThat(monitor.getActiveCount(), is(1L));
FutureTask<Boolean> rollback = new FutureTask<Boolean>(
new TestTotalRollbacks());
executor.execute(rollback);
assertThat(rollback.get(), is(true));
}
protected class TestTotalCommits implements Callable<Boolean> {
@Override
public Boolean call() {
long totalCommits = monitor.getTotalCommits();
begin();
commit();
assertThat(monitor.getTotalCommits(), is(totalCommits + 1));
return Boolean.TRUE;
}
}
@Test
public void isTotalCommitsCorrect() throws InterruptedException,
ExecutionException {
FutureTask<Boolean> commit = new FutureTask<Boolean>(
new TestTotalCommits());
executor.execute(commit);
assertThat(commit.get(), is(true));
}
protected class TestCollectStatistics implements Callable<Boolean> {
@Override
public Boolean call() {
try {
begin();
List<TransactionStatistics> stats = monitor.getActiveStatistics();
stats = monitor.getActiveStatistics();
assertThat((long) stats.size(), is(1L));
commit();
stats = monitor.getActiveStatistics();
assertThat((long) stats.size(), is(0L));
} catch (Exception cause) {
LogFactory.getLog(CanMonitorTransactionsTest.class).error(
"Caught error while collecting statistics", cause);
}
return Boolean.TRUE;
}
}
@Test
public void isActiveStatisticsCollected() throws InterruptedException,
ExecutionException {
FutureTask<Boolean> task = new FutureTask<Boolean>(
new TestCollectStatistics());
executor.execute(task);
assertThat(task.get(), is(true));
}
@Inject
LogCaptureFeature.Result logCaptureResults;
@Test
@LogCaptureFeature.FilterWith(value = CanMonitorTransactionsTest.LogRollbackTraceFilter.class)
public void logContainsRollbackTrace() throws InterruptedException,
ExecutionException, NoLogCaptureFilterException {
FutureTask<Boolean> task = new FutureTask<Boolean>(
new TestLogRollbackTrace());
executor.execute(task);
assertThat(task.get(), is(true));
logCaptureResults.assertHasEvent();
}
protected class TestLogRollbackTrace implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
begin();
rollback();
return true;
}
}
public static class LogRollbackTraceFilter implements
LogCaptureFeature.Filter {
@Override
public boolean accept(LoggingEvent event) {
if (event.getLevel() != Level.TRACE) {
return false;
}
Object msg = event.getMessage();
if (!(msg instanceof TransactionStatistics)) {
return false;
}
TransactionStatistics stats = (TransactionStatistics) msg;
if (!TransactionStatistics.Status.ROLLEDBACK.equals(stats.getStatus())) {
return false;
}
return true;
}
}
@Test
@LogCaptureFeature.FilterWith(value = CanMonitorTransactionsTest.LogMessageFilter.class)
public void logContainsTxKey() throws InterruptedException,
ExecutionException, NoLogCaptureFilterException {
FutureTask<Boolean> task = new FutureTask<Boolean>(
new TestLogRollbackTrace());
executor.execute(task);
assertThat(task.get(), is(true));
logCaptureResults.assertHasEvent();
}
public static class LogMessageFilter implements LogCaptureFeature.Filter {
@Override
public boolean accept(LoggingEvent event) {
return MDC.get("tx") != null;
}
}
protected class TestLogMessage implements Callable<Boolean> {
protected final Log log = LogFactory.getLog(TestLogMessage.class);
@Override
public Boolean call() throws Exception {
begin();
log.warn("logging with active tx");
rollback();
return true;
}
}
}