ConnectionFactory.java

/*
 * This file is part of Waarp Project (named also Waarp or GG).
 *
 *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
 *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
 * individual contributors.
 *
 *  All Waarp Project is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Waarp 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 * Waarp . If not, see <http://www.gnu.org/licenses/>.
 */

package org.waarp.common.database;

import org.apache.commons.pool.impl.GenericObjectPool;
import org.waarp.common.database.properties.DbProperties;
import org.waarp.common.database.properties.H2Properties;
import org.waarp.common.database.properties.MariaDBProperties;
import org.waarp.common.database.properties.MySQLProperties;
import org.waarp.common.database.properties.OracleProperties;
import org.waarp.common.database.properties.PostgreSQLProperties;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;
import org.waarp.common.utility.SystemPropertyUtil;
import org.waarp.common.utility.Version;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * A singleton wrapper of Datasource to get database connection object
 */
public class ConnectionFactory {

  /**
   * Internal Logger
   */
  private static final WaarpLogger logger =
      WaarpLoggerFactory.getLogger(ConnectionFactory.class);

  /**
   * The singleton instance
   */
  private static ConnectionFactory instance;

  /**
   * DbModel
   */
  private final DbProperties properties;

  /**
   * The connection url to be passed to the JDBC driver to establish a
   * connection.
   */
  private final String server;

  /**
   * The connection username to be passed to the JDBC driver to establish a
   * connection.
   */
  private final String user;

  /**
   * The connection password to be passed to the JDBC driver to establish a
   * connection.
   */
  private final String password;

  private static final boolean AUTOCOMMIT = true;

  private static final boolean READONLY = false;

  private static final int MAX_CONNECTIONS_DEFAULT = 10;
  private static final int MAX_IDLE_DEFAULT = 2;

  private int maxConnections = MAX_CONNECTIONS_DEFAULT;
  private int maxIdle = MAX_IDLE_DEFAULT;

  /**
   * The datasource for connection pooling
   */
  private final WaarpBasicDataSource ds;

  /**
   * @param server the connection url of the database
   *
   * @return the DbProperties Object associated with the requested URL
   *
   * @throws UnsupportedOperationException if the requested database
   *     is not supported
   */
  protected static DbProperties propertiesFor(final String server)
      throws UnsupportedOperationException {
    if (server.contains(H2Properties.getProtocolID())) {
      return new H2Properties();
    } else if (server.contains(MariaDBProperties.getProtocolID())) {
      return new MariaDBProperties();
    } else if (server.contains(MySQLProperties.getProtocolID())) {
      return new MySQLProperties();
    } else if (server.contains(OracleProperties.getProtocolID())) {
      return new OracleProperties();
    } else if (server.contains(PostgreSQLProperties.getProtocolID())) {
      return new PostgreSQLProperties();
    } else {
      throw new UnsupportedOperationException(
          "The requested database is not supported");
    }
  }

  /**
   * Initialize the ConnectionFactory
   *
   * @throws UnsupportedOperationException if the requested database
   *     is not supported
   * @throws SQLException if a error occurs while connecting to the
   *     database
   */
  public static void initialize(final String server, final String user,
                                final String password)
      throws SQLException, UnsupportedOperationException {
    if (instance == null) {
      instance =
          new ConnectionFactory(propertiesFor(server), server, user, password);
    }
  }

  /**
   * @return the initialized ConnectionFactory or null if the
   *     ConnectionFactory is not initialized
   */
  public static ConnectionFactory getInstance() {
    return instance;
  }

  /**
   * @return a connection to the Database
   *
   * @throws SQLException if the ConnectionPool is not initialized or
   *     if an error occurs while accessing the
   *     database
   */
  public Connection getConnection() throws SQLException {
    if (ds == null) {
      throw new SQLException("ConnectionFactory is not inialized.");
    }
    try {
      return ds.getConnection();
    } catch (final SQLException e) {
      throw new SQLException("Cannot access database", e);
    }
  }

  /**
   * @param properties
   * @param server
   * @param user
   * @param password
   */
  private ConnectionFactory(final DbProperties properties, final String server,
                            final String user, final String password)
      throws SQLException {
    this.server = server;
    this.user = user;
    this.password = password;
    this.properties = properties;

    if (Version.version().contains("-jre")) {
      ds = new WaarpBasicDataSourceJava8();
    } else {
      ds = new WaarpBasicDataSourceJava6();
    }

    // Initialize DataSource
    ds.setDriverClassName(this.properties.getDriverName());
    ds.setUrl(this.server);
    ds.setUsername(this.user);
    ds.setPassword(this.password);
    ds.setDefaultAutoCommit(AUTOCOMMIT);
    ds.setDefaultReadOnly(READONLY);
    ds.setValidationQuery(this.properties.getValidationQuery());

    // Get maximum connections
    Connection con = null;
    // default
    maxConnections = MAX_CONNECTIONS_DEFAULT;
    try {
      con = getConnection();
      // Max of user value (if not set, default 10) and current maximum
      // connections
      maxConnections = Math.min(properties.getMaximumConnections(con),
                                SystemPropertyUtil.get(
                                    SystemPropertyUtil.WAARP_DATABASE_CONNECTION_MAX,
                                    MAX_CONNECTIONS_DEFAULT));
    } catch (final SQLException e) {
      logger.warn(
          "Cannot fetch maximum connection allowed from database" + " : {}",
          e.getMessage());
    } finally {
      if (con != null) {
        con.close();
      }
    }
    setMaxConnections(maxConnections);
    logger.info("{}", this);
  }

  /**
   * Mainly for Junit
   *
   * @param max
   */
  public void setMaxConnections(final int max) {
    // Minimal value should be 2
    maxConnections = Math.max(max, MAX_IDLE_DEFAULT);
    ds.setMaxActive(maxConnections);
    maxIdle = Math.min(Math.max(maxConnections / 2, MAX_IDLE_DEFAULT * 2),
                       maxConnections);
    if (maxIdle < GenericObjectPool.DEFAULT_MAX_IDLE) {
      ds.setMaxIdle(maxIdle);
    }
    ds.setInitialSize(MAX_IDLE_DEFAULT);
  }

  /**
   * Closes and releases all registered connections and connection pool
   */
  public void close() {
    logger.info("Closing ConnectionFactory");
    ds.close();
  }

  /**
   * @return the current configuration of the database maximum connections
   */
  public int getMaxConnections() {
    return maxConnections;
  }

  /**
   * @return the JDBC connection url property
   */
  public String getServer() {
    return server;
  }

  /**
   * @return the JDBC connection username property
   */
  public String getUser() {
    return user;
  }

  /**
   * @return the JDBC connection password property
   */
  public String getPassword() {
    return password;
  }

  /**
   * @return the associated DbProperties
   */
  public DbProperties getProperties() {
    return properties;
  }

  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder();
    sb.append("Datapool:");
    sb.append(server);
    sb.append(", with user:");
    sb.append(user);
    sb.append(", AutoCommit:");
    sb.append(AUTOCOMMIT);
    sb.append(", DefaultReadOnly:");
    sb.append(READONLY);
    sb.append(", max connecions:");
    sb.append(maxConnections);
    return sb.toString();
  }
}