org.ofbiz.core.entity.transaction.DBCPConnectionFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.ofbiz.core.entity.transaction.DBCPConnectionFactory.java

Source

/*
 * $Id: DBCPConnectionFactory.java,v 1.1 2005/04/01 05:58:03 sfarquhar Exp $
 *
 * <p>Copyright (c) 2001 The Open For Business Project - www.ofbiz.org
 *
 * <p>Permission is hereby granted, free of charge, to any person obtaining a
 *  copy of this software and associated documentation files (the "Software"),
 *  to deal in the Software without restriction, including without limitation
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *  and/or sell copies of the Software, and to permit persons to whom the
 *  Software is furnished to do so, subject to the following conditions:
 *
 * <p>The above copyright notice and this permission notice shall be included
 *  in all copies or substantial portions of the Software.
 *
 * <p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 *  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 *  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
 *  OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
 *  THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package org.ofbiz.core.entity.transaction;

import com.atlassian.util.concurrent.CopyOnWriteMap;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.apache.log4j.Logger;
import org.ofbiz.core.entity.GenericEntityException;
import org.ofbiz.core.entity.config.ConnectionPoolInfo;
import org.ofbiz.core.entity.config.JdbcDatasourceInfo;
import org.ofbiz.core.entity.jdbc.interceptors.connection.ConnectionTracker;
import org.ofbiz.core.util.Debug;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Callable;

import static org.ofbiz.core.entity.util.PropertyUtils.copyOf;
import static org.ofbiz.core.util.UtilValidate.isNotEmpty;

/**
 * DBCP ConnectionFactory - central source for JDBC connections from DBCP
 *
 * This is currently non transactional as DBCP doesn't seem to support transactional datasources yet (DBCP 1.0).
 *
 * @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
 * @version 1.0
 *          Created on Dec 18, 2001, 5:03 PM
 */
public class DBCPConnectionFactory {
    private static final Logger log = Logger.getLogger(DBCPConnectionFactory.class);

    protected static final Map<String, BasicDataSource> dsCache = CopyOnWriteMap.newHashMap();
    protected static final Map<String, ConnectionTracker> trackerCache = CopyOnWriteMap.newHashMap();

    private static final String PROP_JMX = "jmx";
    private static final String DBCP_PROPERTIES = "dbcp.properties";
    private static final String PROP_MBEANNAME = "mbeanName";
    public static final String VALIDATION_QUERY = "select 1";

    public static Connection getConnection(String helperName, JdbcDatasourceInfo jdbcDatasource)
            throws SQLException, GenericEntityException {
        // the DataSource implementation
        BasicDataSource dataSource = dsCache.get(helperName);
        if (dataSource != null) {
            return trackConnection(helperName, dataSource);
        }

        try {
            synchronized (DBCPConnectionFactory.class) {
                //try again inside the synch just in case someone when through while we were waiting
                dataSource = dsCache.get(helperName);
                if (dataSource != null) {
                    return trackConnection(helperName, dataSource);
                }

                // Sets the connection properties. At least 'user' and 'password' should be set.
                final Properties info = jdbcDatasource.getConnectionProperties() != null
                        ? copyOf(jdbcDatasource.getConnectionProperties())
                        : new Properties();

                // Use the BasicDataSourceFactory so we can use all the DBCP properties as per http://commons.apache.org/dbcp/configuration.html
                dataSource = createDataSource(jdbcDatasource);
                dataSource.setConnectionProperties(toString(info));
                // set connection pool attributes
                final ConnectionPoolInfo poolInfo = jdbcDatasource.getConnectionPoolInfo();
                initConnectionPoolSettings(dataSource, poolInfo);

                dataSource.setLogWriter(Debug.getPrintWriter());

                dsCache.put(helperName, dataSource);
                trackerCache.put(helperName, new ConnectionTracker(poolInfo));

                return trackConnection(helperName, dataSource);
            }
        } catch (Exception e) {
            Debug.logError(e, "Error getting datasource via DBCP: " + jdbcDatasource);
        } catch (AbstractMethodError err) {
            if (checkIfProblemMayBeCausedByIsValidMethod(dataSource, err)) {

                unregisterDatasourceFromJmx(dataSource);

                log.warn(
                        "*********************************************** IMPORTANT  ***********************************************");
                log.warn(
                        "                                                                                                          ");
                log.warn(
                        "  We found that you may experience problems with database connectivity because your database driver       ");
                log.warn(
                        "  is not fully JDBC 4 compatible. As a workaround of this problem a validation query was added to your    ");
                log.warn(
                        "  runtime configuration. Please add a line with validation query:                                       \n");
                log.warn(
                        "                          <validation-query>select 1</validation-query>                                 \n");
                log.warn(
                        "  to your JIRA_HOME/dbconfig.xml or update your database driver to version which fully supports JDBC 4.   ");
                log.warn(
                        "  More information about this problem can be found here: https://jira.atlassian.com/browse/JRA-59768      ");
                log.warn(
                        "                                                                                                          ");
                log.warn(
                        "**********************************************************************************************************");

                return getConnection(helperName, getUpdatedJdbcDatasource(jdbcDatasource));
            }
        }

        return null;
    }

