TrackingIndex.java
package gov.usgs.earthquake.aws;
import java.io.ByteArrayInputStream;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.logging.Logger;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReader;
import gov.usgs.earthquake.util.JDBCConnection;
import gov.usgs.util.Config;
/**
* Tracking index stores component state in a database.
*
* Only SQLITE or local development should rely on createSchema.
*
* Mysql Schema Example:<br>
* <pre>
* CREATE TABLE IF NOT EXISTS tracking_index
* (id INTEGER PRIMARY KEY AUTO_INCREMENT
* , created VARCHAR(255)
* , name VARCHAR(255)
* , data LONGTEXT
* , UNIQUE KEY name_index (name)
* ) ENGINE=innodb CHARSET=utf8;
* </pre>
*/
public class TrackingIndex extends JDBCConnection {
private static final Logger LOGGER = Logger.getLogger(
TrackingIndex.class.getName());
/** Variable for the default driver */
public static final String DEFAULT_DRIVER = "org.sqlite.JDBC";
/** Variable for the default table */
public static final String DEFAULT_TABLE = "tracking";
/** Variable for the default URL */
public static final String DEFAULT_URL = "jdbc:sqlite:json_tracking_index.db";
/** Database table name. */
private String table;
/**
* Construct a TrackingIndex using defaults.
*/
public TrackingIndex() {
this(DEFAULT_DRIVER, DEFAULT_URL);
}
/**
* Construct a TrackingIndex with the default table.
* @param driver Driver to use
* @param url URL to use
*/
public TrackingIndex(final String driver, final String url) {
this(driver, url, DEFAULT_TABLE);
}
/**
* Construct a TrackingIndex with custom driver, url, and table.
* @param driver Driver to use
* @param url URL to use
* @param table Table to use
*/
public TrackingIndex(
final String driver, final String url, final String table) {
super(driver, url);
this.table = table;
}
/** @return table */
public String getTable() { return this.table; }
/** @param table Table to set */
public void setTable(final String table) { this.table = table; }
@Override
public void configure(final Config config) throws Exception {
super.configure(config);
if (getDriver() == null) { setDriver(DEFAULT_DRIVER); }
if (getUrl() == null) { setUrl(DEFAULT_URL); }
setTable(config.getProperty("table", DEFAULT_TABLE));
LOGGER.config("[" + getName() + "] driver=" + getDriver());
LOGGER.config("[" + getName() + "] table=" + getTable());
// do not log url, it may contain user/pass
}
/**
* After normal startup, check whether schema exists and attempt to create.
*/
@Override
public void startup() throws Exception {
super.startup();
// make sure schema exists
if (!schemaExists()) {
LOGGER.warning("[" + getName() + "] schema not found, creating");
createSchema();
}
}
/**
* Check whether schema exists.
*
* @return boolean
* @throws Exception if error occurs
*/
public boolean schemaExists() throws Exception {
final String sql = "select * from " + this.table + " limit 1";
beginTransaction();
try (final PreparedStatement test = getConnection().prepareStatement(sql)) {
test.setQueryTimeout(60);
// should throw exception if table does not exist
try (final ResultSet rs = test.executeQuery()) {
rs.next();
}
commitTransaction();
// schema exists
return true;
} catch (Exception e) {
rollbackTransaction();
return false;
}
}
/**
* Attempt to create schema.
*
* Only supports sqlite or mysql. When not using sqlite, relying on this
* method is only recommended for local development.
*
* @throws Exception if error occurs
*/
public void createSchema() throws Exception {
// create schema
beginTransaction();
try (final Statement statement = getConnection().createStatement()) {
String autoIncrement = "";
if (getDriver().contains("mysql")) {
autoIncrement = "AUTO_INCREMENT";
}
statement.executeUpdate(
"CREATE TABLE " + this.table
+ " (id INTEGER PRIMARY KEY " + autoIncrement
+ ", created VARCHAR(255)"
+ ", name VARCHAR(255)"
+ ", data TEXT"
+ ")");
statement.executeUpdate(
"CREATE UNIQUE INDEX name_index ON " + this.table + " (name)");
commitTransaction();
} catch (Exception e) {
rollbackTransaction();
throw e;
}
}
/**
* Get tracking data for specified name.
*
* @param name
* name of tracking data.
* @return null if data not found.
* @throws Exception if error occurs
*/
public synchronized JsonObject getTrackingData(final String name) throws Exception {
JsonObject data = null;
final String sql = "SELECT * FROM " + this.table + " WHERE name=?";
beginTransaction();
try (final PreparedStatement statement = getConnection().prepareStatement(sql)) {
statement.setQueryTimeout(60);
statement.setString(1, name);
// execute and parse data
try (final ResultSet rs = statement.executeQuery()) {
while (rs.next()) {
final String json = rs.getString("data");
try (
final JsonReader jsonReader = Json.createReader(
new ByteArrayInputStream(json.getBytes()))
) {
data = jsonReader.readObject();
}
}
}
commitTransaction();
} catch (Exception e) {
rollbackTransaction();
}
return data;
}
/**
* Remove tracking data.
*
* @param name
* name of tracking data.
* @throws Exception if error occurs
*/
public synchronized void removeTrackingData(final String name) throws Exception {
final String sql = "DELETE FROM " + this.table + " WHERE name=?";
// create schema
beginTransaction();
try (final PreparedStatement statement = getConnection().prepareStatement(sql)) {
statement.setQueryTimeout(60);
statement.setString(1, name);
statement.executeUpdate();
commitTransaction();
} catch (Exception e) {
rollbackTransaction();
throw e;
}
}
/**
* Add or update tracking data.
*
* @param name
* name of tracking data.
* @param data
* data to store.
* @throws Exception if error occurs
*/
public synchronized void setTrackingData(final String name, final JsonObject data) throws Exception {
final String update = "UPDATE " + this.table + " SET data=? WHERE name=?";
// usually updated, try update first
beginTransaction();
try (final PreparedStatement updateStatement = getConnection().prepareStatement(update)) {
updateStatement.setQueryTimeout(60);
updateStatement.setString(1, data.toString());
updateStatement.setString(2, name);
// execute update
final int count = updateStatement.executeUpdate();
// check number of rows updated (whether row already exists)
if (count == 0) {
final String insert = "INSERT INTO " + this.table + " (data, name) VALUES (?, ?)";
// no rows updated
try (final PreparedStatement insertStatement = getConnection().prepareStatement(insert)) {
insertStatement.setQueryTimeout(60);
insertStatement.setString(1, data.toString());
insertStatement.setString(2, name);
// execute insert
insertStatement.executeUpdate();
}
}
commitTransaction();
} catch (Exception e) {
rollbackTransaction();
throw e;
}
}
}