TrackingIndex.java

  1. package gov.usgs.earthquake.aws;

  2. import java.io.ByteArrayInputStream;
  3. import java.sql.PreparedStatement;
  4. import java.sql.ResultSet;
  5. import java.sql.Statement;
  6. import java.util.logging.Logger;

  7. import javax.json.Json;
  8. import javax.json.JsonObject;
  9. import javax.json.JsonReader;

  10. import gov.usgs.earthquake.util.JDBCConnection;
  11. import gov.usgs.util.Config;

  12. /**
  13.  * Tracking index stores component state in a database.
  14.  *
  15.  * Only SQLITE or local development should rely on createSchema.
  16.  *
  17.  * Mysql Schema Example:<br>
  18.  * <pre>
  19.  * CREATE TABLE IF NOT EXISTS tracking_index
  20.  * (id INTEGER PRIMARY KEY AUTO_INCREMENT
  21.  * , created VARCHAR(255)
  22.  * , name VARCHAR(255)
  23.  * , data LONGTEXT
  24.  * , UNIQUE KEY name_index (name)
  25.  * ) ENGINE=innodb CHARSET=utf8;
  26.  * </pre>
  27.  */
  28. public class TrackingIndex extends JDBCConnection {

  29.   private static final Logger LOGGER = Logger.getLogger(
  30.       TrackingIndex.class.getName());

  31.   /** Variable for the default driver */
  32.   public static final String DEFAULT_DRIVER = "org.sqlite.JDBC";
  33.   /** Variable for the default table */
  34.   public static final String DEFAULT_TABLE = "tracking";
  35.   /** Variable for the default URL */
  36.   public static final String DEFAULT_URL = "jdbc:sqlite:json_tracking_index.db";

  37.   /** Database table name. */
  38.   private String table;

  39.   /**
  40.    * Construct a TrackingIndex using defaults.
  41.    */
  42.   public TrackingIndex() {
  43.     this(DEFAULT_DRIVER, DEFAULT_URL);
  44.   }

  45.   /**
  46.    * Construct a TrackingIndex with the default table.
  47.    * @param driver Driver to use
  48.    * @param url URL to use
  49.    */
  50.   public TrackingIndex(final String driver, final String url) {
  51.     this(driver, url, DEFAULT_TABLE);
  52.   }

  53.   /**
  54.    * Construct a TrackingIndex with custom driver, url, and table.
  55.    * @param driver Driver to use
  56.    * @param url URL to use
  57.    * @param table Table to use
  58.    */
  59.   public TrackingIndex(
  60.       final String driver, final String url, final String table) {
  61.     super(driver, url);
  62.     this.table = table;
  63.   }

  64.   /** @return table */
  65.   public String getTable() { return this.table; }
  66.   /** @param table Table to set */
  67.   public void setTable(final String table) { this.table = table; }

  68.   @Override
  69.   public void configure(final Config config) throws Exception {
  70.     super.configure(config);
  71.     if (getDriver() == null) { setDriver(DEFAULT_DRIVER); }
  72.     if (getUrl() == null) { setUrl(DEFAULT_URL); }

  73.     setTable(config.getProperty("table", DEFAULT_TABLE));
  74.     LOGGER.config("[" + getName() + "] driver=" + getDriver());
  75.     LOGGER.config("[" + getName() + "] table=" + getTable());
  76.     // do not log url, it may contain user/pass
  77.   }

  78.   /**
  79.    * After normal startup, check whether schema exists and attempt to create.
  80.    */
  81.   @Override
  82.   public void startup() throws Exception {
  83.     super.startup();
  84.     // make sure schema exists
  85.     if (!schemaExists()) {
  86.       LOGGER.warning("[" + getName() + "] schema not found, creating");
  87.       createSchema();
  88.     }
  89.   }

  90.   /**
  91.    * Check whether schema exists.
  92.    *
  93.    * @return boolean
  94.    * @throws Exception if error occurs
  95.    */
  96.   public boolean schemaExists() throws Exception {
  97.     final String sql = "select * from " + this.table + " limit 1";
  98.     beginTransaction();
  99.     try (final PreparedStatement test = getConnection().prepareStatement(sql)) {
  100.       test.setQueryTimeout(60);
  101.       // should throw exception if table does not exist
  102.       try (final ResultSet rs = test.executeQuery()) {
  103.         rs.next();
  104.       }
  105.       commitTransaction();
  106.       // schema exists
  107.       return true;
  108.     } catch (Exception e) {
  109.       rollbackTransaction();
  110.       return false;
  111.     }
  112.   }

  113.   /**
  114.    * Attempt to create schema.
  115.    *
  116.    * Only supports sqlite or mysql.  When not using sqlite, relying on this
  117.    * method is only recommended for local development.
  118.    *
  119.    * @throws Exception if error occurs
  120.    */
  121.   public void createSchema() throws Exception {
  122.     // create schema
  123.     beginTransaction();
  124.     try (final Statement statement = getConnection().createStatement()) {
  125.       String autoIncrement = "";
  126.       if (getDriver().contains("mysql")) {
  127.         autoIncrement = "AUTO_INCREMENT";
  128.       }
  129.       statement.executeUpdate(
  130.           "CREATE TABLE " + this.table
  131.           + " (id INTEGER PRIMARY KEY " + autoIncrement
  132.           + ", created VARCHAR(255)"
  133.           + ", name VARCHAR(255)"
  134.           + ", data TEXT"
  135.           + ")");
  136.       statement.executeUpdate(
  137.           "CREATE UNIQUE INDEX name_index ON " + this.table + " (name)");
  138.       commitTransaction();
  139.     } catch (Exception e) {
  140.       rollbackTransaction();
  141.       throw e;
  142.     }
  143.   }

  144.   /**
  145.    * Get tracking data for specified name.
  146.    *
  147.    * @param name
  148.    *     name of tracking data.
  149.    * @return null if data not found.
  150.    * @throws Exception if error occurs
  151.    */
  152.   public synchronized JsonObject getTrackingData(final String name) throws Exception {
  153.     JsonObject data = null;

  154.     final String sql = "SELECT * FROM " + this.table + " WHERE name=?";
  155.     beginTransaction();
  156.     try (final PreparedStatement statement = getConnection().prepareStatement(sql)) {
  157.       statement.setQueryTimeout(60);
  158.       statement.setString(1, name);

  159.       // execute and parse data
  160.       try (final ResultSet rs = statement.executeQuery()) {
  161.         while (rs.next()) {
  162.           final String json = rs.getString("data");
  163.           try (
  164.             final JsonReader jsonReader = Json.createReader(
  165.                 new ByteArrayInputStream(json.getBytes()))
  166.           ) {
  167.             data = jsonReader.readObject();
  168.           }
  169.         }
  170.       }
  171.       commitTransaction();
  172.     } catch (Exception e) {
  173.       rollbackTransaction();
  174.     }

  175.     return data;
  176.   }

  177.   /**
  178.    * Remove tracking data.
  179.    *
  180.    * @param name
  181.    *     name of tracking data.
  182.    * @throws Exception if error occurs
  183.    */
  184.   public synchronized void removeTrackingData(final String name) throws Exception {
  185.     final String sql = "DELETE FROM " + this.table + " WHERE name=?";
  186.     // create schema
  187.     beginTransaction();
  188.     try (final PreparedStatement statement = getConnection().prepareStatement(sql)) {
  189.       statement.setQueryTimeout(60);
  190.       statement.setString(1, name);

  191.       statement.executeUpdate();
  192.       commitTransaction();
  193.     } catch (Exception e) {
  194.       rollbackTransaction();
  195.       throw e;
  196.     }
  197.   }

  198.   /**
  199.    * Add or update tracking data.
  200.    *
  201.    * @param name
  202.    *     name of tracking data.
  203.    * @param data
  204.    *     data to store.
  205.    * @throws Exception if error occurs
  206.    */
  207.   public synchronized void setTrackingData(final String name, final JsonObject data) throws Exception {
  208.     final String update = "UPDATE " + this.table + " SET data=? WHERE name=?";
  209.     // usually updated, try update first
  210.     beginTransaction();
  211.     try (final PreparedStatement updateStatement = getConnection().prepareStatement(update)) {
  212.       updateStatement.setQueryTimeout(60);
  213.       updateStatement.setString(1, data.toString());
  214.       updateStatement.setString(2, name);
  215.       // execute update
  216.       final int count = updateStatement.executeUpdate();
  217.       // check number of rows updated (whether row already exists)
  218.       if (count == 0) {
  219.         final String insert = "INSERT INTO " + this.table + " (data, name) VALUES (?, ?)";
  220.         // no rows updated
  221.         try (final PreparedStatement insertStatement = getConnection().prepareStatement(insert)) {
  222.           insertStatement.setQueryTimeout(60);
  223.           insertStatement.setString(1, data.toString());
  224.           insertStatement.setString(2, name);
  225.           // execute insert
  226.           insertStatement.executeUpdate();
  227.         }
  228.       }
  229.       commitTransaction();
  230.     } catch (Exception e) {
  231.       rollbackTransaction();
  232.       throw e;
  233.     }
  234.   }

  235. }