/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2015 by Pentaho : http://www.pentaho.com
*
*******************************************************************************
*
* 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.pentaho.di.core.database;
import static org.junit.Assert.*;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.*;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.commons.dbcp.DelegatingConnection;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.pentaho.di.core.KettleClientEnvironment;
import org.pentaho.di.core.database.util.DatabaseUtil;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.logging.LogChannelInterface;
import org.pentaho.di.i18n.BaseMessages;
public class ConnectionPoolUtilIntegrationTest {
private static final String PASSWORD = "manager";
private static Class<?> PKG = Database.class;
private static final int INITIAL_POOL_SIZE = 1;
Driver driver;
private static long MAX_WAIT_TIME = 1000;
private static int MAX_ACTIVE = 2;
private static String VALIDATION_QUERY = "select 1 from INFORMATION_SCHEMA.USERS";
private LogChannelInterface logChannelInterface;
Properties dsProps;
@BeforeClass
public static void setupBeforeClass() throws KettleException {
KettleClientEnvironment.init();
}
@Before
public void setUp() throws Exception {
driver = mock( Driver.class, RETURNS_MOCKS );
DriverManager.registerDriver( driver );
logChannelInterface = mock( LogChannelInterface.class, RETURNS_MOCKS );
dsProps = new Properties();
dsProps.setProperty( ConnectionPoolUtil.DEFAULT_AUTO_COMMIT, "true" );
dsProps.setProperty( ConnectionPoolUtil.DEFAULT_READ_ONLY, "true" );
dsProps.setProperty( ConnectionPoolUtil.DEFAULT_TRANSACTION_ISOLATION, "1" );
dsProps.setProperty( ConnectionPoolUtil.DEFAULT_CATALOG, "" );
dsProps.setProperty( ConnectionPoolUtil.MAX_IDLE, "30" );
dsProps.setProperty( ConnectionPoolUtil.MIN_IDLE, "3" );
dsProps.setProperty( ConnectionPoolUtil.MAX_WAIT, String.valueOf( MAX_WAIT_TIME ) ); // tested
dsProps.setProperty( ConnectionPoolUtil.VALIDATION_QUERY, VALIDATION_QUERY );
dsProps.setProperty( ConnectionPoolUtil.TEST_ON_BORROW, "true" );
dsProps.setProperty( ConnectionPoolUtil.TEST_ON_RETURN, "true" );
dsProps.setProperty( ConnectionPoolUtil.TEST_WHILE_IDLE, "true" );
dsProps.setProperty( ConnectionPoolUtil.TIME_BETWEEN_EVICTION_RUNS_MILLIS, "300000" );
dsProps.setProperty( ConnectionPoolUtil.POOL_PREPARED_STATEMENTS, "true" ); // tested
dsProps.setProperty( ConnectionPoolUtil.MAX_OPEN_PREPARED_STATEMENTS, "2" ); // tested
dsProps.setProperty( ConnectionPoolUtil.ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED, "true" ); // tested
dsProps.setProperty( ConnectionPoolUtil.REMOVE_ABANDONED, "false" );
dsProps.setProperty( ConnectionPoolUtil.REMOVE_ABANDONED_TIMEOUT, "1000" );
dsProps.setProperty( ConnectionPoolUtil.LOG_ABANDONED, "false" );
}
@After
public void tearDown() throws Exception {
DriverManager.deregisterDriver( driver );
}
@Test
public void testGet_01_ConnectionFromPool() throws Exception {
testGetConnectionFromPool( 1 );
}
@Test
public void testGet_02_ConnectionFromPool() throws Exception {
testGetConnectionFromPool( 2 );
}
@Test
public void testGet_04_ConnectionFromPool() throws Exception {
testGetConnectionFromPool( 4 );
}
@Test
public void testGet_08_ConnectionFromPool() throws Exception {
testGetConnectionFromPool( 8 );
}
@Test
public void testPreparedStatementsProperty() throws Exception {
Connection conn = null;
PreparedStatement[] ps = new PreparedStatement[ 3 ];
try {
DatabaseMeta dbMeta = new DatabaseMeta( "testPreparedStatements", "H2", "JDBC", null, "mem:test", null, "SA", "" );
dbMeta.setConnectionPoolingProperties( dsProps );
conn = ConnectionPoolUtil.getConnection( logChannelInterface, dbMeta, "part1", INITIAL_POOL_SIZE, MAX_ACTIVE );
ps[ 0 ] = conn.prepareStatement( VALIDATION_QUERY );
ps[ 1 ] = conn.prepareStatement( VALIDATION_QUERY );
boolean failed = false;
try {
ps[ 2 ] = conn.prepareStatement( VALIDATION_QUERY );
} catch ( Exception e ) {
failed = true;
}
assertTrue( "Properties 'poolPreparedStatements' or 'maxOpenPreparedStatements' don't work", failed );
} finally {
DatabaseUtil.closeSilently( ps );
DatabaseUtil.closeSilently( conn );
}
}
// maxPoolSize is set to "2", maxWait = MAX_WAIT_TIME
// so after getting the next connection the thread should block for 3 seconds and then
// throw "Cannot get connection exception"
@Test( timeout = 6000 )
public void testMaxActiveProperty() throws Exception {
Connection[] c = new Connection[ 3 ];
DatabaseMeta dbMeta = new DatabaseMeta( "testPreparedStatements", "H2", "JDBC", null, "mem:test", null, "SA", "" );
dbMeta.setConnectionPoolingProperties( dsProps );
try {
c[ 0 ] = ConnectionPoolUtil.getConnection( logChannelInterface, dbMeta, "part1", INITIAL_POOL_SIZE, MAX_ACTIVE );
c[ 1 ] = ConnectionPoolUtil.getConnection( logChannelInterface, dbMeta, "part1", INITIAL_POOL_SIZE, MAX_ACTIVE );
long startTime = System.currentTimeMillis();
try {
// this must wait a bit and throw an exception
c[ 2 ] = ConnectionPoolUtil.getConnection( logChannelInterface, dbMeta, "part1", INITIAL_POOL_SIZE, MAX_ACTIVE );
} catch ( SQLException e ) {
long waitedTime = System.currentTimeMillis() - startTime;
assertFalse( "Waited < maxWait", waitedTime < MAX_WAIT_TIME );
}
} finally {
DatabaseUtil.closeSilently( c );
}
}
@Test
public void testAccessToUnderlyingConnectionAllowedProperty() throws Exception {
Connection conn = null;
DatabaseMeta dbMeta = new DatabaseMeta( "testAccessToUnderlying", "H2", "JDBC", null, "mem:test", null, "SA", "" );
dbMeta.setConnectionPoolingProperties( dsProps );
try {
conn = ConnectionPoolUtil.getConnection( logChannelInterface, dbMeta, "part1", INITIAL_POOL_SIZE, MAX_ACTIVE );
Connection dconn = ( (DelegatingConnection) conn ).getInnermostDelegate();
assertNotNull( "Property 'accessToUnderlyingConnectionAllowed' doesn't work", dconn );
} catch ( Exception e ) {
fail();
}
}
private void testGetConnectionFromPool( final int threadCount ) throws Exception {
ArgumentCaptor<String> captorLogMessage = ArgumentCaptor.forClass( String.class );
final DatabaseMeta dbMeta = mock( DatabaseMeta.class, RETURNS_MOCKS );
when( dbMeta.getDriverClass() ).thenReturn( driver.getClass().getCanonicalName() );
when( dbMeta.getConnectionPoolingProperties() ).thenReturn( new Properties() );
when( dbMeta.environmentSubstitute( anyString() ) ).thenAnswer( new Answer<Object>() {
@Override
public Object answer( InvocationOnMock invocation ) throws Throwable {
return invocation.getArguments()[0];
}
} );
when( dbMeta.getName() ).thenReturn( "CP1" );
when( dbMeta.getPassword() ).thenReturn( PASSWORD );
Callable<Connection> task = new Callable<Connection>() {
@Override
public Connection call() throws Exception {
return ConnectionPoolUtil.getConnection( logChannelInterface, dbMeta, "", INITIAL_POOL_SIZE, threadCount );
}
};
List<Callable<Connection>> tasks = Collections.nCopies( threadCount, task );
ExecutorService executorService = Executors.newFixedThreadPool( threadCount );
List<Future<Connection>> futures = executorService.invokeAll( tasks );
assertNotNull( futures );
assertEquals( threadCount, futures.size() );
//pool should be creates only once for KettleClientEnvironment
verify( logChannelInterface, atMost( 2 ) ).logBasic( captorLogMessage.capture() );
List<String> capturedLogEntry = captorLogMessage.getAllValues();
if ( capturedLogEntry != null && !capturedLogEntry.isEmpty() ) {
assertEquals( BaseMessages.getString( PKG, "Database.CreatingConnectionPool", dbMeta.getName() ), capturedLogEntry.get( 0 ) );
assertEquals( BaseMessages.getString( PKG, "Database.CreatedConnectionPool", dbMeta.getName() ), capturedLogEntry.get( 1 ) );
}
}
}