package org.atricore.idbus.kernel.common.support.jdbc; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.atricore.idbus.kernel.common.support.osgi.ExternalResourcesClassLoader; import org.osgi.framework.BundleContext; import org.springframework.beans.factory.InitializingBean; import org.springframework.osgi.context.BundleContextAware; import java.io.File; import java.io.FileFilter; import java.net.URL; import java.sql.*; import java.util.*; import java.util.logging.Logger; /** * @author <a href=mailto:sgonzalez@atricore.org>Sebastian Gonzalez Oyuela</a> */ public class JDBCDriverManager implements BundleContextAware, InitializingBean { private static final Log logger = LogFactory.getLog(JDBCDriverManager.class); private BundleContext bundleContext; private ExternalResourcesClassLoader defaultDriverLoader = null; private List<DriverDescriptor> configuredDrivers = new ArrayList<DriverDescriptor>(); private Map<String, Driver> cachedDrivers = new HashMap<String, Driver>(); private List<String> defaultDriversUrls; private boolean loadDefaultDrivers; private static long requestedConnections; public List<String> getDefaultDriversUrls() { return defaultDriversUrls; } public void setDefaultDriversUrls(List<String> defaultDriversUrls) { this.defaultDriversUrls = defaultDriversUrls; } public boolean isLoadDefaultDrivers() { return loadDefaultDrivers; } public void setLoadDefaultDrivers(boolean loadDefaultDrivers) { this.loadDefaultDrivers = loadDefaultDrivers; } public void setBundleContext(BundleContext bundleContext) { this.bundleContext = bundleContext; } public BundleContext getBundleContext() { return bundleContext; } public void afterPropertiesSet() throws Exception { if (defaultDriversUrls == null || defaultDriversUrls.size() == 0) { defaultDriversUrls = new ArrayList<String>(); // Build default URL according to platform ... String karafBase = System.getProperty("karaf.base"); // Add starting slash if not present, required in Windows. if (!karafBase.startsWith("/")) karafBase = "/" + karafBase; // Replace Windows file separator, if any karafBase = karafBase.replaceAll("\\\\", "/"); // Add default drivers URL String defaultUrl = "file://" + karafBase + "/lib/jdbc"; defaultDriversUrls.add(defaultUrl); } if (loadDefaultDrivers) { StringBuffer sb = new StringBuffer(); for (String url : defaultDriversUrls) { sb.append(":"); sb.append(url); } logger.info("Loading default drivers from " + sb); boolean refresh = true; for (DriverDescriptor ds : configuredDrivers) { if (!isRegistered(ds)) { try { registerDriver(ds, refresh); refresh = false; logger.info(ds.getName() + "JDBC Driver found for " + ds.getDriverclassName()); } catch (Exception e) { logger.info(ds.getName() + "JDBC Driver not found for " + ds.getDriverclassName()); } } } } } protected ExternalResourcesClassLoader getDefaultDriverLoader() throws Exception { if (defaultDriverLoader == null) { defaultDriverLoader = doMakeDriverLoader(defaultDriversUrls); defaultDriverLoader.refreshClasspath(); } return defaultDriverLoader; } protected ExternalResourcesClassLoader doMakeDriverLoader(Collection<String> classPath) { FileFilter filter = new FileFilter( ) { public boolean accept( File pathname ) { return pathname.isFile() && isDriverFile(pathname.getName()); } }; return bundleContext != null ? new ExternalResourcesClassLoader(bundleContext, classPath, filter) : new ExternalResourcesClassLoader(getClass().getClassLoader(), classPath, filter) ; } public List<DriverDescriptor> getConfiguredDrivers() { return configuredDrivers; } public List<DriverDescriptor> getRegisteredDrivers() { List<DriverDescriptor> registered = new ArrayList<DriverDescriptor>(); for (String driverClass : cachedDrivers.keySet()) { registered.add(getConfiguredDriver(driverClass)); } return registered; } public void setConfiguredDrivers(List<DriverDescriptor> configuredDrivers) { this.configuredDrivers = configuredDrivers; } public Connection getConnection( String driverClass, String url, Properties connectionProperties, Collection<String> driverClassPath ) throws JDBCManagerException { try { if (driverClass == null || "".equals(driverClass)) throw new JDBCManagerException("Driver class canont be null or empty"); if (url == null || "".equals(url)) throw new JDBCManagerException("Connection URL canont be null or empty"); if ( logger.isTraceEnabled()) logger.trace( "Request JDBC Connection: driverClass=" + ( driverClass == null ? "" : driverClass ) + "; url=" + url ); return doConnect( driverClass, url, connectionProperties, driverClassPath ); } catch (SQLException e) { throw new JDBCManagerException(e); } } /** * Implementation of getConnection() methods. Gets connection from either java.sql.DriverManager, * or from IConnectionFactory defined in the extension */ protected synchronized Connection doConnect(String driverClass, String url, Properties connectionProperties, Collection<String> driverClassPath) throws SQLException, JDBCManagerException { // no driverinfo extension for driverClass connectionFactory // no JNDI Data Source URL defined, or // not able to get a JNDI data source connection, // use the JDBC DriverManager instead to get a JDBC connection DriverDescriptor ds = getConfiguredDriver(driverClass); if (ds == null) { List<String> classPath = new ArrayList<String>(); classPath.addAll(driverClassPath); ds = new DriverDescriptor(); ds.setName("Dyamically added driver : " + driverClass); ds.setDriverclassName(driverClass); ds.setJarFileNames(classPath); ds.setUrl(url); this.configuredDrivers.add(ds); } if (!isRegistered(ds)) { registerDriver(ds, false); } if (logger.isTraceEnabled()) logger.trace("Calling DriverManager.getConnection. url=" + url); try { return DriverManager.getConnection(url, connectionProperties); } catch (Exception e) { throw new JDBCManagerException(e); } } protected DriverDescriptor getConfiguredDriver(String driverClass) { for (DriverDescriptor ds : configuredDrivers) { if (ds.getDriverclassName().equals(driverClass)) return ds; } return null; } protected boolean isRegistered(DriverDescriptor ds) { return cachedDrivers.get(ds.getDriverclassName()) != null ; } /** * If driver is found in the drivers directory, its class is not accessible * in this class's ClassLoader. DriverManager will not allow this class to create * connections using such driver. To solve the problem, we create a wrapper Driver in * our class loader, and registration it with DriverManager * * @param driverDescriptor * @param refreshClassLoader * @throws JDBCManagerException */ protected void registerDriver(DriverDescriptor driverDescriptor, boolean refreshClassLoader) throws JDBCManagerException { try { Driver driver = findDriver(driverDescriptor, refreshClassLoader); if (driver != null) { try { if (logger.isDebugEnabled()) logger.debug("Registering with DriverManager: wrapped driver for " + driverDescriptor.getDriverclassName()); DriverManager.registerDriver(new WrappedDriver(driver, driverDescriptor.getDriverclassName())); } catch (SQLException e) { // This shouldn't happen logger.error("Failed to registration wrapped driver instance: " + e.getMessage(), e); } } } catch (Exception e) { throw new JDBCManagerException(e); } } protected Driver findDriver(DriverDescriptor driverDescriptor, boolean refreshClassLoader) throws Exception { String className = driverDescriptor.getDriverclassName(); Class driverClass = null; try { // 1. Try our standard classloader ! driverClass = Class.forName(className); // Driver class in class path if (logger.isDebugEnabled()) logger.debug("Loaded JDBC driver class in class path: " + className); } catch (ClassNotFoundException e) { // 2. Try dirver classpath if (logger.isDebugEnabled()) { logger.debug("Driver class not in class path: " + className + ". Trying to locate driver in drivers directory"); } // Driver not in plugin class path; find it in drivers directory driverClass = loadDriver(driverDescriptor, true, refreshClassLoader); // If driver class still cannot be found, try context classloader if (driverClass == null) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); if (loader != null) { try { driverClass = Class.forName(className, true, loader); } catch (ClassNotFoundException e1) { driverClass = null; } } } } if (driverClass == null) { throw new JDBCManagerException("Cannot load driver class: " + className); } Driver driver = null; try { driver = getDriverInstance(driverClass); } catch (Exception e) { logger.error("Failed to create new instance of JDBC driver:" + className + ". " + e.getMessage()); throw new JDBCManagerException("Failed to create new instance of JDBC driver:" + className + ". " + e.getMessage(), e); } return driver; } protected Class loadDriver(DriverDescriptor driverDescriptor, boolean refreshUrlsWhenFail, boolean refreshClassLoader) throws Exception { String className = driverDescriptor.getDriverclassName(); assert className != null; ExternalResourcesClassLoader driverLoader = driverDescriptor.getDriverLoader(); if (driverLoader == null || refreshClassLoader) { if (logger.isDebugEnabled()) logger.debug("No loader configured for driver " + className); // No driver loader configured, use default or create one with provided jar file names if (driverDescriptor.getJarFileNames() != null && driverDescriptor.getJarFileNames().size() > 0) { if (logger.isDebugEnabled()) { StringBuffer sb = new StringBuffer(); for (String jarFile : driverDescriptor.getJarFileNames()) { sb.append(":").append(jarFile); } logger.debug("No loader configured for driver " + className + ", using class path " + sb.toString()); } driverLoader = doMakeDriverLoader(driverDescriptor.getJarFileNames()); driverLoader.refreshClasspath(); } else { if (logger.isDebugEnabled()) logger.debug("No loader configured for driver " + className + ", using default loader"); driverLoader = getDefaultDriverLoader(); if (refreshClassLoader) driverLoader.refreshClasspath(); } driverDescriptor.setDriverLoader(driverLoader); } try { return driverLoader.loadClass(className); } catch (ClassNotFoundException e) { //re-scan resources. if (refreshUrlsWhenFail && defaultDriverLoader.refreshClasspath()) { if (logger.isDebugEnabled()) logger.debug("Cannot find dirver class, try again after refresh!"); // New driver not found; try loading again return loadDriver(driverDescriptor, false, true); } if (logger.isDebugEnabled()) { logger.debug("ExternalResourcesClassLoader failed to load class " + className + " : " + e.getMessage()); logger.debug("refreshUrlsWhenFail: " + refreshUrlsWhenFail); //logger.error("driverClassPath: " + ); StringBuffer sb = new StringBuffer(); for (URL url : driverLoader.getURLs()) { sb.append("[").append(url).append("]"); } logger.debug("Registered URLs: " + sb.toString()); } // no new driver found; give up logger.info("Driver class not found in drivers directory: " + className); return null; } } protected Driver getDriverInstance(Class driver) throws Exception { String driverName = driver.getName(); if (!this.cachedDrivers.containsKey(driverName)) { Driver instance = null; try { instance = (Driver) driver.newInstance(); } catch (Exception e) { throw new JDBCManagerException(e); } this.cachedDrivers.put(driverName, instance); } return cachedDrivers.get(driverName); } protected boolean isDriverFile(String fileName) { String lcName = fileName.toLowerCase(); return lcName.endsWith(".jar") || lcName.endsWith(".zip"); } // The classloader of a driver (jtds driver, etc.) is // "java.net.FactoryURLClassLoader", whose parent is // "sun.misc.Launcher$AppClassLoader". // The classloader of class Connection (the caller of // DriverManager.getConnection(url, props)) is // "sun.misc.Launcher$AppClassLoader". As the classes loaded by a child // classloader are always not visible to its parent classloader, // DriverManager.getConnection(url, props), called by class Connection, actually // has no access to driver classes, which are loaded by // "java.net.FactoryURLClassLoader". The invoking of this method would return a // "no suitable driver" exception. // On the other hand, if we use class WrappedDriver to wrap drivers. The DriverExt // class is loaded by "sun.misc.Launcher$AppClassLoader", which is same as the // classloader of Connection class. So DriverExt class is visible to // DriverManager.getConnection(url, props). And the invoking of the very method // would success. private static class WrappedDriver implements Driver { private Driver driver; private String driverClass; WrappedDriver(Driver d, String driverClass) { logger.debug(WrappedDriver.class.getName() + ":WrappedDriver=" + driverClass); this.driver = d; this.driverClass = driverClass; } /* * @see java.sql.Driver#acceptsURL(java.lang.String) */ public boolean acceptsURL(String u) throws SQLException { boolean res = this.driver.acceptsURL(u); if (logger.isDebugEnabled()) logger.debug("WrappedDriver(" + driverClass + ").acceptsURL(" + u + ")returns: " + res); return res; } /* * @see java.sql.Driver#connect(java.lang.String, java.util.Properties) */ public Logger getParentLogger() throws SQLFeatureNotSupportedException { throw new SQLFeatureNotSupportedException(); } public java.sql.Connection connect(String u, Properties p) throws SQLException { if (logger.isDebugEnabled()) logger.debug(WrappedDriver.class.getName() + ":" + driverClass + ", connect=" + u); try { return this.driver.connect(u, p); } catch (RuntimeException e) { throw new SQLException(e.getMessage()); } } /* * @see java.sql.Driver#getMajorVersion() */ public int getMajorVersion() { return this.driver.getMajorVersion(); } /* * @see java.sql.Driver#getMinorVersion() */ public int getMinorVersion() { return this.driver.getMinorVersion(); } /* * @see java.sql.Driver#getPropertyInfo(java.lang.String, java.util.Properties) */ public DriverPropertyInfo[] getPropertyInfo(String u, Properties p) throws SQLException { return this.driver.getPropertyInfo(u, p); } /* * @see java.sql.Driver#jdbcCompliant() */ public boolean jdbcCompliant() { return this.driver.jdbcCompliant(); } /* * @see java.lang.Object#toString() */ public String toString() { return driverClass; } } }