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