/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.sqoop.manager.oracle;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * Create and initialize connections to Oracle RDBMS.
 */
public class OracleConnectionFactory {

  protected OracleConnectionFactory() {
  }

  protected static final OraOopLog LOG = OraOopLogFactory
      .getLog(OracleConnectionFactory.class.getName());

  public static Connection createOracleJdbcConnection(
      String jdbcDriverClassName, String jdbcUrl, String username,
      String password) throws SQLException {
    Properties props = null;
    return createOracleJdbcConnection(jdbcDriverClassName, jdbcUrl, username,
        password, props);
  }

  public static Connection createOracleJdbcConnection(
      String jdbcDriverClassName, String jdbcUrl, String username,
      String password, Properties additionalProps) throws SQLException {

    loadJdbcDriver(jdbcDriverClassName);
    Connection connection =
        createConnection(jdbcUrl, username, password, additionalProps);

    // Only OraOopDBRecordReader will call initializeOracleConnection(), as
    // we only need to initialize the session(s) prior to the mapper starting
    // it's job.
    // i.e. We don't need to initialize the sessions in order to get the
    // table's data-files etc.

    // initializeOracleConnection(connection, conf);

    return connection;
  }

  private static void loadJdbcDriver(String jdbcDriverClassName) {

    try {
      Class.forName(jdbcDriverClassName);
    } catch (ClassNotFoundException ex) {
      String errorMsg =
          "Unable to load the jdbc driver class : " + jdbcDriverClassName;
      LOG.error(errorMsg);
      throw new RuntimeException(errorMsg);
    }
  }

  private static Connection createConnection(String jdbcUrl, String username,
      String password, Properties additionalProps) throws SQLException {

    Properties props = new Properties();
    if (username != null) {
      props.put("user", username);
    }

    if (password != null) {
      props.put("password", password);
    }

    if (additionalProps != null && additionalProps.size() > 0) {
      props.putAll(additionalProps);
    }

    try {
      return DriverManager.getConnection(jdbcUrl, props);
    } catch (SQLException ex) {
      String errorMsg = String.format(
        "Unable to obtain a JDBC connection to the URL \"%s\" as user \"%s\": ",
        jdbcUrl, (username != null) ? username : "[null]");
      LOG.error(errorMsg, ex);
      throw ex;
    }
  }

  public static void initializeOracleConnection(Connection connection,
      org.apache.hadoop.conf.Configuration conf) throws SQLException {

    connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

    OraOopOracleQueries.setConnectionTimeZone(connection, conf);

    setSessionClientInfo(connection, conf);

    OraOopOracleQueries.setJdbcFetchSize(connection, conf);

    executeOraOopSessionInitializationStatements(connection, conf);
  }

  protected static void setSessionClientInfo(Connection connection,
      org.apache.hadoop.conf.Configuration conf) {

    String sql = "";
    try {
      sql =
          "begin \n"
              + "  dbms_application_info.set_module(module_name => "
              + "'%s', action_name => '%s'); \n"
              + "end;";

      String oracleSessionActionName =
          conf.get(OraOopConstants.ORACLE_SESSION_ACTION_NAME);

      sql =
          String.format(sql, OraOopConstants.ORACLE_SESSION_MODULE_NAME,
              oracleSessionActionName);

      Statement statement = connection.createStatement();
      statement.execute(sql);
      LOG.info("Initializing Oracle session with SQL :\n" + sql);
    } catch (Exception ex) {
      LOG.error(String.format("An error occurred while attempting to execute "
          + "the following Oracle session-initialization statement:" + "\n%s"
          + "\nError:" + "\n%s", sql, ex.getMessage()));
    }
  }

  protected static void executeOraOopSessionInitializationStatements(
      Connection connection, org.apache.hadoop.conf.Configuration conf) {

    List<String> statements = parseOraOopSessionInitializationStatements(conf);

    if (statements.size() == 0) {
      LOG.warn(String
          .format(
              "No Oracle 'session initialization' statements were found to "
              + "execute.\nCheck that your %s and/or %s files are correctly "
                  + "installed in the ${SQOOP_HOME}/conf directory.",
              OraOopConstants.ORAOOP_SITE_TEMPLATE_FILENAME,
              OraOopConstants.ORAOOP_SITE_FILENAME));
    } else {
      for (String statement : statements) {
        try {
          connection.createStatement().execute(statement);
          LOG.info("Initializing Oracle session with SQL : " + statement);
        } catch (Exception ex) {
          LOG.error(String.format(
              "An error occurred while attempting to execute "
                  + "the following Oracle session-initialization statement:"
                  + "\n%s" + "\nError:" + "\n%s", statement, ex.getMessage()));
        }
      }
    }
  }

  protected static List<String> parseOraOopSessionInitializationStatements(
      org.apache.hadoop.conf.Configuration conf) {

    ArrayList<String> result = new ArrayList<String>();

    if (conf == null) {
      throw new IllegalArgumentException(
          "No configuration argument must be specified.");
    }

    String sessionInitializationStatements =
        conf.get(OraOopConstants.ORAOOP_SESSION_INITIALIZATION_STATEMENTS);
    if (sessionInitializationStatements != null
        && !sessionInitializationStatements.isEmpty()) {
      String[] initializationStatements =
          sessionInitializationStatements.split(";");
      for (String initializationStatement : initializationStatements) {
        initializationStatement = initializationStatement.trim();
        if (initializationStatement != null
            && !initializationStatement.isEmpty()
            && !initializationStatement
                .startsWith(OraOopConstants.Oracle.
                    ORACLE_SQL_STATEMENT_COMMENT_TOKEN)) {

          LOG.debug(String
              .format(
                  "initializationStatement (quoted & pre-expression "
                  + "evaluation) = \"%s\"",
                  initializationStatement));

          initializationStatement =
              OraOopUtilities.replaceConfigurationExpression(
                  initializationStatement, conf);

          result.add(initializationStatement);
        }
      }
    }
    return result;
  }

}
