Reputation: 8596
I am developing an Eclipse plug-in that fits a client-server model. Its a commercial project so we cannot re-distribute the JDBC drivers for the various databases we support with the plug-in.
So I developed a preference page to allow the user locate the jars and have a simple discovery mechanism that iterates through the classes in the jar files, loading each one to verify that it implements the java.sql.Driver interface. This all works great.
But the catch is that I am using Hibernate. And Hibernate uses Class.forName() to instantiate the JDBC driver.
If I try to use the following I get ClassNotFoundException
.
public Object execute(final IRepositoryCallback callback)
{
final DatabaseDriverClassLoader loader = new DatabaseDriverClassLoader(
Activator.getDefault().getDatabaseDriverRegistry());
final ClassLoader oldLoader = Thread.currentThread()
.getContextClassLoader();
try
{
Thread.currentThread().setContextClassLoader(loader);
try
{
final SessionFactory sessionFactory = this.configuration
.buildSessionFactory();
if (sessionFactory != null)
{
final Session session = sessionFactory
.openSession();
if (session != null)
{
// CHECKSTYLE:OFF
try
// CHECKSTYLE:ON
{
return callback.doExecute(session);
}
finally
{
session.close();
}
}
}
connection.close();
}
finally
{
}
}
// CHECKSTYLE:OFF
catch (Exception e)
// CHECKSTYLE:ON
{
RepositoryTemplate.LOG.error(e.getMessage(), e);
}
finally
{
Thread.currentThread().setContextClassLoader(oldLoader);
}
return null;
}
And if I try creating the driver myself as follows I get a SecurityException.
public Object execute(final IRepositoryCallback callback)
{
final DatabaseDriverClassLoader loader = new DatabaseDriverClassLoader(
Activator.getDefault().getDatabaseDriverRegistry());
final ClassLoader oldLoader = Thread.currentThread()
.getContextClassLoader();
try
{
Thread.currentThread().setContextClassLoader(loader);
final Class driverClass = loader.loadClass(this.connectionDriverClassName);
final Driver driver = (Driver)driverClass.newInstance();
DriverManager.registerDriver(driver);
try
{
final Connection connection = DriverManager.getConnection(
this.connectionUrl, this.connectionUsername,
this.connectionPassword);
final SessionFactory sessionFactory = this.configuration
.buildSessionFactory();
if (sessionFactory != null)
{
final Session session = sessionFactory
.openSession(connection);
if (session != null)
{
// CHECKSTYLE:OFF
try
// CHECKSTYLE:ON
{
return callback.doExecute(session);
}
finally
{
session.close();
}
}
}
connection.close();
}
finally
{
DriverManager.deregisterDriver(driver);
}
}
// CHECKSTYLE:OFF
catch (Exception e)
// CHECKSTYLE:ON
{
RepositoryTemplate.LOG.error(e.getMessage(), e);
}
finally
{
Thread.currentThread().setContextClassLoader(oldLoader);
}
return null;
}
EDIT: I am not sure it is the best option but I took the approach of implementing my own ConnectionProvider
which allowed me instantiate the driver using Class.forName()
and I then open the connection using Driver.connect()
instead of DriverManager.getConnection()
. Its pretty basic but I don't need connection pooling in my specific use case.
The configure()
method was as follows:
public void configure(final Properties props)
{
this.url = props.getProperty(Environment.URL);
this.connectionProperties = ConnectionProviderFactory
.getConnectionProperties(props);
final DatabaseDriverClassLoader classLoader = new DatabaseDriverClassLoader(
Activator.getDefault().getDatabaseDriverRegistry());
final String driverClassName = props.getProperty(Environment.DRIVER);
try
{
final Class driverClass = Class.forName(driverClassName, true,
classLoader);
this.driver = (Driver)driverClass.newInstance();
}
catch (ClassNotFoundException e)
{
throw new HibernateException(e);
}
catch (IllegalAccessException e)
{
throw new HibernateException(e);
}
catch (InstantiationException e)
{
throw new HibernateException(e);
}
}
And the getConnection()
method is as follows:
public Connection getConnection()
throws SQLException
{
return this.driver.connect(this.url, this.connectionProperties);
}
Upvotes: 2
Views: 4599
Reputation: 20091
Class.forName()
in OSGi is a major pain. This is not really anyone's fault, just that both use class loaders which do not work the way the other's client is expecting (i.e. OSGi class loader doesn't work in the way that hibernate is expecting).
I think you can go one of a few ways, but the ones I can think of right now are:
DynamicImport-Package
to add the exported packages from your bundled drivers. This way, the client code can still see the class that it'll use, but it doesn't have to know about it until runtime. You may have to experiment with the package pattern, however, to cover all cases (that's why it's less clean).osgi.parentClassloader=app
to your config.ini. This may not fit in with your deployment, especially if you haven't got control of the config.ini
file.URLClassLoader
. This will only work if you have a directory full of driver jars, or the user can directly or indirectly specify the location of the driver jar.Upvotes: 4