Package org.openbel.framework.core.df

Source Code of org.openbel.framework.core.df.DBConnection

/**
* Copyright (C) 2012-2013 Selventa, Inc.
*
* This file is part of the OpenBEL Framework.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The OpenBEL Framework is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the OpenBEL Framework. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms under LGPL v3:
*
* This license does not authorize you and you are prohibited from using the
* name, trademarks, service marks, logos or similar indicia of Selventa, Inc.,
* or, in the discretion of other licensors or authors of the program, the
* name, trademarks, service marks, logos or similar indicia of such authors or
* licensors, in any marketing or advertising materials relating to your
* distribution of the program or any covered product. This restriction does
* not waive or limit your obligation to keep intact all copyright notices set
* forth in the program as delivered to you.
*
* If you distribute the program in whole or in part, or any modified version
* of the program, and you assume contractual liability to the recipient with
* respect to the program or modified version, then you will indemnify the
* authors and licensors of the program for any liabilities that these
* contractual assumptions directly impose on those licensors and authors.
*/
package org.openbel.framework.core.df;

import static java.lang.System.currentTimeMillis;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import org.openbel.framework.common.DBConnectionFailure;
import org.openbel.framework.common.InvalidArgument;
import org.openbel.framework.common.enums.DatabaseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* DBConnection encapsulates the database connection and which
* {@link DatabaseType} it originated from.
*
* @author Anthony Bargnesi {@code <abargnesi@selventa.com>}
* @author Steve Ungerer {@code sungerer@selventa.com}
*/
public class DBConnection {
    private static final Logger logger =
            LoggerFactory.getLogger(DBConnection.class);

    /**
     * Time (in seconds) to wait for Connections.isValid
     */
    private static final int VALIDITY_TIMEOUT = 10;

    /**
     * Number of times to attempt to re-establish a connection
     * if the current connection is no longer valid.
     */
    private static final int REATTEMPTS = 5;

    /**
     * Time (in milliseconds) between validity checks.
     * Defaults to 600000: 10 minutes
     */
    private static final long VALIDITY_CHECK_INTERVAL = 600000;

    private ThreadLocal<Map<String, PreparedStatement>> threadedPsMap =
            new ThreadLocal<Map<String, PreparedStatement>>() {
                @Override
                protected Map<String, PreparedStatement> initialValue() {
                    return new HashMap<String, PreparedStatement>();
                }
            };

    private ThreadLocal<Map<String, Long>> threadedPsTimeMap =
            new ThreadLocal<Map<String, Long>>() {
                @Override
                protected Map<String, Long> initialValue() {
                    return new HashMap<String, Long>();
                }
            };

    private final String url;
    private final DatabaseType type;
    private final String user;
    private final String pass;
    private Connection connection;
    private long lastChecked;

    /**
     * Creates a DB connection.
     *
     * @param url Database JDBC URL
     * @param c Backing {@link Connection connection}
     * @param t {@link DatabaseType database type}
     * @throws InvalidArgument Thrown if any argument is null
     */
    public DBConnection(final Connection c, final DatabaseType t,
            final String url, final String user, final String pass) {
        if (url == null) throw new InvalidArgument("url", url);
        if (c == null) throw new InvalidArgument("c", c);
        if (t == null) throw new InvalidArgument("t", t);
        this.url = url;
        this.connection = c;
        this.type = t;
        this.user = user;
        this.pass = pass;
        this.lastChecked = currentTimeMillis();
    }

    /**
     * Returns the JDBC URL.
     *
     * @return String
     */
    public String getURL() {
        return url;
    }

