/*
Copyright (c) 2002, 2014, Oracle and/or its affiliates. All rights reserved.
The MySQL Connector/J is licensed under the terms of the GPLv2
<http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors.
There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
this software, see the FLOSS License Exception
<http://www.mysql.com/about/legal/licensing/foss-exception.html>.
This program is free software; you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation; version 2
of the License.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this
program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
Floor, Boston, MA 02110-1301 USA
*/
package testsuite.regression;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.sql.ConnectionEvent;
import javax.sql.ConnectionEventListener;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import junit.framework.Test;
import junit.framework.TestSuite;
import testsuite.BaseTestCase;
import com.mysql.jdbc.PacketTooBigException;
import com.mysql.jdbc.jdbc2.optional.ConnectionWrapper;
import com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
/**
* Tests a PooledConnection implementation provided by a JDBC driver. Test case provided by Johnny Macchione from bug database record BUG#884. According to
* the JDBC 2.0 specification:
*
* <p>
* "Each call to PooledConnection.getConnection() must return a newly constructed Connection object that exhibits the default Connection behavior. Only the most
* recent Connection object produced from a particular PooledConnection is open. An existing Connection object is automatically closed, if the getConnection()
* method of its associated Pooled-Connection is called again, before it has been explicitly closed by the application. This gives the application server a way
* to �take away� a Connection from the application if it wishes, and give it out to someone else. This capability will not likely be used frequently in
* practice."
* </p>
*
* <p>
* "When the application calls Connection.close(), an event is triggered that tells the connection pool it can recycle the physical database connection. In
* other words, the event signals the connection pool that the PooledConnection object which originally produced the Connection object generating the event can
* be put back in the connection pool."
* </p>
*
* <p>
* "A Connection-EventListener will also be notified when a fatal error occurs, so that it can make a note not to put a bad PooledConnection object back in the
* cache when the application finishes using it. When an error occurs, the ConnectionEventListener is notified by the JDBC driver, just before the driver throws
* an SQLException to the application to notify it of the same error. Note that automatic closing of a Connection object as discussed in the previous section
* does not generate a connection close event."
* </p>
* The JDBC 3.0 specification states the same in other words:
*
* <p>
* "The Connection.close method closes the logical handle, but the physical connection is maintained. The connection pool manager is notified that the
* underlying PooledConnection object is now available for reuse. If the application attempts to reuse the logical handle, the Connection implementation throws
* an SQLException."
* </p>
*
* <p>
* "For a given PooledConnection object, only the most recently produced logical Connection object will be valid. Any previously existing Connection object is
* automatically closed when the associated PooledConnection.getConnection method is called. Listeners (connection pool managers) are not notified in this case.
* This gives the application server a way to take a connection away from a client. This is an unlikely scenario but may be useful if the application server is
* trying to force an orderly shutdown."
* </p>
*
* <p>
* "A connection pool manager shuts down a physical connection by calling the method PooledConnection.close. This method is typically called only in certain
* circumstances: when the application server is undergoing an orderly shutdown, when the connection cache is being reinitialized, or when the application
* server receives an event indicating that an unrecoverable error has occurred on the connection."
* </p>
* Even though the specification isn't clear about it, I think it is no use
* generating a close event when calling the method PooledConnection.close(),
* even if a logical Connection is open for this PooledConnection, bc the
* PooledConnection will obviously not be returned to the pool.
*/
public final class PooledConnectionRegressionTest extends BaseTestCase {
private ConnectionPoolDataSource cpds;
// Count nb of closeEvent.
protected int closeEventCount;
// Count nb of connectionErrorEvent
protected int connectionErrorEventCount;
/**
* Creates a new instance of ProgressPooledConnectionTest
*
* @param testname
*/
public PooledConnectionRegressionTest(String testname) {
super(testname);
}
/**
* Set up test case before a test is run.
*
* @throws Exception
*/
@Override
public void setUp() throws Exception {
super.setUp();
// Reset event count.
this.closeEventCount = 0;
this.connectionErrorEventCount = 0;
MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource();
ds.setURL(BaseTestCase.dbUrl);
this.cpds = ds;
}
/**
* Runs all test cases in this test suite
*
* @param args
*/
public static void main(String[] args) {
junit.textui.TestRunner.run(PooledConnectionRegressionTest.class);
}
/**
* @return a test suite composed of this test case.
*/
public static Test suite() {
TestSuite suite = new TestSuite(PooledConnectionRegressionTest.class);
return suite;
}
/**
* After the test is run.
*/
@Override
public void tearDown() throws Exception {
this.cpds = null;
super.tearDown();
}
/**
* Tests fix for BUG#7136 ... Statement.getConnection() returning physical
* connection instead of logical connection.
*/
public void testBug7136() {
final ConnectionEventListener conListener = new ConnectionListener();
PooledConnection pc = null;
this.closeEventCount = 0;
try {
pc = this.cpds.getPooledConnection();
pc.addConnectionEventListener(conListener);
Connection _conn = pc.getConnection();
Connection connFromStatement = _conn.createStatement().getConnection();
// This should generate a close event.
connFromStatement.close();
assertEquals("One close event should've been registered", 1, this.closeEventCount);
this.closeEventCount = 0;
_conn = pc.getConnection();
Connection connFromPreparedStatement = _conn.prepareStatement("SELECT 1").getConnection();
// This should generate a close event.
connFromPreparedStatement.close();
assertEquals("One close event should've been registered", 1, this.closeEventCount);
} catch (SQLException ex) {
fail(ex.toString());
} finally {
if (pc != null) {
try {
pc.close();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
}
/**
* Test the nb of closeEvents generated when a Connection is reclaimed. No
* event should be generated in that case.
*/
public void testConnectionReclaim() {
final ConnectionEventListener conListener = new ConnectionListener();
PooledConnection pc = null;
final int NB_TESTS = 5;
try {
pc = this.cpds.getPooledConnection();
pc.addConnectionEventListener(conListener);
for (int i = 0; i < NB_TESTS; i++) {
Connection _conn = pc.getConnection();
try {
// Try to reclaim connection.
System.out.println("Before connection reclaim.");
_conn = pc.getConnection();
System.out.println("After connection reclaim.");
} finally {
if (_conn != null) {
System.out.println("Before connection.close().");
// This should generate a close event.
_conn.close();
System.out.println("After connection.close().");
}
}
}
} catch (SQLException ex) {
ex.printStackTrace();
fail(ex.toString());
} finally {
if (pc != null) {
try {
System.out.println("Before pooledConnection.close().");
// This should not generate a close event.
pc.close();
System.out.println("After pooledConnection.close().");
} catch (SQLException ex) {
ex.printStackTrace();
fail(ex.toString());
}
}
}
assertEquals("Wrong nb of CloseEvents: ", NB_TESTS, this.closeEventCount);
}
/**
* Tests that PacketTooLargeException doesn't clober the connection.
*
* @throws Exception
* if the test fails.
*/
public void testPacketTooLargeException() throws Exception {
final ConnectionEventListener conListener = new ConnectionListener();
PooledConnection pc = null;
pc = this.cpds.getPooledConnection();
pc.addConnectionEventListener(conListener);
createTable("testPacketTooLarge", "(field1 LONGBLOB)");
Connection connFromPool = pc.getConnection();
PreparedStatement pstmtFromPool = ((ConnectionWrapper) connFromPool).clientPrepare("INSERT INTO testPacketTooLarge VALUES (?)");
this.rs = this.stmt.executeQuery("SHOW VARIABLES LIKE 'max_allowed_packet'");
this.rs.next();
int maxAllowedPacket = this.rs.getInt(2);
int numChars = (int) (maxAllowedPacket * 1.2);
pstmtFromPool.setBinaryStream(1, new BufferedInputStream(new FileInputStream(newTempBinaryFile("testPacketTooLargeException", numChars))), numChars);
try {
pstmtFromPool.executeUpdate();
fail("Expecting PacketTooLargeException");
} catch (PacketTooBigException ptbe) {
// We're expecting this one...
}
// This should still work okay, even though the last query on the same connection didn't...
connFromPool.createStatement().executeQuery("SELECT 1");
assertTrue(this.connectionErrorEventCount == 0);
assertTrue(this.closeEventCount == 0);
}
/**
* Test the nb of closeEvents generated by a PooledConnection. A
* JDBC-compliant driver should only generate 1 closeEvent each time
* connection.close() is called.
*/
public void testCloseEvent() {
final ConnectionEventListener conListener = new ConnectionListener();
PooledConnection pc = null;
final int NB_TESTS = 5;
try {
pc = this.cpds.getPooledConnection();
pc.addConnectionEventListener(conListener);
for (int i = 0; i < NB_TESTS; i++) {
Connection pConn = pc.getConnection();
System.out.println("Before connection.close().");
// This should generate a close event.
pConn.close();
System.out.println("After connection.close().");
}
} catch (SQLException ex) {
fail(ex.toString());
} finally {
if (pc != null) {
try {
System.out.println("Before pooledConnection.close().");
// This should not generate a close event.
pc.close();
System.out.println("After pooledConnection.close().");
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
assertEquals("Wrong nb of CloseEvents: ", NB_TESTS, this.closeEventCount);
}
/**
* Listener for PooledConnection events.
*/
protected final class ConnectionListener implements ConnectionEventListener {
/** */
public void connectionClosed(ConnectionEvent event) {
PooledConnectionRegressionTest.this.closeEventCount++;
System.out.println(PooledConnectionRegressionTest.this.closeEventCount + " - Connection closed.");
}
/** */
public void connectionErrorOccurred(ConnectionEvent event) {
PooledConnectionRegressionTest.this.connectionErrorEventCount++;
System.out.println("Connection error: " + event.getSQLException());
}
}
/**
* Tests fix for BUG#35489 - Prepared statements from pooled connections
* cause NPE when closed() under JDBC4
*
* @throws Exception
* if the test fails
*/
public void testBug35489() throws Exception {
MysqlConnectionPoolDataSource pds = new MysqlConnectionPoolDataSource();
pds.setUrl(dbUrl);
this.pstmt = pds.getPooledConnection().getConnection().prepareStatement("SELECT 1");
this.pstmt.execute();
this.pstmt.close();
MysqlXADataSource xads = new MysqlXADataSource();
xads.setUrl(dbUrl);
this.pstmt = xads.getXAConnection().getConnection().prepareStatement("SELECT 1");
this.pstmt.execute();
this.pstmt.close();
xads = new MysqlXADataSource();
xads.setUrl(dbUrl);
xads.setPinGlobalTxToPhysicalConnection(true);
this.pstmt = xads.getXAConnection().getConnection().prepareStatement("SELECT 1");
this.pstmt.execute();
this.pstmt.close();
}
}