|
@@ -22,452 +22,118 @@
|
|
|
|
|
|
package password.pwm.util.db;
|
|
|
|
|
|
-import password.pwm.PwmAboutProperty;
|
|
|
-import password.pwm.PwmApplication;
|
|
|
-import password.pwm.PwmApplicationMode;
|
|
|
-import password.pwm.PwmConstants;
|
|
|
-import password.pwm.config.Configuration;
|
|
|
-import password.pwm.config.PwmSetting;
|
|
|
-import password.pwm.config.option.DataStorageMethod;
|
|
|
-import password.pwm.error.ErrorInformation;
|
|
|
-import password.pwm.error.PwmError;
|
|
|
-import password.pwm.error.PwmException;
|
|
|
-import password.pwm.health.HealthRecord;
|
|
|
-import password.pwm.health.HealthStatus;
|
|
|
-import password.pwm.health.HealthTopic;
|
|
|
-import password.pwm.svc.PwmService;
|
|
|
-import password.pwm.svc.stats.Statistic;
|
|
|
-import password.pwm.svc.stats.StatisticsManager;
|
|
|
import password.pwm.util.java.ClosableIterator;
|
|
|
-import password.pwm.util.java.JavaHelper;
|
|
|
import password.pwm.util.java.JsonUtil;
|
|
|
+import password.pwm.util.java.StringUtil;
|
|
|
import password.pwm.util.java.TimeDuration;
|
|
|
import password.pwm.util.logging.PwmLogger;
|
|
|
|
|
|
-import java.io.File;
|
|
|
-import java.lang.reflect.Method;
|
|
|
import java.sql.Connection;
|
|
|
-import java.sql.DatabaseMetaData;
|
|
|
-import java.sql.Driver;
|
|
|
import java.sql.PreparedStatement;
|
|
|
import java.sql.ResultSet;
|
|
|
import java.sql.SQLException;
|
|
|
-import java.sql.Statement;
|
|
|
-import java.time.Instant;
|
|
|
-import java.util.ArrayList;
|
|
|
-import java.util.Collections;
|
|
|
-import java.util.HashMap;
|
|
|
-import java.util.LinkedHashMap;
|
|
|
-import java.util.List;
|
|
|
import java.util.Map;
|
|
|
-import java.util.Properties;
|
|
|
-import java.util.concurrent.ExecutorService;
|
|
|
-import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
+import java.util.concurrent.locks.ReentrantLock;
|
|
|
|
|
|
/**
|
|
|
* @author Jason D. Rivard
|
|
|
*/
|
|
|
-public class DatabaseAccessorImpl implements PwmService, DatabaseAccessor {
|
|
|
-// ------------------------------ FIELDS ------------------------------
|
|
|
+class DatabaseAccessorImpl implements DatabaseAccessor {
|
|
|
|
|
|
private static final PwmLogger LOGGER = PwmLogger.forClass(DatabaseAccessorImpl.class, true);
|
|
|
- private static final String KEY_COLUMN = "id";
|
|
|
- private static final String VALUE_COLUMN = "value";
|
|
|
|
|
|
- private static final int KEY_COLUMN_LENGTH = PwmConstants.DATABASE_ACCESSOR_KEY_LENGTH;
|
|
|
+ private final Connection connection;
|
|
|
+ private final DatabaseService databaseService;
|
|
|
+ private final DBConfiguration dbConfiguration;
|
|
|
|
|
|
- private static final String KEY_TEST = "write-test-key";
|
|
|
- private static final String KEY_ENGINE_START_PREFIX = "engine-start-";
|
|
|
+ private final boolean traceLogEnabled;
|
|
|
|
|
|
- private DBConfiguration dbConfiguration;
|
|
|
- private Driver driver;
|
|
|
- private String instanceID;
|
|
|
- private boolean traceLogging;
|
|
|
- private volatile Connection connection;
|
|
|
- private volatile PwmService.STATUS status = PwmService.STATUS.NEW;
|
|
|
- private ErrorInformation lastError;
|
|
|
- private PwmApplication pwmApplication;
|
|
|
+ private final ReentrantLock LOCK = new ReentrantLock();
|
|
|
|
|
|
- private JDBCDriverLoader.DriverLoader jdbcDriverLoader;
|
|
|
-
|
|
|
- private ExecutorService masterStatusService;
|
|
|
- private final AtomicBoolean masterStatus = new AtomicBoolean(false);
|
|
|
-
|
|
|
-// --------------------------- CONSTRUCTORS ---------------------------
|
|
|
-
|
|
|
- public DatabaseAccessorImpl()
|
|
|
+ DatabaseAccessorImpl(
|
|
|
+ final DatabaseService databaseService,
|
|
|
+ final DBConfiguration dbConfiguration,
|
|
|
+ final Connection connection,
|
|
|
+ final boolean traceLogEnabled
|
|
|
+ )
|
|
|
{
|
|
|
+ this.connection = connection;
|
|
|
+ this.dbConfiguration = dbConfiguration;
|
|
|
+ this.traceLogEnabled = traceLogEnabled;
|
|
|
+ this.databaseService = databaseService;
|
|
|
}
|
|
|
|
|
|
-// ------------------------ INTERFACE METHODS ------------------------
|
|
|
-
|
|
|
-
|
|
|
-// --------------------- Interface PwmService ---------------------
|
|
|
-
|
|
|
- public STATUS status() {
|
|
|
- return status;
|
|
|
- }
|
|
|
|
|
|
- public void init(final PwmApplication pwmApplication) throws PwmException {
|
|
|
- this.pwmApplication = pwmApplication;
|
|
|
- final Configuration config = pwmApplication.getConfig();
|
|
|
- init(config);
|
|
|
- }
|
|
|
-
|
|
|
- public void close()
|
|
|
+ private void processSqlException(
|
|
|
+ final DatabaseUtil.DebugInfo debugInfo,
|
|
|
+ final SQLException e
|
|
|
+ )
|
|
|
+ throws DatabaseException
|
|
|
{
|
|
|
- status = PwmService.STATUS.CLOSED;
|
|
|
- if (connection != null) {
|
|
|
- try {
|
|
|
- connection.close();
|
|
|
- } catch (Exception e) {
|
|
|
- LOGGER.debug("error while closing DB: " + e.getMessage());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- driver = null;
|
|
|
- } catch (Exception e) {
|
|
|
- LOGGER.debug("error while de-registering driver: " + e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- connection = null;
|
|
|
-
|
|
|
- if (jdbcDriverLoader != null) {
|
|
|
- jdbcDriverLoader.unloadDriver();
|
|
|
- jdbcDriverLoader = null;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void init(final Configuration config) throws PwmException {
|
|
|
-
|
|
|
- this.dbConfiguration = DBConfiguration.fromConfiguration(config);
|
|
|
- this.instanceID = pwmApplication == null ? null : pwmApplication.getInstanceID();
|
|
|
- this.traceLogging = config.readSettingAsBoolean(PwmSetting.DATABASE_DEBUG_TRACE);
|
|
|
-
|
|
|
- if (!dbConfiguration.isEnabled()) {
|
|
|
- status = PwmService.STATUS.CLOSED;
|
|
|
- LOGGER.debug("skipping database connection open, no connection parameters configured");
|
|
|
- }
|
|
|
-
|
|
|
- masterStatusService = JavaHelper.makeSingleThreadExecutorService(pwmApplication, DatabaseAccessorImpl.class);
|
|
|
- }
|
|
|
-
|
|
|
- public List<HealthRecord> healthCheck() {
|
|
|
- if (status == PwmService.STATUS.CLOSED) {
|
|
|
- return Collections.emptyList();
|
|
|
- }
|
|
|
-
|
|
|
- final List<HealthRecord> returnRecords = new ArrayList<>();
|
|
|
-
|
|
|
- try {
|
|
|
- preOperationCheck();
|
|
|
- } catch (DatabaseException e) {
|
|
|
- lastError = e.getErrorInformation();
|
|
|
- returnRecords.add(new HealthRecord(HealthStatus.WARN, HealthTopic.Database, "Database server is not available: " + e.getErrorInformation().toDebugStr()));
|
|
|
- return returnRecords;
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- final Map<String,String> tempMap = new HashMap<>();
|
|
|
- tempMap.put("instance",instanceID);
|
|
|
- tempMap.put("date",(new java.util.Date()).toString());
|
|
|
- this.put(DatabaseTable.PWM_META, DatabaseAccessorImpl.KEY_TEST, JsonUtil.serializeMap(tempMap));
|
|
|
- } catch (PwmException e) {
|
|
|
- returnRecords.add(new HealthRecord(HealthStatus.WARN, HealthTopic.Database, "Error writing to database: " + e.getErrorInformation().toDebugStr()));
|
|
|
- return returnRecords;
|
|
|
- }
|
|
|
-
|
|
|
- if (lastError != null) {
|
|
|
- final TimeDuration errorAge = TimeDuration.fromCurrent(lastError.getDate());
|
|
|
-
|
|
|
- if (errorAge.isShorterThan(TimeDuration.HOUR)) {
|
|
|
- final String msg = "Database server was recently unavailable ("
|
|
|
- + errorAge.asLongString(PwmConstants.DEFAULT_LOCALE)
|
|
|
- + " ago at " + lastError.getDate().toString()+ "): " + lastError.toDebugStr();
|
|
|
- returnRecords.add(new HealthRecord(HealthStatus.CAUTION, HealthTopic.Database, msg));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (returnRecords.isEmpty()) {
|
|
|
- returnRecords.add(new HealthRecord(HealthStatus.GOOD, HealthTopic.Database, "Database connection to " + this.dbConfiguration.getConnectionString() + " okay"));
|
|
|
- }
|
|
|
-
|
|
|
- return returnRecords;
|
|
|
+ final DatabaseException databaseException = DatabaseUtil.convertSqlException(debugInfo, e);
|
|
|
+ databaseService.setLastError(databaseException.getErrorInformation());
|
|
|
+ throw databaseException;
|
|
|
}
|
|
|
|
|
|
-// -------------------------- OTHER METHODS --------------------------
|
|
|
|
|
|
- private synchronized void init()
|
|
|
+ @Override
|
|
|
+ public boolean put(
|
|
|
+ final DatabaseTable table,
|
|
|
+ final String key,
|
|
|
+ final String value
|
|
|
+ )
|
|
|
throws DatabaseException
|
|
|
{
|
|
|
- status = PwmService.STATUS.OPENING;
|
|
|
- final Instant startTime = Instant.now();
|
|
|
- LOGGER.debug("opening connection to database " + this.dbConfiguration.getConnectionString());
|
|
|
+ final DatabaseUtil.DebugInfo debugInfo = DatabaseUtil.DebugInfo.create("put", table, key, value);
|
|
|
|
|
|
- connection = openDB(dbConfiguration);
|
|
|
- for (final DatabaseTable table : DatabaseTable.values()) {
|
|
|
- initTable(connection, table, dbConfiguration);
|
|
|
- }
|
|
|
-
|
|
|
- status = PwmService.STATUS.OPEN;
|
|
|
-
|
|
|
- try {
|
|
|
- put(DatabaseTable.PWM_META, KEY_ENGINE_START_PREFIX + instanceID, JavaHelper.toIsoDate(new java.util.Date()));
|
|
|
- } catch (DatabaseException e) {
|
|
|
- final String errorMsg = "error writing engine start time value: " + e.getMessage();
|
|
|
- throw new DatabaseException(new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE,errorMsg));
|
|
|
- }
|
|
|
-
|
|
|
- LOGGER.debug("successfully connected to remote database (" + TimeDuration.fromCurrent(startTime).asCompactString() + ")");
|
|
|
- }
|
|
|
-
|
|
|
- private Connection openDB(final DBConfiguration dbConfiguration) throws DatabaseException {
|
|
|
- final String connectionURL = dbConfiguration.getConnectionString();
|
|
|
-
|
|
|
- final JDBCDriverLoader.DriverWrapper wrapper = JDBCDriverLoader.loadDriver(pwmApplication, dbConfiguration);
|
|
|
- driver = wrapper.getDriver();
|
|
|
- jdbcDriverLoader = wrapper.getDriverLoader();
|
|
|
-
|
|
|
- try {
|
|
|
- LOGGER.debug("initiating connecting to database " + connectionURL);
|
|
|
- final Properties connectionProperties = new Properties();
|
|
|
- if (dbConfiguration.getUsername() != null && !dbConfiguration.getUsername().isEmpty()) {
|
|
|
- connectionProperties.setProperty("user", dbConfiguration.getUsername());
|
|
|
- }
|
|
|
- if (dbConfiguration.getPassword() != null) {
|
|
|
- connectionProperties.setProperty("password", dbConfiguration.getPassword().getStringValue());
|
|
|
- }
|
|
|
- final Connection connection = driver.connect(connectionURL, connectionProperties);
|
|
|
-
|
|
|
-
|
|
|
- final Map<PwmAboutProperty,String> debugProps = getConnectionDebugProperties(connection);
|
|
|
- LOGGER.debug("connected to database " + connectionURL + ", properties: " + JsonUtil.serializeMap(debugProps));
|
|
|
-
|
|
|
- connection.setAutoCommit(true);
|
|
|
- return connection;
|
|
|
- } catch (Throwable e) {
|
|
|
- final String errorMsg = "error connecting to database: " + JavaHelper.readHostileExceptionMessage(e);
|
|
|
- final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE,errorMsg);
|
|
|
- LOGGER.error(errorInformation);
|
|
|
- throw new DatabaseException(errorInformation);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private static void initTable(final Connection connection, final DatabaseTable table, final DBConfiguration dbConfiguration) throws DatabaseException {
|
|
|
- boolean tableExists = false;
|
|
|
- try {
|
|
|
- checkIfTableExists(connection, table);
|
|
|
- LOGGER.trace("table " + table + " appears to exist");
|
|
|
- tableExists = true;
|
|
|
- } catch (SQLException e) { // assume error was due to table missing;
|
|
|
- LOGGER.trace("error while checking for table: " + e.getMessage() + ", assuming due to table non-existence");
|
|
|
- }
|
|
|
-
|
|
|
- if (!tableExists) {
|
|
|
- createTable(connection, table, dbConfiguration);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private static void createTable(final Connection connection, final DatabaseTable table, final DBConfiguration dbConfiguration) throws DatabaseException {
|
|
|
- {
|
|
|
- final StringBuilder sqlString = new StringBuilder();
|
|
|
- sqlString.append("CREATE table ").append(table.toString()).append(" (").append("\n");
|
|
|
- sqlString.append(" " + KEY_COLUMN + " ").append(dbConfiguration.getColumnTypeKey()).append("(").append(
|
|
|
- KEY_COLUMN_LENGTH).append(") NOT NULL PRIMARY KEY,").append("\n");
|
|
|
- sqlString.append(" " + VALUE_COLUMN + " ").append(dbConfiguration.getColumnTypeValue()).append(" ");
|
|
|
- sqlString.append("\n");
|
|
|
- sqlString.append(")").append("\n");
|
|
|
-
|
|
|
- LOGGER.trace("attempting to execute the following sql statement:\n " + sqlString.toString());
|
|
|
-
|
|
|
- Statement statement = null;
|
|
|
+ return execute(debugInfo, () -> {
|
|
|
+ boolean exists = false;
|
|
|
try {
|
|
|
- statement = connection.createStatement();
|
|
|
- statement.execute(sqlString.toString());
|
|
|
- LOGGER.debug("created table " + table.toString());
|
|
|
- } catch (SQLException ex) {
|
|
|
- final String errorMsg = "error creating new table " + table.toString() + ": " + ex.getMessage();
|
|
|
- final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE, errorMsg);
|
|
|
- throw new DatabaseException(errorInformation);
|
|
|
- } finally {
|
|
|
- close(statement);
|
|
|
+ exists = containsImpl(table, key);
|
|
|
+ } catch (SQLException e) {
|
|
|
+ processSqlException(debugInfo, e);
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- {
|
|
|
- final String indexName = table.toString() + "_IDX";
|
|
|
- final StringBuilder sqlString = new StringBuilder();
|
|
|
- sqlString.append("CREATE index ").append(indexName);
|
|
|
- sqlString.append(" ON ").append(table.toString());
|
|
|
- sqlString.append(" (").append(KEY_COLUMN).append(")");
|
|
|
- Statement statement = null;
|
|
|
|
|
|
- LOGGER.trace("attempting to execute the following sql statement:\n " + sqlString.toString());
|
|
|
-
|
|
|
- try {
|
|
|
- statement = connection.createStatement();
|
|
|
- statement.execute(sqlString.toString());
|
|
|
- LOGGER.debug("created index " + indexName);
|
|
|
- } catch (SQLException ex) {
|
|
|
- final String errorMsg = "error creating new index " + indexName + ": " + ex.getMessage();
|
|
|
- final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE, errorMsg);
|
|
|
- throw new DatabaseException(errorInformation);
|
|
|
- } finally {
|
|
|
- close(statement);
|
|
|
+ final String sqlText;
|
|
|
+ if (!exists) {
|
|
|
+ sqlText = "INSERT INTO " + table.toString() + "(" + DatabaseService.KEY_COLUMN + ", " + DatabaseService.VALUE_COLUMN + ") VALUES(?,?)";
|
|
|
+ } else {
|
|
|
+ sqlText = "UPDATE " + table.toString() + " SET " + DatabaseService.VALUE_COLUMN + "=? WHERE " + DatabaseService.KEY_COLUMN + "=?";
|
|
|
}
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
- private static void checkIfTableExists(final Connection connection, final DatabaseTable table) throws SQLException {
|
|
|
- final StringBuilder sb = new StringBuilder();
|
|
|
- sb.append("SELECT * FROM ").append(table.toString()).append(" WHERE " + KEY_COLUMN + " = '0'");
|
|
|
- Statement statement = null;
|
|
|
- ResultSet resultSet = null;
|
|
|
- try {
|
|
|
- statement = connection.createStatement();
|
|
|
- resultSet = statement.executeQuery(sb.toString());
|
|
|
- } finally {
|
|
|
- close(statement);
|
|
|
- close(resultSet);
|
|
|
- }
|
|
|
+ executeUpdate(sqlText, debugInfo, key, value);
|
|
|
+ return !exists;
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
+
|
|
|
@Override
|
|
|
- public boolean put(
|
|
|
+ public boolean putIfAbsent(
|
|
|
final DatabaseTable table,
|
|
|
final String key,
|
|
|
final String value
|
|
|
)
|
|
|
- throws DatabaseException {
|
|
|
-
|
|
|
- preOperationCheck();
|
|
|
- if (traceLogging) {
|
|
|
- LOGGER.trace("attempting put operation for table=" + table + ", key=" + key);
|
|
|
- }
|
|
|
- if (!contains(table, key)) {
|
|
|
- final String sqlText = "INSERT INTO " + table.toString() + "(" + KEY_COLUMN + ", " + VALUE_COLUMN + ") VALUES(?,?)";
|
|
|
- PreparedStatement statement = null;
|
|
|
+ throws DatabaseException
|
|
|
+ {
|
|
|
+ final DatabaseUtil.DebugInfo debugInfo = DatabaseUtil.DebugInfo.create("putIfAbsent", table, key, value);
|
|
|
|
|
|
+ return execute(debugInfo, () -> {
|
|
|
+ boolean valueExists = false;
|
|
|
try {
|
|
|
- statement = connection.prepareStatement(sqlText);
|
|
|
- statement.setString(1, key);
|
|
|
- statement.setString(2, value);
|
|
|
- statement.executeUpdate();
|
|
|
- } catch (SQLException e) {
|
|
|
- final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE,"put operation failed: " + e.getMessage());
|
|
|
- lastError = errorInformation;
|
|
|
- throw new DatabaseException(errorInformation);
|
|
|
- } finally {
|
|
|
- close(statement);
|
|
|
+ valueExists = containsImpl(table, key);
|
|
|
+ } catch (final SQLException e) {
|
|
|
+ processSqlException(debugInfo, e);
|
|
|
}
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- final String sqlText = "UPDATE " + table.toString() + " SET " + VALUE_COLUMN + "=? WHERE " + KEY_COLUMN + "=?";
|
|
|
- PreparedStatement statement = null;
|
|
|
-
|
|
|
- try {
|
|
|
- statement = connection.prepareStatement(sqlText);
|
|
|
- statement.setString(1, value);
|
|
|
- statement.setString(2, key);
|
|
|
- statement.executeUpdate();
|
|
|
- } catch (SQLException e) {
|
|
|
- final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE,"put operation failed: " + e.getMessage());
|
|
|
- lastError = errorInformation;
|
|
|
- throw new DatabaseException(errorInformation);
|
|
|
- } finally {
|
|
|
- close(statement);
|
|
|
- }
|
|
|
-
|
|
|
- if (traceLogging) {
|
|
|
- final Map<String,Object> debugOutput = new LinkedHashMap<>();
|
|
|
- debugOutput.put("table",table);
|
|
|
- debugOutput.put("key",key);
|
|
|
- debugOutput.put("value",value);
|
|
|
- LOGGER.trace("put operation result: " + JsonUtil.serializeMap(debugOutput, JsonUtil.Flag.PrettyPrint));
|
|
|
- }
|
|
|
-
|
|
|
- updateStats(false,true);
|
|
|
- return true;
|
|
|
- }
|
|
|
|
|
|
- private synchronized void preOperationCheck() throws DatabaseException {
|
|
|
- if (status == PwmService.STATUS.CLOSED) {
|
|
|
- throw new DatabaseException(new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE,"database connection is not open"));
|
|
|
- }
|
|
|
-
|
|
|
- if (status == PwmService.STATUS.NEW) {
|
|
|
- init();
|
|
|
- }
|
|
|
-
|
|
|
- if (!isValid(connection)) {
|
|
|
- init();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private boolean isValid(final Connection connection) {
|
|
|
- if (connection == null) {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- if (status != PwmService.STATUS.OPEN) {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- final Method getFreeSpaceMethod = File.class.getMethod("isValid");
|
|
|
- final Object rawResult = getFreeSpaceMethod.invoke(connection,10);
|
|
|
- return (Boolean) rawResult;
|
|
|
- } catch (NoSuchMethodException e) {
|
|
|
- /* no error, pre java 1.6 doesn't have this method */
|
|
|
- } catch (Exception e) {
|
|
|
- LOGGER.debug("error checking for isValid for " + connection.toString() + ",: " + e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- final StringBuilder sb = new StringBuilder();
|
|
|
- sb.append("SELECT * FROM ").append(DatabaseTable.PWM_META.toString()).append(" WHERE " + KEY_COLUMN + " = ?");
|
|
|
- PreparedStatement statement = null;
|
|
|
- ResultSet resultSet = null;
|
|
|
- try {
|
|
|
- statement = connection.prepareStatement(sb.toString());
|
|
|
- statement.setString(1, KEY_ENGINE_START_PREFIX + instanceID);
|
|
|
- statement.setMaxRows(1);
|
|
|
- resultSet = statement.executeQuery();
|
|
|
- if (resultSet.next()) {
|
|
|
- resultSet.getString(VALUE_COLUMN);
|
|
|
+ if (!valueExists) {
|
|
|
+ final String insertSql = "INSERT INTO " + table.name() + "(" + DatabaseService.KEY_COLUMN + ", " + DatabaseService.VALUE_COLUMN + ") VALUES(?,?)";
|
|
|
+ executeUpdate(insertSql, debugInfo, key, value);
|
|
|
}
|
|
|
- } catch (SQLException e) {
|
|
|
- final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE,"isValid operation failed: " + e.getMessage());
|
|
|
- lastError = errorInformation;
|
|
|
- LOGGER.error(errorInformation.toDebugStr());
|
|
|
- return false;
|
|
|
- } finally {
|
|
|
- close(statement);
|
|
|
- close(resultSet);
|
|
|
- }
|
|
|
- return true;
|
|
|
- }
|
|
|
|
|
|
- private static void close(final Statement statement) {
|
|
|
- if (statement != null) {
|
|
|
- try {
|
|
|
- statement.close();
|
|
|
- } catch (SQLException e) {
|
|
|
- LOGGER.error("unexpected error during close statement object " + e.getMessage(), e);
|
|
|
- }
|
|
|
- }
|
|
|
+ return !valueExists;
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
- private static void close(final ResultSet resultSet) {
|
|
|
- if (resultSet != null) {
|
|
|
- try {
|
|
|
- resultSet.close();
|
|
|
- } catch (SQLException e) {
|
|
|
- LOGGER.error("unexpected error during close resultSet object " + e.getMessage(), e);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
@Override
|
|
|
public boolean contains(
|
|
@@ -476,16 +142,17 @@ public class DatabaseAccessorImpl implements PwmService, DatabaseAccessor {
|
|
|
)
|
|
|
throws DatabaseException
|
|
|
{
|
|
|
- final boolean result = get(table, key) != null;
|
|
|
- if (traceLogging) {
|
|
|
- final Map<String,Object> debugOutput = new LinkedHashMap<>();
|
|
|
- debugOutput.put("table",table);
|
|
|
- debugOutput.put("key",key);
|
|
|
- debugOutput.put("result",result);
|
|
|
- LOGGER.trace("contains operation result: " + JsonUtil.serializeMap(debugOutput, JsonUtil.Flag.PrettyPrint));
|
|
|
- }
|
|
|
- updateStats(true,false);
|
|
|
- return result;
|
|
|
+ final DatabaseUtil.DebugInfo debugInfo = DatabaseUtil.DebugInfo.create("contains", table, key, null);
|
|
|
+
|
|
|
+ return execute(debugInfo, () -> {
|
|
|
+ boolean valueExists = false;
|
|
|
+ try {
|
|
|
+ valueExists = containsImpl(table, key);
|
|
|
+ } catch (final SQLException e) {
|
|
|
+ processSqlException(debugInfo, e);
|
|
|
+ }
|
|
|
+ return valueExists;
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -495,137 +162,114 @@ public class DatabaseAccessorImpl implements PwmService, DatabaseAccessor {
|
|
|
)
|
|
|
throws DatabaseException
|
|
|
{
|
|
|
- if (traceLogging) {
|
|
|
- LOGGER.trace("attempting get operation for table=" + table + ", key=" + key);
|
|
|
- }
|
|
|
- preOperationCheck();
|
|
|
- final StringBuilder sb = new StringBuilder();
|
|
|
- sb.append("SELECT * FROM ").append(table.toString()).append(" WHERE " + KEY_COLUMN + " = ?");
|
|
|
-
|
|
|
- PreparedStatement statement = null;
|
|
|
- ResultSet resultSet = null;
|
|
|
- String returnValue = null;
|
|
|
- try {
|
|
|
- statement = connection.prepareStatement(sb.toString());
|
|
|
- statement.setString(1, key);
|
|
|
- statement.setMaxRows(1);
|
|
|
- resultSet = statement.executeQuery();
|
|
|
+ final DatabaseUtil.DebugInfo debugInfo = DatabaseUtil.DebugInfo.create("get", table, key, null);
|
|
|
|
|
|
- if (resultSet.next()) {
|
|
|
- returnValue = resultSet.getString(VALUE_COLUMN);
|
|
|
- }
|
|
|
- } catch (SQLException e) {
|
|
|
- final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE,"get operation failed: " + e.getMessage());
|
|
|
- lastError = errorInformation;
|
|
|
- throw new DatabaseException(errorInformation);
|
|
|
- } finally {
|
|
|
- close(statement);
|
|
|
- close(resultSet);
|
|
|
- }
|
|
|
+ return execute(debugInfo, () -> {
|
|
|
+ final String sqlStatement = "SELECT * FROM " + table.name() + " WHERE " + DatabaseService.KEY_COLUMN + " = ?";
|
|
|
|
|
|
- if (traceLogging) {
|
|
|
- final LinkedHashMap<String,Object> debugOutput = new LinkedHashMap<>();
|
|
|
- debugOutput.put("table",table);
|
|
|
- debugOutput.put("key",key);
|
|
|
- debugOutput.put("result",returnValue);
|
|
|
- LOGGER.trace("get operation result: " + JsonUtil.serializeMap(debugOutput, JsonUtil.Flag.PrettyPrint));
|
|
|
- }
|
|
|
+ try (PreparedStatement statement = connection.prepareStatement(sqlStatement)) {
|
|
|
+ statement.setString(1, key);
|
|
|
+ statement.setMaxRows(1);
|
|
|
|
|
|
- updateStats(true,false);
|
|
|
- return returnValue;
|
|
|
+ try (ResultSet resultSet= statement.executeQuery()) {
|
|
|
+ if (resultSet.next()) {
|
|
|
+ return resultSet.getString(DatabaseService.VALUE_COLUMN);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (SQLException e) {
|
|
|
+ processSqlException(debugInfo, e);
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public ClosableIterator<String> iterator(final DatabaseTable table)
|
|
|
throws DatabaseException
|
|
|
{
|
|
|
- preOperationCheck();
|
|
|
- return new DBIterator(table);
|
|
|
+ try {
|
|
|
+ LOCK.lock();
|
|
|
+ return new DBIterator(table);
|
|
|
+ } finally {
|
|
|
+ LOCK.unlock();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public boolean remove(
|
|
|
+ public void remove(
|
|
|
final DatabaseTable table,
|
|
|
final String key
|
|
|
)
|
|
|
throws DatabaseException
|
|
|
{
|
|
|
- if (traceLogging) {
|
|
|
- LOGGER.trace("attempting remove operation for table=" + table + ", key=" + key);
|
|
|
- }
|
|
|
+ final DatabaseUtil.DebugInfo debugInfo = DatabaseUtil.DebugInfo.create("remove", table, key, null);
|
|
|
|
|
|
- final boolean result = contains(table, key);
|
|
|
- if (result) {
|
|
|
- final StringBuilder sqlText = new StringBuilder();
|
|
|
- sqlText.append("DELETE FROM ").append(table.toString()).append(" WHERE " + KEY_COLUMN + "=?");
|
|
|
+ execute(debugInfo, () -> {
|
|
|
|
|
|
- PreparedStatement statement = null;
|
|
|
- try {
|
|
|
- statement = connection.prepareStatement(sqlText.toString());
|
|
|
- statement.setString(1, key);
|
|
|
- statement.executeUpdate();
|
|
|
- LOGGER.trace("remove operation succeeded for table=" + table + ", key=" + key);
|
|
|
- } catch (SQLException e) {
|
|
|
- final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE,"remove operation failed: " + e.getMessage());
|
|
|
- lastError = errorInformation;
|
|
|
- throw new DatabaseException(errorInformation);
|
|
|
- } finally {
|
|
|
- close(statement);
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
- if (traceLogging) {
|
|
|
- final Map<String,Object> debugOutput = new LinkedHashMap<>();
|
|
|
- debugOutput.put("table",table);
|
|
|
- debugOutput.put("key",key);
|
|
|
- debugOutput.put("result",result);
|
|
|
- LOGGER.trace("remove operation result: " + JsonUtil.serializeMap(debugOutput, JsonUtil.Flag.PrettyPrint));
|
|
|
- }
|
|
|
+ final String sqlText = "DELETE FROM " + table.name() + " WHERE " + DatabaseService.KEY_COLUMN + "=?";
|
|
|
+ executeUpdate(sqlText, debugInfo, key);
|
|
|
|
|
|
- updateStats(true, false);
|
|
|
- return result;
|
|
|
+ return null;
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public int size(final DatabaseTable table) throws
|
|
|
- DatabaseException {
|
|
|
- preOperationCheck();
|
|
|
+ DatabaseException
|
|
|
+ {
|
|
|
+ final DatabaseUtil.DebugInfo debugInfo = DatabaseUtil.DebugInfo.create("size", table, null, null);
|
|
|
|
|
|
- final StringBuilder sb = new StringBuilder();
|
|
|
- sb.append("SELECT COUNT(" + KEY_COLUMN + ") FROM ").append(table.toString());
|
|
|
+ return execute(debugInfo, () -> {
|
|
|
+ final String sqlStatement = "SELECT COUNT(" + DatabaseService.KEY_COLUMN + ") FROM " + table.name();
|
|
|
+
|
|
|
+
|
|
|
+ try (PreparedStatement statement = connection.prepareStatement(sqlStatement)) {
|
|
|
+ try (ResultSet resultSet = statement.executeQuery()) {
|
|
|
+ if (resultSet.next()) {
|
|
|
+ return resultSet.getInt(1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (SQLException e) {
|
|
|
+ processSqlException(debugInfo, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ boolean isValid() {
|
|
|
+ if (connection == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
|
|
|
- PreparedStatement statement = null;
|
|
|
- ResultSet resultSet = null;
|
|
|
try {
|
|
|
- statement = connection.prepareStatement(sb.toString());
|
|
|
- resultSet = statement.executeQuery();
|
|
|
- if (resultSet.next()) {
|
|
|
- return resultSet.getInt(1);
|
|
|
+ if (connection.isClosed()) {
|
|
|
+ return false;
|
|
|
}
|
|
|
+
|
|
|
+ final int connectionTimeout = dbConfiguration.getConnectionTimeout();
|
|
|
+
|
|
|
+ if (!connection.isValid(connectionTimeout)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
} catch (SQLException e) {
|
|
|
- final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE,"size operation failed: " + e.getMessage());
|
|
|
- lastError = errorInformation;
|
|
|
- throw new DatabaseException(errorInformation);
|
|
|
- } finally {
|
|
|
- close(statement);
|
|
|
- close(resultSet);
|
|
|
+ LOGGER.debug("error while checking connection validity: " + e.getMessage());
|
|
|
}
|
|
|
|
|
|
- updateStats(true,false);
|
|
|
- return 0;
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
-// -------------------------- ENUMERATIONS --------------------------
|
|
|
|
|
|
- // -------------------------- INNER CLASSES --------------------------
|
|
|
|
|
|
public class DBIterator implements ClosableIterator<String> {
|
|
|
private final DatabaseTable table;
|
|
|
private final ResultSet resultSet;
|
|
|
- private java.lang.String nextValue;
|
|
|
+ private String nextValue;
|
|
|
private boolean finished;
|
|
|
|
|
|
- public DBIterator(final DatabaseTable table)
|
|
|
+ DBIterator(final DatabaseTable table)
|
|
|
throws DatabaseException
|
|
|
{
|
|
|
this.table = table;
|
|
@@ -634,24 +278,24 @@ public class DatabaseAccessorImpl implements PwmService, DatabaseAccessor {
|
|
|
}
|
|
|
|
|
|
private ResultSet init() throws DatabaseException {
|
|
|
- final StringBuilder sb = new StringBuilder();
|
|
|
- sb.append("SELECT " + KEY_COLUMN + " FROM ").append(table.toString());
|
|
|
+ final String sqlText = "SELECT " + DatabaseService.KEY_COLUMN + " FROM " + table.name();
|
|
|
|
|
|
try {
|
|
|
- final PreparedStatement statement = connection.prepareStatement(sb.toString());
|
|
|
- return statement.executeQuery();
|
|
|
+ final PreparedStatement statement = connection.prepareStatement(sqlText);
|
|
|
+ final ResultSet resultSet = statement.executeQuery();
|
|
|
+ connection.commit();
|
|
|
+ return resultSet;
|
|
|
} catch (SQLException e) {
|
|
|
- final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE,"get iterator failed: " + e.getMessage());
|
|
|
- lastError = errorInformation;
|
|
|
- throw new DatabaseException(errorInformation);
|
|
|
+ processSqlException(null, e);
|
|
|
}
|
|
|
+ return null; // unreachable
|
|
|
}
|
|
|
|
|
|
public boolean hasNext() {
|
|
|
return !finished;
|
|
|
}
|
|
|
|
|
|
- public java.lang.String next() {
|
|
|
+ public String next() {
|
|
|
if (finished) {
|
|
|
throw new IllegalStateException("iterator completed");
|
|
|
}
|
|
@@ -667,7 +311,7 @@ public class DatabaseAccessorImpl implements PwmService, DatabaseAccessor {
|
|
|
private void getNextItem() {
|
|
|
try {
|
|
|
if (resultSet.next()) {
|
|
|
- nextValue = resultSet.getString(KEY_COLUMN);
|
|
|
+ nextValue = resultSet.getString(DatabaseService.KEY_COLUMN);
|
|
|
} else {
|
|
|
close();
|
|
|
}
|
|
@@ -675,7 +319,7 @@ public class DatabaseAccessorImpl implements PwmService, DatabaseAccessor {
|
|
|
finished = true;
|
|
|
LOGGER.warn("unexpected error during result set iteration: " + e.getMessage());
|
|
|
}
|
|
|
- updateStats(true,false);
|
|
|
+ databaseService.updateStats(DatabaseService.OperationType.READ);
|
|
|
}
|
|
|
|
|
|
public void close() {
|
|
@@ -690,63 +334,96 @@ public class DatabaseAccessorImpl implements PwmService, DatabaseAccessor {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public ServiceInfo serviceInfo()
|
|
|
- {
|
|
|
- if (status() == STATUS.OPEN) {
|
|
|
- return new ServiceInfo(Collections.singletonList(DataStorageMethod.DB));
|
|
|
- } else {
|
|
|
- return new ServiceInfo(Collections.emptyList());
|
|
|
+ private void traceBegin(final DatabaseUtil.DebugInfo debugInfo) {
|
|
|
+ if (!traceLogEnabled) {
|
|
|
+ return;
|
|
|
}
|
|
|
+
|
|
|
+ LOGGER.trace("begin operation: " + StringUtil.mapToString(JsonUtil.deserializeStringMap(JsonUtil.serialize(debugInfo))));
|
|
|
}
|
|
|
|
|
|
- private void updateStats(final boolean readOperation, final boolean writeOperation) {
|
|
|
- if (pwmApplication != null && pwmApplication.getApplicationMode() == PwmApplicationMode.RUNNING) {
|
|
|
- final StatisticsManager statisticsManager = pwmApplication.getStatisticsManager();
|
|
|
- if (statisticsManager != null && statisticsManager.status() == STATUS.OPEN) {
|
|
|
- if (readOperation) {
|
|
|
- statisticsManager.updateEps(Statistic.EpsType.DB_READS,1);
|
|
|
- }
|
|
|
- if (writeOperation) {
|
|
|
- statisticsManager.updateEps(Statistic.EpsType.DB_WRITES,1);
|
|
|
- }
|
|
|
- }
|
|
|
+ private void traceResult(
|
|
|
+ final DatabaseUtil.DebugInfo debugInfo,
|
|
|
+ final Object result
|
|
|
+ ) {
|
|
|
+ if (!traceLogEnabled) {
|
|
|
+ return;
|
|
|
}
|
|
|
+
|
|
|
+ final Map<String,String> map = JsonUtil.deserializeStringMap(JsonUtil.serialize(debugInfo));
|
|
|
+ map.put("duration", TimeDuration.fromCurrent(debugInfo.getStartTime()).asCompactString());
|
|
|
+ if (result != null) {
|
|
|
+ map.put("result", String.valueOf(result));
|
|
|
+ }
|
|
|
+ LOGGER.trace("operation result: " + StringUtil.mapToString(map));
|
|
|
}
|
|
|
|
|
|
+ private interface SqlFunction<T> {
|
|
|
+ T execute() throws DatabaseException;
|
|
|
+ }
|
|
|
|
|
|
- @Override
|
|
|
- public Map<PwmAboutProperty,String> getConnectionDebugProperties() {
|
|
|
- return getConnectionDebugProperties(connection);
|
|
|
+ private <T> T execute(final DatabaseUtil.DebugInfo debugInfo, final SqlFunction<T> sqlFunction) throws DatabaseException
|
|
|
+ {
|
|
|
+ traceBegin(debugInfo);
|
|
|
+
|
|
|
+ try {
|
|
|
+ LOCK.lock();
|
|
|
+
|
|
|
+ try {
|
|
|
+ final T result = sqlFunction.execute();
|
|
|
+ traceResult(debugInfo, result);
|
|
|
+ databaseService.updateStats(DatabaseService.OperationType.WRITE);
|
|
|
+ return result;
|
|
|
+ } finally {
|
|
|
+ DatabaseUtil.commit(connection);
|
|
|
+ }
|
|
|
+
|
|
|
+ } finally {
|
|
|
+ LOCK.unlock();
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ Connection getConnection()
|
|
|
+ {
|
|
|
+ return connection;
|
|
|
}
|
|
|
|
|
|
- private static Map<PwmAboutProperty,String> getConnectionDebugProperties(final Connection connection) {
|
|
|
- if (connection != null) {
|
|
|
+ void close() {
|
|
|
+ try {
|
|
|
+ LOCK.lock();
|
|
|
try {
|
|
|
- final Map<PwmAboutProperty,String> returnObj = new LinkedHashMap<>();
|
|
|
- final DatabaseMetaData databaseMetaData = connection.getMetaData();
|
|
|
- returnObj.put(PwmAboutProperty.database_driverName, databaseMetaData.getDriverName());
|
|
|
- returnObj.put(PwmAboutProperty.database_driverVersion, databaseMetaData.getDriverVersion());
|
|
|
- returnObj.put(PwmAboutProperty.database_databaseProductName, databaseMetaData.getDatabaseProductName());
|
|
|
- returnObj.put(PwmAboutProperty.database_databaseProductVersion, databaseMetaData.getDatabaseProductVersion());
|
|
|
- return Collections.unmodifiableMap(returnObj);
|
|
|
+ connection.close();
|
|
|
} catch (SQLException e) {
|
|
|
- LOGGER.error("error reading jdbc meta data: " + e.getMessage());
|
|
|
+ LOGGER.warn("error while closing connection: " + e.getMessage());
|
|
|
}
|
|
|
+ } finally {
|
|
|
+ LOCK.unlock();
|
|
|
}
|
|
|
- return Collections.emptyMap();
|
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
- public boolean isMasterServer()
|
|
|
+ private boolean containsImpl(final DatabaseTable table, final String key) throws SQLException
|
|
|
{
|
|
|
- return false;
|
|
|
- }
|
|
|
+ final String selectSql = "SELECT * FROM " + table.name() + " WHERE " + DatabaseService.KEY_COLUMN + " = ?";
|
|
|
+ try (PreparedStatement selectStatement = connection.prepareStatement(selectSql);) {
|
|
|
+ selectStatement.setString(1, key);
|
|
|
+ selectStatement.setMaxRows(1);
|
|
|
|
|
|
- private class MasterCheckTask implements Runnable {
|
|
|
- @Override
|
|
|
- public void run()
|
|
|
- {
|
|
|
+ try (ResultSet resultSet = selectStatement.executeQuery()) {
|
|
|
+ return resultSet.next();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
+ private void executeUpdate(final String sqlStatement, final DatabaseUtil.DebugInfo debugInfo, final String... params) throws DatabaseException
|
|
|
+ {
|
|
|
+ try (PreparedStatement statement = connection.prepareStatement(sqlStatement)){
|
|
|
+ for (int i = 0; i < params.length; i++) {
|
|
|
+ statement.setString(i + 1, params[i]);
|
|
|
+ }
|
|
|
+ statement.executeUpdate();
|
|
|
+ } catch (SQLException e) {
|
|
|
+ processSqlException(debugInfo, e);
|
|
|
}
|
|
|
}
|
|
|
}
|