    /**
     * Returns the {@link Connection connection}.
     *
     * @return The database connection
     * @throws SQLException Thrown if the connection is no longer valid and
     * cannot be re-established
     */
    public synchronized Connection getConnection() throws SQLException {
        if ((currentTimeMillis() - lastChecked)
        > VALIDITY_CHECK_INTERVAL) {
            logger.trace("Checking connection validity");
            // check the validity of the connection
            boolean valid = false;
            int t = 0;

            while (!valid && t < REATTEMPTS) {
                try {
                    if (connection == null || connection.isClosed()
                            || !connection.isValid(VALIDITY_TIMEOUT)) {
                        logger.info(
                                "Connection to {} is closed or invalid; attempting to re-establish",
                                url);
                        // remove any cached prepared statements for this connection
                        threadedPsMap.remove();

                        // attempt re-establishment
                        this.connection = DriverManager
                                .getConnection(url, user, pass);
                        logger.info("Re-established connection to {}", url);
                        // do not set valid to true - check this new connection
                    } else {
                        valid = true;
                    }
                } catch (SQLException e) {
                    logger.info("Failed to re-establish connection to " +
                            "{}, try {}/{}; Reason: {}",
                            new Object[] { url, t, REATTEMPTS, e.getMessage() });
                    // set connection to null to prevent the same error from continuously
                    // occurring when checking isClosed() or isValid()
                    this.connection = null;
                }
                t++;
            }
            if (!valid) {
                logger.error(
                        "Failed to re-establish a connection to {} after {} attempts",
                        url, REATTEMPTS);
                throw new SQLException(
                        "Unable to re-establish a valid connection",
                        new DBConnectionFailure(url,
                                "Unable to re-establish a valid connection"));
            }
            this.lastChecked = currentTimeMillis();
        }
        return connection;
    }

    /**
     * Retrieve a {@link PreparedStatement} by a reference key.
     *
     * @param sql
     * @param key
     * @param autoGeneratedKeys
     * @return
     * @throws SQLException
     */
    PreparedStatement getPreparedStatement(String sql, String key,
            int autoGeneratedKeys)
            throws SQLException {
        PreparedStatement ps = null;

        if (threadedPsMap.get().containsKey(key)) {
            ps = threadedPsMap.get().get(key);
            ps = checkValidity(ps, key);
        }

        if (ps == null) { // not in map or ps is null in map
            ps = getConnection().prepareStatement(sql, autoGeneratedKeys);
            threadedPsMap.get().put(key, ps);
            threadedPsTimeMap.get().put(key, currentTimeMillis());
        }

        return ps;
    }

    /**
     * Retrieve a {@link PreparedStatement} by a reference key.
     *
     * @param sql
     * @param key
     * @param columnNames
     * @return
     * @throws SQLException
     */
    PreparedStatement getPreparedStatement(String sql, String key,
            String[] columnNames) throws SQLException {
        PreparedStatement ps = null;

        if (threadedPsMap.get().containsKey(key)) {
            ps = threadedPsMap.get().get(key);
            ps = checkValidity(ps, key);
        }

        if (ps == null) { // not in map or ps is null in map
            ps = getConnection().prepareStatement(sql, columnNames);
            threadedPsMap.get().put(key, ps);
            threadedPsTimeMap.get().put(key, currentTimeMillis());
        }

        return ps;
    }

    /**
     * Retrieve a {@link PreparedStatement} by a reference key.
     *
     * @param sql {@link String} SQL statement
     * @param key {@link String} key as a means of identifying the {@code sql}
     * parameter
     * @param rsType Result set type (i.e.,
     * {@link java.sql.ResultSet#TYPE_SCROLL_INSENSITIVE},
     * {@link java.sql.ResultSet#TYPE_SCROLL_SENSITIVE}, or
     * {@link java.sql.ResultSet#TYPE_FORWARD_ONLY})
     * @param rsConcurrency Result set constants (i.e.,
     * {@link java.sql.ResultSet#CONCUR_READ_ONLY},
     * {@link java.sql.ResultSet#CONCUR_UPDATABLE})
     * @return {@link PreparedStatement}
     * @throws SQLException Thrown if a database access error occurs, the
     * connection is closed, or the given parameters are not {@code ResultSet}
     * constants
     */
    PreparedStatement getPreparedStatement(String sql, String key,
            int rsType, int rsConcurrency) throws SQLException {
        PreparedStatement ps = null;

        if (threadedPsMap.get().containsKey(key)) {
            ps = threadedPsMap.get().get(key);
            ps = checkValidity(ps, key);
        }

        if (ps == null) { // not in map or ps is null in map
            ps = getConnection().prepareStatement(sql, rsType, rsConcurrency);
            threadedPsMap.get().put(key, ps);
            threadedPsTimeMap.get().put(key, currentTimeMillis());
        }

        return ps;
    }

