/*
 * Copyright 2012 LinkedIn Corp.
 *
 * Licensed 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 azkaban.database;

import java.sql.SQLException;

import org.apache.commons.dbutils.DbUtils;
import org.apache.log4j.Logger;

import java.sql.PreparedStatement;
import java.sql.Connection;

import azkaban.utils.Props;

public class DataSourceUtils {

  private static Logger logger = Logger.getLogger(DataSourceUtils.class);

  /**
   * Property types
   */
  public static enum PropertyType {
    DB(1);

    private int numVal;

    PropertyType(int numVal) {
      this.numVal = numVal;
    }

    public int getNumVal() {
      return numVal;
    }

    public static PropertyType fromInteger(int x) {
      switch (x) {
      case 1:
        return DB;
      default:
        return DB;
      }
    }
  }

  /**
   * Create Datasource from parameters in the properties
   *
   * @param props
   * @return
   */
  public static AzkabanDataSource getDataSource(Props props) {
    String databaseType = props.getString("database.type");

    AzkabanDataSource dataSource = null;
    if (databaseType.equals("mysql")) {
      int port = props.getInt("mysql.port");
      String host = props.getString("mysql.host");
      String database = props.getString("mysql.database");
      String user = props.getString("mysql.user");
      String password = props.getString("mysql.password");
      int numConnections = props.getInt("mysql.numconnections");

      dataSource =
          getMySQLDataSource(host, port, database, user, password,
              numConnections);
    } else if (databaseType.equals("h2")) {
      String path = props.getString("h2.path");
      dataSource = getH2DataSource(path);
    }

    return dataSource;
  }

  /**
   * Create a MySQL DataSource
   *
   * @param host
   * @param port
   * @param dbName
   * @param user
   * @param password
   * @param numConnections
   * @return
   */
  public static AzkabanDataSource getMySQLDataSource(String host, Integer port,
      String dbName, String user, String password, Integer numConnections) {
    return new MySQLBasicDataSource(host, port, dbName, user, password,
        numConnections);
  }

  /**
   * Create H2 DataSource
   *
   * @param file
   * @return
   */
  public static AzkabanDataSource getH2DataSource(String file) {
    return new EmbeddedH2BasicDataSource(file);
  }

  /**
   * Hidden datasource
   */
  private DataSourceUtils() {
  }

  /**
   * MySQL data source based on AzkabanDataSource
   *
   */
  public static class MySQLBasicDataSource extends AzkabanDataSource {

    private static MonitorThread monitorThread = null;

    private final String url;

    private MySQLBasicDataSource(String host, int port, String dbName,
        String user, String password, int numConnections) {
      super();

      url = "jdbc:mysql://" + (host + ":" + port + "/" + dbName);
      addConnectionProperty("useUnicode", "yes");
      addConnectionProperty("characterEncoding", "UTF-8");
      setDriverClassName("com.mysql.jdbc.Driver");
      setUsername(user);
      setPassword(password);
      setUrl(url);
      setMaxActive(numConnections);
      setValidationQuery("/* ping */ select 1");
      setTestOnBorrow(true);

      if (monitorThread == null) {
        monitorThread = new MonitorThread(this);
        monitorThread.start();
      }
    }

    @Override
    public boolean allowsOnDuplicateKey() {
      return true;
    }

    @Override
    public String getDBType() {
      return "mysql";
    }

    private class MonitorThread extends Thread {
      private static final long MONITOR_THREAD_WAIT_INTERVAL_MS = 30 * 1000;
      private boolean shutdown = false;
      MySQLBasicDataSource dataSource;

      public MonitorThread(MySQLBasicDataSource mysqlSource) {
        this.setName("MySQL-DB-Monitor-Thread");
        setDaemon(true);
        dataSource = mysqlSource;
      }

      @SuppressWarnings("unused")
      public void shutdown() {
        shutdown = true;
        this.interrupt();
      }

      public void run() {
        while (!shutdown) {
          synchronized (this) {
            try {
              pingDB();
              wait(MONITOR_THREAD_WAIT_INTERVAL_MS);
            } catch (InterruptedException e) {
              logger.info("Interrupted. Probably to shut down.");
            }
          }
        }
      }

      private void pingDB() {
        Connection connection = null;
        try {
          connection = dataSource.getConnection();
          PreparedStatement query = connection.prepareStatement("SELECT 1");
          query.execute();
        } catch (SQLException e) {
          logger.error("Unable to reach MySQL server on " + url);
        } finally {
          DbUtils.closeQuietly(connection);
        }
      }
    }

  }

  /**
   * H2 Datasource
   *
   */
  public static class EmbeddedH2BasicDataSource extends AzkabanDataSource {
    private EmbeddedH2BasicDataSource(String filePath) {
      super();
      String url = "jdbc:h2:file:" + filePath;
      setDriverClassName("org.h2.Driver");
      setUrl(url);
    }

    @Override
    public boolean allowsOnDuplicateKey() {
      return false;
    }

    @Override
    public String getDBType() {
      return "h2";
    }
  }
}

@@ -3076,64 +3076,8 @@
  %7B%0A%0A
-    private static MonitorThread monitorThread = null;%0A%0A
     
@@ -3102,16 +3102,16 @@
 ng url;%0A
+
 %0A    pri
@@ -3666,132 +3666,8 @@
 ue);
-%0A%0A      if (monitorThread == null) %7B%0A        monitorThread = new MonitorThread(this);%0A        monitorThread.start();%0A      %7D
 %0A   
@@ -3826,1292 +3826,14 @@
 l%22;%0A
+
     %7D%0A
-%0A    private class MonitorThread extends Thread %7B%0A      private static final long MONITOR_THREAD_WAIT_INTERVAL_MS = 30 * 1000;%0A      private boolean shutdown = false;%0A      MySQLBasicDataSource dataSource;%0A%0A      public MonitorThread(MySQLBasicDataSource mysqlSource) %7B%0A        this.setName(%22MySQL-DB-Monitor-Thread%22);%0A        setDaemon(true);%0A        dataSource = mysqlSource;%0A      %7D%0A%0A      @SuppressWarnings(%22unused%22)%0A      public void shutdown() %7B%0A        shutdown = true;%0A        this.interrupt();%0A      %7D%0A%0A      public void run() %7B%0A        while (!shutdown) %7B%0A          synchronized (this) %7B%0A            try %7B%0A              pingDB();%0A              wait(MONITOR_THREAD_WAIT_INTERVAL_MS);%0A            %7D catch (InterruptedException e) %7B%0A              logger.info(%22Interrupted. Probably to shut down.%22);%0A            %7D%0A          %7D%0A        %7D%0A      %7D%0A%0A      private void pingDB() %7B%0A        Connection connection = null;%0A        try %7B%0A          connection = dataSource.getConnection();%0A          PreparedStatement query = connection.prepareStatement(%22SELECT 1%22);%0A          query.execute();%0A        %7D catch (SQLException e) %7B%0A          logger.error(%22Unable to reach MySQL server on %22 + url);%0A        %7D finally %7B%0A          DbUtils.closeQuietly(connection);%0A        %7D%0A      %7D%0A    %7D%0A%0A
   %7D%0A

Remove the DB monitoring thread (#974)