0

I have a simple wrapper over JDBC Driver class and I would like my Driver to be registered with the DriverManager automatically. I followed the How is driver class located in JDBC4 to set up the META-INF/services/java.sql.Driver file and mentioned my wrapped driver.

My jdbc url is configured to append "sample:" with other standard driver's url.

For example, for MySQL the url will be

sample:jdbc:mysql://localhost:3306/test

My SampleDriver:

package com.trial

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Collections;
import java.util.Optional;
import java.util.Properties;
import java.util.logging.Logger;

public class SampleDriver implements Driver {

    private static final Driver INSTANCE = new SampleDriver();
    private Driver driver;

    public SampleDriver() {
    }

    public SampleDriver(Driver driver) {
        this.driver = driver;
    }

    static {
        try {
            DriverManager.registerDriver(INSTANCE);
        } catch (SQLException e) {
            throw new IllegalStateException("Could not register Sample Driver with DriverManager", e);
        }
    }

    /**
     * Get a Connection to the database from the real driver that this
     * wrapper is tracing on.
     *
     * @param url  JDBC connection URL .
     * @param info a list of arbitrary string tag/value pairs as connection
     *             arguments. Normally at least a "user" and "password" property should
     *             be included.
     * @return a <code>Connection</code> object that represents a connection to
     * the URL.
     * @throws SQLException if a database access error occurs
     */
    @Override
    public Connection connect(String url, Properties info) throws SQLException {

        Optional<SampleDriver> realDriver = getRealDriver(url);

        String realUrl = extractRealURL(url);

        if (!realDriver.isPresent()) {
            throw new SQLException("Unable to find a driver that accepts the url " + realUrl);
        }

        Connection conn = realDriver.get().driver.connect(realUrl, info);

        if (null == conn) {
            throw new SQLException("Invalid or unknown driver url : " + realUrl);
        }

        return conn;
    }

    /**
     * Given a sample: type URL, find the underlying real driver
     * that accepts the URL.
     *
     * @param url JDBC connection URL.
     * @return Real driver for the given URL. Optional.empty is returned
     * if the URL is not a sample: type URL or there is
     * no underlying driver that accepts the URL.
     * @throws SQLException if a database access error occurs.
     */
    private Optional<SampleDriver> getRealDriver(String url) throws SQLException {
        if (null == url || url.trim().isEmpty()) {
            throw new IllegalArgumentException("url is required");
        }

        Optional<Driver> realDriver;
        int instanceId;

        if (url.startsWith(getURLPrefix())) {

            String realUrl = extractRealURL(url);

            try {
                realDriver = Collections.list(DriverManager.getDrivers()).stream().
                    filter(driver -> {
                        try {
                            return driver.acceptsURL(realUrl);
                        } catch (SQLException e) {
                            throw new RuntimeException(e);
                        }
                    }).findFirst();

                return Optional.of(new SampleDriver(realDriver.orElse(null)));

            } catch (Exception e) {
                throw new SQLException(e);
            }
        }
        return Optional.empty();
    }

    /**
     * Given a <code>sample:</code> type URL, extract and return the real URL
     *
     * @return Real URL client wanted to connect with.
     * if the URL is not a <code>sample:</code> return the given input
     */
    private String extractRealURL(String url) {
        return url.replaceFirst(getURLPrefix(), "");
    }

    /**
     * Get the String used to prefix real URL
     *
     * @return the URL prefix
     */
    private String getURLPrefix() {
        return "sample:";
    }

    /**
     * Returns true if this is a <code>sample:</code> URL
     *
     * @param url JDBC URL.
     * @return true if this Driver can handle the URL.
     * @throws SQLException if a database access error occurs
     */
    @Override
    public boolean acceptsURL(String url) throws SQLException {
        return url != null && url.startsWith(getURLPrefix());
    }

