diff --git a/src/main/java/fr/xephi/authme/datasource/ColumnContext.java b/src/main/java/fr/xephi/authme/datasource/ColumnContext.java deleted file mode 100644 index cbc743fa..00000000 --- a/src/main/java/fr/xephi/authme/datasource/ColumnContext.java +++ /dev/null @@ -1,20 +0,0 @@ -package fr.xephi.authme.datasource; - -import fr.xephi.authme.settings.Settings; - -import java.util.HashMap; -import java.util.Map; - -public class ColumnContext { - - private final Settings settings; - private final Map, String> columnNames = new HashMap<>(); - - public ColumnContext(Settings settings) { - this.settings = settings; - } - - public String getName(AuthMeColumns column) { - return columnNames.computeIfAbsent(column, k -> settings.getProperty(k.getNameProperty())); - } -} diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index f098d468..8e501ae0 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -6,6 +6,7 @@ import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.columnshandler.AuthMeColumns; import fr.xephi.authme.datasource.columnshandler.AuthMeColumnsHandler; import fr.xephi.authme.datasource.mysqlextensions.MySqlExtension; import fr.xephi.authme.datasource.mysqlextensions.MySqlExtensionsFactory; @@ -13,7 +14,6 @@ import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.DatabaseSettings; import fr.xephi.authme.settings.properties.HooksSettings; -import fr.xephi.authme.util.StringUtils; import java.sql.Connection; import java.sql.DatabaseMetaData; @@ -103,8 +103,7 @@ public class MySQL implements DataSource { this.tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE); this.columnOthers = settings.getProperty(HooksSettings.MYSQL_OTHER_USERNAME_COLS); this.col = new Columns(settings); - this.columnsHandler = - AuthMeColumnsHandler.createForMySql(sql -> getConnection().prepareStatement(sql), settings); + this.columnsHandler = AuthMeColumnsHandler.createForMySql(this::getConnection, settings); this.sqlExtension = extensionsFactory.buildExtension(col); this.poolSize = settings.getProperty(DatabaseSettings.MYSQL_POOL_SIZE); this.maxLifetime = settings.getProperty(DatabaseSettings.MYSQL_CONNECTION_MAX_LIFETIME); @@ -317,33 +316,11 @@ public class MySQL implements DataSource { @Override public boolean saveAuth(PlayerAuth auth) { + columnsHandler.insert(auth, + AuthMeColumns.NAME, AuthMeColumns.NICK_NAME, AuthMeColumns.PASSWORD, AuthMeColumns.SALT, + AuthMeColumns.EMAIL, AuthMeColumns.REGISTRATION_DATE, AuthMeColumns.REGISTRATION_IP); + try (Connection con = getConnection()) { - // TODO ljacqu 20171104: Replace with generic columns util to clean this up - boolean useSalt = !col.SALT.isEmpty() || !StringUtils.isEmpty(auth.getPassword().getSalt()); - boolean hasEmail = auth.getEmail() != null; - String emailPlaceholder = hasEmail ? "?" : "DEFAULT"; - - String sql = "INSERT INTO " + tableName + "(" - + col.NAME + "," + col.PASSWORD + "," + col.REAL_NAME - + "," + col.EMAIL + "," + col.REGISTRATION_DATE + "," + col.REGISTRATION_IP - + (useSalt ? "," + col.SALT : "") - + ") VALUES (?,?,?," + emailPlaceholder + ",?,?" + (useSalt ? ",?" : "") + ");"; - try (PreparedStatement pst = con.prepareStatement(sql)) { - int index = 1; - pst.setString(index++, auth.getNickname()); - pst.setString(index++, auth.getPassword().getHash()); - pst.setString(index++, auth.getRealName()); - if (hasEmail) { - pst.setString(index++, auth.getEmail()); - } - pst.setObject(index++, auth.getRegistrationDate()); - pst.setString(index++, auth.getRegistrationIp()); - if (useSalt) { - pst.setString(index++, auth.getPassword().getSalt()); - } - pst.executeUpdate(); - } - if (!columnOthers.isEmpty()) { for (String column : columnOthers) { try (PreparedStatement pst = con.prepareStatement( diff --git a/src/main/java/fr/xephi/authme/datasource/SQLite.java b/src/main/java/fr/xephi/authme/datasource/SQLite.java index 57d12a6b..0b2361aa 100644 --- a/src/main/java/fr/xephi/authme/datasource/SQLite.java +++ b/src/main/java/fr/xephi/authme/datasource/SQLite.java @@ -4,11 +4,11 @@ import ch.jalu.datasourcecolumns.data.DataSourceValues; import com.google.common.annotations.VisibleForTesting; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.columnshandler.AuthMeColumns; import fr.xephi.authme.datasource.columnshandler.AuthMeColumnsHandler; import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.DatabaseSettings; -import fr.xephi.authme.util.StringUtils; import java.io.File; import java.sql.Connection; @@ -254,46 +254,9 @@ public class SQLite implements DataSource { @Override public boolean saveAuth(PlayerAuth auth) { - PreparedStatement pst = null; - try { - HashedPassword password = auth.getPassword(); - if (col.SALT.isEmpty()) { - if (!StringUtils.isEmpty(auth.getPassword().getSalt())) { - ConsoleLogger.warning("Warning! Detected hashed password with separate salt but the salt column " - + "is not set in the config!"); - } - - pst = con.prepareStatement("INSERT INTO " + tableName + "(" + col.NAME + "," + col.PASSWORD - + "," + col.REAL_NAME + "," + col.EMAIL - + "," + col.REGISTRATION_DATE + "," + col.REGISTRATION_IP - + ") VALUES (?,?,?,?,?,?);"); - pst.setString(1, auth.getNickname()); - pst.setString(2, password.getHash()); - pst.setString(3, auth.getRealName()); - pst.setString(4, auth.getEmail()); - pst.setLong(5, auth.getRegistrationDate()); - pst.setString(6, auth.getRegistrationIp()); - pst.executeUpdate(); - } else { - pst = con.prepareStatement("INSERT INTO " + tableName + "(" + col.NAME + "," + col.PASSWORD - + "," + col.REAL_NAME + "," + col.EMAIL - + "," + col.REGISTRATION_DATE + "," + col.REGISTRATION_IP + "," + col.SALT - + ") VALUES (?,?,?,?,?,?,?);"); - pst.setString(1, auth.getNickname()); - pst.setString(2, password.getHash()); - pst.setString(3, auth.getRealName()); - pst.setString(4, auth.getEmail()); - pst.setLong(5, auth.getRegistrationDate()); - pst.setString(6, auth.getRegistrationIp()); - pst.setString(7, password.getSalt()); - pst.executeUpdate(); - } - } catch (SQLException ex) { - logSqlException(ex); - } finally { - close(pst); - } - return true; + return columnsHandler.insert(auth, + AuthMeColumns.NAME, AuthMeColumns.NICK_NAME, AuthMeColumns.PASSWORD, AuthMeColumns.SALT, + AuthMeColumns.EMAIL, AuthMeColumns.REGISTRATION_DATE, AuthMeColumns.REGISTRATION_IP); } @Override diff --git a/src/main/java/fr/xephi/authme/datasource/AuthMeColumns.java b/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumns.java similarity index 61% rename from src/main/java/fr/xephi/authme/datasource/AuthMeColumns.java rename to src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumns.java index 657084ae..246bf558 100644 --- a/src/main/java/fr/xephi/authme/datasource/AuthMeColumns.java +++ b/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumns.java @@ -1,14 +1,27 @@ -package fr.xephi.authme.datasource; +package fr.xephi.authme.datasource.columnshandler; import ch.jalu.configme.properties.Property; import ch.jalu.datasourcecolumns.ColumnType; import ch.jalu.datasourcecolumns.DependentColumn; -import ch.jalu.datasourcecolumns.StandardTypes; 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 the column type + * @see PlayerAuth + */ public final class AuthMeColumns implements DependentColumn { public static final AuthMeColumns NAME = createString( @@ -20,15 +33,24 @@ public final class AuthMeColumns implements DependentColumn PASSWORD = createString( DatabaseSettings.MYSQL_COL_PASSWORD, auth -> auth.getPassword().getHash()); - public static final AuthMeColumns SALT = new AuthMeColumns<>( - StandardTypes.STRING, DatabaseSettings.MYSQL_COL_SALT, auth -> auth.getPassword().getSalt(), true); + public static final AuthMeColumns SALT = createString( + DatabaseSettings.MYSQL_COL_SALT, auth -> auth.getPassword().getSalt(), OPTIONAL); public static final AuthMeColumns EMAIL = createString( - DatabaseSettings.MYSQL_COL_EMAIL, PlayerAuth::getEmail); + DatabaseSettings.MYSQL_COL_EMAIL, PlayerAuth::getEmail, DEFAULT_FOR_NULL); public static final AuthMeColumns LAST_IP = createString( DatabaseSettings.MYSQL_COL_LAST_IP, PlayerAuth::getLastIp); + public static final AuthMeColumns GROUP_ID = createInteger( + DatabaseSettings.MYSQL_COL_GROUP, PlayerAuth::getGroupId, OPTIONAL); + + public static final AuthMeColumns REGISTRATION_IP = createString( + DatabaseSettings.MYSQL_COL_REGISTER_IP, PlayerAuth::getRegistrationIp); + + public static final AuthMeColumns REGISTRATION_DATE = createLong( + DatabaseSettings.MYSQL_COL_REGISTER_DATE, PlayerAuth::getRegistrationDate); + public static final AuthMeColumns LOCATION_X = createDouble( DatabaseSettings.MYSQL_COL_LASTLOC_X, PlayerAuth::getQuitLocX); @@ -52,28 +74,15 @@ public final class AuthMeColumns implements DependentColumn nameProperty; private final Function playerAuthGetter; private final boolean isOptional; + private final boolean useDefaultForNull; - private AuthMeColumns(ColumnType type, Property nameProperty, Function playerAuthGetter, - boolean isOptional) { + AuthMeColumns(ColumnType type, Property nameProperty, Function playerAuthGetter, + boolean isOptional, boolean useDefaultForNull) { this.columnType = type; this.nameProperty = nameProperty; this.playerAuthGetter = playerAuthGetter; this.isOptional = isOptional; - } - - private static AuthMeColumns createString(Property nameProperty, - Function getter) { - return new AuthMeColumns<>(StandardTypes.STRING, nameProperty, getter, false); - } - - private static AuthMeColumns createDouble(Property nameProperty, - Function getter) { - return new AuthMeColumns<>(new DoubleType(), nameProperty, getter, false); - } - - private static AuthMeColumns createFloat(Property nameProperty, - Function getter) { - return new AuthMeColumns<>(new FloatType(), nameProperty, getter, false); + this.useDefaultForNull = useDefaultForNull; } @@ -103,23 +112,6 @@ public final class AuthMeColumns implements DependentColumn { - - @Override - public Class getClazz() { - return Double.class; - } - } - - private static final class FloatType implements ColumnType { - - @Override - public Class getClazz() { - return Float.class; - } + return useDefaultForNull && columnContext.hasDefaultSupport(); } } diff --git a/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumnsFactory.java b/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumnsFactory.java new file mode 100644 index 00000000..420a26bc --- /dev/null +++ b/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumnsFactory.java @@ -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 createInteger(Property nameProperty, + Function playerAuthGetter, + ColumnOptions... options) { + return createInternal(StandardTypes.INTEGER, nameProperty, playerAuthGetter, options); + } + + static AuthMeColumns createLong(Property nameProperty, + Function playerAuthGetter, + ColumnOptions... options) { + return createInternal(StandardTypes.LONG, nameProperty, playerAuthGetter, options); + } + + static AuthMeColumns createString(Property nameProperty, + Function playerAuthGetter, + ColumnOptions... options) { + return createInternal(StandardTypes.STRING, nameProperty, playerAuthGetter, options); + } + + static AuthMeColumns createDouble(Property nameProperty, + Function playerAuthGetter, + ColumnOptions... options) { + return createInternal(StandardTypes.DOUBLE, nameProperty, playerAuthGetter, options); + } + + static AuthMeColumns createFloat(Property nameProperty, + Function playerAuthGetter, + ColumnOptions... options) { + return createInternal(StandardTypes.FLOAT, nameProperty, playerAuthGetter, options); + } + + private static AuthMeColumns createInternal(ColumnType type, Property nameProperty, + Function 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 + } +} diff --git a/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumnsHandler.java b/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumnsHandler.java index b4c02dc9..a35c8c97 100644 --- a/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumnsHandler.java +++ b/src/main/java/fr/xephi/authme/datasource/columnshandler/AuthMeColumnsHandler.java @@ -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 sqlColHandler = new SqlColumnsHandler<>(preparedStatementGenerator, - columnContext, tableName, nameColumn, new ResultSetValueRetriever<>(columnContext), - new PredicateSqlGenerator<>(columnContext)); + SqlColumnsHandler 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; + } + } } diff --git a/src/main/java/fr/xephi/authme/datasource/columnshandler/ColumnContext.java b/src/main/java/fr/xephi/authme/datasource/columnshandler/ColumnContext.java new file mode 100644 index 00000000..8759935d --- /dev/null +++ b/src/main/java/fr/xephi/authme/datasource/columnshandler/ColumnContext.java @@ -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, 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; + } +} diff --git a/src/main/java/fr/xephi/authme/datasource/columnshandler/ConnectionSupplier.java b/src/main/java/fr/xephi/authme/datasource/columnshandler/ConnectionSupplier.java new file mode 100644 index 00000000..4d419a21 --- /dev/null +++ b/src/main/java/fr/xephi/authme/datasource/columnshandler/ConnectionSupplier.java @@ -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; + +} diff --git a/src/main/java/fr/xephi/authme/datasource/columnshandler/MySqlPreparedStatementGenerator.java b/src/main/java/fr/xephi/authme/datasource/columnshandler/MySqlPreparedStatementGenerator.java new file mode 100644 index 00000000..c20357ae --- /dev/null +++ b/src/main/java/fr/xephi/authme/datasource/columnshandler/MySqlPreparedStatementGenerator.java @@ -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(); + } + } +} diff --git a/src/test/java/fr/xephi/authme/ClassesConsistencyTest.java b/src/test/java/fr/xephi/authme/ClassesConsistencyTest.java index 4005dc79..c7f09398 100644 --- a/src/test/java/fr/xephi/authme/ClassesConsistencyTest.java +++ b/src/test/java/fr/xephi/authme/ClassesConsistencyTest.java @@ -5,8 +5,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import fr.xephi.authme.data.captcha.CaptchaCodeStorage; -import fr.xephi.authme.datasource.AuthMeColumns; import fr.xephi.authme.datasource.Columns; +import fr.xephi.authme.datasource.columnshandler.AuthMeColumns; import fr.xephi.authme.datasource.mysqlextensions.MySqlExtension; import fr.xephi.authme.initialization.HasCleanup; import fr.xephi.authme.process.register.executors.RegistrationMethod;