    private static void unregisterDatasourceFromJmx(BasicDataSource dataSource) {
        final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        try {
            mbs.unregisterMBean(ObjectName.getInstance(dataSource.getJmxName()));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @VisibleForTesting
    static JdbcDatasourceInfo getUpdatedJdbcDatasource(JdbcDatasourceInfo jdbcDatasource) {

        final ConnectionPoolInfo cpi = jdbcDatasource.getConnectionPoolInfo();
        final ConnectionPoolInfo newConnectionPoolInfo = new ConnectionPoolInfo(cpi.getMaxSize(), cpi.getMinSize(),
                cpi.getMaxWait(), cpi.getSleepTime(), cpi.getLifeTime(), cpi.getDeadLockMaxWait(),
                cpi.getDeadLockRetryWait(), VALIDATION_QUERY, cpi.getMinEvictableTimeMillis(),
                cpi.getTimeBetweenEvictionRunsMillis());

        return new JdbcDatasourceInfo(jdbcDatasource.getUri(), jdbcDatasource.getDriverClassName(),
                jdbcDatasource.getUsername(), jdbcDatasource.getPassword(), jdbcDatasource.getIsolationLevel(),
                jdbcDatasource.getConnectionProperties(), newConnectionPoolInfo);
    }

    @VisibleForTesting
    static boolean checkIfProblemMayBeCausedByIsValidMethod(final BasicDataSource dataSource,
            final AbstractMethodError error) {
        final String validationQuery = dataSource.getValidationQuery();
        if (validationQuery == null || validationQuery.isEmpty()) {
            final List<StackTraceElement> stackTraceElements = Lists.newArrayList(error.getStackTrace());
            return stackTraceElements.stream()
                    .anyMatch(stackTraceElement -> stackTraceElement.getMethodName().contains("isValid"));
        }
        return false;
    }

    private static void initConnectionPoolSettings(final BasicDataSource dataSource,
            final ConnectionPoolInfo poolInfo) {
        if (poolInfo == null) {
            return;
        }

        dataSource.setMaxTotal(poolInfo.getMaxSize());
        dataSource.setMinIdle(poolInfo.getMinSize());
        dataSource.setMaxIdle(poolInfo.getMaxIdle());
        dataSource.setMaxWaitMillis(poolInfo.getMaxWait());
        dataSource.setDefaultCatalog(poolInfo.getDefaultCatalog());

        if (poolInfo.getInitialSize() != null) {
            dataSource.setInitialSize(poolInfo.getInitialSize());
        }

        if (isNotEmpty(poolInfo.getValidationQuery())) {
            // testOnBorrow defaults to true when this is set, but can still be forced to false
            dataSource.setTestOnBorrow(poolInfo.getTestOnBorrow() == null || poolInfo.getTestOnBorrow());
            if (poolInfo.getTestOnReturn() != null) {
                dataSource.setTestOnReturn(poolInfo.getTestOnReturn());
            }
            if (poolInfo.getTestWhileIdle() != null) {
                dataSource.setTestWhileIdle(poolInfo.getTestWhileIdle());
            }
            dataSource.setValidationQuery(poolInfo.getValidationQuery());
            if (poolInfo.getValidationQueryTimeout() != null) {
                dataSource.setValidationQueryTimeout(poolInfo.getValidationQueryTimeout());
            }
        }

        if (poolInfo.getPoolPreparedStatements() != null) {
            dataSource.setPoolPreparedStatements(poolInfo.getPoolPreparedStatements());
            if (dataSource.isPoolPreparedStatements() && poolInfo.getMaxOpenPreparedStatements() != null) {
                dataSource.setMaxOpenPreparedStatements(poolInfo.getMaxOpenPreparedStatements());
            }
        }

        if (poolInfo.getRemoveAbandonedOnBorrow() != null) {
            dataSource.setRemoveAbandonedOnBorrow(poolInfo.getRemoveAbandonedOnBorrow());
        }

        if (poolInfo.getRemoveAbandonedOnMaintanance() != null) {
            dataSource.setRemoveAbandonedOnMaintenance(poolInfo.getRemoveAbandonedOnMaintanance());
        }

        if (poolInfo.getRemoveAbandonedTimeout() != null) {
            dataSource.setRemoveAbandonedTimeout(poolInfo.getRemoveAbandonedTimeout());
        }

        if (poolInfo.getMinEvictableTimeMillis() != null) {
            dataSource.setMinEvictableIdleTimeMillis(poolInfo.getMinEvictableTimeMillis());
        }

        if (poolInfo.getNumTestsPerEvictionRun() != null) {
            dataSource.setNumTestsPerEvictionRun(poolInfo.getNumTestsPerEvictionRun());
        }

        if (poolInfo.getTimeBetweenEvictionRunsMillis() != null) {
            dataSource.setTimeBetweenEvictionRunsMillis(poolInfo.getTimeBetweenEvictionRunsMillis());
        }

    }

    private static BasicDataSource createDataSource(JdbcDatasourceInfo jdbcDatasource) throws Exception {
        final Properties dbcpProperties = loadDbcpProperties();

        final BasicDataSource dataSource = BasicDataSourceFactory.createDataSource(dbcpProperties);
        dataSource.setDriverClassLoader(Thread.currentThread().getContextClassLoader());
        dataSource.setDriverClassName(jdbcDatasource.getDriverClassName());
        dataSource.setUrl(jdbcDatasource.getUri());
        dataSource.setUsername(jdbcDatasource.getUsername());
        dataSource.setPassword(jdbcDatasource.getPassword());

        if (isNotEmpty(jdbcDatasource.getIsolationLevel())) {
            dataSource.setDefaultTransactionIsolation(
                    TransactionIsolations.fromString(jdbcDatasource.getIsolationLevel()));
        }

        if (dbcpProperties.containsKey(PROP_JMX) && Boolean.valueOf(dbcpProperties.getProperty(PROP_JMX))) {
            dataSource.setJmxName(
                    ObjectName.getInstance(dbcpProperties.getProperty(PROP_MBEANNAME)).getCanonicalName());
        }

        return dataSource;
    }

    private static String toString(Properties properties) {
        List<String> props = new ArrayList<String>();
        for (String key : properties.stringPropertyNames()) {
            props.add(key + "=" + properties.getProperty(key));
        }

        return Joiner.on(';').skipNulls().join(props);
    }

    private static Properties loadDbcpProperties() {
        Properties dbcpProperties = new Properties();

        // load everything in c3p0.properties
        InputStream fileProperties = DBCPConnectionFactory.class.getResourceAsStream("/" + DBCP_PROPERTIES);
        if (fileProperties != null) {
            try {
                dbcpProperties.load(fileProperties);
            } catch (IOException e) {
                log.error("Error loading " + DBCP_PROPERTIES, e);
            }
        }

        // also look at all dbcp.* system properties
        Properties systemProperties = System.getProperties();
        for (String systemProp : systemProperties.stringPropertyNames()) {
            final String prefix = "dbcp.";
            if (systemProp.startsWith(prefix)) {
                dbcpProperties.setProperty(systemProp.substring(prefix.length()), System.getProperty(systemProp));
            }
        }

        return dbcpProperties;
    }

    private static Connection trackConnection(final String helperName, final DataSource dataSource) {
        ConnectionTracker connectionTracker = trackerCache.get(helperName);
        return connectionTracker.trackConnection(helperName, new Callable<Connection>() {
            public Connection call() throws Exception {
                return dataSource.getConnection();
            }
        });
    }

    /**
     * Shuts down and removes a datasource, if it exists
     *
     * @param helperName The name of the datasource to remove
     */
    public synchronized static void removeDatasource(String helperName) {
        BasicDataSource dataSource = dsCache.get(helperName);
        if (dataSource != null) {
            try {
                dataSource.close();
                unregisterMBeanIfPresent();
            } catch (Exception e) {
                Debug.logError(e, "Error closing connection pool in DBCP");
            }

            dsCache.remove(helperName);
        }
        trackerCache.remove(helperName);
    }

    private static void unregisterMBeanIfPresent() {
        // We want to make sure mBean will be unregistered
        final Properties dbcpProperties = loadDbcpProperties();
        if (dbcpProperties.containsKey(PROP_JMX) && Boolean.valueOf(dbcpProperties.getProperty(PROP_JMX))) {
            final String mBeanName = dbcpProperties.getProperty(PROP_MBEANNAME);
            try {
                final ObjectName objectName = ObjectName.getInstance(dbcpProperties.getProperty(PROP_MBEANNAME));
                final MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
                if (platformMBeanServer.isRegistered(objectName)) {
                    platformMBeanServer.unregisterMBean(objectName);
                }
            } catch (Exception e) {
                log.error("Exception un-registering MBean data source " + mBeanName, e);
            }
        }
    }
}