#1539 DataSource columns: close MySQL connections, add missing columns, use newly built-in types, improve column initialization

This commit is contained in:
ljacqu
2018-03-24 21:16:43 +01:00
parent 5a58f2c44f
commit 881ef6a640
10 changed files with 238 additions and 142 deletions
@@ -0,0 +1,117 @@
package fr.xephi.authme.datasource.columnshandler;
import ch.jalu.configme.properties.Property;
import ch.jalu.datasourcecolumns.ColumnType;
import ch.jalu.datasourcecolumns.DependentColumn;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.settings.properties.DatabaseSettings;
import java.util.function.Function;
import static fr.xephi.authme.datasource.columnshandler.AuthMeColumnsFactory.ColumnOptions.DEFAULT_FOR_NULL;
import static fr.xephi.authme.datasource.columnshandler.AuthMeColumnsFactory.ColumnOptions.OPTIONAL;
import static fr.xephi.authme.datasource.columnshandler.AuthMeColumnsFactory.createDouble;
import static fr.xephi.authme.datasource.columnshandler.AuthMeColumnsFactory.createFloat;
import static fr.xephi.authme.datasource.columnshandler.AuthMeColumnsFactory.createInteger;
import static fr.xephi.authme.datasource.columnshandler.AuthMeColumnsFactory.createLong;
import static fr.xephi.authme.datasource.columnshandler.AuthMeColumnsFactory.createString;
/**
* Column definitions for the AuthMe table.
*
* @param <T> the column type
* @see PlayerAuth
*/
public final class AuthMeColumns<T> implements DependentColumn<T, ColumnContext, PlayerAuth> {
public static final AuthMeColumns<String> NAME = createString(
DatabaseSettings.MYSQL_COL_NAME, PlayerAuth::getNickname);
public static final AuthMeColumns<String> NICK_NAME = createString(
DatabaseSettings.MYSQL_COL_REALNAME, PlayerAuth::getRealName);
public static final AuthMeColumns<String> PASSWORD = createString(
DatabaseSettings.MYSQL_COL_PASSWORD, auth -> auth.getPassword().getHash());
public static final AuthMeColumns<String> SALT = createString(
DatabaseSettings.MYSQL_COL_SALT, auth -> auth.getPassword().getSalt(), OPTIONAL);
public static final AuthMeColumns<String> EMAIL = createString(
DatabaseSettings.MYSQL_COL_EMAIL, PlayerAuth::getEmail, DEFAULT_FOR_NULL);
public static final AuthMeColumns<String> LAST_IP = createString(
DatabaseSettings.MYSQL_COL_LAST_IP, PlayerAuth::getLastIp);
public static final AuthMeColumns<Integer> GROUP_ID = createInteger(
DatabaseSettings.MYSQL_COL_GROUP, PlayerAuth::getGroupId, OPTIONAL);
public static final AuthMeColumns<String> REGISTRATION_IP = createString(
DatabaseSettings.MYSQL_COL_REGISTER_IP, PlayerAuth::getRegistrationIp);
public static final AuthMeColumns<Long> REGISTRATION_DATE = createLong(
DatabaseSettings.MYSQL_COL_REGISTER_DATE, PlayerAuth::getRegistrationDate);
public static final AuthMeColumns<Double> LOCATION_X = createDouble(
DatabaseSettings.MYSQL_COL_LASTLOC_X, PlayerAuth::getQuitLocX);
public static final AuthMeColumns<Double> LOCATION_Y = createDouble(
DatabaseSettings.MYSQL_COL_LASTLOC_Y, PlayerAuth::getQuitLocY);
public static final AuthMeColumns<Double> LOCATION_Z = createDouble(
DatabaseSettings.MYSQL_COL_LASTLOC_Z, PlayerAuth::getQuitLocZ);
public static final AuthMeColumns<String> LOCATION_WORLD = createString(
DatabaseSettings.MYSQL_COL_LASTLOC_WORLD, PlayerAuth::getWorld);
public static final AuthMeColumns<Float> LOCATION_YAW = createFloat(
DatabaseSettings.MYSQL_COL_LASTLOC_YAW, PlayerAuth::getYaw);
public static final AuthMeColumns<Float> LOCATION_PITCH = createFloat(
DatabaseSettings.MYSQL_COL_LASTLOC_PITCH, PlayerAuth::getPitch);
private final ColumnType<T> columnType;
private final Property<String> nameProperty;
private final Function<PlayerAuth, T> playerAuthGetter;
private final boolean isOptional;
private final boolean useDefaultForNull;
AuthMeColumns(ColumnType<T> type, Property<String> nameProperty, Function<PlayerAuth, T> playerAuthGetter,
boolean isOptional, boolean useDefaultForNull) {
this.columnType = type;
this.nameProperty = nameProperty;
this.playerAuthGetter = playerAuthGetter;
this.isOptional = isOptional;
this.useDefaultForNull = useDefaultForNull;
}
public Property<String> getNameProperty() {
return nameProperty;
}
@Override
public T getValueFromDependent(PlayerAuth playerAuth) {
return playerAuthGetter.apply(playerAuth);
}
@Override
public String resolveName(ColumnContext columnContext) {
return columnContext.getName(this);
}
@Override
public ColumnType<T> getType() {
return columnType;
}
@Override
public boolean isColumnUsed(ColumnContext columnContext) {
return !isOptional || !resolveName(columnContext).isEmpty();
}
@Override
public boolean useDefaultForNullValue(ColumnContext columnContext) {
return useDefaultForNull && columnContext.hasDefaultSupport();
}
}
@@ -0,0 +1,76 @@
package fr.xephi.authme.datasource.columnshandler;
import ch.jalu.configme.properties.Property;
import ch.jalu.datasourcecolumns.ColumnType;
import ch.jalu.datasourcecolumns.StandardTypes;
import fr.xephi.authme.data.auth.PlayerAuth;
import java.util.function.Function;
/**
* Util class for initializing {@link AuthMeColumns} constants.
*/
final class AuthMeColumnsFactory {
private AuthMeColumnsFactory() {
}
static AuthMeColumns<Integer> createInteger(Property<String> nameProperty,
Function<PlayerAuth, Integer> playerAuthGetter,
ColumnOptions... options) {
return createInternal(StandardTypes.INTEGER, nameProperty, playerAuthGetter, options);
}
static AuthMeColumns<Long> createLong(Property<String> nameProperty,
Function<PlayerAuth, Long> playerAuthGetter,
ColumnOptions... options) {
return createInternal(StandardTypes.LONG, nameProperty, playerAuthGetter, options);
}
static AuthMeColumns<String> createString(Property<String> nameProperty,
Function<PlayerAuth, String> playerAuthGetter,
ColumnOptions... options) {
return createInternal(StandardTypes.STRING, nameProperty, playerAuthGetter, options);
}
static AuthMeColumns<Double> createDouble(Property<String> nameProperty,
Function<PlayerAuth, Double> playerAuthGetter,
ColumnOptions... options) {
return createInternal(StandardTypes.DOUBLE, nameProperty, playerAuthGetter, options);
}
static AuthMeColumns<Float> createFloat(Property<String> nameProperty,
Function<PlayerAuth, Float> playerAuthGetter,
ColumnOptions... options) {
return createInternal(StandardTypes.FLOAT, nameProperty, playerAuthGetter, options);
}
private static <T> AuthMeColumns<T> createInternal(ColumnType<T> type, Property<String> nameProperty,
Function<PlayerAuth, T> authGetter, ColumnOptions... options) {
return new AuthMeColumns<>(type, nameProperty, authGetter, isOptional(options), hasDefaultForNull(options));
}
private static boolean isOptional(ColumnOptions[] options) {
return containsInArray(ColumnOptions.OPTIONAL, options);
}
private static boolean hasDefaultForNull(ColumnOptions[] options) {
return containsInArray(ColumnOptions.DEFAULT_FOR_NULL, options);
}
private static boolean containsInArray(ColumnOptions needle, ColumnOptions[] haystack) {
for (ColumnOptions option : haystack) {
if (option == needle) {
return true;
}
}
return false;
}
enum ColumnOptions {
OPTIONAL,
DEFAULT_FOR_NULL
}
}
@@ -4,12 +4,9 @@ import ch.jalu.datasourcecolumns.data.DataSourceValue;
import ch.jalu.datasourcecolumns.data.DataSourceValues;
import ch.jalu.datasourcecolumns.data.UpdateValues;
import ch.jalu.datasourcecolumns.sqlimplementation.PredicateSqlGenerator;
import ch.jalu.datasourcecolumns.sqlimplementation.PreparedStatementGenerator;
import ch.jalu.datasourcecolumns.sqlimplementation.ResultSetValueRetriever;
import ch.jalu.datasourcecolumns.sqlimplementation.SqlColumnsHandler;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.AuthMeColumns;
import fr.xephi.authme.datasource.ColumnContext;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.DatabaseSettings;
@@ -38,7 +35,7 @@ public final class AuthMeColumnsHandler {
* @return created column handler
*/
public static AuthMeColumnsHandler createForSqlite(Connection connection, Settings settings) {
ColumnContext columnContext = new ColumnContext(settings);
ColumnContext columnContext = new ColumnContext(settings, false);
String tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
String nameColumn = settings.getProperty(DatabaseSettings.MYSQL_COL_NAME);
@@ -50,19 +47,18 @@ public final class AuthMeColumnsHandler {
/**
* Creates a column handler for MySQL.
*
* @param preparedStatementGenerator supplier of SQL prepared statements with a connection to the database
* @param connectionSupplier supplier of connections from the connection pool
* @param settings plugin settings
* @return created column handler
*/
public static AuthMeColumnsHandler createForMySql(PreparedStatementGenerator preparedStatementGenerator,
Settings settings) {
ColumnContext columnContext = new ColumnContext(settings);
public static AuthMeColumnsHandler createForMySql(ConnectionSupplier connectionSupplier, Settings settings) {
ColumnContext columnContext = new ColumnContext(settings, true);
String tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
String nameColumn = settings.getProperty(DatabaseSettings.MYSQL_COL_NAME);
SqlColumnsHandler<ColumnContext, String> sqlColHandler = new SqlColumnsHandler<>(preparedStatementGenerator,
columnContext, tableName, nameColumn, new ResultSetValueRetriever<>(columnContext),
new PredicateSqlGenerator<>(columnContext));
SqlColumnsHandler<ColumnContext, String> sqlColHandler = new SqlColumnsHandler<>(
new MySqlPreparedStatementGenerator(connectionSupplier), columnContext, tableName, nameColumn,
new ResultSetValueRetriever<>(columnContext), new PredicateSqlGenerator<>(columnContext));
return new AuthMeColumnsHandler(sqlColHandler);
}
@@ -138,4 +134,20 @@ public final class AuthMeColumnsHandler {
public DataSourceValues retrieve(String name, AuthMeColumns<?>... columns) throws SQLException {
return internalHandler.retrieve(name.toLowerCase(), columns);
}
/**
* Inserts the given values into a new row, as taken from the player auth.
*
* @param auth the player auth to get values from
* @param columns the columns to insert
* @return true upon success, false otherwise
*/
public boolean insert(PlayerAuth auth, AuthMeColumns<?>... columns) {
try {
return internalHandler.insert(auth, columns);
} catch (SQLException e) {
logSqlException(e);
return false;
}
}
}
@@ -0,0 +1,35 @@
package fr.xephi.authme.datasource.columnshandler;
import fr.xephi.authme.settings.Settings;
import java.util.HashMap;
import java.util.Map;
/**
* Context for resolving the properties of {@link AuthMeColumns} entries.
*/
public class ColumnContext {
private final Settings settings;
private final Map<AuthMeColumns<?>, String> columnNames = new HashMap<>();
private final boolean hasDefaultSupport;
/**
* Constructor.
*
* @param settings plugin settings
* @param hasDefaultSupport whether or not the underlying database has support for the {@code DEFAULT} keyword
*/
public ColumnContext(Settings settings, boolean hasDefaultSupport) {
this.settings = settings;
this.hasDefaultSupport = hasDefaultSupport;
}
public String getName(AuthMeColumns<?> column) {
return columnNames.computeIfAbsent(column, k -> settings.getProperty(k.getNameProperty()));
}
public boolean hasDefaultSupport() {
return hasDefaultSupport;
}
}
@@ -0,0 +1,17 @@
package fr.xephi.authme.datasource.columnshandler;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Supplier of connections to a database.
*/
@FunctionalInterface
public interface ConnectionSupplier {
/**
* @return connection object to the database
*/
Connection get() throws SQLException;
}
@@ -0,0 +1,44 @@
package fr.xephi.authme.datasource.columnshandler;
import ch.jalu.datasourcecolumns.sqlimplementation.PreparedStatementGenerator;
import ch.jalu.datasourcecolumns.sqlimplementation.PreparedStatementResult;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* Implementation of {@link PreparedStatementGenerator} for MySQL which ensures that the connection
* taken from the connection pool is also closed after the prepared statement has been executed.
*/
class MySqlPreparedStatementGenerator implements PreparedStatementGenerator {
private final ConnectionSupplier connectionSupplier;
MySqlPreparedStatementGenerator(ConnectionSupplier connectionSupplier) {
this.connectionSupplier = connectionSupplier;
}
@Override
public PreparedStatementResult create(String sql) throws SQLException {
Connection connection = connectionSupplier.get();
return new MySqlPreparedStatementResult(connection, connection.prepareStatement(sql));
}
/** Prepared statement result which also closes the associated connection. */
private static final class MySqlPreparedStatementResult extends PreparedStatementResult {
private final Connection connection;
MySqlPreparedStatementResult(Connection connection, PreparedStatement preparedStatement) {
super(preparedStatement);
this.connection = connection;
}
@Override
public void close() throws SQLException {
super.close();
connection.close();
}
}
}