001: /*
002: Copyright (C) 2002-2004 MySQL AB
003:
004: This program is free software; you can redistribute it and/or modify
005: it under the terms of version 2 of the GNU General Public License as
006: published by the Free Software Foundation.
007:
008: There are special exceptions to the terms and conditions of the GPL
009: as it is applied to this software. View the full text of the
010: exception in file EXCEPTIONS-CONNECTOR-J in the directory of this
011: software distribution.
012:
013: This program is distributed in the hope that it will be useful,
014: but WITHOUT ANY WARRANTY; without even the implied warranty of
015: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: GNU General Public License for more details.
017:
018: You should have received a copy of the GNU General Public License
019: along with this program; if not, write to the Free Software
020: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021:
022:
023:
024: */
025: package testsuite.regression;
026:
027: import java.io.BufferedInputStream;
028: import java.io.FileInputStream;
029: import java.sql.Connection;
030: import java.sql.PreparedStatement;
031: import java.sql.SQLException;
032:
033: import javax.sql.ConnectionEvent;
034: import javax.sql.ConnectionEventListener;
035: import javax.sql.ConnectionPoolDataSource;
036: import javax.sql.PooledConnection;
037:
038: import junit.framework.Test;
039: import junit.framework.TestSuite;
040: import testsuite.BaseTestCase;
041:
042: import com.mysql.jdbc.PacketTooBigException;
043: import com.mysql.jdbc.jdbc2.optional.ConnectionWrapper;
044: import com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource;
045:
046: /**
047: * Tests a PooledConnection implementation provided by a JDBC driver. Test case
048: * provided by Johnny Macchione from bug database record BUG#884. According to
049: * the JDBC 2.0 specification:
050: *
051: * <p>
052: * "Each call to PooledConnection.getConnection() must return a newly
053: * constructed Connection object that exhibits the default Connection behavior.
054: * Only the most recent Connection object produced from a particular
055: * PooledConnection is open. An existing Connection object is automatically
056: * closed, if the getConnection() method of its associated Pooled-Connection is
057: * called again, before it has been explicitly closed by the application. This
058: * gives the application server a way to �take away� a Connection from the
059: * application if it wishes, and give it out to someone else. This capability
060: * will not likely be used frequently in practice."
061: * </p>
062: *
063: * <p>
064: * "When the application calls Connection.close(), an event is triggered that
065: * tells the connection pool it can recycle the physical database connection. In
066: * other words, the event signals the connection pool that the PooledConnection
067: * object which originally produced the Connection object generating the event
068: * can be put back in the connection pool."
069: * </p>
070: *
071: * <p>
072: * "A Connection-EventListener will also be notified when a fatal error occurs,
073: * so that it can make a note not to put a bad PooledConnection object back in
074: * the cache when the application finishes using it. When an error occurs, the
075: * ConnectionEventListener is notified by the JDBC driver, just before the
076: * driver throws an SQLException to the application to notify it of the same
077: * error. Note that automatic closing of a Connection object as discussed in the
078: * previous section does not generate a connection close event."
079: * </p>
080: * The JDBC 3.0 specification states the same in other words:
081: *
082: * <p>
083: * "The Connection.close method closes the logical handle, but the physical
084: * connection is maintained. The connection pool manager is notified that the
085: * underlying PooledConnection object is now available for reuse. If the
086: * application attempts to reuse the logical handle, the Connection
087: * implementation throws an SQLException."
088: * </p>
089: *
090: * <p>
091: * "For a given PooledConnection object, only the most recently produced logical
092: * Connection object will be valid. Any previously existing Connection object is
093: * automatically closed when the associated PooledConnection.getConnection
094: * method is called. Listeners (connection pool managers) are not notified in
095: * this case. This gives the application server a way to take a connection away
096: * from a client. This is an unlikely scenario but may be useful if the
097: * application server is trying to force an orderly shutdown."
098: * </p>
099: *
100: * <p>
101: * "A connection pool manager shuts down a physical connection by calling the
102: * method PooledConnection.close. This method is typically called only in
103: * certain circumstances: when the application server is undergoing an orderly
104: * shutdown, when the connection cache is being reinitialized, or when the
105: * application server receives an event indicating that an unrecoverable error
106: * has occurred on the connection."
107: * </p>
108: * Even though the specification isn't clear about it, I think it is no use
109: * generating a close event when calling the method PooledConnection.close(),
110: * even if a logical Connection is open for this PooledConnection, bc the
111: * PooledConnection will obviously not be returned to the pool.
112: *
113: * @author fcr
114: */
115: public final class PooledConnectionRegressionTest extends BaseTestCase {
116: private ConnectionPoolDataSource cpds;
117:
118: // Count nb of closeEvent.
119: private int closeEventCount;
120:
121: // Count nb of connectionErrorEvent
122: private int connectionErrorEventCount;
123:
124: /**
125: * Creates a new instance of ProgressPooledConnectionTest
126: *
127: * @param testname
128: * DOCUMENT ME!
129: */
130: public PooledConnectionRegressionTest(String testname) {
131: super (testname);
132: }
133:
134: /**
135: * Set up test case before a test is run.
136: *
137: * @throws Exception
138: * DOCUMENT ME!
139: */
140: public void setUp() throws Exception {
141: super .setUp();
142:
143: // Reset event count.
144: this .closeEventCount = 0;
145: this .connectionErrorEventCount = 0;
146:
147: MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource();
148:
149: ds.setURL(BaseTestCase.dbUrl);
150:
151: this .cpds = ds;
152: }
153:
154: /**
155: * Runs all test cases in this test suite
156: *
157: * @param args
158: */
159: public static void main(String[] args) {
160: junit.textui.TestRunner
161: .run(PooledConnectionRegressionTest.class);
162: }
163:
164: /**
165: * DOCUMENT ME!
166: *
167: * @return a test suite composed of this test case.
168: */
169: public static Test suite() {
170: TestSuite suite = new TestSuite(
171: PooledConnectionRegressionTest.class);
172:
173: return suite;
174: }
175:
176: /**
177: * After the test is run.
178: */
179: public void tearDown() {
180: this .cpds = null;
181: }
182:
183: /**
184: * Tests fix for BUG#7136 ... Statement.getConnection() returning physical
185: * connection instead of logical connection.
186: */
187: public void testBug7136() {
188: final ConnectionEventListener conListener = new ConnectionListener();
189: PooledConnection pc = null;
190: this .closeEventCount = 0;
191:
192: try {
193: pc = this .cpds.getPooledConnection();
194:
195: pc.addConnectionEventListener(conListener);
196:
197: Connection conn = pc.getConnection();
198:
199: Connection connFromStatement = conn.createStatement()
200: .getConnection();
201:
202: // This should generate a close event.
203:
204: connFromStatement.close();
205:
206: assertEquals("One close event should've been registered",
207: 1, this .closeEventCount);
208:
209: this .closeEventCount = 0;
210:
211: conn = pc.getConnection();
212:
213: Connection connFromPreparedStatement = conn
214: .prepareStatement("SELECT 1").getConnection();
215:
216: // This should generate a close event.
217:
218: connFromPreparedStatement.close();
219:
220: assertEquals("One close event should've been registered",
221: 1, this .closeEventCount);
222:
223: } catch (SQLException ex) {
224: fail(ex.toString());
225: } finally {
226: if (pc != null) {
227: try {
228: pc.close();
229: } catch (SQLException ex) {
230: ex.printStackTrace();
231: }
232: }
233: }
234: }
235:
236: /**
237: * Test the nb of closeEvents generated when a Connection is reclaimed. No
238: * event should be generated in that case.
239: */
240: public void testConnectionReclaim() {
241: final ConnectionEventListener conListener = new ConnectionListener();
242: PooledConnection pc = null;
243: final int NB_TESTS = 5;
244:
245: try {
246: pc = this .cpds.getPooledConnection();
247:
248: pc.addConnectionEventListener(conListener);
249:
250: for (int i = 0; i < NB_TESTS; i++) {
251: Connection conn = pc.getConnection();
252:
253: try {
254: // Try to reclaim connection.
255: System.out.println("Before connection reclaim.");
256:
257: conn = pc.getConnection();
258:
259: System.out.println("After connection reclaim.");
260: } finally {
261: if (conn != null) {
262: System.out
263: .println("Before connection.close().");
264:
265: // This should generate a close event.
266: conn.close();
267:
268: System.out.println("After connection.close().");
269: }
270: }
271: }
272: } catch (SQLException ex) {
273: ex.printStackTrace();
274: fail(ex.toString());
275: } finally {
276: if (pc != null) {
277: try {
278: System.out
279: .println("Before pooledConnection.close().");
280:
281: // This should not generate a close event.
282: pc.close();
283:
284: System.out
285: .println("After pooledConnection.close().");
286: } catch (SQLException ex) {
287: ex.printStackTrace();
288: fail(ex.toString());
289: }
290: }
291: }
292:
293: assertEquals("Wrong nb of CloseEvents: ", NB_TESTS,
294: this .closeEventCount);
295: }
296:
297: /**
298: * Tests that PacketTooLargeException doesn't clober the connection.
299: *
300: * @throws Exception
301: * if the test fails.
302: */
303: public void testPacketTooLargeException() throws Exception {
304: final ConnectionEventListener conListener = new ConnectionListener();
305: PooledConnection pc = null;
306:
307: pc = this .cpds.getPooledConnection();
308:
309: pc.addConnectionEventListener(conListener);
310:
311: try {
312: this .stmt
313: .executeUpdate("DROP TABLE IF EXISTS testPacketTooLarge");
314: this .stmt
315: .executeUpdate("CREATE TABLE testPacketTooLarge(field1 LONGBLOB)");
316:
317: Connection connFromPool = pc.getConnection();
318: PreparedStatement pstmtFromPool = ((ConnectionWrapper) connFromPool)
319: .clientPrepare("INSERT INTO testPacketTooLarge VALUES (?)");
320:
321: this .rs = this .stmt
322: .executeQuery("SHOW VARIABLES LIKE 'max_allowed_packet'");
323: this .rs.next();
324:
325: int maxAllowedPacket = this .rs.getInt(2);
326:
327: int numChars = (int) (maxAllowedPacket * 1.2);
328:
329: pstmtFromPool.setBinaryStream(1, new BufferedInputStream(
330: new FileInputStream(newTempBinaryFile(
331: "testPacketTooLargeException", numChars))),
332: numChars);
333:
334: try {
335: pstmtFromPool.executeUpdate();
336: fail("Expecting PacketTooLargeException");
337: } catch (PacketTooBigException ptbe) {
338: // We're expecting this one...
339: }
340:
341: // This should still work okay, even though the last query on the
342: // same
343: // connection didn't...
344: connFromPool.createStatement().executeQuery("SELECT 1");
345:
346: assertTrue(this .connectionErrorEventCount == 0);
347: assertTrue(this .closeEventCount == 0);
348: } finally {
349: this .stmt
350: .executeUpdate("DROP TABLE IF EXISTS testPacketTooLarge");
351: }
352: }
353:
354: /**
355: * Test the nb of closeEvents generated by a PooledConnection. A
356: * JDBC-compliant driver should only generate 1 closeEvent each time
357: * connection.close() is called.
358: */
359: public void testCloseEvent() {
360: final ConnectionEventListener conListener = new ConnectionListener();
361: PooledConnection pc = null;
362: final int NB_TESTS = 5;
363:
364: try {
365: pc = this .cpds.getPooledConnection();
366:
367: pc.addConnectionEventListener(conListener);
368:
369: for (int i = 0; i < NB_TESTS; i++) {
370: Connection pConn = pc.getConnection();
371:
372: System.out.println("Before connection.close().");
373:
374: // This should generate a close event.
375: pConn.close();
376:
377: System.out.println("After connection.close().");
378: }
379: } catch (SQLException ex) {
380: fail(ex.toString());
381: } finally {
382: if (pc != null) {
383: try {
384: System.out
385: .println("Before pooledConnection.close().");
386:
387: // This should not generate a close event.
388: pc.close();
389:
390: System.out
391: .println("After pooledConnection.close().");
392: } catch (SQLException ex) {
393: ex.printStackTrace();
394: }
395: }
396: }
397: assertEquals("Wrong nb of CloseEvents: ", NB_TESTS,
398: this .closeEventCount);
399: }
400:
401: /**
402: * Listener for PooledConnection events.
403: */
404: private final class ConnectionListener implements
405: ConnectionEventListener {
406: /** */
407: public void connectionClosed(ConnectionEvent event) {
408: PooledConnectionRegressionTest.this .closeEventCount++;
409: System.out
410: .println(PooledConnectionRegressionTest.this .closeEventCount
411: + " - Connection closed.");
412: }
413:
414: /** */
415: public void connectionErrorOccurred(ConnectionEvent event) {
416: PooledConnectionRegressionTest.this .connectionErrorEventCount++;
417: System.out.println("Connection error: "
418: + event.getSQLException());
419: }
420: }
421: }
|