/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.sql; import java.io.InputStream; import java.lang.reflect.Method; import java.net.URL; import java.sql.Connection; import java.sql.Driver; import java.sql.SQLException; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Properties; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.PostConstruct; import javax.resource.spi.ManagedConnectionFactory; import javax.sql.ConnectionPoolDataSource; import javax.sql.DataSource; import javax.sql.PooledConnection; import javax.sql.XADataSource; import com.caucho.config.Config; import com.caucho.config.ConfigException; import com.caucho.config.program.ConfigProgram; import com.caucho.config.program.ContainerProgram; import com.caucho.config.program.PropertyStringProgram; import com.caucho.config.program.PropertyValueProgram; import com.caucho.config.type.ConfigType; import com.caucho.config.type.TypeFactory; import com.caucho.config.types.InitParam; import com.caucho.lifecycle.Lifecycle; import com.caucho.management.server.JdbcDriverMXBean; import com.caucho.naming.Jndi; import com.caucho.sql.spy.SpyConnectionPoolDataSource; import com.caucho.tools.profiler.ConnectionPoolDataSourceWrapper; import com.caucho.tools.profiler.DriverWrapper; import com.caucho.tools.profiler.ProfilerPoint; import com.caucho.tools.profiler.ProfilerPointConfig; import com.caucho.tools.profiler.XADataSourceWrapper; import com.caucho.util.Alarm; import com.caucho.util.IoUtil; import com.caucho.util.L10N; import com.caucho.vfs.ReadStream; import com.caucho.vfs.Vfs; import com.caucho.xml.QName; /** * Configures the database driver. */ public class DriverConfig { protected static final Logger log = Logger.getLogger(DriverConfig.class.getName()); private static final L10N L = new L10N(DriverConfig.class); @SuppressWarnings("unused") private static final int TYPE_UNKNOWN = 0; private static final int TYPE_DRIVER = 1; private static final int TYPE_POOL = 2; private static final int TYPE_XA = 3; private static final int TYPE_JCA = 4; private static final int TYPE_DATA_SOURCE = 5; /** * The key used to look into the properties passed to the * connect method to find the username. */ public static final String PROPERTY_USER = "user" ; /** * The key used to look into the properties passed to the * connect method to find the password. */ public static final String PROPERTY_PASSWORD = "password" ; private static final QName URL = new QName("url"); private static final QName USER = new QName("user"); private static final QName PASSWORD = new QName("password"); private DBPoolImpl _dbPool; private Class<?> _driverClass; private String _driverURL; private String _user; private String _password; private Properties _info; private ContainerProgram _init = new ContainerProgram(); private int _driverType; private Object _driverObject; private ManagedConnectionFactory _jcaDataSource; private ConnectionPoolDataSource _poolDataSource; private XADataSource _xaDataSource; private Driver _driver; private int _index; private Lifecycle _lifecycle = new Lifecycle(); private DriverAdmin _admin = new DriverAdmin(this); // statistics private long _connectionCountTotal; private AtomicLong _connectionFailCountTotal = new AtomicLong(); private long _lastFailTime; private ProfilerPoint _profilerPoint; /** * Null constructor for the Driver interface; called by the JNDI * configuration. Applications should not call this directly. */ public DriverConfig(DBPoolImpl pool) { _dbPool = pool; _info = new Properties(); } /** * Returns the DBPool. */ public DBPoolImpl getDBPool() { return _dbPool; } /** * Sets the driver as data source. */ public void setDriverType(String type) throws ConfigException { if ("ConnectionPoolDataSource".equals(type)) { _driverType = TYPE_POOL; } else if ("XADataSource".equals(type)) { _driverType = TYPE_XA; } else if ("ManagedConnectionFactory".equals(type)) { _driverType = TYPE_JCA; } else if ("Driver".equals(type)) { _driverType = TYPE_DRIVER; } else if ("DataSource".equals(type)) { _driverType = TYPE_DATA_SOURCE; } else if (hasDriverTypeMethod(_driverClass)) { _init.addProgram(new PropertyStringProgram("driverType", type)); } else { throw new ConfigException(L.l("'{0}' is an unknown driver type. Valid types are 'ConnectionPoolDataSource', 'XADataSource' and 'Driver'")); } } private boolean hasDriverTypeMethod(Class<?> cl) { if (cl == null) return false; for (Method method : cl.getMethods()) { if (method.getName().equals("setDriverType") && method.getParameterTypes().length == 1) { return true; } } return false; } /** * Sets the driver as data source. */ public void setDataSource(Object dataSource) throws ConfigException { if (dataSource instanceof String) dataSource = Jndi.lookup((String) dataSource); if (_driverType == TYPE_XA) _xaDataSource = (XADataSource) dataSource; else if (_driverType == TYPE_POOL) _poolDataSource = (ConnectionPoolDataSource) dataSource; else if (dataSource instanceof XADataSource) _xaDataSource = (XADataSource) dataSource; else if (dataSource instanceof ConnectionPoolDataSource) _poolDataSource = (ConnectionPoolDataSource) dataSource; else if (dataSource instanceof ManagedConnectionFactory) _jcaDataSource = (ManagedConnectionFactory) dataSource; else throw new ConfigException(L.l("data-source '{0}' is of type '{1}' which does not implement XADataSource or ConnectionPoolDataSource.", dataSource, dataSource.getClass().getName())); } /** * Returns the JDBC driver class for the pooled object. */ public Class<?> getDriverClass() { return _driverClass; } /** * Sets the JDBC driver class underlying the pooled object. */ public void setType(Class<?> driverClass) throws ConfigException { _driverClass = driverClass; if (! Driver.class.isAssignableFrom(driverClass) && ! XADataSource.class.isAssignableFrom(driverClass) && ! ConnectionPoolDataSource.class.isAssignableFrom(driverClass) && ! ManagedConnectionFactory.class.isAssignableFrom(driverClass) && ! DataSource.class.isAssignableFrom(driverClass)) { throw new ConfigException(L.l("'{0}' is not a valid database type, because it does not implement Driver, XADataSource, ConnectionPoolDataSource, ManagedConnectionFactory, or DataSource..", driverClass.getName())); } Config.checkCanInstantiate(driverClass); } public String getType() { return _driverClass.getName(); } /** * Returns the connection's JDBC url. */ public String getURL() { return _driverURL; } /** * Sets the connection's JDBC url. */ public void setURL(String url) { _driverURL = url; _lifecycle.setName("JdbcDriver[" + url + "]"); } /** * Adds to the builder program. */ public void addBuilderProgram(ConfigProgram program) { _init.addProgram(program); } public void setProperty(String name, Object value) { _init.addProgram(new PropertyValueProgram(name, value)); } /** * Returns the connection's user. */ public String getUser() { return _user; } /** * Sets the connection's user. */ public void setUser(String user) { _user = user; } /** * Returns the connection's password */ public String getPassword() { return _password; } /** * Sets the connection's password */ public void setPassword(String password) { _password = password; } /** * Sets a property from the underlying driver. Used to set driver * properties not handled by DBPool. * * @param name property name for the driver * @param value the driver's value of the property name */ public void setInitParam(InitParam initParam) { validateInitParam(); HashMap<String,String> paramMap = initParam.getParameters(); Iterator<String> iter = paramMap.keySet().iterator(); while (iter.hasNext()) { String key = iter.next(); _info.setProperty(key, paramMap.get(key)); } } /** * Sets a property from the underlying driver. Used to set driver * properties not handled by DBPool. * * @param name property name for the driver * @param value the driver's value of the property name */ public void setInitParam(String key, String value) { _info.setProperty(key, value); } /** * Returns the properties. */ public Properties getInfo() { return _info; } /** * Returns the driver object. */ public Driver getDriver() throws SQLException { Object obj = getDriverObject(); if (obj instanceof Driver) return (Driver) obj; else return null; } /** * Sets the driver object. */ public void setDriver(Driver driver) throws SQLException { _driver = driver; _driverObject = driver; } public void setDriverObject(Object driverObject) { _driverObject = driverObject; } /** * Returns the driver pool. */ public ConnectionPoolDataSource getPoolDataSource() throws SQLException { return _poolDataSource; } /** * Sets the pooled data source driver. */ public void setPoolDataSource(ConnectionPoolDataSource pDataSource) throws SQLException { _poolDataSource = pDataSource; _driverObject = _poolDataSource; } /** * Returns any XADataSource. */ public XADataSource getXADataSource() { return _xaDataSource; } /** * Sets the xa data source driver. */ public void setXADataSource(XADataSource xaDataSource) throws SQLException { _xaDataSource = xaDataSource; _driverObject = _xaDataSource; } /** * Returns the managed connection factory. */ public ManagedConnectionFactory getManagedConnectionFactory() { return _jcaDataSource; } /** * Returns true if the driver is XA enabled. */ public boolean isXATransaction() { return _xaDataSource != null && _dbPool.isXA(); } /** * Returns true if the driver is XA enabled. */ public boolean isLocalTransaction() { return _dbPool.isXA(); } /** * Configure a ProfilerPointConfig, used to create a ProfilerPoint * that is then passed to setProfiler(). * The returned ProfilerPointConfig has a default name set to the URL of * this driver, */ public ProfilerPointConfig createProfilerPoint() { ProfilerPointConfig profilerPointConfig = new ProfilerPointConfig(); profilerPointConfig.setName(getURL()); profilerPointConfig.setCategorizing(true); return profilerPointConfig; } /** * Enables profiling for this driver. */ public void setProfilerPoint(ProfilerPoint profilerPoint) { _profilerPoint = profilerPoint; } public void setIndex(int index) { _index = index; } public int getIndex() { return _index; } /** * Returns the driver admin. */ public JdbcDriverMXBean getAdmin() { return _admin; } /** * Initialize the pool's data source * * <ul> * <li>If data-source is set, look it up in JNDI. * <li>Else if the driver is a pooled or xa data source, use it. * <li>Else create wrappers. * </ul> */ synchronized void initDataSource(boolean isTransactional, String spyId, boolean isSpy) throws SQLException { if (! _lifecycle.toActive()) return; if (_xaDataSource == null && _poolDataSource == null) { initDriver(); Object driverObject = getDriverObject(); if (driverObject == null) { throw new SQLExceptionWrapper(L.l("driver '{0}' has not been configured for pool {1}. <database> needs a <driver type='...'>.", _driverClass, getDBPool().getName())); } if (_driverType == TYPE_XA) _xaDataSource = (XADataSource) _driverObject; else if (_driverType == TYPE_POOL) _poolDataSource = (ConnectionPoolDataSource) _driverObject; else if (_driverType == TYPE_DRIVER) _driver = (Driver) _driverObject; else if (driverObject instanceof XADataSource) _xaDataSource = (XADataSource) _driverObject; else if (_driverObject instanceof ConnectionPoolDataSource) _poolDataSource = (ConnectionPoolDataSource) _driverObject; else if (_driverObject instanceof ManagedConnectionFactory) _jcaDataSource = (ManagedConnectionFactory) _driverObject; else if (_driverObject instanceof Driver) _driver = (Driver) _driverObject; else if (_driverObject instanceof DataSource) _poolDataSource = new ConnectionPoolAdapter((DataSource) _driverObject); else throw new SQLExceptionWrapper(L.l("driver '{0}' has not been configured for pool {1}. <database> needs a <driver type='...'>.", _driverClass, getDBPool().getName())); /* if (! isTransactional && _xaDataSource != null) { throw new SQLExceptionWrapper(L.l("XADataSource '{0}' must be configured as transactional. Either configure it with <xa>true</xa> or use the database's ConnectionPoolDataSource driver or the old java.sql.Driver driver.", _xaDataSource)); } */ } spyId = spyId + ".d" + _index; if (! isSpy) { } else if (_poolDataSource != null) { _poolDataSource = new SpyConnectionPoolDataSource(_poolDataSource, spyId); } _admin.register(); if (_profilerPoint != null) { if (log.isLoggable(Level.FINE)) log.fine(_profilerPoint.toString()); if (_xaDataSource != null) _xaDataSource = new XADataSourceWrapper(_profilerPoint, _xaDataSource); else if (_poolDataSource != null) _poolDataSource = new ConnectionPoolDataSourceWrapper(_profilerPoint, _poolDataSource); else if (_driver != null) _driver = new DriverWrapper(_profilerPoint, _driver); } if (_info.size() != 0) { validateInitParam(); } } Lifecycle getLifecycle() { return _lifecycle; } boolean start() { return _lifecycle.toActive(); } boolean stop() { return _lifecycle.toStop(); } private void validateInitParam() { if (_jcaDataSource != null) { throw new ConfigException(L.l("<init-param> cannot be used with a JCA data source. Use the init-param key as a tag, like <key>value</key>")); } else if (_poolDataSource != null) { throw new ConfigException(L.l("<init-param> cannot be used with a ConnectionPoolDataSource. Use the init-param key as a tag, like <key>value</key>")); } else if (_xaDataSource != null) { throw new ConfigException(L.l("<init-param> cannot be used with an XADataSource. Use the init-param key as a tag, like <key>value</key>")); } } /** * Returns the driver object configured for the database. */ synchronized Object getDriverObject() throws SQLException { if (_driverObject != null) return _driverObject; else if (_driverClass == null) return null; if (log.isLoggable(Level.CONFIG)) log.config("loading driver: " + _driverClass.getName()); try { _driverObject = _driverClass.newInstance(); } catch (Exception e) { throw new SQLExceptionWrapper(e); } return _driverObject; } /** * Creates a connection. */ PooledConnection createPooledConnection(String user, String password) throws SQLException { PooledConnection conn = null; if (_xaDataSource != null) { if (user == null && password == null) conn = _xaDataSource.getXAConnection(); else conn = _xaDataSource.getXAConnection(user, password); /* if (! _isTransactional) { throw new SQLExceptionWrapper(L.l("XADataSource '{0}' must be configured as transactional. Either configure it with <xa>true</xa> or use the database's ConnectionPoolDataSource driver or the old java.sql.Driver driver.", _xaDataSource)); } */ } else if (_poolDataSource != null) { /* if (_isTransactional) { throw new SQLExceptionWrapper(L.l("ConnectionPoolDataSource '{0}' can not be configured as transactional. Either use the database's XADataSource driver or the old java.sql.Driver driver.", _poolDataSource)); } */ if (user == null && password == null) conn = _poolDataSource.getPooledConnection(); else conn = _poolDataSource.getPooledConnection(user, password); } return conn; } /** * Creates a connection. */ Connection createDriverConnection(String user, String password) throws SQLException { if (! _lifecycle.isActive()) return null; if (_xaDataSource != null || _poolDataSource != null) throw new IllegalStateException(); if (_driver == null) throw new IllegalStateException(); Driver driver = _driver; String url = getURL(); if (url == null) throw new SQLException(L.l("can't create connection with null url")); try { Properties properties = new Properties(); properties.putAll(getInfo()); if (user != null) properties.put("user", user); else properties.put("user", ""); if (password != null) properties.put("password", password); else properties.put("password", ""); Connection conn; if (driver != null) conn = driver.connect(url, properties); else conn = java.sql.DriverManager.getConnection(url, properties); synchronized (this) { _connectionCountTotal++; } return conn; } catch (SQLException e) { _connectionFailCountTotal.incrementAndGet(); _lastFailTime = Alarm.getCurrentTime(); throw e; } } @PostConstruct public void init() { if (_driverClass == null && _poolDataSource == null && _xaDataSource == null) { if (_driverURL == null) throw new ConfigException(L.l("<driver> requires a 'type' or 'url'")); String driver = findDriverByUrl(_driverURL); if (driver == null) throw new ConfigException(L.l("url='{0}' does not have a known driver. The driver class must be specified by a 'type' parameter.", _driverURL)); Class<?> driverClass = null; try { ClassLoader loader = Thread.currentThread().getContextClassLoader(); driverClass = Class.forName(driver, false, loader); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw ConfigException.create(e); } setType(driverClass); } } /** * Initializes the JDBC driver. */ public void initDriver() throws SQLException { if (! _lifecycle.toInit()) return; Object driverObject = getDriverObject(); if (driverObject != null) { } else if (_xaDataSource != null || _poolDataSource != null) return; else { throw new SQLExceptionWrapper(L.l("driver '{0}' has not been configured for pool {1}. <database> needs either a <data-source> or a <type>.", _driverClass, getDBPool().getName())); } ConfigType<?> configType = TypeFactory.getType(driverObject); // server/14g1 if (_driverURL != null) { if (! configType.setProperty(driverObject, URL, _driverURL)) { if (! (driverObject instanceof Driver)) { throw new ConfigException(L.l("database: 'url' is an unknown property of '{0}'", driverObject.getClass().getName())); } } } if (_user != null) { if (! configType.setProperty(driverObject, USER, _user)) { if (! (driverObject instanceof Driver)) { throw new ConfigException(L.l("database: 'user' is an unknown property of '{0}'", driverObject.getClass().getName())); } } } if (_password != null) { if (! configType.setProperty(driverObject, PASSWORD, _password)) { if (! (driverObject instanceof Driver)) { throw new ConfigException(L.l("database: 'password' is an unknown property of '{0}'", driverObject.getClass().getName())); } } } try { if (_init != null) { _init.configure(driverObject); _init = null; } Config.init(driverObject); } catch (Throwable e) { log.log(Level.FINE, e.toString(), e); throw new SQLExceptionWrapper(e); } } protected String findDriverByUrl(String url) { String driver = DatabaseManager.findDriverByUrl(_driverURL); if (driver != null) return driver; ClassLoader loader = Thread.currentThread().getContextClassLoader(); try { Enumeration<URL> e = loader.getResources("META-INF/services/java.sql.Driver"); while (e.hasMoreElements()) { URL serviceUrl = e.nextElement(); driver = testDriver(url, serviceUrl); if (driver != null) return driver; } } catch (Exception e) { log.log(Level.WARNING, e.toString(), e); } return null; } private String testDriver(String url, URL serviceURL) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); InputStream is = null; try { is = serviceURL.openStream(); ReadStream in = Vfs.openRead(is); String line; while ((line = in.readLine()) != null) { int p = line.indexOf('#'); if (p >= 0) line = line.substring(p); line = line.trim(); if (line.length() == 0) continue; try { Class<?> cl = Class.forName(line, false, loader); Driver driver = (Driver) cl.newInstance(); if (driver.acceptsURL(url)) return cl.getName(); } catch (Exception e) { log.log(Level.WARNING, e.toString(), e); } } } catch (Exception e) { log.log(Level.WARNING, e.toString(), e); } finally { IoUtil.close(is); } return null; } // // statistics // /** * Returns the total number of connections made. */ public long getConnectionCountTotal() { return _connectionCountTotal; } /** * Returns the total number of failing connections */ public long getConnectionFailCountTotal() { return _connectionFailCountTotal.get(); } /** * Returns the time of the last connection */ public long getLastFailTime() { return _lastFailTime; } /** * Returns a string description of the pool. */ public String toString() { return "JdbcDriver[" + _driverURL + "]"; } }