/**
* Copyright 2010 Wallace Wadge
*
* 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 com.jolbox.bonecp;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
/** Test class for statement cache
* @author wwadge
*
*/
@SuppressWarnings("deprecation")
public class TestStatementCache {
/** Mock handle. */
private IStatementCache mockCache;
/** Mock handle. */
private Logger mockLogger;
/** Config clone. */
private BoneCPConfig config;
/** Mock handle. */
private MockJDBCDriver driver;
/** Mock setup.
* @throws ClassNotFoundException
* @throws SQLException
*/
@BeforeClass
public static void setup() throws ClassNotFoundException, SQLException{
}
/**
* Init.
* @throws SQLException
*/
@Before
public void beforeTest() throws SQLException{
driver = new MockJDBCDriver(new MockJDBCAnswer() {
public Connection answer() throws SQLException {
return new MockConnection();
}
});
mockCache = createNiceMock(IStatementCache.class);
mockLogger = createNiceMock(Logger.class);
config = CommonTestUtils.getConfigClone();
config.setJdbcUrl(CommonTestUtils.url);
config.setUsername(CommonTestUtils.username);
config.setPassword(CommonTestUtils.password);
config.setIdleConnectionTestPeriodInMinutes(0);
config.setIdleMaxAgeInMinutes(0);
config.setStatementsCacheSize(0);
config.setReleaseHelperThreads(0);
config.setDisableConnectionTracking(true);
config.setStatementReleaseHelperThreads(0);
config.setStatisticsEnabled(true);
}
/** After cleanup.
* @throws SQLException
*/
@After
public void teardown() throws SQLException{
driver.disable();
}
/** Prepared statement tests.
* @throws SQLException
* @throws SecurityException
* @throws NoSuchFieldException
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
@Test
public void testPreparedStatement() throws SQLException, SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
BoneCP dsb = null ;
CommonTestUtils.logTestInfo("Tests that prepared statements are obtained from cache when set.");
config.setMinConnectionsPerPartition(10);
config.setMaxConnectionsPerPartition(20);
config.setAcquireIncrement(5);
config.setPartitionCount(1);
config.setStatementsCacheSize(1);
config.setLogStatementsEnabled(true);
config.setStatementReleaseHelperThreads(0);
dsb = new BoneCP(config);
ConnectionHandle con = (ConnectionHandle) dsb.getConnection();
Field preparedStatement = con.getClass().getDeclaredField("preparedStatementCache");
preparedStatement.setAccessible(true);
// switch to our mock
preparedStatement.set(con, mockCache);
expect(mockCache.get(isA(String.class))).andReturn(null);
// mockCache.put(isA(String.class), isA(PreparedStatement.class));
replay(mockCache);
Statement statement = con.prepareStatement(CommonTestUtils.TEST_QUERY);
statement.close();
verify(mockCache);
reset(mockCache);
expect(mockCache.get(isA(String.class))).andReturn(null);
replay(mockCache);
con.prepareStatement(CommonTestUtils.TEST_QUERY);
statement.close();
verify(mockCache);
dsb.shutdown();
statement.close();
con.close();
dsb.close();
CommonTestUtils.logPass();
}
/** Test case for when item is not in cache.
* @throws SQLException
* @throws SecurityException
* @throws NoSuchFieldException
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
@Test
public void testStatementCacheNotInCache() throws SQLException, SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
CommonTestUtils.logTestInfo("Tests statement not in cache.");
StatementCache cache = new StatementCache(5, false, null);
assertNull(cache.get("nonExistent"));
CommonTestUtils.logPass();
}
/** Test case method for calling different get signatures.
* @throws SQLException
* @throws SecurityException
* @throws NoSuchFieldException
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
@Test
public void testStatementCacheDifferentGetSignatures() throws SQLException, SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
CommonTestUtils.logTestInfo("Tests statement get() signatures.");
CommonTestUtils.logTestInfo("Tests statement close (put in cache).");
String sql = CommonTestUtils.TEST_QUERY;
BoneCP dsb = null ;
config.setMinConnectionsPerPartition(1);
config.setMaxConnectionsPerPartition(5);
config.setAcquireIncrement(1);
config.setPartitionCount(1);
config.setStatementsCacheSize(5);
config.setStatementReleaseHelperThreads(0);
dsb = new BoneCP(config);
Connection conn = dsb.getConnection();
Statement statement = conn.prepareStatement(sql);
statement.close();
StatementCache cache = new StatementCache(5, false, null);
cache.putIfAbsent("test1", (StatementHandle)statement);
assertNotNull(cache.get("test1"));
assertNull(cache.get("test1", 1));
assertNull(cache.get("test1", new int[]{1}));
assertNull(cache.get("test1", new String[]{"1"}));
assertNull(cache.get("test1", 1, 1));
assertNull(cache.get("test1", 1, 1, 1));
dsb.close();
CommonTestUtils.logPass();
}
/** Test case for cache put.
* @throws SQLException
* @throws SecurityException
* @throws NoSuchFieldException
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
@Test
public void testStatementCachePut() throws SQLException, SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
CommonTestUtils.logTestInfo("Tests statement close (put in cache).");
String sql = CommonTestUtils.TEST_QUERY;
BoneCP dsb = null ;
config.setMinConnectionsPerPartition(1);
config.setMaxConnectionsPerPartition(5);
config.setAcquireIncrement(1);
config.setPartitionCount(1);
config.setStatementsCacheSize(5);
config.setStatementReleaseHelperThreads(0);
config.setStatisticsEnabled(true);
dsb = new BoneCP(config);
Connection conn = dsb.getConnection();
Statement statement = conn.prepareStatement(sql);
statement.close();
Field statementCache = conn.getClass().getDeclaredField("preparedStatementCache");
statementCache.setAccessible(true);
IStatementCache cache = (IStatementCache) statementCache.get(conn);
statement = cache.get(sql);
assertNotNull(statement);
// Calling again should not provide the same object
assertNull(cache.get(sql));
// now pretend we have 1 connection being asked for the same statement
// twice
statement = conn.prepareStatement(sql);
Statement statement2 = conn.prepareStatement(sql);
statement.close(); // release it again
statement2.close(); // release the other one
statement2.close();
statement.close();
conn.close();
dsb.shutdown();
dsb.close();
CommonTestUtils.logPass();
}
/** Test limits.
* @throws SQLException
* @throws SecurityException
* @throws NoSuchFieldException
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
@Test
public void testStatementCacheLimits() throws SQLException, SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException{
CommonTestUtils.logTestInfo("Tests statement caching module.");
String sql = CommonTestUtils.TEST_QUERY;
BoneCP dsb = null ;
config.setMinConnectionsPerPartition(2);
config.setMaxConnectionsPerPartition(5);
config.setAcquireIncrement(1);
config.setPartitionCount(1);
config.setStatementsCacheSize(5);
dsb = new BoneCP(config);
Connection conn = dsb.getConnection();
StatementHandle statement = (StatementHandle)conn.prepareStatement(sql);
StatementCache cache = new StatementCache(5, true, new Statistics(dsb));
cache.putIfAbsent("test1", statement);
cache.putIfAbsent("test2", statement);
cache.putIfAbsent("test3", statement);
cache.putIfAbsent("test4", statement);
cache.putIfAbsent("test5", statement);
conn.close();
for (int i=0; i < 5000000; i++){
cache.putIfAbsent("test"+i, statement);
if ((i % 10000) == 0){
System.gc();
}
if (cache.size() != i) {
break;
}
}
// some elements should have been dropped in the cache
assertFalse(cache.size()==5000000);
dsb.shutdown();
CommonTestUtils.logPass();
}
/** Tests statement cache clear.
* @throws SecurityException
* @throws NoSuchFieldException
* @throws IllegalArgumentException
* @throws IllegalAccessException
* @throws SQLException
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
public void testStatementCacheClear() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, SQLException {
ConcurrentMap mockCache = createNiceMock(ConcurrentMap.class);
List<StatementHandle> mockStatementCollections = createNiceMock(List.class);
StatementCache testClass = new StatementCache(1, false, null);
Field field = testClass.getClass().getDeclaredField("cache");
field.setAccessible(true);
field.set(testClass, mockCache);
Iterator<StatementHandle> mockIterator = createNiceMock(Iterator.class);
StatementHandle mockStatement = createNiceMock(StatementHandle.class);
expect(mockCache.values()).andReturn(mockStatementCollections).anyTimes();
expect(mockStatementCollections.iterator()).andReturn(mockIterator).anyTimes();
expect(mockIterator.hasNext()).andReturn(true).times(2).andReturn(false).once();
expect(mockIterator.next()).andReturn(mockStatement).anyTimes();
mockStatement.close();
expectLastCall().once().andThrow(new SQLException()).once();
mockCache.clear();
expectLastCall().once();
replay(mockCache, mockStatementCollections, mockIterator,mockStatement);
testClass.clear();
verify(mockCache, mockStatement);
}
/** Proper closure test
* @throws SecurityException
* @throws NoSuchFieldException
* @throws IllegalArgumentException
* @throws IllegalAccessException
* @throws SQLException
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
public void testStatementCacheCheckForProperClosure() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, SQLException {
ConcurrentMap mockCache = createNiceMock(ConcurrentMap.class);
List<StatementHandle> mockStatementCollections = createNiceMock(List.class);
StatementCache testClass = new StatementCache(1, false, null);
Field field = testClass.getClass().getDeclaredField("cache");
field.setAccessible(true);
field.set(testClass, mockCache);
field = testClass.getClass().getDeclaredField("logger");
TestUtils.setFinalStatic(field, mockLogger);
Iterator<StatementHandle> mockIterator = createNiceMock(Iterator.class);
StatementHandle mockStatement = createNiceMock(StatementHandle.class);
expect(mockCache.values()).andReturn(mockStatementCollections).anyTimes();
expect(mockStatementCollections.iterator()).andReturn(mockIterator).anyTimes();
expect(mockIterator.hasNext()).andReturn(true).once().andReturn(false).once();
expect(mockIterator.next()).andReturn(mockStatement).anyTimes();
expect(mockStatement.isClosed()).andReturn(false).once();
mockLogger.error((String)anyObject());
expectLastCall().once();
replay(mockCache, mockStatementCollections, mockIterator,mockStatement, mockLogger);
testClass.checkForProperClosure();
verify(mockCache, mockStatement, mockLogger);
}
}