diff --git a/Datura/src/main/java/fns/datura/Datura.java b/Datura/src/main/java/fns/datura/Datura.java index 5fa5c92..9309f77 100644 --- a/Datura/src/main/java/fns/datura/Datura.java +++ b/Datura/src/main/java/fns/datura/Datura.java @@ -32,7 +32,7 @@ import fns.datura.punishment.Locker; import fns.datura.sql.MySQL; import fns.patchwork.base.Registration; import fns.patchwork.command.CommandHandler; -import fns.patchwork.service.SubscriptionProvider; +import fns.patchwork.provider.SubscriptionProvider; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; diff --git a/Datura/src/main/java/fns/datura/sql/MySQL.java b/Datura/src/main/java/fns/datura/sql/MySQL.java index 00770cd..2fd4816 100644 --- a/Datura/src/main/java/fns/datura/sql/MySQL.java +++ b/Datura/src/main/java/fns/datura/sql/MySQL.java @@ -26,11 +26,11 @@ package fns.datura.sql; import fns.patchwork.base.Patchwork; import fns.patchwork.base.Shortcuts; import fns.patchwork.sql.SQL; +import fns.patchwork.sql.SQLResult; import fns.patchwork.utils.container.Identity; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; -import java.sql.ResultSet; import java.sql.SQLException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -74,7 +74,7 @@ public class MySQL implements SQL .append(password); } - public CompletableFuture getRow(final String table, final String column, final Identity identity) + public CompletableFuture getRow(final String table, final String column, final Identity identity) { return executeQuery("SELECT * FROM ? WHERE ? = ?", table, column, identity.getId()); } @@ -83,99 +83,104 @@ public class MySQL implements SQL public CompletableFuture prepareStatement(final String query, final Object... args) { return getConnection() - .thenApplyAsync(connection -> - { - try - { - final PreparedStatement statement = connection.prepareStatement(query); - for (int i = 0; i < args.length; i++) - { - statement.setObject(i + 1, args[i]); - } - return statement; - } catch (SQLException ex) - { - throw new CompletionException("Failed to prepare statement: " - + query + "\n", ex); - } - }, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()); + .thenApplyAsync(connection -> + { + try + { + final PreparedStatement statement = connection.prepareStatement(query); + for (int i = 0; i < args.length; i++) + { + statement.setObject(i + 1, args[i]); + } + return statement; + } + catch (SQLException ex) + { + throw new CompletionException("Failed to prepare statement: " + + query + "\n", ex); + } + }, Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()); } private CompletableFuture getConnection() { return CompletableFuture.supplyAsync(() -> - { - try - { - return DriverManager.getConnection(url.toString()); - } catch (SQLException ex) - { - throw new CompletionException("Failed to connect to the database: " - + url.toString() + "\n", ex); - } - }, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()); + { + try + { + return DriverManager.getConnection(url.toString()); + } + catch (SQLException ex) + { + throw new CompletionException("Failed to connect to the database: " + + url.toString() + "\n", ex); + } + }, Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()); } @Override - public CompletableFuture executeQuery(final String query, final Object... args) + public CompletableFuture executeQuery(final String query, final Object... args) { return prepareStatement(query, args) - .thenApplyAsync(statement -> - { - try - { - return statement.executeQuery(); - } catch (SQLException ex) - { - throw new CompletionException( - "Failed to retrieve a result set from query: " + .thenApplyAsync(statement -> + { + try + { + return new SQLResult(statement.executeQuery()); + } + catch (SQLException ex) + { + throw new CompletionException( + "Failed to retrieve a result set from query: " + query + "\n", ex); - } - }, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()); + } + }, Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()); } @Override public CompletableFuture executeUpdate(final String query, final Object... args) { return prepareStatement(query, args) - .thenApplyAsync(statement -> - { - try - { - return statement.executeUpdate(); - } catch (SQLException ex) - { - throw new CompletionException("Failed to execute update: " - + query + "\n", ex); - } - }, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()); + .thenApplyAsync(statement -> + { + try + { + return statement.executeUpdate(); + } + catch (SQLException ex) + { + throw new CompletionException("Failed to execute update: " + + query + "\n", ex); + } + }, Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()); } @Override public CompletableFuture execute(final String query, final Object... args) { return prepareStatement(query, args) - .thenApplyAsync(statement -> - { - try - { - return statement.execute(); - } catch (SQLException ex) - { - throw new CompletionException("Failed to execute statement: " - + query + "\n", ex); - } - }, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()); + .thenApplyAsync(statement -> + { + try + { + return statement.execute(); + } + catch (SQLException ex) + { + throw new CompletionException("Failed to execute statement: " + + query + "\n", ex); + } + }, Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()); } @Override @@ -201,42 +206,27 @@ public class MySQL implements SQL final Identity identity, final Class type) { return executeQuery("SELECT ? FROM ? WHERE ? = ?", column, table, key, identity.getId()) - .thenApplyAsync(resultSet -> - { - try - { - if (resultSet.next()) - { - return resultSet.getObject(column, type); - } - } catch (SQLException ex) - { - throw new CompletionException( - "Failed to retrieve column: " + column + " from table: " + table + " " + - "where primary key: " + key + " is equal to: " + identity.getId() + "\n", - ex); - } - return null; - }, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()); + .thenApplyAsync(resultSet -> (resultSet.hasNext()) ? resultSet.autoCast(1, column, type) : null, + Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()); } public CompletableFuture updateColumn(final String table, final String column, final Object value, final String key, final Identity identity) { return executeUpdate("UPDATE ? SET ? = ? WHERE ? = ?", table, column, value, key, identity.getId()) - .thenApplyAsync(result -> result > 0, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()); + .thenApplyAsync(result -> result > 0, Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()); } public CompletableFuture deleteRow(final String table, final String key, final Identity identity) { return executeUpdate("DELETE FROM ? WHERE ? = ?", table, key, identity.getId()) - .thenApplyAsync(result -> result > 0, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()); + .thenApplyAsync(result -> result > 0, Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()); } public CompletableFuture insertRow(final String table, final Object... values) diff --git a/Datura/src/main/java/fns/datura/user/SimpleUserData.java b/Datura/src/main/java/fns/datura/user/SimpleUserData.java index 0a19c6a..5462a15 100644 --- a/Datura/src/main/java/fns/datura/user/SimpleUserData.java +++ b/Datura/src/main/java/fns/datura/user/SimpleUserData.java @@ -34,10 +34,8 @@ import fns.patchwork.sql.SQL; import fns.patchwork.user.User; import fns.patchwork.user.UserData; import fns.patchwork.utils.logging.FNS4J; -import java.sql.SQLException; import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; -import org.apache.commons.lang3.exception.ExceptionUtils; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -69,14 +67,14 @@ public class SimpleUserData implements UserData } private SimpleUserData( - final UUID uuid, - final String username, - final User user, - final Group group, - final long playtime, - final boolean canInteract, - final long balance, - final boolean transactionsFrozen) + final UUID uuid, + final String username, + final User user, + final Group group, + final long playtime, + final boolean canInteract, + final long balance, + final boolean transactionsFrozen) { this.uuid = uuid; this.username = username; @@ -93,56 +91,53 @@ public class SimpleUserData implements UserData { return sql.executeQuery("SELECT * FROM users WHERE UUID = ?", uuid) .thenApplyAsync(result -> - { - try - { - if (result.next()) - { - final String g = result.getString("group"); + { - final UUID u = UUID.fromString(uuid); - final String username = result.getString("username"); + if (result.hasNext()) + { + final String g = result.getString("group"); - final Player player = Bukkit.getPlayer(u); + final UUID u = UUID.fromString(uuid); + final String username = result.getString("username"); - if (player == null) - throw new IllegalStateException("Player should be online but they are not!"); + final Player player = Bukkit.getPlayer(u); - final User user = new FreedomUser(player); - final Group group = Registration - .getGroupRegistry() - .getGroup(g); + if (player == null) + throw new IllegalStateException( + "Player should be online but they are not!"); - final long playtime = result.getLong("playtime"); - final boolean canInteract = result.getBoolean("canInteract"); - final long balance = result.getLong("balance"); - final boolean transactionsFrozen = result.getBoolean("transactionsFrozen"); + final User user = new FreedomUser(player); + final Group group = Registration + .getGroupRegistry() + .getGroup(g); - return new SimpleUserData(u, username, user, group, playtime, - canInteract, balance, transactionsFrozen); - } - } catch (SQLException ex) - { - final String sb = "An error occurred while trying to retrieve user data for" + - " UUID " + - uuid + - " from the database." + - "\nCaused by: " + - ExceptionUtils.getRootCauseMessage(ex) + - "\nStack trace: " + - ExceptionUtils.getStackTrace(ex); + final long playtime = result.getLong("playtime"); + final boolean canInteract = result.getBoolean("canInteract"); + final long balance = result.getLong("balance"); + final boolean transactionsFrozen = result.getBoolean("transactionsFrozen"); - FNS4J.getLogger("Datura") - .error(sb); - } + return new SimpleUserData(u, username, user, group, playtime, + canInteract, balance, transactionsFrozen); + } + else + { + final String sb = "An error occurred while trying to retrieve user data for" + + " UUID " + + uuid + + " from the database."; - final Player player = Bukkit.getPlayer(UUID.fromString(uuid)); - if (player == null) throw new IllegalStateException("Player should be online but they are not!"); + FNS4J.getLogger("Datura") + .error(sb); + } - return new SimpleUserData(player); - }, Shortcuts.provideModule(Patchwork.class) - .getExecutor() - .getAsync()) + final Player player = Bukkit.getPlayer(UUID.fromString(uuid)); + if (player == null) + throw new IllegalStateException("Player should be online but they are not!"); + + return new SimpleUserData(player); + }, Shortcuts.provideModule(Patchwork.class) + .getExecutor() + .getAsync()) .join(); } diff --git a/Fossil/src/main/java/fns/fossil/Fossil.java b/Fossil/src/main/java/fns/fossil/Fossil.java index a07711f..22157e4 100644 --- a/Fossil/src/main/java/fns/fossil/Fossil.java +++ b/Fossil/src/main/java/fns/fossil/Fossil.java @@ -27,7 +27,7 @@ import fns.fossil.cmd.CakeCommand; import fns.fossil.trail.Trailer; import fns.patchwork.base.Registration; import fns.patchwork.command.CommandHandler; -import fns.patchwork.service.SubscriptionProvider; +import fns.patchwork.provider.SubscriptionProvider; import org.bukkit.plugin.java.JavaPlugin; public class Fossil extends JavaPlugin diff --git a/Patchwork/src/main/java/fns/patchwork/base/Patchwork.java b/Patchwork/src/main/java/fns/patchwork/base/Patchwork.java index b60d596..60c9b55 100644 --- a/Patchwork/src/main/java/fns/patchwork/base/Patchwork.java +++ b/Patchwork/src/main/java/fns/patchwork/base/Patchwork.java @@ -25,8 +25,8 @@ package fns.patchwork.base; import fns.patchwork.display.adminchat.AdminChatDisplay; import fns.patchwork.event.EventBus; -import fns.patchwork.service.FreedomExecutor; -import fns.patchwork.service.SubscriptionProvider; +import fns.patchwork.provider.ExecutorProvider; +import fns.patchwork.provider.SubscriptionProvider; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; @@ -40,9 +40,9 @@ public class Patchwork extends JavaPlugin */ private EventBus eventBus; /** - * The {@link FreedomExecutor} for this plugin. + * The {@link ExecutorProvider} for this plugin. */ - private FreedomExecutor executor; + private ExecutorProvider executor; /** * The {@link AdminChatDisplay} for this plugin. */ @@ -64,7 +64,7 @@ public class Patchwork extends JavaPlugin public void onEnable() { eventBus = new EventBus(this); - executor = new FreedomExecutor(this); + executor = new ExecutorProvider(this); acdisplay = new AdminChatDisplay(this); @@ -80,11 +80,11 @@ public class Patchwork extends JavaPlugin } /** - * Gets the {@link FreedomExecutor} for this plugin. + * Gets the {@link ExecutorProvider} for this plugin. * - * @return the {@link FreedomExecutor} + * @return the {@link ExecutorProvider} */ - public FreedomExecutor getExecutor() + public ExecutorProvider getExecutor() { return executor; } diff --git a/Patchwork/src/main/java/fns/patchwork/base/Registration.java b/Patchwork/src/main/java/fns/patchwork/base/Registration.java index db491fa..8818cf3 100644 --- a/Patchwork/src/main/java/fns/patchwork/base/Registration.java +++ b/Patchwork/src/main/java/fns/patchwork/base/Registration.java @@ -27,6 +27,7 @@ import fns.patchwork.registry.ConfigRegistry; import fns.patchwork.registry.EventRegistry; import fns.patchwork.registry.GroupRegistry; import fns.patchwork.registry.ModuleRegistry; +import fns.patchwork.registry.SQLRegistry; import fns.patchwork.registry.ServiceTaskRegistry; import fns.patchwork.registry.UserRegistry; @@ -62,6 +63,10 @@ public class Registration * The {@link ConfigRegistry} */ private static final ConfigRegistry configRegistry = new ConfigRegistry(); + /** + * The SQL Registry + */ + private static final SQLRegistry sqlRegistry = new SQLRegistry(); private Registration() { @@ -115,4 +120,12 @@ public class Registration { return configRegistry; } + + /** + * @return The {@link SQLRegistry} + */ + public static SQLRegistry getSQLRegistry() + { + return sqlRegistry; + } } \ No newline at end of file diff --git a/Patchwork/src/main/java/fns/patchwork/base/Shortcuts.java b/Patchwork/src/main/java/fns/patchwork/base/Shortcuts.java index 9482202..3dcd19e 100644 --- a/Patchwork/src/main/java/fns/patchwork/base/Shortcuts.java +++ b/Patchwork/src/main/java/fns/patchwork/base/Shortcuts.java @@ -23,7 +23,10 @@ package fns.patchwork.base; +import fns.patchwork.provider.ExecutorProvider; +import fns.patchwork.sql.SQL; import fns.patchwork.user.User; +import java.util.Optional; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; @@ -37,13 +40,23 @@ public final class Shortcuts public static T provideModule(final Class pluginClass) { return Registration.getModuleRegistry() - .getProvider(pluginClass) - .getModule(); + .getProvider(pluginClass) + .getModule(); } public static User getUser(final Player player) { return Registration.getUserRegistry() - .getUser(player); + .getUser(player); + } + + public static ExecutorProvider getExecutors() + { + return provideModule(Patchwork.class).getExecutor(); + } + + public static Optional getSQL() + { + return Registration.getSQLRegistry().getSQL(); } } diff --git a/Patchwork/src/main/java/fns/patchwork/service/FreedomExecutor.java b/Patchwork/src/main/java/fns/patchwork/provider/ExecutorProvider.java similarity index 97% rename from Patchwork/src/main/java/fns/patchwork/service/FreedomExecutor.java rename to Patchwork/src/main/java/fns/patchwork/provider/ExecutorProvider.java index 3a5eb6c..cbdd2c9 100644 --- a/Patchwork/src/main/java/fns/patchwork/service/FreedomExecutor.java +++ b/Patchwork/src/main/java/fns/patchwork/provider/ExecutorProvider.java @@ -21,7 +21,7 @@ * SOFTWARE. */ -package fns.patchwork.service; +package fns.patchwork.provider; import fns.patchwork.base.Patchwork; import java.util.concurrent.CompletableFuture; @@ -34,7 +34,7 @@ import org.bukkit.plugin.java.JavaPlugin; * This class is here for both convenience purposes, and also for the sake of providing easy access to executors for * {@link CompletableFuture} invocations. */ -public class FreedomExecutor +public class ExecutorProvider { /** * An executor which runs tasks synchronously. @@ -46,9 +46,9 @@ public class FreedomExecutor private final Executor asyncExecutor; /** - * Creates a new {@link FreedomExecutor} instance. + * Creates a new {@link ExecutorProvider} instance. */ - public FreedomExecutor(final Patchwork patchwork) + public ExecutorProvider(final Patchwork patchwork) { syncExecutor = r -> Bukkit.getScheduler() .runTask(patchwork, r); diff --git a/Patchwork/src/main/java/fns/patchwork/service/SubscriptionProvider.java b/Patchwork/src/main/java/fns/patchwork/provider/SubscriptionProvider.java similarity index 97% rename from Patchwork/src/main/java/fns/patchwork/service/SubscriptionProvider.java rename to Patchwork/src/main/java/fns/patchwork/provider/SubscriptionProvider.java index 5d410b7..c15bf5c 100644 --- a/Patchwork/src/main/java/fns/patchwork/service/SubscriptionProvider.java +++ b/Patchwork/src/main/java/fns/patchwork/provider/SubscriptionProvider.java @@ -21,8 +21,12 @@ * SOFTWARE. */ -package fns.patchwork.service; +package fns.patchwork.provider; +import fns.patchwork.service.Service; +import fns.patchwork.service.ServiceSubscription; +import fns.patchwork.service.Task; +import fns.patchwork.service.TaskSubscription; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; diff --git a/Patchwork/src/main/java/fns/patchwork/registry/SQLRegistry.java b/Patchwork/src/main/java/fns/patchwork/registry/SQLRegistry.java new file mode 100644 index 0000000..9f7eac3 --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/registry/SQLRegistry.java @@ -0,0 +1,44 @@ +/* + * This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite + * Copyright (C) 2023 Simplex Development and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.patchwork.registry; + +import fns.patchwork.sql.SQL; +import java.util.Optional; + +public class SQLRegistry +{ + private SQL sql; + + public SQLRegistry() { + this.sql = null; + } + + public Optional getSQL() { + return (sql == null) ? Optional.empty() : Optional.of(sql); + } + + public void setSQL(final SQL sql) { + this.sql = sql; + } +} diff --git a/Patchwork/src/main/java/fns/patchwork/registry/ServiceTaskRegistry.java b/Patchwork/src/main/java/fns/patchwork/registry/ServiceTaskRegistry.java index c346c0c..00d7ffa 100644 --- a/Patchwork/src/main/java/fns/patchwork/registry/ServiceTaskRegistry.java +++ b/Patchwork/src/main/java/fns/patchwork/registry/ServiceTaskRegistry.java @@ -25,7 +25,7 @@ package fns.patchwork.registry; import fns.patchwork.service.Service; import fns.patchwork.service.ServiceSubscription; -import fns.patchwork.service.SubscriptionProvider; +import fns.patchwork.provider.SubscriptionProvider; import fns.patchwork.service.Task; import fns.patchwork.service.TaskSubscription; import java.util.ArrayList; diff --git a/Patchwork/src/main/java/fns/patchwork/service/Service.java b/Patchwork/src/main/java/fns/patchwork/service/Service.java index b541c89..dc3a779 100644 --- a/Patchwork/src/main/java/fns/patchwork/service/Service.java +++ b/Patchwork/src/main/java/fns/patchwork/service/Service.java @@ -23,6 +23,8 @@ package fns.patchwork.service; +import fns.patchwork.provider.SubscriptionProvider; + /** * Represents a ticking service. Services may be asynchronous or synchronous, however there are some restrictions: *
    diff --git a/Patchwork/src/main/java/fns/patchwork/sql/SQL.java b/Patchwork/src/main/java/fns/patchwork/sql/SQL.java index d68409a..5b66a10 100644 --- a/Patchwork/src/main/java/fns/patchwork/sql/SQL.java +++ b/Patchwork/src/main/java/fns/patchwork/sql/SQL.java @@ -31,7 +31,7 @@ public interface SQL { CompletableFuture prepareStatement(final String query, final Object... args); - CompletableFuture executeQuery(final String query, final Object... args); + CompletableFuture executeQuery(final String query, final Object... args); CompletableFuture executeUpdate(final String query, final Object... args); diff --git a/Patchwork/src/main/java/fns/patchwork/sql/SQLResult.java b/Patchwork/src/main/java/fns/patchwork/sql/SQLResult.java new file mode 100644 index 0000000..b23dbd5 --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/sql/SQLResult.java @@ -0,0 +1,348 @@ +/* + * This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite + * Copyright (C) 2023 Simplex Development and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.patchwork.sql; + +import fns.patchwork.utils.container.Pair; +import fns.patchwork.utils.logging.FNS4J; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +public class SQLResult +{ + private final Map> resultMap = new HashMap<>(); + + /** + * This constructor will create a new SQLResult object from the specified ResultSet. + * This will iterate through all rows and columns of the ResultSet and store them in a Map. + * The Map will contain keys of integers representing the row number, and values of Maps + * containing the column names and their values. + * + * @param resultSet The ResultSet to create the SQLResult object from. + */ + public SQLResult(final ResultSet resultSet) + { + try + { + final ResultSetMetaData metaData = resultSet.getMetaData(); + final int columnCount = metaData.getColumnCount(); + + int rowIndex = 0; + + while (resultSet.next()) + { + rowIndex++; + final Map rowMap = new HashMap<>(); + + for (int columnIndex = 1; columnIndex <= columnCount; columnIndex++) + { + String columnName = metaData.getColumnName(columnIndex); + Object columnValue = resultSet.getObject(columnIndex); + rowMap.put(columnName, columnValue); + } + + resultMap.put(rowIndex, rowMap); + } + } + catch (SQLException ex) + { + FNS4J.getLogger("Tyr").error(ex.getMessage()); + } + } + + /** + * This method will return a map of all rows and their columns and values. + * + * @return A Map containing all rows and their columns and values. + */ + public Map> getResultMap() + { + return resultMap; + } + + /** + * This method will return a map of all columns and their values from the specified row. + * + * @param rowIndex The row index to get the column names from. + * @return A Map containing all column names and their values from the specified row. + */ + public Map getRow(final int rowIndex) + { + return resultMap.get(rowIndex); + } + + /** + * This method will return the value from the specified row and column. + * + * @param rowIndex The row index to get the column name from. + * @param columnName The column name to get the value from. + * @return The value from the specified row and column. + */ + public Object getValue(final int rowIndex, final String columnName) + { + return resultMap.get(rowIndex).get(columnName); + } + + /** + * This method will return the first value from the first row of the result set. + * + * @return A Pair containing the column name and the stored value. + */ + public Pair getFirst() + { + return new Pair<>(resultMap.get(1).entrySet().iterator().next().getKey(), + resultMap.get(1).entrySet().iterator().next().getValue()); + } + + /** + * This method will return the first value from the specified row of the result set. + * + * @param rowIndex The row index to get the column name from. + * @return A Pair containing the column name and the stored value. + */ + public Pair getFirst(final int rowIndex) + { + return new Pair<>(resultMap.get(rowIndex).entrySet().iterator().next().getKey(), + resultMap.get(rowIndex).entrySet().iterator().next().getValue()); + } + + /** + * This method will return the last value from the first row of the result set. + * + * @return A Pair containing the column name and the stored value. + */ + public Pair getLast() + { + return new Pair<>(resultMap.get(1).entrySet().iterator().next().getKey(), + resultMap.get(1).entrySet().iterator().next().getValue()); + } + + /** + * This method will return the last value from the specified row of the result set. + * + * @param rowIndex The row index to get the column name from. + * @return A Pair containing the column name and the stored value. + */ + public Pair getLast(final int rowIndex) + { + return new Pair<>(resultMap.get(rowIndex).entrySet().iterator().next().getKey(), + resultMap.get(rowIndex).entrySet().iterator().next().getValue()); + } + + /** + * This method will attempt to retrieve the value from the specified row and column, + * and cast it to the specified class. This will throw a {@link ClassCastException} if the + * returned value is not an instance of the provided class. + * + * @param rowIndex The row index to get the column name from. + * @param columnName The column name to get the value from. + * @param clazz The class to cast the value to. + * @param The expected type. + * @return The value from the specified row and column, cast to the specified class. + */ + public T autoCast(final int rowIndex, final String columnName, final Class clazz) + { + final Object value = resultMap.get(rowIndex).get(columnName); + + if (!clazz.isInstance(value)) + throw new ClassCastException("Cannot cast " + value.getClass().getName() + " to " + clazz.getName()); + + return clazz.cast(resultMap.get(rowIndex).get(columnName)); + } + + /** + * @param rowIndex The row index to get the column names from. + * @return A Set containing all column names from the specified row of the result set. + */ + public Set getColumnNames(final int rowIndex) + { + return resultMap.get(rowIndex).keySet(); + } + + /** + * @return A Set containing all column names from the first row of the result set. + */ + public Set getColumnNames() + { + return resultMap.get(1).keySet(); + } + + /** + * This method will apply the specified consumer to all rows of the result set. + * + * @param columnConsumer The consumer to apply to all rows of the result set. + */ + public void accept(final Consumer> columnConsumer) + { + this.resultMap.forEach((integer, map) -> columnConsumer.accept(map)); + } + + /** + * Checks to see if the result set contains the specified row number. + * Best used in a for loop, using {@link #rowCount()} as the upper bound. + * + * @param rowIndex The row index to check. + * @return True if the result set contains the specified row number, false otherwise. + */ + public boolean hasNext(final int rowIndex) + { + return this.resultMap.containsKey(rowIndex + 1); + } + + /** + * Checks to see if the result set has the first row. + * If row 1 doesn't exist, it's safe to say the result set is empty. + * + * @return True if the result set has row 1, false otherwise. + */ + public boolean hasNext() + { + return this.resultMap.containsKey(1); + } + + /** + * @return The number of rows in the result set. + */ + public int rowCount() + { + return this.resultMap.size(); + } + + /** + * @param rowIndex The row index from which to count columns. + * @return The number of columns in the specified row. + */ + public int columnCount(final int rowIndex) + { + return this.resultMap.get(rowIndex).size(); + } + + /** + * Retrieves a String value from the specified row and column. + * + * @param rowIndex The row index to get the column name from. + * @param columnName The column name to get the value from. + * @return The String value from the specified row and column. + * @see #autoCast(int, String, Class) + */ + public String getString(final int rowIndex, final String columnName) + { + return autoCast(rowIndex, columnName, String.class); + } + + /** + * This method will attempt to retrieve a String value from the specified column within the first row of the + * result set. + * + * @param columnName The column name to get the value from. + * @return The String value from the specified column within the first row of the result set. + * @see #getString(int, String) + */ + public String getString(final String columnName) + { + return getString(1, columnName); + } + + /** + * Retrieves an Integer value from the specified row and column. + * + * @param rowIndex The row index to get the column name from. + * @param columnName The column name to get the value from. + * @return The Integer value from the specified row and column. + * @see #autoCast(int, String, Class) + */ + public int getInteger(final int rowIndex, final String columnName) + { + return autoCast(rowIndex, columnName, Integer.class); + } + + /** + * This method will attempt to retrieve an Integer value from the specified column within the first row of the + * result set. + * + * @param columnName The column name to get the value from. + * @return The Integer value from the specified column within the first row of the result set. + * @see #getInteger(int, String) + */ + public int getInteger(final String columnName) + { + return getInteger(1, columnName); + } + + /** + * Retrieves a Long value from the specified row and column. + * + * @param rowIndex The row index to get the column name from. + * @param columnName The column name to get the value from. + * @return The Long value from the specified row and column. + * @see #autoCast(int, String, Class) + */ + public long getLong(final int rowIndex, final String columnName) + { + return autoCast(rowIndex, columnName, Long.class); + } + + /** + * This method will attempt to retrieve a Long value from the specified column within the first row of the + * result set. + * + * @param columnName The column name to get the value from. + * @return The Long value from the specified column within the first row of the result set. + * @see #getLong(int, String) + */ + public long getLong(final String columnName) + { + return getLong(1, columnName); + } + + /** + * Retrieves a Double value from the specified row and column. + * + * @param rowIndex The row index to get the column name from. + * @param columnName The column name to get the value from. + * @return The Double value from the specified row and column. + * @see #autoCast(int, String, Class) + */ + public boolean getBoolean(final int rowIndex, final String columnName) + { + return autoCast(rowIndex, columnName, Boolean.class); + } + + /** + * This method will attempt to retrieve a Boolean value from the specified column within the first row of the + * result set. + * + * @param columnName The column name to get the value from. + * @return The Boolean value from the specified column within the first row of the result set. + * @see #getBoolean(int, String) + */ + public boolean getBoolean(final String columnName) + { + return getBoolean(1, columnName); + } +} diff --git a/Tyr/build.gradle b/Tyr/build.gradle index 4bd615f..8a40b19 100644 --- a/Tyr/build.gradle +++ b/Tyr/build.gradle @@ -16,11 +16,10 @@ bukkit { } dependencies { - compileOnly project(":Patchwork") - compileOnly project(":Datura") + compileOnly project(path: ":Patchwork") + compileOnly project(path: ":Datura") - library 'com.hierynomus:sshj:0.28.0' - library 'org.bouncycastle:bcprov-jdk18on:1.76' + library 'com.j256.two-factor-auth:two-factor-auth:1.3' testImplementation platform('org.junit:junit-bom:5.9.1') testImplementation 'org.junit.jupiter:junit-jupiter' diff --git a/Tyr/src/main/java/fns/tyr/Tyr.java b/Tyr/src/main/java/fns/tyr/Tyr.java new file mode 100644 index 0000000..d1c5c16 --- /dev/null +++ b/Tyr/src/main/java/fns/tyr/Tyr.java @@ -0,0 +1,48 @@ +/* + * This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite + * Copyright (C) 2023 Simplex Development and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.tyr; + +import fns.datura.Datura; +import fns.patchwork.base.Shortcuts; +import fns.patchwork.sql.SQL; +import fns.patchwork.utils.logging.FNS4J; + +public class Tyr +{ + public void onEnable() + { + final SQL sql = Shortcuts.provideModule(Datura.class).getSQL(); + sql.createTable("sessionData", + "user VARCHAR(16) NOT NULL PRIMARY KEY, secretKey VARCHAR(64) NOT NULL;") + .whenCompleteAsync((result, throwable) -> + { + if (throwable != null) + FNS4J.getLogger("Tyr") + .error(throwable.getMessage()); + }, Shortcuts.getExecutors() + .getAsync()); + + + } +} diff --git a/Tyr/src/main/java/fns/tyr/data/SQLEntry.java b/Tyr/src/main/java/fns/tyr/data/SQLEntry.java new file mode 100644 index 0000000..9347c75 --- /dev/null +++ b/Tyr/src/main/java/fns/tyr/data/SQLEntry.java @@ -0,0 +1,93 @@ +/* + * This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite + * Copyright (C) 2023 Simplex Development and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.tyr.data; + +import fns.patchwork.base.Shortcuts; +import fns.patchwork.utils.logging.FNS4J; +import fns.tyr.oauth.Identity; +import java.sql.SQLException; + +public class SQLEntry +{ + private final Identity identity; + + public SQLEntry(final Identity identity) + { + this.identity = identity; + } + + public static SQLEntry load(final String username) + { + return Shortcuts.getSQL() + .map(c -> + c.executeQuery("SELECT * FROM sessionData WHERE user = ?;", username) + .thenApplyAsync(result -> + { + SQLEntry entry = null; + try + { + if (result.next()) + { + final String user = result.getString("user"); + final String secretKey = result.getString("secretKey"); + + final Identity i = new Identity(user, secretKey); + + entry = new SQLEntry(i); + FNS4J.getLogger("Tyr") + .info("Loaded entry for " + username); + } + else + { + entry = new SQLEntry(Identity.of(username)); + FNS4J.getLogger("Tyr") + .info("Created a new entry for " + username); + } + } + catch (SQLException ex) + { + FNS4J.getLogger("Tyr").error(ex.getMessage()); + } + return entry; + }, Shortcuts.getExecutors() + .getAsync()) + .join()) + .orElseThrow(() -> new IllegalStateException("SQL is not initialized!")); + } + + public void save() + { + Shortcuts.getSQL() + .orElseThrow(() -> new IllegalStateException("SQL is not available!")) + .executeUpdate("INSERT INTO sessionData (user, secretKey) VALUES (?, ?);", + this.identity.username(), + this.identity.secretKey()) + .whenCompleteAsync((result, throwable) -> + { + if (throwable != null) + FNS4J.getLogger("Tyr").error(throwable.getMessage()); + }, Shortcuts.getExecutors() + .getAsync()); + } +} diff --git a/Tyr/src/main/java/fns/tyr/oauth/Identity.java b/Tyr/src/main/java/fns/tyr/oauth/Identity.java new file mode 100644 index 0000000..93b7cf2 --- /dev/null +++ b/Tyr/src/main/java/fns/tyr/oauth/Identity.java @@ -0,0 +1,31 @@ +/* + * This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite + * Copyright (C) 2023 Simplex Development and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.tyr.oauth; + +public record Identity(String username, String secretKey) +{ + public static Identity of(final String username) { + return new Identity(username, TOTP.createSecretKey()); + } +} diff --git a/Tyr/src/main/java/fns/tyr/oauth/OAuth2.java b/Tyr/src/main/java/fns/tyr/oauth/OAuth2.java new file mode 100644 index 0000000..998dd3f --- /dev/null +++ b/Tyr/src/main/java/fns/tyr/oauth/OAuth2.java @@ -0,0 +1,74 @@ +/* + * This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite + * Copyright (C) 2023 Simplex Development and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.tyr.oauth; + +import fns.patchwork.base.Shortcuts; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +public class OAuth2 +{ + private final Set identitySet; + + public OAuth2() + { + this.identitySet = new HashSet<>(); + } + + public void addIdentity(Identity identity) + { + this.identitySet.add(identity); + } + + public void removeIdentity(Identity identity) + { + this.identitySet.remove(identity); + } + + public Optional getIdentity(final String username) + { + return this.identitySet.stream() + .filter(identity -> identity.username().equals(username)) + .findFirst(); + } + + public void loadAll() + { + Shortcuts.getSQL() + .ifPresent(sql -> sql.executeQuery("SELECT * FROM sessionData;") + .thenAcceptAsync(result -> + { + for (int i = 1; i < result.rowCount(); i++) + { + final String username = result.getString(i, + "user"); + final String secretKey = result.getString(i, + "secretKey"); + this.addIdentity( + new Identity(username, secretKey)); + } + })); + } +} diff --git a/Tyr/src/main/java/fns/tyr/oauth/TOTP.java b/Tyr/src/main/java/fns/tyr/oauth/TOTP.java new file mode 100644 index 0000000..7a7420b --- /dev/null +++ b/Tyr/src/main/java/fns/tyr/oauth/TOTP.java @@ -0,0 +1,63 @@ +/* + * This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite + * Copyright (C) 2023 Simplex Development and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.tyr.oauth; + +import com.j256.twofactorauth.TimeBasedOneTimePasswordUtil; +import fns.patchwork.utils.logging.FNS4J; +import java.security.GeneralSecurityException; + +/** + * User-friendly version of TimeBasedOneTimePasswordUtil. + */ +public final class TOTP +{ + private TOTP() + { + throw new AssertionError("This class cannot be instantiated."); + } + + public static String createSecretKey() + { + return TimeBasedOneTimePasswordUtil.generateBase32Secret(32); + } + + public static String createQRCode(final String username, final String secretKey) + { + return TimeBasedOneTimePasswordUtil.qrImageUrl(username, secretKey); + } + + public static boolean verify(final String secretKey, final int userCode) + { + try + { + int vCode = TimeBasedOneTimePasswordUtil.generateCurrentNumber(secretKey); + return vCode == userCode; + } + catch (GeneralSecurityException ex) + { + FNS4J.getLogger("Tyr").error("Failed to verify TOTP code: " + ex.getMessage()); + return false; + } + } +}