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                            else if (StringUtil.equalsIgnoreCase(
299                                                    key, "hikariConnectionCustomizerClassName")) {
300    
301                                    key = "connectionCustomizerClassName";
302                            }
303    
304                            // Ignore Liferay property
305    
306                            if (isPropertyLiferay(key)) {
307                                    continue;
308                            }
309    
310                            // Ignore C3P0 property
311    
312                            if (isPropertyC3PO(key)) {
313                                    continue;
314                            }
315    
316                            // Ignore DBCP property
317    
318                            if (isPropertyDBCP(key)) {
319                                    continue;
320                            }
321    
322                            // Ignore Tomcat JDBC property
323    
324                            if (isPropertyTomcat(key)) {
325                                    continue;
326                            }
327    
328                            // Set HikariCP property
329    
330                            try {
331                                    BeanUtil.setProperty(hikariDataSource, key, value);
332                            }
333                            catch (Exception e) {
334                                    if (_log.isWarnEnabled()) {
335                                            _log.warn(
336                                                    "Property " + key + " is an invalid HikariCP property");
337                                    }
338                            }
339                    }
340    
341                    return (DataSource)hikariDataSource;
342            }
343    
344            protected DataSource initDataSourceTomcat(Properties properties)
345                    throws Exception {
346    
347                    PoolProperties poolProperties = new PoolProperties();
348    
349                    for (Map.Entry<Object, Object> entry : properties.entrySet()) {
350                            String key = (String)entry.getKey();
351                            String value = (String)entry.getValue();
352    
353                            // Ignore Liferay property
354    
355                            if (isPropertyLiferay(key)) {
356                                    continue;
357                            }
358    
359                            // Ignore C3P0 property
360    
361                            if (isPropertyC3PO(key)) {
362                                    continue;
363                            }
364    
365                            // Ignore HikariCP property
366    
367                            if (isPropertyHikariCP(key)) {
368                                    continue;
369                            }
370    
371                            // Set Tomcat JDBC property
372    
373                            try {
374                                    BeanUtil.setProperty(poolProperties, key, value);
375                            }
376                            catch (Exception e) {
377                                    if (_log.isWarnEnabled()) {
378                                            _log.warn(
379                                                    "Property " + key + " is an invalid Tomcat JDBC " +
380                                                            "property");
381                                    }
382                            }
383                    }
384    
385                    String poolName = StringUtil.randomString();
386    
387                    poolProperties.setName(poolName);
388    
389                    org.apache.tomcat.jdbc.pool.DataSource dataSource =
390                            new org.apache.tomcat.jdbc.pool.DataSource(poolProperties);
391    
392                    if (poolProperties.isJmxEnabled()) {
393                            Registry registry = RegistryUtil.getRegistry();
394    
395                            _serviceTracker = registry.trackServices(
396                                    MBeanServer.class,
397                                    new MBeanServerServiceTrackerCustomizer(dataSource, poolName));
398    
399                            _serviceTracker.open();
400                    }
401    
402                    return dataSource;
403            }
404    
405            protected boolean isPropertyC3PO(String key) {
406                    if (StringUtil.equalsIgnoreCase(key, "acquireIncrement") ||
407                            StringUtil.equalsIgnoreCase(key, "acquireRetryAttempts") ||
408                            StringUtil.equalsIgnoreCase(key, "acquireRetryDelay") ||
409                            StringUtil.equalsIgnoreCase(key, "connectionCustomizerClassName") ||
410                            StringUtil.equalsIgnoreCase(key, "idleConnectionTestPeriod") ||
411                            StringUtil.equalsIgnoreCase(key, "maxIdleTime") ||
412                            StringUtil.equalsIgnoreCase(key, "maxPoolSize") ||
413                            StringUtil.equalsIgnoreCase(key, "minPoolSize") ||
414                            StringUtil.equalsIgnoreCase(key, "numHelperThreads") ||
415                            StringUtil.equalsIgnoreCase(key, "preferredTestQuery")) {
416    
417                            return true;
418                    }
419                    else {
420                            return false;
421                    }
422            }
423    
424            protected boolean isPropertyDBCP(String key) {
425                    if (StringUtil.equalsIgnoreCase(key, "defaultTransactionIsolation") ||
426                            StringUtil.equalsIgnoreCase(key, "maxActive") ||
427                            StringUtil.equalsIgnoreCase(key, "minIdle") ||
428                            StringUtil.equalsIgnoreCase(key, "removeAbandonedTimeout")) {
429    
430                            return true;
431                    }
432                    else {
433                            return false;
434                    }
435            }
436    
437            protected boolean isPropertyHikariCP(String key) {
438                    if (StringUtil.equalsIgnoreCase(key, "autoCommit") ||
439                            StringUtil.equalsIgnoreCase(key, "connectionTimeout") ||
440                            StringUtil.equalsIgnoreCase(
441                                    key, "hikariConnectionCustomizerClassName") ||
442                            StringUtil.equalsIgnoreCase(key, "idleTimeout") ||
443                            StringUtil.equalsIgnoreCase(key, "maximumPoolSize") ||
444                            StringUtil.equalsIgnoreCase(key, "maxLifetime") ||
445                            StringUtil.equalsIgnoreCase(key, "minimumIdle") ||
446                            StringUtil.equalsIgnoreCase(key, "registerMbeans")) {
447    
448                            return true;
449                    }
450                    else {
451                            return false;
452                    }
453            }
454    
455            protected boolean isPropertyLiferay(String key) {
456                    if (StringUtil.equalsIgnoreCase(key, "jndi.name") ||
457                            StringUtil.equalsIgnoreCase(key, "liferay.pool.provider")) {
458    
459                            return true;
460                    }
461                    else {
462                            return false;
463                    }
464            }
465    
466            protected boolean isPropertyTomcat(String key) {
467                    if (StringUtil.equalsIgnoreCase(key, "fairQueue") ||
468                            StringUtil.equalsIgnoreCase(key, "jdbcInterceptors") ||
469                            StringUtil.equalsIgnoreCase(key, "jmxEnabled") ||
470                            StringUtil.equalsIgnoreCase(key, "timeBetweenEvictionRunsMillis") ||
471                            StringUtil.equalsIgnoreCase(key, "useEquals")) {
472    
473                            return true;
474                    }
475                    else {
476                            return false;
477                    }
478            }
479    
480            protected void testDatabaseClass(Properties properties) throws Exception {
481                    String driverClassName = properties.getProperty("driverClassName");
482    
483                    try {
484                            Class.forName(driverClassName);
485                    }
486                    catch (ClassNotFoundException cnfe) {
487                            if (!ServerDetector.isJetty() && !ServerDetector.isTomcat()) {
488                                    throw cnfe;
489                            }
490    
491                            String url = PropsUtil.get(
492                                    PropsKeys.SETUP_DATABASE_JAR_URL, new Filter(driverClassName));
493                            String name = PropsUtil.get(
494                                    PropsKeys.SETUP_DATABASE_JAR_NAME, new Filter(driverClassName));
495    
496                            if (Validator.isNull(url) || Validator.isNull(name)) {
497                                    throw cnfe;
498                            }
499    
500                            ClassLoader classLoader = SystemException.class.getClassLoader();
501    
502                            if (!(classLoader instanceof URLClassLoader)) {
503                                    _log.error(
504                                            "Unable to install JAR because the system class loader " +
505                                                    "is not an instance of URLClassLoader");
506    
507                                    return;
508                            }
509    
510                            JarUtil.downloadAndInstallJar(
511                                    new URL(url), PropsValues.LIFERAY_LIB_GLOBAL_DIR, name,
512                                    (URLClassLoader)classLoader);
513                    }
514            }
515    
516            protected void testLiferayPoolProviderClass(String className)
517                    throws Exception {
518    
519                    try {
520                            Class.forName(className);
521                    }
522                    catch (ClassNotFoundException cnfe) {
523                            if (!ServerDetector.isJetty() && !ServerDetector.isTomcat()) {
524                                    throw cnfe;
525                            }
526    
527                            String url = PropsUtil.get(
528                                    PropsKeys.SETUP_LIFERAY_POOL_PROVIDER_JAR_URL,
529                                    new Filter(PropsValues.JDBC_DEFAULT_LIFERAY_POOL_PROVIDER));
530                            String name = PropsUtil.get(
531                                    PropsKeys.SETUP_LIFERAY_POOL_PROVIDER_JAR_NAME,
532                                    new Filter(PropsValues.JDBC_DEFAULT_LIFERAY_POOL_PROVIDER));
533    
534                            if (Validator.isNull(url) || Validator.isNull(name)) {
535                                    throw cnfe;
536                            }
537    
538                            ClassLoader classLoader = ClassLoaderUtil.getPortalClassLoader();
539    
540                            if (!(classLoader instanceof URLClassLoader)) {
541                                    _log.error(
542                                            "Unable to install JAR because the portal class loader " +
543                                                    "is not an instance of URLClassLoader");
544    
545                                    return;
546                            }
547    
548                            JarUtil.downloadAndInstallJar(
549                                    new URL(url), PropsValues.LIFERAY_LIB_PORTAL_DIR, name,
550                                    (URLClassLoader)classLoader);
551                    }
552            }
553    
554            private static final String _HIKARICP_DATASOURCE_CLASS_NAME =
555                    "com.zaxxer.hikari.HikariDataSource";
556    
557            private static final String _TOMCAT_JDBC_POOL_OBJECT_NAME_PREFIX =
558                    "TomcatJDBCPool:type=ConnectionPool,name=";
559    
560            private static final Log _log = LogFactoryUtil.getLog(
561                    DataSourceFactoryImpl.class);
562    
563            private static final PACL _pacl = new NoPACL();
564    
565            private ServiceTracker <MBeanServer, MBeanServer> _serviceTracker;
566    
567            private static class NoPACL implements PACL {
568    
569                    @Override
570                    public DataSource getDataSource(DataSource dataSource) {
571                            return dataSource;
572                    }
573    
574            }
575    
576            private class MBeanServerServiceTrackerCustomizer
577                    implements ServiceTrackerCustomizer<MBeanServer, MBeanServer> {
578    
579                    public MBeanServerServiceTrackerCustomizer(
580                                    org.apache.tomcat.jdbc.pool.DataSource dataSource,
581                                    String poolName)
582                            throws MalformedObjectNameException {
583    
584                            _dataSource = dataSource;
585                            _objectName = new ObjectName(
586                                    _TOMCAT_JDBC_POOL_OBJECT_NAME_PREFIX + poolName);
587                    }
588    
589                    @Override
590                    public MBeanServer addingService(
591                            ServiceReference<MBeanServer> serviceReference) {
592    
593                            Registry registry = RegistryUtil.getRegistry();
594    
595                            MBeanServer mBeanServer = registry.getService(serviceReference);
596    
597                            try {
598                                    org.apache.tomcat.jdbc.pool.ConnectionPool jdbcConnectionPool =
599                                            _dataSource.createPool();
600    
601                                    ConnectionPool jmxConnectionPool =
602                                            jdbcConnectionPool.getJmxPool();
603    
604                                    mBeanServer.registerMBean(jmxConnectionPool, _objectName);
605                            }
606                            catch (Exception e) {
607                                    _log.error(e, e);
608                            }
609    
610                            return mBeanServer;
611                    }
612    
613                    @Override
614                    public void modifiedService(
615                            ServiceReference<MBeanServer> serviceReference,
616                            MBeanServer mBeanServer) {
617                    }
618    
619                    @Override
620                    public void removedService(
621                            ServiceReference<MBeanServer> serviceReference,
622                            MBeanServer mBeanServer) {
623    
624                            Registry registry = RegistryUtil.getRegistry();
625    
626                            registry.ungetService(serviceReference);
627    
628                            try {
629                                    mBeanServer.unregisterMBean(_objectName);
630                            }
631                            catch (Exception e) {
632                                    _log.error(e, e);
633                            }
634                    }
635    
636                    private final org.apache.tomcat.jdbc.pool.DataSource _dataSource;
637                    private final ObjectName _objectName;
638    
639            }
640    
641    }