001    /**
002     * Copyright (c) 2000-2013 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.jndi.JNDIUtil;
021    import com.liferay.portal.kernel.log.Log;
022    import com.liferay.portal.kernel.log.LogFactoryUtil;
023    import com.liferay.portal.kernel.security.pacl.DoPrivileged;
024    import com.liferay.portal.kernel.util.FileUtil;
025    import com.liferay.portal.kernel.util.HttpUtil;
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.FileImpl;
033    import com.liferay.portal.util.HttpImpl;
034    import com.liferay.portal.util.JarUtil;
035    import com.liferay.portal.util.PropsUtil;
036    import com.liferay.portal.util.PropsValues;
037    
038    import com.mchange.v2.c3p0.ComboPooledDataSource;
039    
040    import java.lang.management.ManagementFactory;
041    
042    import java.util.Enumeration;
043    import java.util.Map;
044    import java.util.Properties;
045    
046    import javax.management.MBeanServer;
047    import javax.management.ObjectName;
048    
049    import javax.naming.Context;
050    import javax.naming.InitialContext;
051    
052    import javax.sql.DataSource;
053    
054    import jodd.bean.BeanUtil;
055    
056    import org.apache.commons.dbcp.BasicDataSourceFactory;
057    import org.apache.tomcat.jdbc.pool.PoolProperties;
058    import org.apache.tomcat.jdbc.pool.jmx.ConnectionPool;
059    
060    /**
061     * @author Brian Wing Shun Chan
062     * @author Shuyang Zhou
063     */
064    @DoPrivileged
065    public class DataSourceFactoryImpl implements DataSourceFactory {
066    
067            @Override
068            public void destroyDataSource(DataSource dataSource) throws Exception {
069                    while (dataSource instanceof DataSourceWrapper) {
070                            DataSourceWrapper dataSourceWrapper = (DataSourceWrapper)dataSource;
071    
072                            dataSource = dataSourceWrapper.getWrappedDataSource();
073                    }
074    
075                    if (dataSource instanceof ComboPooledDataSource) {
076                            ComboPooledDataSource comboPooledDataSource =
077                                    (ComboPooledDataSource)dataSource;
078    
079                            comboPooledDataSource.close();
080                    }
081                    else if (dataSource instanceof org.apache.tomcat.jdbc.pool.DataSource) {
082                            org.apache.tomcat.jdbc.pool.DataSource tomcatDataSource =
083                                    (org.apache.tomcat.jdbc.pool.DataSource)dataSource;
084    
085                            tomcatDataSource.close();
086                    }
087            }
088    
089            @Override
090            public DataSource initDataSource(Properties properties) throws Exception {
091                    Properties defaultProperties = PropsUtil.getProperties(
092                            "jdbc.default.", true);
093    
094                    PropertiesUtil.merge(defaultProperties, properties);
095    
096                    properties = defaultProperties;
097    
098                    String jndiName = properties.getProperty("jndi.name");
099    
100                    if (Validator.isNotNull(jndiName)) {
101                            try {
102                                    Properties jndiEnvironmentProperties = PropsUtil.getProperties(
103                                            PropsKeys.JNDI_ENVIRONMENT, true);
104    
105                                    Context context = new InitialContext(jndiEnvironmentProperties);
106    
107                                    return (DataSource)JNDIUtil.lookup(context, jndiName);
108                            }
109                            catch (Exception e) {
110                                    _log.error("Unable to lookup " + jndiName, e);
111                            }
112                    }
113    
114                    if (_log.isDebugEnabled()) {
115                            _log.debug("Data source properties:\n");
116    
117                            SortedProperties sortedProperties = new SortedProperties(
118                                    properties);
119    
120                            _log.debug(PropertiesUtil.toString(sortedProperties));
121                    }
122    
123                    testClassForName(properties);
124    
125                    DataSource dataSource = null;
126    
127                    String liferayPoolProvider =
128                            PropsValues.JDBC_DEFAULT_LIFERAY_POOL_PROVIDER;
129    
130                    if (StringUtil.equalsIgnoreCase(liferayPoolProvider, "c3p0") ||
131                            StringUtil.equalsIgnoreCase(liferayPoolProvider, "c3po")) {
132    
133                            if (_log.isDebugEnabled()) {
134                                    _log.debug("Initializing C3P0 data source");
135                            }
136    
137                            dataSource = initDataSourceC3PO(properties);
138                    }
139                    else if (StringUtil.equalsIgnoreCase(liferayPoolProvider, "dbcp")) {
140                            if (_log.isDebugEnabled()) {
141                                    _log.debug("Initializing DBCP data source");
142                            }
143    
144                            dataSource = initDataSourceDBCP(properties);
145                    }
146                    else {
147                            if (_log.isDebugEnabled()) {
148                                    _log.debug("Initializing Tomcat data source");
149                            }
150    
151                            dataSource = initDataSourceTomcat(properties);
152                    }
153    
154                    if (_log.isDebugEnabled()) {
155                            _log.debug("Created data source " + dataSource.getClass());
156                    }
157    
158                    return _pacl.getDataSource(dataSource);
159            }
160    
161            @Override
162            public DataSource initDataSource(
163                            String driverClassName, String url, String userName,
164                            String password, String jndiName)
165                    throws Exception {
166    
167                    Properties properties = new Properties();
168    
169                    properties.setProperty("driverClassName", driverClassName);
170                    properties.setProperty("url", url);
171                    properties.setProperty("username", userName);
172                    properties.setProperty("password", password);
173                    properties.setProperty("jndi.name", jndiName);
174    
175                    return initDataSource(properties);
176            }
177    
178            public static interface PACL {
179    
180                    public DataSource getDataSource(DataSource dataSource);
181    
182            }
183    
184            protected DataSource initDataSourceC3PO(Properties properties)
185                    throws Exception {
186    
187                    ComboPooledDataSource comboPooledDataSource =
188                            new ComboPooledDataSource();
189    
190                    String identityToken = StringUtil.randomString();
191    
192                    comboPooledDataSource.setIdentityToken(identityToken);
193    
194                    Enumeration<String> enu =
195                            (Enumeration<String>)properties.propertyNames();
196    
197                    while (enu.hasMoreElements()) {
198                            String key = enu.nextElement();
199                            String value = properties.getProperty(key);
200    
201                            // Map org.apache.commons.dbcp.BasicDataSource to C3PO
202    
203                            if (StringUtil.equalsIgnoreCase(key, "driverClassName")) {
204                                    key = "driverClass";
205                            }
206                            else if (StringUtil.equalsIgnoreCase(key, "url")) {
207                                    key = "jdbcUrl";
208                            }
209                            else if (StringUtil.equalsIgnoreCase(key, "username")) {
210                                    key = "user";
211                            }
212    
213                            // Ignore Liferay properties
214    
215                            if (isPropertyLiferay(key)) {
216                                    continue;
217                            }
218    
219                            // Ignore DBCP properties
220    
221                            if (isPropertyDBCP(key)) {
222                                    continue;
223                            }
224    
225                            // Ignore Tomcat
226    
227                            if (isPropertyTomcat(key)) {
228                                    continue;
229                            }
230    
231                            try {
232                                    BeanUtil.setProperty(comboPooledDataSource, key, value);
233                            }
234                            catch (Exception e) {
235                                    if (_log.isWarnEnabled()) {
236                                            _log.warn(
237                                                    "Property " + key + " is not a valid C3PO property");
238                                    }
239                            }
240                    }
241    
242                    return comboPooledDataSource;
243            }
244    
245            protected DataSource initDataSourceDBCP(Properties properties)
246                    throws Exception {
247    
248                    return BasicDataSourceFactory.createDataSource(properties);
249            }
250    
251            protected DataSource initDataSourceTomcat(Properties properties)
252                    throws Exception {
253    
254                    PoolProperties poolProperties = new PoolProperties();
255    
256                    for (Map.Entry<Object, Object> entry : properties.entrySet()) {
257                            String key = (String)entry.getKey();
258                            String value = (String)entry.getValue();
259    
260                            // Ignore Liferay properties
261    
262                            if (isPropertyLiferay(key)) {
263                                    continue;
264                            }
265    
266                            // Ignore C3P0 properties
267    
268                            if (isPropertyC3PO(key)) {
269                                    continue;
270                            }
271    
272                            try {
273                                    BeanUtil.setProperty(poolProperties, key, value);
274                            }
275                            catch (Exception e) {
276                                    if (_log.isWarnEnabled()) {
277                                            _log.warn(
278                                                    "Property " + key + " is not a valid Tomcat JDBC " +
279                                                            "Connection Pool property");
280                                    }
281                            }
282                    }
283    
284                    String poolName = StringUtil.randomString();
285    
286                    poolProperties.setName(poolName);
287    
288                    org.apache.tomcat.jdbc.pool.DataSource dataSource =
289                            new org.apache.tomcat.jdbc.pool.DataSource(poolProperties);
290    
291                    if (poolProperties.isJmxEnabled()) {
292                            org.apache.tomcat.jdbc.pool.ConnectionPool jdbcConnectionPool =
293                                    dataSource.createPool();
294    
295                            ConnectionPool jmxConnectionPool = jdbcConnectionPool.getJmxPool();
296    
297                            MBeanServer mBeanServer =
298                                    ManagementFactory.getPlatformMBeanServer();
299    
300                            ObjectName objectName = new ObjectName(
301                                    _TOMCAT_JDBC_POOL_OBJECT_NAME_PREFIX + poolName);
302    
303                            mBeanServer.registerMBean(jmxConnectionPool, objectName);
304                    }
305    
306                    return dataSource;
307            }
308    
309            protected boolean isPropertyC3PO(String key) {
310                    if (StringUtil.equalsIgnoreCase(key, "acquireIncrement") ||
311                            StringUtil.equalsIgnoreCase(key, "acquireRetryAttempts") ||
312                            StringUtil.equalsIgnoreCase(key, "acquireRetryDelay") ||
313                            StringUtil.equalsIgnoreCase(key, "connectionCustomizerClassName") ||
314                            StringUtil.equalsIgnoreCase(key, "idleConnectionTestPeriod") ||
315                            StringUtil.equalsIgnoreCase(key, "initialPoolSize") ||
316                            StringUtil.equalsIgnoreCase(key, "maxIdleTime") ||
317                            StringUtil.equalsIgnoreCase(key, "maxPoolSize") ||
318                            StringUtil.equalsIgnoreCase(key, "minPoolSize") ||
319                            StringUtil.equalsIgnoreCase(key, "numHelperThreads") ||
320                            StringUtil.equalsIgnoreCase(key, "preferredTestQuery")) {
321    
322                            return true;
323                    }
324    
325                    return false;
326            }
327    
328            protected boolean isPropertyDBCP(String key) {
329                    if (StringUtil.equalsIgnoreCase(key, "defaultTransactionIsolation") ||
330                            StringUtil.equalsIgnoreCase(key, "maxActive") ||
331                            StringUtil.equalsIgnoreCase(key, "minIdle") ||
332                            StringUtil.equalsIgnoreCase(key, "removeAbandonedTimeout")) {
333    
334                            return true;
335                    }
336    
337                    return false;
338            }
339    
340            protected boolean isPropertyLiferay(String key) {
341                    if (StringUtil.equalsIgnoreCase(key, "jndi.name") ||
342                            StringUtil.equalsIgnoreCase(key, "liferay.pool.provider")) {
343    
344                            return true;
345                    }
346    
347                    return false;
348            }
349    
350            protected boolean isPropertyTomcat(String key) {
351                    if (StringUtil.equalsIgnoreCase(key, "fairQueue") ||
352                            StringUtil.equalsIgnoreCase(key, "jdbcInterceptors") ||
353                            StringUtil.equalsIgnoreCase(key, "jmxEnabled") ||
354                            StringUtil.equalsIgnoreCase(key, "timeBetweenEvictionRunsMillis") ||
355                            StringUtil.equalsIgnoreCase(key, "useEquals")) {
356    
357                            return true;
358                    }
359    
360                    return false;
361            }
362    
363            protected void testClassForName(Properties properties) throws Exception {
364                    String driverClassName = properties.getProperty("driverClassName");
365    
366                    try {
367                            Class.forName(driverClassName);
368                    }
369                    catch (ClassNotFoundException cnfe) {
370                            if (!ServerDetector.isGeronimo() && !ServerDetector.isJetty() &&
371                                    !ServerDetector.isTomcat()) {
372    
373                                    throw cnfe;
374                            }
375    
376                            String url = PropsUtil.get(
377                                    PropsKeys.SETUP_DATABASE_JAR_URL, new Filter(driverClassName));
378                            String name = PropsUtil.get(
379                                    PropsKeys.SETUP_DATABASE_JAR_NAME, new Filter(driverClassName));
380    
381                            if (Validator.isNull(url) || Validator.isNull(name)) {
382                                    throw cnfe;
383                            }
384    
385                            if (HttpUtil.getHttp() == null) {
386                                    HttpUtil httpUtil = new HttpUtil();
387    
388                                    httpUtil.setHttp(new HttpImpl());
389                            }
390    
391                            if (FileUtil.getFile() == null) {
392                                    FileUtil fileUtil = new FileUtil();
393    
394                                    fileUtil.setFile(new FileImpl());
395                            }
396    
397                            JarUtil.downloadAndInstallJar(true, url, name, null);
398                    }
399            }
400    
401            private static final String _TOMCAT_JDBC_POOL_OBJECT_NAME_PREFIX =
402                    "TomcatJDBCPool:type=ConnectionPool,name=";
403    
404            private static Log _log = LogFactoryUtil.getLog(
405                    DataSourceFactoryImpl.class);
406    
407            private static PACL _pacl = new NoPACL();
408    
409            private static class NoPACL implements PACL {
410    
411                    @Override
412                    public DataSource getDataSource(DataSource dataSource) {
413                            return dataSource;
414                    }
415    
416            }
417    
418    }