tags:

views:

201

answers:

3

I'm writing a database validation tool in Java and have preference screens so the user can define their database connections. The tool should be able to cope with DB2, Oracle, Postgresql and Mysql as a minimum.

What I would really like is to be able to present the user with a list of their installed jdbc drivers as part of this process.

Can anyone supply a code snippet for discovering installed JDBC drivers ?

+3  A: 

This should help:

java.sql.DriverManager.getDrivers()
Kico Lobo
Since this is IMHO incorrectly accepted, I would only emphasize more: this works *only* if the drivers are **actually** loaded; either manually by `Class#forName()` or automagically by `META-INF/services`. This does NOT detect drivers which *are* in classpath, but which are *not* loaded.
BalusC
+3  A: 
java.sql.DriverManager.getDrivers()

is not all.

As the doc says

Retrieves an Enumeration with all of the currently loaded JDBC drivers to which the current caller has access.

That means loaded drivers (with Class.forName()), not installed (say available thru a JAR).

Normally you would deliver your software with all JDBC driver jars that your program can work. Dependent what the user will connect to (oracle, access, db2) the program must load the appropiated driver.

PeterMmm
I was under the impression that distributing 3rd party drivers in my own jars would violate copyright - or something legal anyway
Steve De Caux
Actually it'll include drivers referred to via the `jdbc.drivers` system property and those made available through the service provider (`META-INF/services`) mechanism.
Tom Hawtin - tackline
+6  A: 

To the point, you need to scan the entire classpath (and subfolders) for classes implementing java.sql.Driver. This way you will also cover drivers which are not loaded manually by Class#forName() or automagically by META-INF/services.

Here's a basic example:

public static void main(String[] args) throws Exception {
    List<Class<Driver>> drivers = findClassesImplementing(Driver.class);
    System.out.println(drivers);
}        

public static <T extends Object> List<Class<T>> findClassesImplementing(Class<T> cls) throws IOException {
    List<Class<T>> classes = new ArrayList<Class<T>>();

    for (URL root : Collections.list(Thread.currentThread().getContextClassLoader().getResources(""))) {
        for (File file : findFiles(new File(root.getFile()), ".+\\.jar$")) {
            JarFile jarFile = new JarFile(file);
            for (JarEntry jarEntry : Collections.list(jarFile.entries())) {
                String name = jarEntry.getName();
                if (name.endsWith(".class")) try {
                    Class<?> found = Class.forName(name.replace("/", ".").replaceAll("\\.class$", ""));
                    if (cls.isAssignableFrom(found)) {
                        classes.add((Class<T>) found);
                    }
                } catch (Throwable ignore) {
                    // No real class file, or JAR not in classpath, or missing links.
                }
            }
        }
    }

    return classes;
}

public static List<File> findFiles(File directory, final String pattern) throws IOException {
    File[] files = directory.listFiles(new FileFilter() {
        public boolean accept(File file) {
            return file.isDirectory() || file.getName().matches(pattern);
        }
    });

    List<File> found = new ArrayList<File>(files.length);

    for (File file : files) {
        if (file.isDirectory()) {
            found.addAll(findFiles(file, pattern));
        } else {
            found.add(file);
        }
    }

    return found;
}

Instead you can also consider to use the Google Reflections API which does this all in a single line:

Set<Class<? extends Driver>> drivers = reflections.getSubTypesOf(Driver.class);
BalusC
In this solution, aren't you executing static blocks on the classes you are testing?
Kico Lobo
Yes, they would get executed. You can't go around that.
BalusC
Thx bigtime, BalusC
Steve De Caux
Minor (very minor) comment:"//." doesn't seem to work in a regex pattern - but substituting ".+[.]jar$" for ".+\\.jar$" works OK
Steve De Caux
It should be `\\.`, not `//.`. The code snippet is an exact copypaste and just works fine here.
BalusC
my exact copypaste doesn't ! - I don't know why - but you're right, I mistyped "//." instead of "\\." in my comment.
Steve De Caux
A couple more comments :1. getContextClassLoader().getResources("") only returns directory names from the classpath, specified jar files are not returned. I've substituted a method based on System.getProperty("java.class.path") to retrieve all of the named jar files in the classpath as well as the dirs2. I added an "if (directory.canRead()) {" statement at the top of the findFiles method, which stops null pointer exceptions (I know this shouldn't happen, but then again.... )...but I've gotta say, this is a great code snippet
Steve De Caux
Sure, I already protected myself by saying "basic example" ;) Good luck and happy coding.
BalusC
Also, if your directories contain spaces, you may want to URL decode the directory name before trying to find files in it the directory.
Sualeh Fatehi