    /**
     * Gets information about the possible properties for the real driver.
     *
     * @param url  the URL of the database to which to connect
     * @param info a proposed list of tag/value pairs that will be sent on connect
     *             open
     * @return an array of <code>DriverPropertyInfo</code> objects describing
     * possible properties. This array may be an empty array if no
     * properties are required.
     * @throws SQLException if a database access error occurs
     */
    @Override
    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
            Optional<SampleDriver> realDriver = getRealDriver(url);
            DriverPropertyInfo[] propertyInfo;
            if (realDriver.isEmpty()) {
                propertyInfo = new DriverPropertyInfo[0];
            } else {
                propertyInfo = realDriver.get().driver.getPropertyInfo(url, info);
            }
            return propertyInfo;
    }

    /**
     * Returns 1 for the major version of the wrapped driver.
     *
     * @return Major version of the wrapped driver
     */
    @Override
    public int getMajorVersion() {
        return 1;
    }

    /**
     * Returns 0 for the minor version of the wrapped driver.
     *
     * @return Minor version of the wrapped driver
     */
    @Override
    public int getMinorVersion() {
        return 0;
    }

    /**
     * Returns true for the wrapped driver. Only to support jdbc compliant drivers.
     *
     * @return <code>true</code>
     */
    @Override
    public boolean jdbcCompliant() {
        return true;
    }

    /**
     * @return the parent Logger for the wrapped driver. Not implemented.
     * @throws SQLFeatureNotSupportedException if the driver does not use
     *                                         {@code java.util.logging}.
     */
    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        throw new SQLFeatureNotSupportedException("getParentLogger is not yet implemented for " + this.getClass().getName());
    }
}

Contents of my java.sql.Driver file:

com.trial.SampleDriver

But the Driver Manager does not load the driver class automatically. I get the java.sql.SQLException: No suitable driver found for sample:jdbc:mysql://localhost:3306/test

On debugging I found that the DriverManager.getDrivers() return empty list. Even mysql driver is not registered automatically. I use the following mysql version:

<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>5.1.46</version>
</dependency>

Is it required to have some mapping between the URL and the Driver? I thought implementing the acceptsURL should resolve that matching.

I use Jdk-11.

EDIT:

The calling code:

GenericObjectPool gPool = new GenericObjectPool();
...
ConnectionFactory cf = new DriverManagerConnectionFactory(uri, props);
PoolableConnectionFactory pcf = new PoolableConnectionFactory(cf, gPool, null, null, false, true);
Connection conn = (Connection) gPool.borrowObject();

Connection conn = (Connection) gPool.borrowObject(); --> This causes the excecption. The exception stack trace shows that DriverManager.getConnection() call causes the exception.

user2761431
  • 925
  • 2
  • 11
  • 26
  • Share your whole code please. – akortex Apr 22 '20 at 21:57
  • @akortex91Added – user2761431 Apr 22 '20 at 22:17
  • Also a calling point where you get the exception would be useful. – akortex Apr 22 '20 at 22:32
  • It throws the exception at that point. – user2761431 Apr 23 '20 at 00:17
  • JDBC itself doesn't call `acceptsUrl`, it only uses `Driver.connect`: if a driver doesn't recognize an URL, it must return `null`. However, have you verified that the driver is actually loaded. What kind of application is this, and where on the classpath is the driver. Automatic driverloading only works for drivers on the initial classpath, and - for example - not for drivers located in a WAR. This also mean that your decision to throw a `SQLException` under those circumstances is the wrong one (no driver found, or driver returned `null`). – Mark Rotteveel Apr 23 '20 at 11:52
  • And finally, your driver doesn't conform to the JDBC-url naming conventions. A JDBC url must start with `jdbc::`, and although technically JDBC does not enforce this, using `sample:jdbc::` violates the specification, at minimum it should be `jdbc:sample::` – Mark Rotteveel Apr 23 '20 at 12:08
  • @MarkRotteveel tried with jdbc:sample, didn't work. – user2761431 Apr 23 '20 at 19:18

0 Answers0