Java tutorial
/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2017 by Hitachi Vantara : 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 ConnectionPoolUtilIntegrationIT { 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)); } } }