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