001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.dao.jdbc;
016    
017    import com.liferay.portal.dao.jdbc.pool.metrics.C3P0ConnectionPoolMetrics;
018    import com.liferay.portal.dao.jdbc.pool.metrics.DBCPConnectionPoolMetrics;
019    import com.liferay.portal.dao.jdbc.pool.metrics.HikariConnectionPoolMetrics;
020    import com.liferay.portal.dao.jdbc.pool.metrics.TomcatConnectionPoolMetrics;
021    import com.liferay.portal.dao.jdbc.util.DataSourceWrapper;
022    import com.liferay.portal.dao.jdbc.util.RetryDataSourceWrapper;
023    import com.liferay.portal.kernel.configuration.Filter;
024    import com.liferay.portal.kernel.dao.db.DBManagerUtil;
025    import com.liferay.portal.kernel.dao.db.DBType;
026    import com.liferay.portal.kernel.dao.jdbc.DataSourceFactory;
027    import com.liferay.portal.kernel.dao.jdbc.pool.metrics.ConnectionPoolMetrics;
028    import com.liferay.portal.kernel.exception.SystemException;
029    import com.liferay.portal.kernel.jndi.JNDIUtil;
030    import com.liferay.portal.kernel.log.Log;
031    import com.liferay.portal.kernel.log.LogFactoryUtil;
032    import com.liferay.portal.kernel.security.pacl.DoPrivileged;
033    import com.liferay.portal.kernel.util.ClassLoaderUtil;
034    import com.liferay.portal.kernel.util.PropertiesUtil;
035    import com.liferay.portal.kernel.util.PropsKeys;
036    import com.liferay.portal.kernel.util.ServerDetector;
037    import com.liferay.portal.kernel.util.SortedProperties;
038    import com.liferay.portal.kernel.util.StringUtil;
039    import com.liferay.portal.kernel.util.Validator;
040    import com.liferay.portal.spring.hibernate.DialectDetector;
041    import com.liferay.portal.util.JarUtil;
042    import com.liferay.portal.util.PropsUtil;
043    import com.liferay.portal.util.PropsValues;
044    import com.liferay.registry.Registry;
045    import com.liferay.registry.RegistryUtil;
046    import com.liferay.registry.ServiceReference;
047    import com.liferay.registry.ServiceTracker;
048    import com.liferay.registry.ServiceTrackerCustomizer;
049    
050    import com.mchange.v2.c3p0.ComboPooledDataSource;
051    
052    import java.net.URL;
053    import java.net.URLClassLoader;
054    
055    import java.util.Enumeration;
056    import java.util.Map;
057    import java.util.Properties;
058    
059    import javax.management.MBeanServer;
060    import javax.management.MalformedObjectNameException;
061    import javax.management.ObjectName;
062    
063    import javax.naming.Context;
064    import javax.naming.InitialContext;
065    
066    import javax.sql.DataSource;
067    
068    import jodd.bean.BeanUtil;
069    
070    import org.apache.commons.dbcp.BasicDataSource;
071    import org.apache.commons.dbcp.BasicDataSourceFactory;
072    import org.apache.tomcat.jdbc.pool.PoolProperties;
073    import org.apache.tomcat.jdbc.pool.jmx.ConnectionPool;
074    
075    /**
076     * @author Brian Wing Shun Chan
077     * @author Shuyang Zhou
078     */
079    @DoPrivileged
080    public class DataSourceFactoryImpl implements DataSourceFactory {
081    
082            @Override
083            public void destroyDataSource(DataSource dataSource) throws Exception {
084                    while (dataSource instanceof DataSourceWrapper) {
085                            DataSourceWrapper dataSourceWrapper = (DataSourceWrapper)dataSource;
086    
087                            dataSource = dataSourceWrapper.getWrappedDataSource();
088                    }
089    
090                    if (dataSource instanceof ComboPooledDataSource) {
091                            ComboPooledDataSource comboPooledDataSource =
092                                    (ComboPooledDataSource)dataSource;
093    
094                            comboPooledDataSource.close();
095                    }
096                    else if (dataSource instanceof org.apache.tomcat.jdbc.pool.DataSource) {
097                            org.apache.tomcat.jdbc.pool.DataSource tomcatDataSource =
098                                    (org.apache.tomcat.jdbc.pool.DataSource)dataSource;
099    
100                            if (_serviceTracker != null) {
101                                    _serviceTracker.close();
102                            }
103    
104                            tomcatDataSource.close();
105                    }
106            }
107    
108            @Override
109            public DataSource initDataSource(Properties properties) throws Exception {
110                    Properties defaultProperties = PropsUtil.getProperties(
111                            "jdbc.default.", true);
112    
113                    PropertiesUtil.merge(defaultProperties, properties);
114    
115                    properties = defaultProperties;
116    
117                    String jndiName = properties.getProperty("jndi.name");
118    
119                    if (Validator.isNotNull(jndiName)) {
120                            try {
121                                    Properties jndiEnvironmentProperties = PropsUtil.getProperties(
122                                            PropsKeys.JNDI_ENVIRONMENT, true);
123    
124                                    Context context = new InitialContext(jndiEnvironmentProperties);
125    
126                                    return (DataSource)JNDIUtil.lookup(context, jndiName);
127                            }
128                            catch (Exception e) {
129                                    _log.error("Unable to lookup " + jndiName, e);
130                            }
131                    }
132    
133                    if (_log.isDebugEnabled()) {
134                            _log.debug("Data source properties:\n");
135    
136                            SortedProperties sortedProperties = new SortedProperties(
137                                    properties);
138    
139                            _log.debug(PropertiesUtil.toString(sortedProperties));
140                    }
141    
142                    testDatabaseClass(properties);
143    
144                    DataSource dataSource = null;
145    
146                    String liferayPoolProvider =
147                            PropsValues.JDBC_DEFAULT_LIFERAY_POOL_PROVIDER;
148    
149                    if (StringUtil.equalsIgnoreCase(liferayPoolProvider, "c3p0") ||
150                            StringUtil.equalsIgnoreCase(liferayPoolProvider, "c3po")) {
151    
152                            if (_log.isDebugEnabled()) {
153                                    _log.debug("Initializing C3P0 data source");
154                            }
155    
156                            dataSource = initDataSourceC3PO(properties);
157                    }
158                    else if (StringUtil.equalsIgnoreCase(liferayPoolProvider, "dbcp")) {
159                            if (_log.isDebugEnabled()) {
160                                    _log.debug("Initializing DBCP data source");
161                            }
162    
163                            dataSource = initDataSourceDBCP(properties);
164                    }
165                    else if (StringUtil.equalsIgnoreCase(liferayPoolProvider, "hikaricp")) {
166                            if (_log.isDebugEnabled()) {
167                                    _log.debug("Initializing HikariCP data source");
168                            }
169    
170                            dataSource = initDataSourceHikariCP(properties);
171                    }
172                    else {
173                            if (_log.isDebugEnabled()) {
174                                    _log.debug("Initializing Tomcat data source");
175                            }
176    
177                            dataSource = initDataSourceTomcat(properties);
178                    }
179    
180                    if (_log.isDebugEnabled()) {
181                            _log.debug("Created data source " + dataSource.getClass());
182                    }
183    
184                    if (PropsValues.RETRY_DATA_SOURCE_MAX_RETRIES > 0) {
185                            DBType dbType = DBManagerUtil.getDBType(
186                                    DialectDetector.getDialect(dataSource));
187    
188                            if (dbType == DBType.SYBASE) {
189                                    dataSource = new RetryDataSourceWrapper(dataSource);
190                            }
191                    }
192    
193                    return _pacl.getDataSource(dataSource);
194            }
195    
196            @Override
197            public DataSource initDataSource(
198                            String driverClassName, String url, String userName,
199                            String password, String jndiName)
200                    throws Exception {
201    
202                    Properties properties = new Properties();
203    
204                    properties.setProperty("driverClassName", driverClassName);
205                    properties.setProperty("url", url);
206                    properties.setProperty("username", userName);
207                    properties.setProperty("password", password);
208                    properties.setProperty("jndi.name", jndiName);
209    
210                    return initDataSource(properties);
211            }
212    
213            public interface PACL {
214    
215                    public DataSource getDataSource(DataSource dataSource);
216    
217            }
218    
219            protected DataSource initDataSourceC3PO(Properties properties)
220                    throws Exception {
221    
222                    ComboPooledDataSource comboPooledDataSource =
223                            new ComboPooledDataSource();
224    
225                    String identityToken = StringUtil.randomString();
226    
227                    comboPooledDataSource.setIdentityToken(identityToken);
228    
229                    Enumeration<String> enu =
230                            (Enumeration<String>)properties.propertyNames();
231    
232                    while (enu.hasMoreElements()) {
233                            String key = enu.nextElement();
234                            String value = properties.getProperty(key);
235    
236                            // Map org.apache.commons.dbcp.BasicDataSource to C3PO
237    
238                            if (StringUtil.equalsIgnoreCase(key, "driverClassName")) {
239                                    key = "driverClass";
240                            }
241                            else if (StringUtil.equalsIgnoreCase(key, "url")) {
242                                    key = "jdbcUrl";
243                            }
244                            else if (StringUtil.equalsIgnoreCase(key, "username")) {
245                                    key = "user";
246                            }
247    
248                            // Ignore Liferay property
249    
250                            if (isPropertyLiferay(key)) {
251                                    continue;
252                            }
253    
254                            // Ignore DBCP property
255    
256                            if (isPropertyDBCP(key)) {
257                                    continue;
258                            }
259    
260                            // Ignore HikariCP property
261    
262                            if (isPropertyHikariCP(key)) {
263                                    continue;
264                            }
265    
266                            // Ignore Tomcat JDBC property
267    
268                            if (isPropertyTomcat(key)) {
269                                    continue;
270                            }
271    
272                            // Set C3PO property
273    
274                            try {
275                                    BeanUtil.setProperty(comboPooledDataSource, key, value);
276                            }
277                            catch (Exception e) {
278                                    if (_log.isWarnEnabled()) {
279                                            _log.warn(
280                                                    "Property " + key + " is an invalid C3PO property");
281                                    }
282                            }
283                    }
284    
285                    registerConnectionPoolMetrics(
286                            new C3P0ConnectionPoolMetrics(comboPooledDataSource));
287    
288                    return comboPooledDataSource;
289            }
290    
291            protected DataSource initDataSourceDBCP(Properties properties)
292                    throws Exception {
293    
294                    DataSource dataSource = BasicDataSourceFactory.createDataSource(
295                            properties);
296    
297                    registerConnectionPoolMetrics(
298                            new DBCPConnectionPoolMetrics((BasicDataSource)dataSource));
299    
300                    return dataSource;
301            }
302    
303            protected DataSource initDataSourceHikariCP(Properties properties)
304                    throws Exception {
305    
306                    testLiferayPoolProviderClass(_HIKARICP_DATASOURCE_CLASS_NAME);
307    
308                    Thread currentThread = Thread.currentThread();
309    
310                    ClassLoader contextClassLoader = currentThread.getContextClassLoader();
311    
312                    Class<?> hikariDataSourceClazz = contextClassLoader.loadClass(
313                            _HIKARICP_DATASOURCE_CLASS_NAME);
314    
315                    Object hikariDataSource = hikariDataSourceClazz.newInstance();
316    
317                    for (Map.Entry<Object, Object> entry : properties.entrySet()) {
318                            String key = (String)entry.getKey();
319                            String value = (String)entry.getValue();
320    
321                            // Map org.apache.commons.dbcp.BasicDataSource to Hikari CP
322    
323                            if (StringUtil.equalsIgnoreCase(key, "url")) {
324                                    key = "jdbcUrl";
325                            }
326    
327                            // Ignore Liferay property
328    
329                            if (isPropertyLiferay(key)) {
330                                    continue;
331                            }
332    
333                            // Ignore C3P0 property
334    
335                            if (isPropertyC3PO(key)) {
336                                    continue;
337                            }
338    
339                            // Ignore DBCP property
340    
341                            if (isPropertyDBCP(key)) {
342                                    continue;
343                            }
344    
345                            // Ignore Tomcat JDBC property
346    
347                            if (isPropertyTomcat(key)) {
348                                    continue;
349                            }
350    
351                            // Set HikariCP property
352    
353                            try {
354                                    BeanUtil.setProperty(hikariDataSource, key, value);
355                            }
356                            catch (Exception e) {
357                                    if (_log.isWarnEnabled()) {
358                                            _log.warn(
359                                                    "Property " + key + " is an invalid HikariCP property");
360                                    }
361                            }
362                    }
363    
364                    registerConnectionPoolMetrics(
365                            new HikariConnectionPoolMetrics(hikariDataSource));
366    
367                    return (DataSource)hikariDataSource;
368            }
369    
370            protected DataSource initDataSourceTomcat(Properties properties)
371                    throws Exception {
372    
373                    PoolProperties poolProperties = new PoolProperties();
374    
375                    for (Map.Entry<Object, Object> entry : properties.entrySet()) {
376                            String key = (String)entry.getKey();
377                            String value = (String)entry.getValue();
378    
379                            // Ignore Liferay property
380    
381                            if (isPropertyLiferay(key)) {
382                                    continue;
383                            }
384    
385                            // Ignore C3P0 property
386    
387                            if (isPropertyC3PO(key)) {
388                                    continue;
389                            }
390    
391                            // Ignore HikariCP property
392    
393                            if (isPropertyHikariCP(key)) {
394                                    continue;
395                            }
396    
397                            // Set Tomcat JDBC property
398    
399                            try {
400                                    BeanUtil.setProperty(poolProperties, key, value);
401                            }
402                            catch (Exception e) {
403                                    if (_log.isWarnEnabled()) {
404                                            _log.warn(
405                                                    "Property " + key + " is an invalid Tomcat JDBC " +
406                                                            "property");
407                                    }
408                            }
409                    }
410    
411                    String poolName = StringUtil.randomString();
412    
413                    poolProperties.setName(poolName);
414    
415                    org.apache.tomcat.jdbc.pool.DataSource dataSource =
416                            new org.apache.tomcat.jdbc.pool.DataSource(poolProperties);
417    
418                    if (poolProperties.isJmxEnabled()) {
419                            Registry registry = RegistryUtil.getRegistry();
420    
421                            _serviceTracker = registry.trackServices(
422                                    MBeanServer.class,
423                                    new MBeanServerServiceTrackerCustomizer(dataSource, poolName));
424    
425                            _serviceTracker.open();
426                    }
427    
428                    registerConnectionPoolMetrics(
429                            new TomcatConnectionPoolMetrics(dataSource));
430    
431                    return dataSource;
432            }
433    
434            protected boolean isPropertyC3PO(String key) {
435                    if (StringUtil.equalsIgnoreCase(key, "acquireIncrement") ||
436                            StringUtil.equalsIgnoreCase(key, "acquireRetryAttempts") ||
437                            StringUtil.equalsIgnoreCase(key, "acquireRetryDelay") ||
438                            StringUtil.equalsIgnoreCase(key, "connectionCustomizerClassName") ||
439                            StringUtil.equalsIgnoreCase(key, "idleConnectionTestPeriod") ||
440                            StringUtil.equalsIgnoreCase(key, "initialPoolSize") ||
441                            StringUtil.equalsIgnoreCase(key, "maxIdleTime") ||
442                            StringUtil.equalsIgnoreCase(key, "maxPoolSize") ||
443                            StringUtil.equalsIgnoreCase(key, "minPoolSize") ||
444                            StringUtil.equalsIgnoreCase(key, "numHelperThreads") ||
445                            StringUtil.equalsIgnoreCase(key, "preferredTestQuery")) {
446    
447                            return true;
448                    }
449    
450                    return false;
451            }
452    
453            protected boolean isPropertyDBCP(String key) {
454                    if (StringUtil.equalsIgnoreCase(key, "defaultTransactionIsolation") ||
455                            StringUtil.equalsIgnoreCase(key, "maxActive") ||
456                            StringUtil.equalsIgnoreCase(key, "minIdle") ||
457                            StringUtil.equalsIgnoreCase(key, "removeAbandonedTimeout")) {
458    
459                            return true;
460                    }
461    
462                    return false;
463            }
464    
465            protected boolean isPropertyHikariCP(String key) {
466                    if (StringUtil.equalsIgnoreCase(key, "autoCommit") ||
467                            StringUtil.equalsIgnoreCase(key, "connectionTestQuery") ||
468                            StringUtil.equalsIgnoreCase(key, "connectionTimeout") ||
469                            StringUtil.equalsIgnoreCase(key, "idleTimeout") ||
470                            StringUtil.equalsIgnoreCase(key, "initializationFailFast") ||
471                            StringUtil.equalsIgnoreCase(key, "maximumPoolSize") ||
472                            StringUtil.equalsIgnoreCase(key, "maxLifetime") ||
473                            StringUtil.equalsIgnoreCase(key, "minimumIdle") ||
474                            StringUtil.equalsIgnoreCase(key, "registerMbeans")) {
475    
476                            return true;
477                    }
478    
479                    return false;
480            }
481    
482            protected boolean isPropertyLiferay(String key) {
483                    if (StringUtil.equalsIgnoreCase(key, "jndi.name") ||
484                            StringUtil.equalsIgnoreCase(key, "liferay.pool.provider")) {
485    
486                            return true;
487                    }
488    
489                    return false;
490            }
491    
492            protected boolean isPropertyTomcat(String key) {
493                    if (StringUtil.equalsIgnoreCase(key, "fairQueue") ||
494                            StringUtil.equalsIgnoreCase(key, "jdbcInterceptors") ||
495                            StringUtil.equalsIgnoreCase(key, "jmxEnabled") ||
496                            StringUtil.equalsIgnoreCase(key, "timeBetweenEvictionRunsMillis") ||
497                            StringUtil.equalsIgnoreCase(key, "useEquals")) {
498    
499                            return true;
500                    }
501    
502                    return false;
503            }
504    
505            protected void registerConnectionPoolMetrics(
506                    ConnectionPoolMetrics connectionPoolMetrics) {
507    
508                    Registry registry = RegistryUtil.getRegistry();
509    
510                    registry.registerService(
511                            ConnectionPoolMetrics.class, connectionPoolMetrics);
512            }
513    
514            protected void testDatabaseClass(Properties properties) throws Exception {
515                    String driverClassName = properties.getProperty("driverClassName");
516    
517                    try {
518                            Class.forName(driverClassName);
519                    }
520                    catch (ClassNotFoundException cnfe) {
521                            if (!ServerDetector.isJetty() && !ServerDetector.isTomcat()) {
522                                    throw cnfe;
523                            }
524    
525                            String url = PropsUtil.get(
526                                    PropsKeys.SETUP_DATABASE_JAR_URL, new Filter(driverClassName));
527                            String name = PropsUtil.get(
528                                    PropsKeys.SETUP_DATABASE_JAR_NAME, new Filter(driverClassName));
529    
530                            if (Validator.isNull(url) || Validator.isNull(name)) {
531                                    throw cnfe;
532                            }
533    
534                            ClassLoader classLoader = SystemException.class.getClassLoader();
535    
536                            if (!(classLoader instanceof URLClassLoader)) {
537                                    _log.error(
538                                            "Unable to install JAR because the system class loader " +
539                                                    "is not an instance of URLClassLoader");
540    
541                                    return;
542                            }
543    
544                            JarUtil.downloadAndInstallJar(
545                                    new URL(url), PropsValues.LIFERAY_LIB_GLOBAL_DIR, name,
546                                    (URLClassLoader)classLoader);
547                    }
548            }
549    
550            protected void testLiferayPoolProviderClass(String className)
551                    throws Exception {
552    
553                    try {
554                            Class.forName(className);
555                    }
556                    catch (ClassNotFoundException cnfe) {
557                            if (!ServerDetector.isJetty() && !ServerDetector.isTomcat()) {
558                                    throw cnfe;
559                            }
560    
561                            String url = PropsUtil.get(
562                                    PropsKeys.SETUP_LIFERAY_POOL_PROVIDER_JAR_URL,
563                                    new Filter(PropsValues.JDBC_DEFAULT_LIFERAY_POOL_PROVIDER));
564                            String name = PropsUtil.get(
565                                    PropsKeys.SETUP_LIFERAY_POOL_PROVIDER_JAR_NAME,
566                                    new Filter(PropsValues.JDBC_DEFAULT_LIFERAY_POOL_PROVIDER));
567    
568                            if (Validator.isNull(url) || Validator.isNull(name)) {
569                                    throw cnfe;
570                            }
571    
572                            ClassLoader classLoader = ClassLoaderUtil.getPortalClassLoader();
573    
574                            if (!(classLoader instanceof URLClassLoader)) {
575                                    _log.error(
576                                            "Unable to install JAR because the portal class loader " +
577                                                    "is not an instance of URLClassLoader");
578    
579                                    return;
580                            }
581    
582                            JarUtil.downloadAndInstallJar(
583                                    new URL(url), PropsValues.LIFERAY_LIB_PORTAL_DIR, name,
584                                    (URLClassLoader)classLoader);
585                    }
586            }
587    
588            private static final String _HIKARICP_DATASOURCE_CLASS_NAME =
589                    "com.zaxxer.hikari.HikariDataSource";
590    
591            private static final String _TOMCAT_JDBC_POOL_OBJECT_NAME_PREFIX =
592                    "TomcatJDBCPool:type=ConnectionPool,name=";
593    
594            private static final Log _log = LogFactoryUtil.getLog(
595                    DataSourceFactoryImpl.class);
596    
597            private static final PACL _pacl = new NoPACL();
598    
599            private ServiceTracker <MBeanServer, MBeanServer> _serviceTracker;
600    
601            private static class MBeanServerServiceTrackerCustomizer
602                    implements ServiceTrackerCustomizer<MBeanServer, MBeanServer> {
603    
604                    public MBeanServerServiceTrackerCustomizer(
605                                    org.apache.tomcat.jdbc.pool.DataSource dataSource,
606                                    String poolName)
607                            throws MalformedObjectNameException {
608    
609                            _dataSource = dataSource;
610                            _objectName = new ObjectName(
611                                    _TOMCAT_JDBC_POOL_OBJECT_NAME_PREFIX + poolName);
612                    }
613    
614                    @Override
615                    public MBeanServer addingService(
616                            ServiceReference<MBeanServer> serviceReference) {
617    
618                            Registry registry = RegistryUtil.getRegistry();
619    
620                            MBeanServer mBeanServer = registry.getService(serviceReference);
621    
622                            try {
623                                    org.apache.tomcat.jdbc.pool.ConnectionPool jdbcConnectionPool =
624                                            _dataSource.createPool();
625    
626                                    ConnectionPool jmxConnectionPool =
627                                            jdbcConnectionPool.getJmxPool();
628    
629                                    mBeanServer.registerMBean(jmxConnectionPool, _objectName);
630                            }
631                            catch (Exception e) {
632                                    _log.error(e, e);
633                            }
634    
635                            return mBeanServer;
636                    }
637    
638                    @Override
639                    public void modifiedService(
640                            ServiceReference<MBeanServer> serviceReference,
641                            MBeanServer mBeanServer) {
642                    }
643    
644                    @Override
645                    public void removedService(
646                            ServiceReference<MBeanServer> serviceReference,
647                            MBeanServer mBeanServer) {
648    
649                            Registry registry = RegistryUtil.getRegistry();
650    
651                            registry.ungetService(serviceReference);
652    
653                            try {
654                                    mBeanServer.unregisterMBean(_objectName);
655                            }
656                            catch (Exception e) {
657                                    _log.error(e, e);
658                            }
659                    }
660    
661                    private final org.apache.tomcat.jdbc.pool.DataSource _dataSource;
662                    private final ObjectName _objectName;
663    
664            }
665    
666            private static class NoPACL implements PACL {
667    
668                    @Override
669                    public DataSource getDataSource(DataSource dataSource) {
670                            return dataSource;
671                    }
672    
673            }
674    
675    }