/*
* Copyright 2015-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.glowroot.agent.plugin.jdbc;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.Set;
import com.google.common.collect.Sets;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.DelegatingConnection;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.glowroot.agent.it.harness.AppUnderTest;
import org.glowroot.agent.it.harness.Container;
import org.glowroot.agent.it.harness.Containers;
import org.glowroot.agent.it.harness.TransactionMarker;
import org.glowroot.wire.api.model.TraceOuterClass.Trace;
import static org.assertj.core.api.Assertions.assertThat;
public class ConnectionAndTxLifecycleIT {
private static final String PLUGIN_ID = "jdbc";
private static Container container;
@BeforeClass
public static void setUp() throws Exception {
container = Containers.create();
}
@AfterClass
public static void tearDown() throws Exception {
container.close();
}
@After
public void afterEachTest() throws Exception {
container.checkAndReset();
}
@Test
public void testConnectionLifecycle() throws Exception {
// given
container.getConfigService().setPluginProperty(PLUGIN_ID,
"captureConnectionLifecycleTraceEntries", true);
// when
Trace trace = container.execute(ExecuteGetConnectionAndConnectionClose.class);
// then
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEqualTo("jdbc get connection");
entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEqualTo("jdbc connection close");
assertThat(i.hasNext()).isFalse();
}
@Test
public void testConnectionLifecycleDisabled() throws Exception {
// given
container.getConfigService().setPluginProperty(PLUGIN_ID, "captureGetConnection", false);
container.getConfigService().setPluginProperty(PLUGIN_ID, "captureConnectionClose", false);
// when
Trace trace = container.execute(ExecuteGetConnectionAndConnectionClose.class);
// then
Trace.Header header = trace.getHeader();
Trace.Timer rootTimer = header.getMainThreadRootTimer();
assertThat(rootTimer.getChildTimerList()).isEmpty();
assertThat(header.getEntryCount()).isZero();
}
@Test
public void testConnectionLifecyclePartiallyDisabled() throws Exception {
// given
container.getConfigService().setPluginProperty(PLUGIN_ID, "captureConnectionClose", true);
// when
Trace trace = container.execute(ExecuteGetConnectionAndConnectionClose.class);
// then
Trace.Header header = trace.getHeader();
Trace.Timer rootTimer = header.getMainThreadRootTimer();
assertThat(rootTimer.getChildTimerList()).hasSize(2);
// ordering is by total desc, so order is not fixed
Set<String> childTimerNames = Sets.newHashSet();
childTimerNames.add(rootTimer.getChildTimerList().get(0).getName());
childTimerNames.add(rootTimer.getChildTimerList().get(1).getName());
assertThat(childTimerNames).containsOnly("jdbc get connection", "jdbc connection close");
assertThat(header.getEntryCount()).isZero();
}
@Test
public void testConnectionLifecycleGetConnectionThrows() throws Exception {
// given
container.getConfigService().setPluginProperty(PLUGIN_ID,
"captureConnectionLifecycleTraceEntries", true);
// when
Trace trace = container.execute(ExecuteGetConnectionOnThrowingDataSource.class);
// then
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEqualTo("jdbc get connection");
assertThat(entry.getError().getMessage()).isEqualTo("A getconnection failure");
assertThat(i.hasNext()).isFalse();
}
@Test
public void testConnectionLifecycleGetConnectionThrowsDisabled() throws Exception {
// given
container.getConfigService().setPluginProperty(PLUGIN_ID, "captureGetConnection", false);
container.getConfigService().setPluginProperty(PLUGIN_ID, "captureConnectionClose", false);
// when
Trace trace = container.execute(ExecuteGetConnectionOnThrowingDataSource.class);
// then
Trace.Header header = trace.getHeader();
Trace.Timer rootTimer = header.getMainThreadRootTimer();
assertThat(rootTimer.getChildTimerList()).isEmpty();
assertThat(header.getEntryCount()).isZero();
}
@Test
public void testConnectionLifecycleGetConnectionThrowsPartiallyDisabled() throws Exception {
// when
Trace trace = container.execute(ExecuteGetConnectionOnThrowingDataSource.class);
// then
Trace.Header header = trace.getHeader();
Trace.Timer rootTimer = header.getMainThreadRootTimer();
assertThat(rootTimer.getChildTimerList()).hasSize(1);
assertThat(rootTimer.getChildTimerList().get(0).getName()).isEqualTo("jdbc get connection");
assertThat(header.getEntryCount()).isZero();
}
@Test
public void testConnectionLifecycleCloseConnectionThrows() throws Exception {
// given
container.getConfigService().setPluginProperty(PLUGIN_ID,
"captureConnectionLifecycleTraceEntries", true);
// when
Trace trace = container.execute(ExecuteCloseConnectionOnThrowingDataSource.class);
// then
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEqualTo("jdbc get connection");
entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEqualTo("jdbc connection close");
assertThat(entry.getError().getMessage()).isEqualTo("A close failure");
assertThat(i.hasNext()).isFalse();
}
@Test
public void testConnectionLifecycleCloseConnectionThrowsDisabled() throws Exception {
// given
container.getConfigService().setPluginProperty(PLUGIN_ID, "captureGetConnection", false);
container.getConfigService().setPluginProperty(PLUGIN_ID, "captureConnectionClose", false);
// when
Trace trace = container.execute(ExecuteCloseConnectionOnThrowingDataSource.class);
// then
Trace.Header header = trace.getHeader();
Trace.Timer rootTimer = header.getMainThreadRootTimer();
assertThat(rootTimer.getChildTimerList()).isEmpty();
assertThat(header.getEntryCount()).isZero();
}
@Test
public void testConnectionLifecycleCloseConnectionThrowsPartiallyDisabled() throws Exception {
// given
container.getConfigService().setPluginProperty(PLUGIN_ID, "captureConnectionClose", true);
// when
Trace trace = container.execute(ExecuteCloseConnectionOnThrowingDataSource.class);
// then
Trace.Header header = trace.getHeader();
Trace.Timer rootTimer = header.getMainThreadRootTimer();
assertThat(rootTimer.getChildTimerList()).hasSize(2);
// ordering is by total desc, so order is not fixed
Set<String> childTimerNames = Sets.newHashSet();
childTimerNames.add(rootTimer.getChildTimerList().get(0).getName());
childTimerNames.add(rootTimer.getChildTimerList().get(1).getName());
assertThat(childTimerNames).containsOnly("jdbc get connection", "jdbc connection close");
assertThat(header.getEntryCount()).isZero();
}
@Test
public void testTransactionLifecycle() throws Exception {
// given
container.getConfigService().setPluginProperty(PLUGIN_ID,
"captureTransactionLifecycleTraceEntries", true);
// when
Trace trace = container.execute(ExecuteSetAutoCommit.class);
// then
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEqualTo("jdbc set autocommit: false");
entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEqualTo("jdbc set autocommit: true");
if (i.hasNext()) {
entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEqualTo("jdbc commit");
}
assertThat(i.hasNext()).isFalse();
}
@Test
public void testTransactionLifecycleThrowing() throws Exception {
// given
container.getConfigService().setPluginProperty(PLUGIN_ID,
"captureTransactionLifecycleTraceEntries", true);
// when
Trace trace = container.execute(ExecuteSetAutoCommitThrowing.class);
// then
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEqualTo("jdbc set autocommit: false");
assertThat(entry.getError().getMessage()).isEqualTo("A setautocommit failure");
entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEqualTo("jdbc set autocommit: true");
assertThat(entry.getError().getMessage()).isEqualTo("A setautocommit failure");
assertThat(i.hasNext()).isFalse();
}
@Test
public void testConnectionLifecycleAndTransactionLifecycleTogether() throws Exception {
// given
container.getConfigService().setPluginProperty(PLUGIN_ID,
"captureConnectionLifecycleTraceEntries", true);
container.getConfigService().setPluginProperty(PLUGIN_ID,
"captureTransactionLifecycleTraceEntries", true);
// when
Trace trace = container.execute(ExecuteGetConnectionAndConnectionClose.class);
// then
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEqualTo("jdbc get connection (autocommit: true)");
entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEqualTo("jdbc connection close");
assertThat(i.hasNext()).isFalse();
}
public static class ExecuteGetConnectionAndConnectionClose
implements AppUnderTest, TransactionMarker {
private BasicDataSource dataSource;
@Override
public void executeApp() throws Exception {
dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbc.JDBCDriver");
dataSource.setUrl("jdbc:hsqldb:mem:test");
// BasicDataSource opens and closes a test connection on first getConnection(),
// so just getting that out of the way before starting transaction
dataSource.getConnection().close();
transactionMarker();
}
@Override
public void transactionMarker() throws Exception {
dataSource.getConnection().close();
}
}
public static class ExecuteGetConnectionOnThrowingDataSource
implements AppUnderTest, TransactionMarker {
private BasicDataSource dataSource;
@Override
public void executeApp() throws Exception {
dataSource = new BasicDataSource() {
@Override
public Connection getConnection() throws SQLException {
throw new SQLException("A getconnection failure");
}
};
dataSource.setDriverClassName("org.hsqldb.jdbc.JDBCDriver");
dataSource.setUrl("jdbc:hsqldb:mem:test");
transactionMarker();
}
@Override
public void transactionMarker() throws Exception {
try {
dataSource.getConnection();
} catch (SQLException e) {
}
}
}
public static class ExecuteCloseConnectionOnThrowingDataSource
implements AppUnderTest, TransactionMarker {
private BasicDataSource dataSource;
@Override
public void executeApp() throws Exception {
dataSource = new BasicDataSource() {
private boolean first = true;
@Override
public Connection getConnection() throws SQLException {
if (first) {
// BasicDataSource opens and closes a test connection on first
// getConnection()
first = false;
return super.getConnection();
}
return new DelegatingConnection(super.getConnection()) {
@Override
public void close() throws SQLException {
throw new SQLException("A close failure");
}
};
}
};
dataSource.setDriverClassName("org.hsqldb.jdbc.JDBCDriver");
dataSource.setUrl("jdbc:hsqldb:mem:test");
// BasicDataSource opens and closes a test connection on first getConnection(),
// so just getting that out of the way before starting transaction
dataSource.getConnection().close();
transactionMarker();
}
@Override
public void transactionMarker() throws Exception {
try {
dataSource.getConnection().close();
} catch (SQLException e) {
}
}
}
public static class ExecuteSetAutoCommit implements AppUnderTest, TransactionMarker {
private Connection connection;
@Override
public void executeApp() throws Exception {
connection = Connections.createConnection();
try {
transactionMarker();
} finally {
Connections.closeConnection(connection);
}
}
@Override
public void transactionMarker() throws Exception {
connection.setAutoCommit(false);
connection.setAutoCommit(true);
}
}
public static class ExecuteSetAutoCommitThrowing implements AppUnderTest, TransactionMarker {
private Connection connection;
@Override
public void executeApp() throws Exception {
connection = new DelegatingConnection(Connections.createConnection()) {
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
throw new SQLException("A setautocommit failure");
}
};
try {
transactionMarker();
} finally {
Connections.closeConnection(connection);
}
}
@Override
public void transactionMarker() {
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
}
try {
connection.setAutoCommit(true);
} catch (SQLException e) {
}
}
}
}