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