    /**
     * Validate the {@link Connection} backing a {@link PreparedStatement}.
     *
     * @param ps
     * @param key
     * @return the provided {@link PreparedStatement} if valid,
     *         <code>null</code> if the statement's {@link Connection} was
     *         invalid.
     */
    private PreparedStatement checkValidity(PreparedStatement ps, String key) {
        long currentTime = currentTimeMillis();
        Long lastChecked = threadedPsTimeMap.get().get(key);
        if (lastChecked == null) {
            // if never checked, setting to 0 ensures check will occur
            // (as long as we're not less than 10min past 1 Jan 1970)
            lastChecked = 0L;
        }
        if (currentTime - lastChecked.longValue() > VALIDITY_CHECK_INTERVAL) {
            try {
                Connection psConn = ps.getConnection();
                if (psConn == null || psConn.isClosed()
                        || !psConn.isValid(VALIDITY_TIMEOUT)) {
                    logger.info("Connection for PreparedStatement is closed or "
                            + "invalid; removing from cache");
                    // remove the prepared statement w/ invalid connection
                    threadedPsMap.get().remove(key);
                    ps = null;
                }
            } catch (SQLException e) {
                logger.info("Failed to check Prepared Statement validity.", e);
                ps = null;
            }
        }
        return ps;
    }

    /**
     * Closes all cached {@link PreparedStatement} objects and then
     * closes the database {@link Connection}.
     *
     * <p>
     * This must be called before the instance goes out of scope.
     * </p>
     */
    public synchronized void close() {
        for (PreparedStatement ps : threadedPsMap.get().values()) {
            if (null != ps) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    // swallowed
                }
            }
        }
        try {
            if (null != connection) {
                this.connection.close();
            }
        } catch (SQLException e) {
            // swallowed
        }
    }

    /**
     * Returns the {@link DatabaseType type}.
     *
     * @return The database type
     */
    public DatabaseType getType() {
        return type;
    }

    /**
     * Returns {@code true} if the database type is {@link DatabaseType#DERBY},
     * {@code false} otherwise.
     *
     * @return boolean
     */
    public boolean isDerby() {
        return type == DatabaseType.DERBY;
    }

    /**
     * Returns {@code true} if the database type is {@link DatabaseType#MYSQL},
     * {@code false} otherwise.
     *
     * @return boolean
     */
    public boolean isMysql() {
        return type == DatabaseType.MYSQL;
    }

    /**
     * Returns {@code true} if the database type is {@link DatabaseType#ORACLE},
     * {@code false} otherwise.
     *
     * @return boolean
     */
    public boolean isOracle() {
        return type == DatabaseType.ORACLE;
    }

    /**
     * Returns {@code true} if the database type is {@link DatabaseType#POSTGRESQL},
     * {@code false} otherwise.
     *
     * @return boolean
     */
    public boolean isPostgresql() {
        return type == DatabaseType.POSTGRESQL;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("DBConnection [\n");

        builder.append("\turl=");
        builder.append(url);
        builder.append(",\n");

        builder.append("\ttype=");
        builder.append(type);
        builder.append(",\n");

        builder.append("\tconnection=");
        builder.append(connection);
        builder.append("\n");

        builder.append("]");
        return builder.toString();
    }
}
TOP

Related Classes of org.openbel.framework.core.df.DBConnection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.