001: /*
002: * Derby - org.apache.derbyTesting.functionTests.tests.jdbc4.VerifySignatures
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.sql.*;
024: import javax.sql.*;
025:
026: import java.lang.reflect.Method;
027: import java.lang.reflect.Modifier;
028: import java.util.Arrays;
029: import java.util.HashSet;
030: import java.util.Set;
031: import junit.framework.Test;
032: import junit.framework.TestCase;
033: import junit.framework.TestSuite;
034: import org.apache.derbyTesting.functionTests.util.TestUtil;
035: import org.apache.derbyTesting.functionTests.util.TestDataSourceFactory;
036: import org.apache.derbyTesting.junit.BaseJDBCTestCase;
037: import org.apache.derbyTesting.junit.TestConfiguration;
038:
039: /**
040: * JUnit test which checks that all methods specified by the
041: * interfaces in JDBC 4.0 are implemented. The test requires JVM 1.6
042: * to run.
043: */
044: public class VerifySignatures extends BaseJDBCTestCase {
045:
046: /**
047: * All the java.sql and javax.sql interfaces specified by JDBC 4.0.
048: */
049: private final static Class[] JDBC_INTERFACES = {
050: java.sql.Array.class, java.sql.Blob.class,
051: java.sql.CallableStatement.class, java.sql.Clob.class,
052: java.sql.Connection.class, java.sql.DatabaseMetaData.class,
053: java.sql.Driver.class, java.sql.NClob.class,
054: java.sql.ParameterMetaData.class,
055: java.sql.PreparedStatement.class, java.sql.Ref.class,
056: java.sql.ResultSet.class, java.sql.ResultSetMetaData.class,
057: java.sql.RowId.class, java.sql.Savepoint.class,
058: java.sql.SQLData.class, java.sql.SQLInput.class,
059: java.sql.SQLOutput.class, java.sql.SQLXML.class,
060: java.sql.Statement.class, java.sql.Struct.class,
061: java.sql.Wrapper.class, javax.sql.CommonDataSource.class,
062: javax.sql.ConnectionEventListener.class,
063: javax.sql.ConnectionPoolDataSource.class,
064: javax.sql.DataSource.class,
065: javax.sql.PooledConnection.class, javax.sql.RowSet.class,
066: javax.sql.RowSetInternal.class,
067: javax.sql.RowSetListener.class,
068: javax.sql.RowSetMetaData.class,
069: javax.sql.RowSetReader.class, javax.sql.RowSetWriter.class,
070: javax.sql.StatementEventListener.class,
071: javax.sql.XAConnection.class, javax.sql.XADataSource.class, };
072:
073: /**
074: * Creates a new instance.
075: */
076: public VerifySignatures() {
077: super ("VerifySignatures");
078: }
079:
080: /**
081: * Build a suite of tests to be run.
082: *
083: * @return a test suite
084: * @exception SQLException if a database error occurs
085: */
086: public static Test suite() throws SQLException {
087: // set of all implementation/interface pairs found
088: Set<ClassInfo> classes = new HashSet<ClassInfo>();
089:
090: collectClassesFromDataSource(classes);
091: collectClassesFromConnectionPoolDataSource(classes);
092: collectClassesFromXADataSource(classes);
093: addClass(classes,
094: DriverManager.getDriver(
095: TestConfiguration.getCurrent().getJDBCUrl())
096: .getClass(), java.sql.Driver.class);
097:
098: TestSuite suite = new TestSuite();
099:
100: // all interfaces for which tests have been generated
101: Set<Class> interfaces = new HashSet<Class>();
102:
103: for (ClassInfo pair : classes) {
104: // some methods are defined in many interfaces, so collect
105: // them in a set first to avoid duplicates
106: Set<Method> methods = new HashSet<Method>();
107: for (Class iface : getAllInterfaces(pair.jdbcInterface)) {
108: interfaces.add(iface);
109: for (Method method : iface.getMethods()) {
110: methods.add(method);
111: }
112: }
113: for (Method method : methods) {
114: suite.addTest(new MethodTestCase(
115: pair.derbyImplementation, method));
116: }
117: }
118: suite.addTest(new InterfaceCoverageTestCase(interfaces));
119: return suite;
120: }
121:
122: /**
123: * Obtain a connection from a <code>DataSource</code> object and
124: * perform JDBC operations on it. Collect the classes of all JDBC
125: * objects that are found.
126: *
127: * @param classes set into which classes are collected
128: * @exception SQLException if a database error occurs
129: */
130: private static void collectClassesFromDataSource(
131: Set<ClassInfo> classes) throws SQLException {
132: DataSource ds = TestDataSourceFactory.getDataSource();
133: addClass(classes, ds.getClass(), javax.sql.DataSource.class);
134: collectClassesFromConnection(ds.getConnection(TestConfiguration
135: .getCurrent().getUserName(), TestConfiguration
136: .getCurrent().getUserPassword()), classes);
137: }
138:
139: /**
140: * Obtain a connection from a <code>ConnectionPoolDataSource</code>
141: * object and perform JDBC operations on it. Collect the classes
142: * of all JDBC objects that are found.
143: *
144: * @param classes set into which classes are collected
145: * @exception SQLException if a database error occurs
146: */
147: private static void collectClassesFromConnectionPoolDataSource(
148: Set<ClassInfo> classes) throws SQLException {
149: ConnectionPoolDataSource cpds = TestDataSourceFactory
150: .getConnectionPoolDataSource();
151: addClass(classes, cpds.getClass(),
152: javax.sql.ConnectionPoolDataSource.class);
153:
154: PooledConnection pc = cpds.getPooledConnection(
155: TestConfiguration.getCurrent().getUserName(),
156: TestConfiguration.getCurrent().getUserPassword());
157: addClass(classes, pc.getClass(),
158: javax.sql.PooledConnection.class);
159:
160: collectClassesFromConnection(pc.getConnection(), classes);
161:
162: pc.close();
163: }
164:
165: /**
166: * Obtain a connection from an <code>XADataSource</code> object
167: * and perform JDBC operations on it. Collect the classes of all
168: * JDBC objects that are found.
169: *
170: * @param classes set into which classes are collected
171: * @exception SQLException if a database error occurs
172: */
173: private static void collectClassesFromXADataSource(
174: Set<ClassInfo> classes) throws SQLException {
175: XADataSource xads = TestDataSourceFactory.getXADataSource();
176: addClass(classes, xads.getClass(), javax.sql.XADataSource.class);
177:
178: XAConnection xaconn = xads.getXAConnection(TestConfiguration
179: .getCurrent().getUserName(), TestConfiguration
180: .getCurrent().getUserPassword());
181: addClass(classes, xaconn.getClass(),
182: javax.sql.XAConnection.class);
183:
184: collectClassesFromConnection(xaconn.getConnection(), classes);
185: }
186:
187: /**
188: * Perform JDBC operations on a <code>Connection</code>. Collect
189: * the classes of all JDBC objects that are found.
190: *
191: * @param conn connection to a database
192: * @param classes set into which classes are collected
193: * @exception SQLException if a database error occurs
194: */
195: private static void collectClassesFromConnection(Connection conn,
196: Set<ClassInfo> classes) throws SQLException {
197: conn.setAutoCommit(false);
198: addClass(classes, conn.getClass(), java.sql.Connection.class);
199:
200: Savepoint sp = conn.setSavepoint();
201: addClass(classes, sp.getClass(), java.sql.Savepoint.class);
202: conn.releaseSavepoint(sp);
203:
204: DatabaseMetaData dmd = conn.getMetaData();
205: addClass(classes, dmd.getClass(),
206: java.sql.DatabaseMetaData.class);
207:
208: collectClassesFromStatement(conn, classes);
209: collectClassesFromPreparedStatement(conn, classes);
210: collectClassesFromCallableStatement(conn, classes);
211: conn.rollback();
212: conn.close();
213: }
214:
215: /**
216: * Perform JDBC operations on a <code>Statement</code>. Collect
217: * the classes of all JDBC objects that are found.
218: *
219: * @param conn connection to a database
220: * @param classes set into which classes are collected
221: * @exception SQLException if a database error occurs
222: */
223: private static void collectClassesFromStatement(Connection conn,
224: Set<ClassInfo> classes) throws SQLException {
225: Statement stmt = conn.createStatement();
226: addClass(classes, stmt.getClass(), java.sql.Statement.class);
227:
228: stmt.execute("CREATE TABLE t (id INT PRIMARY KEY, "
229: + "b BLOB(10), c CLOB(10))");
230: stmt.execute("INSERT INTO t (id, b, c) VALUES (1, " + "CAST ("
231: + TestUtil.stringToHexLiteral("101010001101")
232: + "AS BLOB(10)), CAST ('hello' AS CLOB(10)))");
233:
234: ResultSet rs = stmt.executeQuery("SELECT id, b, c FROM t");
235: addClass(classes, rs.getClass(), java.sql.ResultSet.class);
236: rs.next();
237: Blob b = rs.getBlob(2);
238: addClass(classes, b.getClass(), java.sql.Blob.class);
239: Clob c = rs.getClob(3);
240: addClass(classes, c.getClass(), java.sql.Clob.class);
241: ResultSetMetaData rsmd = rs.getMetaData();
242: addClass(classes, rsmd.getClass(),
243: java.sql.ResultSetMetaData.class);
244: rs.close();
245:
246: stmt.close();
247: conn.rollback();
248: }
249:
250: /**
251: * Perform JDBC operations on a <code>PreparedStatement</code>.
252: * Collect the classes of all JDBC objects that are found.
253: *
254: * @param conn connection to a database
255: * @param classes set into which classes are collected
256: * @exception SQLException if a database error occurs
257: */
258: private static void collectClassesFromPreparedStatement(
259: Connection conn, Set<ClassInfo> classes)
260: throws SQLException {
261: PreparedStatement ps = conn.prepareStatement("VALUES(1)");
262: addClass(classes, ps.getClass(),
263: java.sql.PreparedStatement.class);
264: ResultSet rs = ps.executeQuery();
265: addClass(classes, rs.getClass(), java.sql.ResultSet.class);
266: rs.close();
267:
268: ParameterMetaData pmd = ps.getParameterMetaData();
269: addClass(classes, pmd.getClass(),
270: java.sql.ParameterMetaData.class);
271:
272: ps.close();
273: }
274:
275: /**
276: * Perform JDBC operations on a <code>CallableStatement</code>.
277: * Collect the classes of all JDBC objects that are found.
278: *
279: * @param conn connection to a database
280: * @param classes set into which classes are collected
281: * @exception SQLException if a database error occurs
282: */
283: private static void collectClassesFromCallableStatement(
284: Connection conn, Set<ClassInfo> classes)
285: throws SQLException {
286: CallableStatement cs = conn
287: .prepareCall("CALL SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(0)");
288: addClass(classes, cs.getClass(),
289: java.sql.CallableStatement.class);
290:
291: ParameterMetaData pmd = cs.getParameterMetaData();
292: addClass(classes, pmd.getClass(),
293: java.sql.ParameterMetaData.class);
294:
295: cs.close();
296: }
297:
298: /**
299: * Adds a <code>ClassInfo</code> object to a set.
300: *
301: * @param classes set to which the class should be added
302: * @param implementation Derby implementation class
303: * @param iface JDBC interface supposed to be implemented
304: */
305: private static void addClass(Set<ClassInfo> classes,
306: Class implementation, Class iface) {
307: classes.add(new ClassInfo(implementation, iface));
308: }
309:
310: /**
311: * Get the set consisting of an interface and all its
312: * super-interfaces.
313: *
314: * @param iface an interface
315: * @return the set consisting of <code>iface</code> and all its
316: * super-interfaces
317: */
318: private static Set<Class> getAllInterfaces(Class iface) {
319: Set<Class> set = new HashSet<Class>();
320: set.add(iface);
321: for (Class super Iface : iface.getInterfaces()) {
322: set.add(super Iface);
323: set.addAll(getAllInterfaces(super Iface));
324: }
325: return set;
326: }
327:
328: /**
329: * Test case which checks that a class implements a specific
330: * method.
331: */
332: private static class MethodTestCase extends TestCase {
333: /** The Derby implementation class which is tested. */
334: private final Class derbyImplementation;
335: /** The method that should be implemented. */
336: private final Method ifaceMethod;
337:
338: /**
339: * Creates a new <code>MethodTestCase</code> instance.
340: *
341: * @param imp the class to test
342: * @param method the method to look for
343: */
344: private MethodTestCase(Class imp, Method method) {
345: super ("MethodTestCase{Class=" + imp.getName() + ",Method="
346: + method + "}");
347: derbyImplementation = imp;
348: ifaceMethod = method;
349: }
350:
351: /**
352: * Run the test. Check that the method is implemented and that
353: * its signature is correct.
354: *
355: * @exception NoSuchMethodException if the method is not found
356: */
357: public void runTest() throws NoSuchMethodException {
358: assertFalse("Implementation class is interface",
359: derbyImplementation.isInterface());
360:
361: Method impMethod = derbyImplementation.getMethod(
362: ifaceMethod.getName(), ifaceMethod
363: .getParameterTypes());
364:
365: assertEquals("Incorrect return type", ifaceMethod
366: .getReturnType(), impMethod.getReturnType());
367:
368: int modifiers = impMethod.getModifiers();
369: assertTrue("Non-public method", Modifier
370: .isPublic(modifiers));
371: assertFalse("Abstract method", Modifier
372: .isAbstract(modifiers));
373: assertFalse("Static method", Modifier.isStatic(modifiers));
374:
375: Class[] declaredExceptions = ifaceMethod
376: .getExceptionTypes();
377: for (Class exception : impMethod.getExceptionTypes()) {
378: if (RuntimeException.class.isAssignableFrom(exception)) {
379: continue;
380: }
381: assertNotNull("Incompatible throws clause",
382: findCompatibleClass(exception,
383: declaredExceptions));
384: }
385: }
386:
387: /**
388: * Search an array of classes for a class that is identical to
389: * or a super-class of the specified exception class.
390: *
391: * @param exception an exception class
392: * @param declared an array of exception classes declared to
393: * be thrown by a method
394: * @return a class that is compatible with the specified
395: * exception class, or <code>null</code> if no compatible
396: * class is found
397: */
398: private Class findCompatibleClass(Class exception,
399: Class[] declared) {
400: for (Class<?> dec : declared) {
401: if (dec.isAssignableFrom(exception)) {
402: return dec;
403: }
404: }
405: return null;
406: }
407: }
408:
409: /**
410: * Test case which checks that all relevant JDBC interfaces are
411: * covered by the test.
412: */
413: private static class InterfaceCoverageTestCase extends TestCase {
414:
415: /** The interfaces that have been tested by
416: * <code>MethodTestCase</code>. */
417: private final Set<Class> checkedInterfaces;
418: /** All JDBC interfaces whose implementations are relevant to
419: * test. */
420: private final Set<Class> jdbcInterfaces;
421:
422: /**
423: * Creates a new <code>InterfaceCoverageTestCase</code> instance.
424: *
425: * @param interfaces the interfaces that have been tested
426: */
427: private InterfaceCoverageTestCase(Set<Class> interfaces) {
428: super ("InterfaceCoverageTestCase");
429: checkedInterfaces = interfaces;
430: jdbcInterfaces = new HashSet<Class>(Arrays
431: .asList(JDBC_INTERFACES));
432:
433: // Remove the interfaces that we know we haven't checked.
434:
435: // Interfaces that Derby doesn't implement:
436: jdbcInterfaces.remove(java.sql.Array.class);
437: jdbcInterfaces.remove(java.sql.NClob.class);
438: jdbcInterfaces.remove(java.sql.Ref.class);
439: jdbcInterfaces.remove(java.sql.SQLData.class);
440: jdbcInterfaces.remove(java.sql.SQLInput.class);
441: jdbcInterfaces.remove(java.sql.SQLOutput.class);
442: jdbcInterfaces.remove(java.sql.SQLXML.class);
443: jdbcInterfaces.remove(java.sql.Struct.class);
444: jdbcInterfaces.remove(javax.sql.RowSet.class);
445: jdbcInterfaces.remove(javax.sql.RowSetInternal.class);
446: jdbcInterfaces.remove(javax.sql.RowSetListener.class);
447: jdbcInterfaces.remove(javax.sql.RowSetMetaData.class);
448: jdbcInterfaces.remove(javax.sql.RowSetReader.class);
449: jdbcInterfaces.remove(javax.sql.RowSetWriter.class);
450:
451: // Derby implements RowId classes, but has no way to
452: // obtain an object of that type.
453: jdbcInterfaces.remove(java.sql.RowId.class);
454:
455: // The event listener interfaces are implemented in
456: // application code, not in Derby code.
457: jdbcInterfaces
458: .remove(javax.sql.ConnectionEventListener.class);
459: jdbcInterfaces
460: .remove(javax.sql.StatementEventListener.class);
461: }
462:
463: /**
464: * Run the test. Check that all relevant interfaces have been
465: * tested.
466: */
467: public void runTest() {
468: jdbcInterfaces.removeAll(checkedInterfaces);
469: assertTrue("Unchecked interfaces: " + jdbcInterfaces,
470: jdbcInterfaces.isEmpty());
471: }
472: }
473:
474: /**
475: * Data structure holding a Derby implementation class and the
476: * JDBC interface it is supposed to implement.
477: */
478: private static class ClassInfo {
479: /** Derby implementation class. */
480: Class derbyImplementation;
481: /** JDBC interface which should be implemented. */
482: Class jdbcInterface;
483:
484: /**
485: * Creates a new <code>ClassInfo</code> instance.
486: *
487: * @param imp the Derby implementation class
488: * @param iface the JDBC interface
489: */
490: ClassInfo(Class imp, Class iface) {
491: derbyImplementation = imp;
492: jdbcInterface = iface;
493: }
494:
495: /**
496: * Checks whether this object is equal to another object.
497: *
498: * @param x another object
499: * @return <code>true</code> if the objects are equal,
500: * <code>false</code> otherwise
501: */
502: public boolean equals(Object x) {
503: if (x instanceof ClassInfo) {
504: ClassInfo ci = (ClassInfo) x;
505: return derbyImplementation
506: .equals(ci.derbyImplementation)
507: && jdbcInterface.equals(ci.jdbcInterface);
508: }
509: return false;
510: }
511:
512: /**
513: * Calculate hash code.
514: *
515: * @return hash code
516: */
517: public int hashCode() {
518: return derbyImplementation.hashCode()
519: ^ jdbcInterface.hashCode();
520: }
521: }
522: }
|