001: /*
002: * Derby - org.apache.derbyTesting.functionTests.tests.jdbc4.ClosedObjectTest
003: *
004: Licensed to the Apache Software Foundation (ASF) under one or more
005: contributor license agreements. See the NOTICE file distributed with
006: this work for additional information regarding copyright ownership.
007: The ASF licenses this file to You under the Apache License, Version 2.0
008: (the "License"); you may not use this file except in compliance with
009: the License. You may obtain a copy of the License at
010:
011: http://www.apache.org/licenses/LICENSE-2.0
012:
013: Unless required by applicable law or agreed to in writing, software
014: distributed under the License is distributed on an "AS IS" BASIS,
015: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016: See the License for the specific language governing permissions and
017: limitations under the License.
018: *
019: */
020:
021: package org.apache.derbyTesting.functionTests.tests.jdbc4;
022:
023: import java.lang.reflect.InvocationTargetException;
024: import java.lang.reflect.Method;
025: import java.sql.CallableStatement;
026: import java.sql.SQLClientInfoException;
027: import java.sql.Connection;
028: import java.sql.PreparedStatement;
029: import java.sql.ResultSet;
030: import java.sql.SQLException;
031: import java.sql.SQLFeatureNotSupportedException;
032: import java.sql.Statement;
033: import java.util.Arrays;
034: import java.util.Properties;
035: import javax.sql.ConnectionPoolDataSource;
036: import javax.sql.DataSource;
037: import javax.sql.PooledConnection;
038: import javax.sql.XAConnection;
039: import javax.sql.XADataSource;
040: import junit.extensions.TestSetup;
041: import junit.framework.Test;
042: import junit.framework.TestSuite;
043: import org.apache.derbyTesting.functionTests.util.TestDataSourceFactory;
044: import org.apache.derbyTesting.junit.BaseJDBCTestCase;
045: import org.apache.derbyTesting.junit.TestConfiguration;
046:
047: /**
048: * Test that all methods on <code>ResultSet</code>,
049: * <code>Statement</code>, <code>PreparedStatement</code>,
050: * <code>CallableStatement</code> and <code>Connection</code> objects
051: * throw the appropriate exceptions when the objects are closed.
052: */
053: public class ClosedObjectTest extends BaseJDBCTestCase {
054: /** The method to test. */
055: private final Method method_;
056: /** Test decorator which provides a closed object to invoke a
057: * method on. */
058: private final ObjectDecorator decorator_;
059: /** Name of the test. */
060: private String name_;
061:
062: /**
063: * Creates a new <code>ClosedObjectTest</code> instance.
064: *
065: * @param method the method to test
066: * @param decorator a decorator which provides a closed object
067: */
068: public ClosedObjectTest(Method method, ObjectDecorator decorator) {
069: super ("testClosedObjects");
070: method_ = method;
071: decorator_ = decorator;
072: // setting name temporarily, we don't know the real class name yet
073: name_ = method.getDeclaringClass().getName() + "."
074: + method.getName();
075: }
076:
077: /**
078: * Gets the name of the test.
079: *
080: * @return name of the test
081: */
082: public String getName() {
083: return name_;
084: }
085:
086: /**
087: * Runs a test case. A method is called on a closed object, and it
088: * is checked that the appropriate exception is thrown.
089: *
090: * @exception Throwable if an error occurs
091: */
092: public void testClosedObjects() throws Throwable {
093: try {
094: Object object = decorator_.getClosedObject();
095:
096: // update name of test with real class name
097: name_ = object.getClass() + "." + method_.getName();
098:
099: method_.invoke(object, getNullArguments(method_
100: .getParameterTypes()));
101: assertFalse("No exception was thrown", decorator_
102: .expectsException(method_));
103: } catch (InvocationTargetException ite) {
104: try {
105: throw ite.getCause();
106: } catch (SQLFeatureNotSupportedException fnse) {
107: // if we don't support the method, it is OK that we
108: // throw this exception
109: } catch (SQLException sqle) {
110: decorator_.checkException(method_, sqle);
111: }
112: }
113: }
114:
115: /**
116: * Creates the test suite and fills it with tests using
117: * <code>DataSource</code>, <code>ConnectionPoolDataSource</code>
118: * and <code>XADataSource</code> to obtain objects.
119: *
120: * @return a <code>Test</code> value
121: * @exception Exception if an error occurs while building the test suite
122: */
123: public static Test suite() {
124: TestSuite topSuite = new TestSuite();
125:
126: TestSuite dsSuite = new TestSuite();
127: DataSourceDecorator dsDecorator = new DataSourceDecorator(
128: dsSuite);
129: topSuite.addTest(dsDecorator);
130: fillDataSourceSuite(dsSuite, dsDecorator);
131:
132: TestSuite poolSuite = new TestSuite();
133: PoolDataSourceDecorator poolDecorator = new PoolDataSourceDecorator(
134: poolSuite);
135: topSuite.addTest(poolDecorator);
136: fillDataSourceSuite(poolSuite, poolDecorator);
137:
138: TestSuite xaSuite = new TestSuite();
139: XADataSourceDecorator xaDecorator = new XADataSourceDecorator(
140: xaSuite);
141: topSuite.addTest(xaDecorator);
142: fillDataSourceSuite(xaSuite, xaDecorator);
143:
144: return topSuite;
145: }
146:
147: /**
148: * Fills a test suite which is contained in a
149: * <code>DataSourceDecorator</code> with tests for
150: * <code>ResultSet</code>, <code>Statement</code>,
151: * <code>PreparedStatement</code>, <code>CallableStatement</code>
152: * and <code>Connection</code>.
153: *
154: * @param suite the test suite to fill
155: * @param dsDecorator the decorator for the test suite
156: */
157: private static void fillDataSourceSuite(TestSuite suite,
158: DataSourceDecorator dsDecorator) {
159: TestSuite rsSuite = new TestSuite();
160: ResultSetObjectDecorator rsDecorator = new ResultSetObjectDecorator(
161: rsSuite, dsDecorator);
162: suite.addTest(rsDecorator);
163: fillObjectSuite(rsSuite, rsDecorator, ResultSet.class);
164:
165: TestSuite stmtSuite = new TestSuite();
166: StatementObjectDecorator stmtDecorator = new StatementObjectDecorator(
167: stmtSuite, dsDecorator);
168: suite.addTest(stmtDecorator);
169: fillObjectSuite(stmtSuite, stmtDecorator, Statement.class);
170:
171: TestSuite psSuite = new TestSuite();
172: PreparedStatementObjectDecorator psDecorator = new PreparedStatementObjectDecorator(
173: psSuite, dsDecorator);
174: suite.addTest(psDecorator);
175: fillObjectSuite(psSuite, psDecorator, PreparedStatement.class);
176:
177: TestSuite csSuite = new TestSuite();
178: CallableStatementObjectDecorator csDecorator = new CallableStatementObjectDecorator(
179: csSuite, dsDecorator);
180: suite.addTest(csDecorator);
181: fillObjectSuite(csSuite, csDecorator, CallableStatement.class);
182:
183: TestSuite connSuite = new TestSuite();
184: ConnectionObjectDecorator connDecorator = new ConnectionObjectDecorator(
185: connSuite, dsDecorator);
186: suite.addTest(connDecorator);
187: fillObjectSuite(connSuite, connDecorator, Connection.class);
188: }
189:
190: /**
191: * Fills a suite with tests for all the methods of an interface.
192: *
193: * @param suite the suite to fill
194: * @param decorator a decorator for the test (used for obtaining a
195: * closed object to test the method on)
196: * @param iface the interface which contains the methods to test
197: */
198: private static void fillObjectSuite(TestSuite suite,
199: ObjectDecorator decorator, Class iface) {
200: for (Method m : iface.getMethods()) {
201: ClosedObjectTest cot = new ClosedObjectTest(m, decorator);
202: suite.addTest(cot);
203: }
204: }
205:
206: /**
207: * Takes an array of classes and returns an array of objects with
208: * null values compatible with the classes. Helper method for
209: * converting a parameter list to an argument list.
210: *
211: * @param params a <code>Class[]</code> value
212: * @return an <code>Object[]</code> value
213: */
214: private static Object[] getNullArguments(Class[] params) {
215: Object[] args = new Object[params.length];
216: for (int i = 0; i < params.length; i++) {
217: args[i] = getNullValueForType(params[i]);
218: }
219: return args;
220: }
221:
222: /**
223: * Returns a null value compatible with the class. For instance,
224: * return <code>Boolean.FALSE</code> for primitive booleans, 0 for
225: * primitive integers and <code>null</code> for non-primitive
226: * types.
227: *
228: * @param type a <code>Class</code> value
229: * @return a null value
230: */
231: private static Object getNullValueForType(Class type) {
232: if (!type.isPrimitive()) {
233: return null;
234: }
235: if (type == Boolean.TYPE) {
236: return Boolean.FALSE;
237: }
238: if (type == Character.TYPE) {
239: return new Character((char) 0);
240: }
241: if (type == Byte.TYPE) {
242: return new Byte((byte) 0);
243: }
244: if (type == Short.TYPE) {
245: return new Short((short) 0);
246: }
247: if (type == Integer.TYPE) {
248: return new Integer(0);
249: }
250: if (type == Long.TYPE) {
251: return new Long(0L);
252: }
253: if (type == Float.TYPE) {
254: return new Float(0f);
255: }
256: if (type == Double.TYPE) {
257: return new Double(0d);
258: }
259: fail("Don't know how to handle type " + type);
260: return null; // unreachable statement
261: }
262:
263: /**
264: * Abstract decorator class with functionality for obtaining a
265: * closed object.
266: */
267: private static abstract class ObjectDecorator extends TestSetup {
268: /** Decorator which provides a connection. */
269: private final DataSourceDecorator decorator_;
270: /** The closed object. Must be set by a sub-class. */
271: protected Object object_;
272:
273: /**
274: * Creates a new <code>ObjectDecorator</code> instance.
275: *
276: * @param test a test or suite to decorate
277: * @param decorator a decorator which provides a connection
278: */
279: public ObjectDecorator(Test test, DataSourceDecorator decorator) {
280: super (test);
281: decorator_ = decorator;
282: }
283:
284: /**
285: * Returns the closed object.
286: *
287: * @return a closed object
288: */
289: public Object getClosedObject() {
290: return object_;
291: }
292:
293: /**
294: * Checks whether a method expects an exception to be thrown
295: * when the object is closed. Currently, only
296: * <code>close()</code>, <code>isClosed()</code> and
297: * <code>isValid()</code> don't expect exceptions.
298: *
299: * @param method a method
300: * @return <code>true</code> if an exception is expected
301: */
302: public boolean expectsException(Method method) {
303: final String[] exceptionLessMethods = { "close",
304: "isClosed", "isValid", };
305: for (String name : exceptionLessMethods) {
306: if (name.equals(method.getName()))
307: return false;
308: }
309: return true;
310: }
311:
312: /**
313: * Checks whether an exception is of the expected type for
314: * that method.
315: *
316: * @param method a method
317: * @param sqle an exception
318: * @exception SQLException if the exception was not expected
319: */
320: public final void checkException(Method method,
321: SQLException sqle) throws SQLException {
322: if (!expectsException(method)) {
323: throw sqle;
324: }
325: checkSQLState(method, sqle);
326: }
327:
328: /**
329: * Checks whether the SQL state is as expected.
330: *
331: * @param method a <code>Method</code> value
332: * @param sqle a <code>SQLException</code> value
333: * @exception SQLException if an error occurs
334: */
335: protected abstract void checkSQLState(Method method,
336: SQLException sqle) throws SQLException;
337:
338: /**
339: * Helper method for creating a connection.
340: *
341: * @return a connection
342: * @exception SQLException if an error occurs
343: */
344: protected Connection createConnection() throws SQLException {
345: return decorator_.newConnection();
346: }
347:
348: /**
349: * Helper method for creating a statement.
350: *
351: * @return a statement
352: * @exception SQLException if an error occurs
353: */
354: protected Statement createStatement() throws SQLException {
355: return decorator_.getConnection().createStatement();
356: }
357:
358: /**
359: * Helper method for creating a prepared statement.
360: *
361: * @param sql statement text
362: * @return a prepared statement
363: * @exception SQLException if an error occurs
364: */
365: protected PreparedStatement prepareStatement(String sql)
366: throws SQLException {
367: return decorator_.getConnection().prepareStatement(sql);
368: }
369:
370: /**
371: * Helper method for creating a callable statement.
372: *
373: * @param call statement text
374: * @return a callable statement
375: * @exception SQLException if an error occurs
376: */
377: protected CallableStatement prepareCall(String call)
378: throws SQLException {
379: return decorator_.getConnection().prepareCall(call);
380: }
381: }
382:
383: /**
384: * Decorator class for testing methods on a closed result set.
385: */
386: private static class ResultSetObjectDecorator extends
387: ObjectDecorator {
388: /** Statement used for creating the result set to test. */
389: private Statement stmt_;
390:
391: /**
392: * Creates a new <code>ResultSetObjectDecorator</code> instance.
393: *
394: * @param test the test to decorate
395: * @param decorator decorator used for obtaining a statement
396: */
397: public ResultSetObjectDecorator(Test test,
398: DataSourceDecorator decorator) {
399: super (test, decorator);
400: }
401:
402: /**
403: * Sets up the test. Creates a result set and closes it.
404: *
405: * @exception SQLException if an error occurs
406: */
407: public void setUp() throws SQLException {
408: stmt_ = createStatement();
409: ResultSet rs = stmt_.executeQuery("VALUES(1)");
410: rs.close();
411: object_ = rs;
412: }
413:
414: /**
415: * Tears down the test. Closes open resources.
416: *
417: * @exception SQLException if an error occurs
418: */
419: public void tearDown() throws SQLException {
420: stmt_.close();
421: }
422:
423: /**
424: * Checks whether the exception has the expected SQL state
425: * (XCL16 - result set is closed).
426: *
427: * @param method a <code>Method</code> value
428: * @param sqle a <code>SQLException</code> value
429: * @exception SQLException if an error occurs
430: */
431: protected void checkSQLState(Method method, SQLException sqle)
432: throws SQLException {
433: if (sqle.getSQLState().equals("XCL16")) {
434: // everything is OK, do nothing
435: } else {
436: // unexpected exception
437: throw sqle;
438: }
439: }
440: }
441:
442: /**
443: * Decorator class for testing methods on a closed statement.
444: */
445: private static class StatementObjectDecorator extends
446: ObjectDecorator {
447: /**
448: * Creates a new <code>StatementObjectDecorator</code> instance.
449: *
450: * @param test the test to decorate
451: * @param decorator decorator which provides a statement
452: */
453: public StatementObjectDecorator(Test test,
454: DataSourceDecorator decorator) {
455: super (test, decorator);
456: }
457:
458: /**
459: * Sets up the test. Creates a statement and closes it.
460: *
461: * @exception SQLException if an error occurs
462: */
463: public void setUp() throws SQLException {
464: Statement stmt = createStatement();
465: stmt.close();
466: object_ = stmt;
467: }
468:
469: /**
470: * Checks whether the exception has the expected SQL state
471: * (statement is closed). When using embedded, XJ012 is
472: * expected. When using the client driver, XCL31 is expected.
473: *
474: * @param method a <code>Method</code> value
475: * @param sqle a <code>SQLException</code> value
476: * @exception SQLException if an error occurs
477: */
478: protected void checkSQLState(Method method, SQLException sqle)
479: throws SQLException {
480: String sqlState = sqle.getSQLState();
481: if (sqlState.equals("XJ012")) {
482: // expected, do nothing
483: } else {
484: // unexpected exception
485: throw sqle;
486: }
487: }
488: }
489:
490: /**
491: * Decorator class for testing methods on a closed prepared statement.
492: */
493: private static class PreparedStatementObjectDecorator extends
494: StatementObjectDecorator {
495: /**
496: * Creates a new <code>PreparedStatementObjectDecorator</code>
497: * instance.
498: *
499: * @param test the test to decorate
500: * @param decorator decorator which provides a prepared statement
501: */
502: public PreparedStatementObjectDecorator(Test test,
503: DataSourceDecorator decorator) {
504: super (test, decorator);
505: }
506:
507: /**
508: * Sets up the test. Prepares a statement and closes it.
509: *
510: * @exception SQLException if an error occurs
511: */
512: public void setUp() throws SQLException {
513: PreparedStatement ps = prepareStatement("VALUES(1)");
514: ps.close();
515: object_ = ps;
516: }
517:
518: /**
519: * Checks whether the exception has the expected SQL state
520: * (statement is closed), or XJ016 indicating it is a
521: * Statement method not meant to be invoked on a
522: * PreparedStatement.
523: *
524: * @param method a <code>Method</code> value
525: * @param sqle a <code>SQLException</code> value
526: * @exception SQLException if an error occurs
527: */
528: protected void checkSQLState(Method method, SQLException sqle)
529: throws SQLException {
530: if (method.getDeclaringClass() == Statement.class
531: && sqle.getSQLState().equals("XJ016")) {
532: // XJ016 is "blah,blah not allowed on a prepared
533: // statement", so it's OK to get this one
534: } else {
535: super .checkSQLState(method, sqle);
536: }
537:
538: }
539: }
540:
541: /**
542: * Decorator class for testing methods on a closed callable statement.
543: */
544: private static class CallableStatementObjectDecorator extends
545: PreparedStatementObjectDecorator {
546: /**
547: * Creates a new <code>CallableStatementObjectDecorator</code>
548: * instance.
549: *
550: * @param test the test to decorate
551: * @param decorator decorator which provides a callable statement
552: */
553: public CallableStatementObjectDecorator(Test test,
554: DataSourceDecorator decorator) {
555: super (test, decorator);
556: }
557:
558: /**
559: * Sets up the test. Prepares a call and closes the statement.
560: *
561: * @exception SQLException if an error occurs
562: */
563: public void setUp() throws SQLException {
564: CallableStatement cs = prepareCall("CALL SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)");
565: cs.close();
566: object_ = cs;
567: }
568: }
569:
570: /**
571: * Decorator class for testing methods on a closed connection.
572: */
573: private static class ConnectionObjectDecorator extends
574: ObjectDecorator {
575: /**
576: * Creates a new <code>ConnectionObjectDecorator</code> instance.
577: *
578: * @param test the test to decorate
579: * @param decorator decorator which provides a connection
580: */
581: public ConnectionObjectDecorator(Test test,
582: DataSourceDecorator decorator) {
583: super (test, decorator);
584: }
585:
586: /**
587: * Sets up the test. Creates a connection and closes it.
588: *
589: * @exception SQLException if an error occurs
590: */
591: public void setUp() throws SQLException {
592: Connection conn = createConnection();
593: conn.rollback(); // cannot close active transactions
594: conn.close();
595: object_ = conn;
596: }
597:
598: /**
599: * Checks that the exception has an expected SQL state (08003
600: * - no current connection). Also accept
601: * <code>SQLClientInfoException</code>s from
602: * <code>setClientInfo()</code>.
603: *
604: * @param method a <code>Method</code> value
605: * @param sqle a <code>SQLException</code> value
606: * @exception SQLException if an error occurs
607: */
608: protected void checkSQLState(Method method, SQLException sqle)
609: throws SQLException {
610: if (sqle instanceof SQLClientInfoException
611: && method.getName().equals("setClientInfo")
612: && Arrays
613: .asList(method.getParameterTypes())
614: .equals(
615: Arrays
616: .asList(new Class[] { Properties.class }))) {
617: // setClientInfo(Properties) should throw
618: // ClientInfoException, so this is OK
619: } else if (sqle.getSQLState().equals("08003")) {
620: // expected, connection closed
621: } else {
622: // unexpected exception
623: throw sqle;
624: }
625: }
626: }
627:
628: /**
629: * Decorator class used for obtaining connections through a
630: * <code>DataSource</code>.
631: */
632: private static class DataSourceDecorator extends TestSetup {
633: /** Connection shared by many tests. */
634: private Connection connection_;
635:
636: /**
637: * Creates a new <code>DataSourceDecorator</code> instance.
638: *
639: * @param test the test to decorate
640: */
641: public DataSourceDecorator(Test test) {
642: super (test);
643: }
644:
645: /**
646: * Sets up the test by creating a connection.
647: *
648: * @exception SQLException if an error occurs
649: */
650: public final void setUp() throws SQLException {
651: connection_ = newConnection();
652: }
653:
654: /**
655: * Gets the connection created when the test was set up.
656: *
657: * @return a <code>Connection</code> value
658: */
659: public final Connection getConnection() {
660: return connection_;
661: }
662:
663: /**
664: * Creates a new connection with auto-commit set to false.
665: *
666: * @return a <code>Connection</code> value
667: * @exception SQLException if an error occurs
668: */
669: public final Connection newConnection() throws SQLException {
670: Connection conn = newConnection_();
671: conn.setAutoCommit(false);
672: return conn;
673: }
674:
675: /**
676: * Tears down the test and closes the connection.
677: *
678: * @exception SQLException if an error occurs
679: */
680: public final void tearDown() throws SQLException {
681: connection_.rollback();
682: connection_.close();
683: }
684:
685: /**
686: * Creates a new connection using a <code>DataSource</code>.
687: *
688: * @return a <code>Connection</code> value
689: * @exception SQLException if an error occurs
690: */
691: protected Connection newConnection_() throws SQLException {
692: DataSource ds = TestDataSourceFactory.getDataSource();
693: return ds.getConnection(TestConfiguration.getCurrent()
694: .getUserName(), TestConfiguration.getCurrent()
695: .getUserPassword());
696: }
697: }
698:
699: /**
700: * Decorator class used for obtaining connections through a
701: * <code>ConnectionPoolDataSource</code>.
702: */
703: private static class PoolDataSourceDecorator extends
704: DataSourceDecorator {
705: /**
706: * Creates a new <code>PoolDataSourceDecorator</code> instance.
707: *
708: * @param test the test to decorate
709: */
710: public PoolDataSourceDecorator(Test test) {
711: super (test);
712: }
713:
714: /**
715: * Creates a new connection using a
716: * <code>ConnectionPoolDataSource</code>.
717: *
718: * @return a <code>Connection</code> value
719: * @exception SQLException if an error occurs
720: */
721: protected Connection newConnection_() throws SQLException {
722: ConnectionPoolDataSource ds = TestDataSourceFactory
723: .getConnectionPoolDataSource();
724: PooledConnection pc = ds.getPooledConnection(
725: TestConfiguration.getCurrent().getUserName(),
726: TestConfiguration.getCurrent().getUserPassword());
727: return pc.getConnection();
728: }
729: }
730:
731: /**
732: * Decorator class used for obtaining connections through an
733: * <code>XADataSource</code>.
734: */
735: private static class XADataSourceDecorator extends
736: DataSourceDecorator {
737: /**
738: * Creates a new <code>XADataSourceDecorator</code> instance.
739: *
740: * @param test the test to decorate
741: */
742: public XADataSourceDecorator(Test test) {
743: super (test);
744: }
745:
746: /**
747: * Creates a new connection using an <code>XADataSource</code>.
748: *
749: * @return a <code>Connection</code> value
750: * @exception SQLException if an error occurs
751: */
752: protected Connection newConnection_() throws SQLException {
753: XADataSource ds = TestDataSourceFactory.getXADataSource();
754: XAConnection xac = ds.getXAConnection(TestConfiguration
755: .getCurrent().getUserName(), TestConfiguration
756: .getCurrent().getUserPassword());
757: return xac.getConnection();
758: }
759: }
760: }
|