From 9be69f1ac77fd209201cd0b4725c0546bd0edb13 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 14 Jan 2016 15:51:47 +0000 Subject: [PATCH] Improve JDBC driver dependency management and class name test coverage Previously, the DatabaseDriver enumeration contained entries for some databases without having dependency management for the database driver dependency. This leads to the possibility of a user inadvertently using the wrong version of a driver where the class names do not match those listed in the enumeration. A further problem is that we do not test that the class names listed in the enumeration match the names of Driver and XADataSource implementations in the database driver. This commit completes the database driver dependency management so that dependency management is provided for every driver that is both listed in DatabaseDriver and available in Maven Central. It also adds tests for DatabaseDriver that ensures that each class that is listed exists and implements the required interface (java.sql.Driver or javax.sql.XADataSource). Closes gh-4946 --- spring-boot-dependencies/pom.xml | 18 ++++ spring-boot/pom.xml | 50 ++++++++++ .../jdbc/DatabaseDriverClassNameTests.java | 95 +++++++++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 spring-boot/src/test/java/org/springframework/boot/jdbc/DatabaseDriverClassNameTests.java diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 7b92b9ee12e..0cbc13dcbdd 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -45,6 +45,7 @@ 5.12.2 2.7.7 + 1.9.31 1.1.0 1.8.8 2.3.0 @@ -106,6 +107,7 @@ 1.2.3 2.0.0 1.2 + 1.3.1 4.12 3.4.2 2.4.1 @@ -147,6 +149,7 @@ 1.0.2.RELEASE 1.1.2.RELEASE 2.2.3.RELEASE + 3.8.11.2 3.1.0 ${javax-mail.version} 2.1.4.RELEASE @@ -615,6 +618,11 @@ thymeleaf-extras-data-attribute ${thymeleaf-extras-data-attribute.version} + + com.google.appengine + appengine-api-1.0-sdk + ${appengine.version} + com.google.code.gson gson @@ -873,6 +881,11 @@ ehcache ${ehcache.version} + + net.sourceforge.jtds + jtds + ${jtds.version} + net.sourceforge.nekohtml nekohtml @@ -1980,6 +1993,11 @@ + + org.xerial + sqlite-jdbc + ${sqlite-jdbc.version} + org.thymeleaf thymeleaf diff --git a/spring-boot/pom.xml b/spring-boot/pom.xml index 903c315d1ae..291535ccf74 100644 --- a/spring-boot/pom.xml +++ b/spring-boot/pom.xml @@ -231,11 +231,56 @@ true + + com.google.appengine + appengine-api-1.0-sdk + test + + + com.h2database + h2 + test + + + mysql + mysql-connector-java + test + + + net.sourceforge.jtds + jtds + test + + + org.apache.derby + derby + test + org.apache.httpcomponents httpasyncclient test + + org.firebirdsql.jdbc + jaybird-jdk18 + test + + + org.hsqldb + hsqldb + test + + + org.mariadb.jdbc + mariadb-java-client + test + + + org.postgresql + postgresql + test + org.slf4j jcl-over-slf4j @@ -251,6 +296,11 @@ spring-data-redis test + + org.xerial + sqlite-jdbc + test + diff --git a/spring-boot/src/test/java/org/springframework/boot/jdbc/DatabaseDriverClassNameTests.java b/spring-boot/src/test/java/org/springframework/boot/jdbc/DatabaseDriverClassNameTests.java new file mode 100644 index 00000000000..234658d7127 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/jdbc/DatabaseDriverClassNameTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * 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.springframework.boot.jdbc; + +import java.io.IOException; +import java.sql.Driver; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +import javax.sql.XADataSource; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import org.springframework.asm.ClassReader; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for the class names in the {@link DatabaseDriver} enumeration. + * + * @author Andy Wilkinson + */ +@RunWith(Parameterized.class) +public class DatabaseDriverClassNameTests { + + private static final EnumSet excludedDrivers = EnumSet.of( + DatabaseDriver.UNKNOWN, DatabaseDriver.ORACLE, DatabaseDriver.SQLSERVER, + DatabaseDriver.DB2, DatabaseDriver.DB2_AS400, DatabaseDriver.INFORMIX, + DatabaseDriver.TERADATA); + + private final String className; + + private final Class requiredType; + + @Parameters(name = "{0} {2}") + public static List parameters() { + DatabaseDriver[] databaseDrivers = DatabaseDriver.values(); + List parameters = new ArrayList(); + for (DatabaseDriver databaseDriver : databaseDrivers) { + if (excludedDrivers.contains(databaseDriver)) { + continue; + } + parameters.add(new Object[] { databaseDriver, + databaseDriver.getDriverClassName(), Driver.class }); + if (databaseDriver.getXaDataSourceClassName() != null) { + parameters.add(new Object[] { databaseDriver, + databaseDriver.getXaDataSourceClassName(), XADataSource.class }); + } + } + return parameters; + } + + public DatabaseDriverClassNameTests(DatabaseDriver driver, String className, + Class requiredType) { + this.className = className; + this.requiredType = requiredType; + } + + @Test + public void databaseClassIsOfRequiredType() throws Exception { + assertThat(getInterfaceNames(this.className.replace('.', '/')) + .contains(this.requiredType.getName().replace('.', '/'))); + } + + private List getInterfaceNames(String className) throws IOException { + // Use ASM to avoid unwanted side-effects of loading JDBC drivers + ClassReader classReader = new ClassReader( + getClass().getResourceAsStream("/" + className + ".class")); + List interfaceNames = new ArrayList(); + for (String name : classReader.getInterfaces()) { + interfaceNames.add(name); + interfaceNames.addAll(getInterfaceNames(name)); + } + return interfaceNames; + } + +}