/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library 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. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.dao.jdbc; import com.liferay.portal.dao.jdbc.pool.metrics.C3P0ConnectionPoolMetrics; import com.liferay.portal.dao.jdbc.pool.metrics.DBCPConnectionPoolMetrics; import com.liferay.portal.dao.jdbc.pool.metrics.HikariConnectionPoolMetrics; import com.liferay.portal.dao.jdbc.pool.metrics.TomcatConnectionPoolMetrics; import com.liferay.portal.dao.jdbc.util.DataSourceWrapper; import com.liferay.portal.dao.jdbc.util.RetryDataSourceWrapper; import com.liferay.portal.kernel.configuration.Filter; import com.liferay.portal.kernel.dao.db.DBManagerUtil; import com.liferay.portal.kernel.dao.db.DBType; import com.liferay.portal.kernel.dao.jdbc.DataSourceFactory; import com.liferay.portal.kernel.dao.jdbc.pool.metrics.ConnectionPoolMetrics; import com.liferay.portal.kernel.exception.SystemException; import com.liferay.portal.kernel.jndi.JNDIUtil; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.security.pacl.DoPrivileged; import com.liferay.portal.kernel.util.CharPool; import com.liferay.portal.kernel.util.ClassLoaderUtil; import com.liferay.portal.kernel.util.PropertiesUtil; import com.liferay.portal.kernel.util.PropsKeys; import com.liferay.portal.kernel.util.ServerDetector; import com.liferay.portal.kernel.util.SortedProperties; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.kernel.util.Validator; import com.liferay.portal.spring.hibernate.DialectDetector; import com.liferay.portal.util.JarUtil; import com.liferay.portal.util.PropsUtil; import com.liferay.portal.util.PropsValues; import com.liferay.registry.Registry; import com.liferay.registry.RegistryUtil; import com.liferay.registry.ServiceReference; import com.liferay.registry.ServiceTracker; import com.liferay.registry.ServiceTrackerCustomizer; import com.mchange.v2.c3p0.ComboPooledDataSource; import java.net.URL; import java.net.URLClassLoader; import java.util.Enumeration; import java.util.Map; import java.util.Properties; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.naming.Context; import javax.naming.InitialContext; import javax.sql.DataSource; import jodd.bean.BeanUtil; import org.apache.commons.dbcp.BasicDataSource; import org.apache.commons.dbcp.BasicDataSourceFactory; import org.apache.tomcat.jdbc.pool.PoolProperties; import org.apache.tomcat.jdbc.pool.jmx.ConnectionPool; /** * @author Brian Wing Shun Chan * @author Shuyang Zhou */ @DoPrivileged public class DataSourceFactoryImpl implements DataSourceFactory { @Override public void destroyDataSource(DataSource dataSource) throws Exception { while (dataSource instanceof DataSourceWrapper) { DataSourceWrapper dataSourceWrapper = (DataSourceWrapper)dataSource; dataSource = dataSourceWrapper.getWrappedDataSource(); } if (dataSource instanceof ComboPooledDataSource) { ComboPooledDataSource comboPooledDataSource = (ComboPooledDataSource)dataSource; comboPooledDataSource.close(); } else if (dataSource instanceof org.apache.tomcat.jdbc.pool.DataSource) { org.apache.tomcat.jdbc.pool.DataSource tomcatDataSource = (org.apache.tomcat.jdbc.pool.DataSource)dataSource; if (_serviceTracker != null) { _serviceTracker.close(); } tomcatDataSource.close(); } } @Override public DataSource initDataSource(Properties properties) throws Exception { Properties defaultProperties = PropsUtil.getProperties( "jdbc.default.", true); PropertiesUtil.merge(defaultProperties, properties); properties = defaultProperties; String jndiName = properties.getProperty("jndi.name"); if (Validator.isNotNull(jndiName)) { try { Properties jndiEnvironmentProperties = PropsUtil.getProperties( PropsKeys.JNDI_ENVIRONMENT, true); Context context = new InitialContext(jndiEnvironmentProperties); return (DataSource)JNDIUtil.lookup(context, jndiName); } catch (Exception e) { _log.error("Unable to lookup " + jndiName, e); } } if (_log.isDebugEnabled()) { _log.debug("Data source properties:\n"); SortedProperties sortedProperties = new SortedProperties( properties); _log.debug(PropertiesUtil.toString(sortedProperties)); } testDatabaseClass(properties); DataSource dataSource = null; String liferayPoolProvider = PropsValues.JDBC_DEFAULT_LIFERAY_POOL_PROVIDER; if (StringUtil.equalsIgnoreCase(liferayPoolProvider, "c3p0") || StringUtil.equalsIgnoreCase(liferayPoolProvider, "c3po")) { if (_log.isDebugEnabled()) { _log.debug("Initializing C3P0 data source"); } dataSource = initDataSourceC3PO(properties); } else if (StringUtil.equalsIgnoreCase(liferayPoolProvider, "dbcp")) { if (_log.isDebugEnabled()) { _log.debug("Initializing DBCP data source"); } dataSource = initDataSourceDBCP(properties); } else if (StringUtil.equalsIgnoreCase(liferayPoolProvider, "hikaricp")) { if (_log.isDebugEnabled()) { _log.debug("Initializing HikariCP data source"); } dataSource = initDataSourceHikariCP(properties); } else { if (_log.isDebugEnabled()) { _log.debug("Initializing Tomcat data source"); } dataSource = initDataSourceTomcat(properties); } if (_log.isDebugEnabled()) { _log.debug("Created data source " + dataSource.getClass()); } if (PropsValues.RETRY_DATA_SOURCE_MAX_RETRIES > 0) { DBType dbType = DBManagerUtil.getDBType( DialectDetector.getDialect(dataSource)); if (dbType == DBType.SYBASE) { dataSource = new RetryDataSourceWrapper(dataSource); } } return _pacl.getDataSource(dataSource); } @Override public DataSource initDataSource( String driverClassName, String url, String userName, String password, String jndiName) throws Exception { Properties properties = new Properties(); properties.setProperty("driverClassName", driverClassName); properties.setProperty("url", url); properties.setProperty("username", userName); properties.setProperty("password", password); properties.setProperty("jndi.name", jndiName); return initDataSource(properties); } public interface PACL { public DataSource getDataSource(DataSource dataSource); } protected DataSource initDataSourceC3PO(Properties properties) throws Exception { ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); String identityToken = StringUtil.randomString(); comboPooledDataSource.setIdentityToken(identityToken); String connectionPropertiesString = (String)properties.remove( "connectionProperties"); if (connectionPropertiesString != null) { Properties connectionProperties = PropertiesUtil.load( StringUtil.replace( connectionPropertiesString, CharPool.SEMICOLON, CharPool.NEW_LINE)); comboPooledDataSource.setProperties(connectionProperties); } Enumeration<String> enu = (Enumeration<String>)properties.propertyNames(); while (enu.hasMoreElements()) { String key = enu.nextElement(); String value = properties.getProperty(key); // Map org.apache.commons.dbcp.BasicDataSource to C3PO if (StringUtil.equalsIgnoreCase(key, "driverClassName")) { key = "driverClass"; } else if (StringUtil.equalsIgnoreCase(key, "url")) { key = "jdbcUrl"; } else if (StringUtil.equalsIgnoreCase(key, "username")) { key = "user"; } // Ignore Liferay property if (isPropertyLiferay(key)) { continue; } // Ignore DBCP property if (isPropertyDBCP(key)) { continue; } // Ignore HikariCP property if (isPropertyHikariCP(key)) { continue; } // Ignore Tomcat JDBC property if (isPropertyTomcat(key)) { continue; } // Set C3PO property try { BeanUtil.setProperty(comboPooledDataSource, key, value); } catch (Exception e) { if (_log.isWarnEnabled()) { _log.warn( "Property " + key + " is an invalid C3PO property"); } } } registerConnectionPoolMetrics( new C3P0ConnectionPoolMetrics(comboPooledDataSource)); return comboPooledDataSource; } protected DataSource initDataSourceDBCP(Properties properties) throws Exception { DataSource dataSource = BasicDataSourceFactory.createDataSource( properties); registerConnectionPoolMetrics( new DBCPConnectionPoolMetrics((BasicDataSource)dataSource)); return dataSource; } protected DataSource initDataSourceHikariCP(Properties properties) throws Exception { testLiferayPoolProviderClass(_HIKARICP_DATASOURCE_CLASS_NAME); Thread currentThread = Thread.currentThread(); ClassLoader contextClassLoader = currentThread.getContextClassLoader(); Class<?> hikariDataSourceClazz = contextClassLoader.loadClass( _HIKARICP_DATASOURCE_CLASS_NAME); Object hikariDataSource = hikariDataSourceClazz.newInstance(); String connectionPropertiesString = (String)properties.remove( "connectionProperties"); if (connectionPropertiesString != null) { Properties connectionProperties = PropertiesUtil.load( StringUtil.replace( connectionPropertiesString, CharPool.SEMICOLON, CharPool.NEW_LINE)); BeanUtil.setProperty( hikariDataSource, "dataSourceProperties", connectionProperties); } for (Map.Entry<Object, Object> entry : properties.entrySet()) { String key = (String)entry.getKey(); String value = (String)entry.getValue(); // Map org.apache.commons.dbcp.BasicDataSource to Hikari CP if (StringUtil.equalsIgnoreCase(key, "url")) { key = "jdbcUrl"; } // Ignore Liferay property if (isPropertyLiferay(key)) { continue; } // Ignore C3P0 property if (isPropertyC3PO(key)) { continue; } // Ignore DBCP property if (isPropertyDBCP(key)) { continue; } // Ignore Tomcat JDBC property if (isPropertyTomcat(key)) { continue; } // Set HikariCP property try { BeanUtil.setProperty(hikariDataSource, key, value); } catch (Exception e) { if (_log.isWarnEnabled()) { _log.warn( "Property " + key + " is an invalid HikariCP property"); } } } registerConnectionPoolMetrics( new HikariConnectionPoolMetrics(hikariDataSource)); return (DataSource)hikariDataSource; } protected DataSource initDataSourceTomcat(Properties properties) throws Exception { PoolProperties poolProperties = new PoolProperties(); for (Map.Entry<Object, Object> entry : properties.entrySet()) { String key = (String)entry.getKey(); String value = (String)entry.getValue(); // Ignore Liferay property if (isPropertyLiferay(key)) { continue; } // Ignore C3P0 property if (isPropertyC3PO(key)) { continue; } // Ignore HikariCP property if (isPropertyHikariCP(key)) { continue; } // Set Tomcat JDBC property try { BeanUtil.setProperty(poolProperties, key, value); } catch (Exception e) { if (_log.isWarnEnabled()) { _log.warn( "Property " + key + " is an invalid Tomcat JDBC " + "property"); } } } String poolName = StringUtil.randomString(); poolProperties.setName(poolName); org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource(poolProperties); if (poolProperties.isJmxEnabled()) { Registry registry = RegistryUtil.getRegistry(); _serviceTracker = registry.trackServices( MBeanServer.class, new MBeanServerServiceTrackerCustomizer(dataSource, poolName)); _serviceTracker.open(); } registerConnectionPoolMetrics( new TomcatConnectionPoolMetrics(dataSource)); return dataSource; } protected boolean isPropertyC3PO(String key) { if (StringUtil.equalsIgnoreCase(key, "acquireIncrement") || StringUtil.equalsIgnoreCase(key, "acquireRetryAttempts") || StringUtil.equalsIgnoreCase(key, "acquireRetryDelay") || StringUtil.equalsIgnoreCase(key, "connectionCustomizerClassName") || StringUtil.equalsIgnoreCase(key, "idleConnectionTestPeriod") || StringUtil.equalsIgnoreCase(key, "initialPoolSize") || StringUtil.equalsIgnoreCase(key, "maxIdleTime") || StringUtil.equalsIgnoreCase(key, "maxPoolSize") || StringUtil.equalsIgnoreCase(key, "minPoolSize") || StringUtil.equalsIgnoreCase(key, "numHelperThreads") || StringUtil.equalsIgnoreCase(key, "preferredTestQuery")) { return true; } return false; } protected boolean isPropertyDBCP(String key) { if (StringUtil.equalsIgnoreCase(key, "defaultTransactionIsolation") || StringUtil.equalsIgnoreCase(key, "maxActive") || StringUtil.equalsIgnoreCase(key, "minIdle") || StringUtil.equalsIgnoreCase(key, "removeAbandonedTimeout")) { return true; } return false; } protected boolean isPropertyHikariCP(String key) { if (StringUtil.equalsIgnoreCase(key, "autoCommit") || StringUtil.equalsIgnoreCase(key, "connectionTestQuery") || StringUtil.equalsIgnoreCase(key, "connectionTimeout") || StringUtil.equalsIgnoreCase(key, "idleTimeout") || StringUtil.equalsIgnoreCase(key, "initializationFailFast") || StringUtil.equalsIgnoreCase(key, "maximumPoolSize") || StringUtil.equalsIgnoreCase(key, "maxLifetime") || StringUtil.equalsIgnoreCase(key, "minimumIdle") || StringUtil.equalsIgnoreCase(key, "registerMbeans")) { return true; } return false; } protected boolean isPropertyLiferay(String key) { if (StringUtil.equalsIgnoreCase(key, "jndi.name") || StringUtil.equalsIgnoreCase(key, "liferay.pool.provider")) { return true; } return false; } protected boolean isPropertyTomcat(String key) { if (StringUtil.equalsIgnoreCase(key, "fairQueue") || StringUtil.equalsIgnoreCase(key, "initialSize") || StringUtil.equalsIgnoreCase(key, "jdbcInterceptors") || StringUtil.equalsIgnoreCase(key, "jmxEnabled") || StringUtil.equalsIgnoreCase(key, "maxIdle") || StringUtil.equalsIgnoreCase(key, "testWhileIdle") || StringUtil.equalsIgnoreCase(key, "timeBetweenEvictionRunsMillis") || StringUtil.equalsIgnoreCase(key, "useEquals") || StringUtil.equalsIgnoreCase(key, "validationQuery")) { return true; } return false; } protected void registerConnectionPoolMetrics( ConnectionPoolMetrics connectionPoolMetrics) { Registry registry = RegistryUtil.getRegistry(); registry.registerService( ConnectionPoolMetrics.class, connectionPoolMetrics); } protected void testDatabaseClass(Properties properties) throws Exception { String driverClassName = properties.getProperty("driverClassName"); try { Class.forName(driverClassName); } catch (ClassNotFoundException cnfe) { if (!ServerDetector.isJetty() && !ServerDetector.isTomcat()) { throw cnfe; } String url = PropsUtil.get( PropsKeys.SETUP_DATABASE_JAR_URL, new Filter(driverClassName)); String name = PropsUtil.get( PropsKeys.SETUP_DATABASE_JAR_NAME, new Filter(driverClassName)); if (Validator.isNull(url) || Validator.isNull(name)) { throw cnfe; } ClassLoader classLoader = SystemException.class.getClassLoader(); if (!(classLoader instanceof URLClassLoader)) { _log.error( "Unable to install JAR because the system class loader " + "is not an instance of URLClassLoader"); return; } JarUtil.downloadAndInstallJar( new URL(url), PropsValues.LIFERAY_LIB_GLOBAL_DIR, name, (URLClassLoader)classLoader); } } protected void testLiferayPoolProviderClass(String className) throws Exception { try { Class.forName(className); } catch (ClassNotFoundException cnfe) { if (!ServerDetector.isJetty() && !ServerDetector.isTomcat()) { throw cnfe; } String url = PropsUtil.get( PropsKeys.SETUP_LIFERAY_POOL_PROVIDER_JAR_URL, new Filter(PropsValues.JDBC_DEFAULT_LIFERAY_POOL_PROVIDER)); String name = PropsUtil.get( PropsKeys.SETUP_LIFERAY_POOL_PROVIDER_JAR_NAME, new Filter(PropsValues.JDBC_DEFAULT_LIFERAY_POOL_PROVIDER)); if (Validator.isNull(url) || Validator.isNull(name)) { throw cnfe; } ClassLoader classLoader = ClassLoaderUtil.getPortalClassLoader(); if (!(classLoader instanceof URLClassLoader)) { _log.error( "Unable to install JAR because the portal class loader " + "is not an instance of URLClassLoader"); return; } JarUtil.downloadAndInstallJar( new URL(url), PropsValues.LIFERAY_LIB_PORTAL_DIR, name, (URLClassLoader)classLoader); } } private static final String _HIKARICP_DATASOURCE_CLASS_NAME = "com.zaxxer.hikari.HikariDataSource"; private static final String _TOMCAT_JDBC_POOL_OBJECT_NAME_PREFIX = "TomcatJDBCPool:type=ConnectionPool,name="; private static final Log _log = LogFactoryUtil.getLog( DataSourceFactoryImpl.class); private static final PACL _pacl = new NoPACL(); private ServiceTracker<MBeanServer, MBeanServer> _serviceTracker; private static class MBeanServerServiceTrackerCustomizer implements ServiceTrackerCustomizer<MBeanServer, MBeanServer> { public MBeanServerServiceTrackerCustomizer( org.apache.tomcat.jdbc.pool.DataSource dataSource, String poolName) throws MalformedObjectNameException { _dataSource = dataSource; _objectName = new ObjectName( _TOMCAT_JDBC_POOL_OBJECT_NAME_PREFIX + poolName); } @Override public MBeanServer addingService( ServiceReference<MBeanServer> serviceReference) { Registry registry = RegistryUtil.getRegistry(); MBeanServer mBeanServer = registry.getService(serviceReference); try { org.apache.tomcat.jdbc.pool.ConnectionPool jdbcConnectionPool = _dataSource.createPool(); ConnectionPool jmxConnectionPool = jdbcConnectionPool.getJmxPool(); mBeanServer.registerMBean(jmxConnectionPool, _objectName); } catch (Exception e) { _log.error(e, e); } return mBeanServer; } @Override public void modifiedService( ServiceReference<MBeanServer> serviceReference, MBeanServer mBeanServer) { } @Override public void removedService( ServiceReference<MBeanServer> serviceReference, MBeanServer mBeanServer) { Registry registry = RegistryUtil.getRegistry(); registry.ungetService(serviceReference); try { mBeanServer.unregisterMBean(_objectName); } catch (Exception e) { _log.error(e, e); } } private final org.apache.tomcat.jdbc.pool.DataSource _dataSource; private final ObjectName _objectName; } private static class NoPACL implements PACL { @Override public DataSource getDataSource(DataSource dataSource) { return dataSource; } } }