diff --git a/SkyblockCore-BungeeCord/src/main/java/me/illusion/skyblockcore/bungee/SkyblockBungeePlugin.java b/SkyblockCore-BungeeCord/src/main/java/me/illusion/skyblockcore/bungee/SkyblockBungeePlugin.java index dcd7f58..677aa12 100644 --- a/SkyblockCore-BungeeCord/src/main/java/me/illusion/skyblockcore/bungee/SkyblockBungeePlugin.java +++ b/SkyblockCore-BungeeCord/src/main/java/me/illusion/skyblockcore/bungee/SkyblockBungeePlugin.java @@ -1,5 +1,6 @@ package me.illusion.skyblockcore.bungee; +import java.io.File; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import lombok.Getter; @@ -9,11 +10,10 @@ import me.illusion.skyblockcore.common.command.audience.SkyblockAudience; import me.illusion.skyblockcore.common.command.manager.SkyblockCommandManager; import me.illusion.skyblockcore.common.config.SkyblockMessagesFile; -import me.illusion.skyblockcore.common.config.impl.SkyblockCacheDatabasesFile; -import me.illusion.skyblockcore.common.config.impl.SkyblockDatabasesFile; -import me.illusion.skyblockcore.common.database.SkyblockDatabaseRegistry; +import me.illusion.skyblockcore.common.database.registry.SkyblockDatabaseRegistry; import me.illusion.skyblockcore.common.event.manager.SkyblockEventManager; import me.illusion.skyblockcore.common.event.manager.SkyblockEventManagerImpl; +import me.illusion.skyblockcore.common.utilities.file.IOUtils; import me.illusion.skyblockcore.proxy.SkyblockProxyPlatform; import me.illusion.skyblockcore.proxy.command.PlaySkyblockCommand; import me.illusion.skyblockcore.proxy.config.SkyblockMatchmakingFile; @@ -30,8 +30,6 @@ @Getter public class SkyblockBungeePlugin extends Plugin implements SkyblockProxyPlatform { - private SkyblockCacheDatabasesFile cacheDatabasesFile; - private SkyblockDatabasesFile databasesFile; private SkyblockMatchmakingFile matchmakingFile; private SkyblockDatabaseRegistry databaseRegistry; @@ -51,8 +49,6 @@ public void onEnable() { commandManager = new BungeeSkyblockCommandManager(this); - cacheDatabasesFile = new SkyblockCacheDatabasesFile(this); - databasesFile = new SkyblockDatabasesFile(this); matchmakingFile = new SkyblockMatchmakingFile(this); messagesFile = new SkyblockMessagesFile(this, "proxy-messages"); @@ -84,22 +80,25 @@ private void initMatchmaking() { } private void loadDatabase() { - databaseRegistry.tryEnableMultiple(cacheDatabasesFile, databasesFile).thenAccept(success -> { - if (Boolean.FALSE.equals(success)) { // The future returns a boxed boolean - getLogger().severe("Failed to enable databases. Disabling plugin."); - disableExceptionally(); + File databasesFolder = new File(getDataFolder(), "databases"); + + IOUtils.traverseAndLoad(databasesFolder, file -> { + if (!file.getName().endsWith(".yml")) { return; } - initMatchmaking(); + databaseRegistry.loadPossible(configurationProvider.loadConfiguration(file)); + }); + + databaseRegistry.finishLoading().thenRun(() -> { finishEnable(); + initMatchmaking(); }); } @Override public void onDisable() { - databaseRegistry.getChosenCacheDatabase().flush().join(); - databaseRegistry.getChosenDatabase().flush().join(); + databaseRegistry.shutdown().join(); } @Override diff --git a/SkyblockCore-BungeeCord/src/main/java/me/illusion/skyblockcore/bungee/config/BungeeConfigurationProvider.java b/SkyblockCore-BungeeCord/src/main/java/me/illusion/skyblockcore/bungee/config/BungeeConfigurationProvider.java index d19f5d4..3adc2df 100644 --- a/SkyblockCore-BungeeCord/src/main/java/me/illusion/skyblockcore/bungee/config/BungeeConfigurationProvider.java +++ b/SkyblockCore-BungeeCord/src/main/java/me/illusion/skyblockcore/bungee/config/BungeeConfigurationProvider.java @@ -1,11 +1,14 @@ package me.illusion.skyblockcore.bungee.config; import java.io.File; +import java.io.IOException; import me.illusion.skyblockcore.bungee.SkyblockBungeePlugin; import me.illusion.skyblockcore.bungee.utilities.config.BungeeConfigurationAdapter; import me.illusion.skyblockcore.bungee.utilities.storage.BungeeYMLBase; import me.illusion.skyblockcore.common.config.ConfigurationProvider; -import me.illusion.skyblockcore.common.config.ReadOnlyConfigurationSection; +import me.illusion.skyblockcore.common.config.section.ConfigurationSection; +import net.md_5.bungee.config.Configuration; +import net.md_5.bungee.config.YamlConfiguration; /** * Bungee implementation of {@link ConfigurationProvider}. @@ -24,8 +27,23 @@ public File getDataFolder() { } @Override - public ReadOnlyConfigurationSection loadConfiguration(File file) { + public ConfigurationSection loadConfiguration(File file) { BungeeYMLBase base = new BungeeYMLBase(plugin, file, true); - return BungeeConfigurationAdapter.adapt("", base.getConfiguration()); + return BungeeConfigurationAdapter.adapt(file, this, "", base.getConfiguration()); + } + + @Override + public void saveConfiguration(ConfigurationSection section, File file) { + Configuration configuration = new Configuration(); + + BungeeConfigurationAdapter.writeTo(section, configuration); + + net.md_5.bungee.config.ConfigurationProvider provider = net.md_5.bungee.config.ConfigurationProvider.getProvider(YamlConfiguration.class); + + try { + provider.save(configuration, file); + } catch (IOException e) { + throw new RuntimeException(e); + } } } diff --git a/SkyblockCore-BungeeCord/src/main/java/me/illusion/skyblockcore/bungee/utilities/config/BungeeConfigurationAdapter.java b/SkyblockCore-BungeeCord/src/main/java/me/illusion/skyblockcore/bungee/utilities/config/BungeeConfigurationAdapter.java index aec850c..07fce27 100644 --- a/SkyblockCore-BungeeCord/src/main/java/me/illusion/skyblockcore/bungee/utilities/config/BungeeConfigurationAdapter.java +++ b/SkyblockCore-BungeeCord/src/main/java/me/illusion/skyblockcore/bungee/utilities/config/BungeeConfigurationAdapter.java @@ -1,8 +1,11 @@ package me.illusion.skyblockcore.bungee.utilities.config; +import java.io.File; import java.util.HashMap; import java.util.Map; -import me.illusion.skyblockcore.common.config.ReadOnlyConfigurationSection; +import me.illusion.skyblockcore.common.config.ConfigurationProvider; +import me.illusion.skyblockcore.common.config.section.ConfigurationSection; +import me.illusion.skyblockcore.common.config.section.WritableConfigurationSection; import net.md_5.bungee.config.Configuration; public final class BungeeConfigurationAdapter { @@ -12,13 +15,13 @@ private BungeeConfigurationAdapter() { } /** - * Adapt a {@link Configuration} to a {@link ReadOnlyConfigurationSection}, flattening the section + * Adapt a {@link Configuration} to a {@link ConfigurationSection}, flattening the section * * @param name The name of the section, this parameter only exists because Bungee's configuration system does not store the name of the section * @param configuration The configuration to adapt * @return The adapted section */ - public static ReadOnlyConfigurationSection adapt(String name, Configuration configuration) { + public static ConfigurationSection adapt(File file, ConfigurationProvider provider, String name, Configuration configuration) { if (configuration == null) { return null; } @@ -30,11 +33,11 @@ public static ReadOnlyConfigurationSection adapt(String name, Configuration conf Object value = entry.getValue(); if (value instanceof Configuration) { - map.put(key, adapt(key, (Configuration) value)); + map.put(key, adapt(file, provider, key, (Configuration) value)); } } - return new ReadOnlyConfigurationSection(name, map); + return new WritableConfigurationSection(name, map, file, provider); } /** @@ -52,4 +55,18 @@ public static Map getValues(Configuration configuration) { return map; } + + public static void writeTo(ConfigurationSection skyblockSection, Configuration configuration) { + for (String key : skyblockSection.getKeys()) { + Object value = skyblockSection.get(key); + + if (value instanceof ConfigurationSection section) { + Configuration subSection = configuration.getSection(key); + + writeTo(section, subSection); + } else { + configuration.set(key, value); + } + } + } } diff --git a/SkyblockCore-Common/build.gradle b/SkyblockCore-Common/build.gradle index 38c8d00..3237acc 100644 --- a/SkyblockCore-Common/build.gradle +++ b/SkyblockCore-Common/build.gradle @@ -22,7 +22,7 @@ dependencies { implementation 'com.google.guava:guava:32.1.1-jre' implementation 'redis.clients:jedis:5.0.1' - implementation 'org.mongodb:mongodb-driver-sync:4.10.2' + compileOnly 'org.mongodb:mongodb-driver-sync:4.10.2' } def targetJavaVersion = 17 diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/AbstractConfiguration.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/AbstractConfiguration.java index f340b96..edbedb1 100644 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/AbstractConfiguration.java +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/AbstractConfiguration.java @@ -1,6 +1,7 @@ package me.illusion.skyblockcore.common.config; import java.io.File; +import me.illusion.skyblockcore.common.config.section.ConfigurationSection; import me.illusion.skyblockcore.common.platform.SkyblockPlatform; /** @@ -10,7 +11,7 @@ public abstract class AbstractConfiguration { private final File file; private final ConfigurationProvider provider; - protected ReadOnlyConfigurationSection configuration; + protected ConfigurationSection configuration; protected AbstractConfiguration(SkyblockPlatform platform, String fileName) { provider = platform.getConfigurationProvider(); @@ -24,7 +25,7 @@ protected AbstractConfiguration(SkyblockPlatform platform, String fileName) { * * @return The configuration section of this configuration. */ - public ReadOnlyConfigurationSection getConfiguration() { + public ConfigurationSection getConfiguration() { return configuration; } diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/ConfigurationProvider.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/ConfigurationProvider.java index a505f0e..efb5745 100644 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/ConfigurationProvider.java +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/ConfigurationProvider.java @@ -1,6 +1,7 @@ package me.illusion.skyblockcore.common.config; import java.io.File; +import me.illusion.skyblockcore.common.config.section.ConfigurationSection; /** * Represents a configuration provider, which is used to load configuration files. @@ -20,7 +21,7 @@ public interface ConfigurationProvider { * @param file The file to load the configuration from. * @return The configuration section of the file. */ - ReadOnlyConfigurationSection loadConfiguration(File file); + ConfigurationSection loadConfiguration(File file); /** * Loads a configuration from a file name. @@ -28,8 +29,16 @@ public interface ConfigurationProvider { * @param fileName The name of the file to load the configuration from. * @return The configuration section of the file. */ - default ReadOnlyConfigurationSection loadConfiguration(String fileName) { + default ConfigurationSection loadConfiguration(String fileName) { return loadConfiguration(new File(getDataFolder(), fileName)); } + /** + * Saves a configuration to a file. + * + * @param section The configuration section to save. + * @param file The file to save the configuration to. + */ + void saveConfiguration(ConfigurationSection section, File file); + } diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/ReadOnlyConfigurationSection.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/ReadOnlyConfigurationSection.java deleted file mode 100644 index 51f1796..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/ReadOnlyConfigurationSection.java +++ /dev/null @@ -1,107 +0,0 @@ -package me.illusion.skyblockcore.common.config; - -import java.util.Map; - -/** - * Represents a read-only configuration section. This is similar to bukkit's system, but it is not tied to bukkit, and can be adapted to other systems, like - * json - */ -public class ReadOnlyConfigurationSection { - - private final Map internalMap; - private final String name; - - public ReadOnlyConfigurationSection(String name, Map internalMap) { - this.name = name; - this.internalMap = internalMap; - } - - public String getName() { - return name; - } - - public boolean contains(String path) { - return internalMap.containsKey(path); - } - - public boolean isSet(String path) { - return contains(path); - } - - public ReadOnlyConfigurationSection getSection(String path) { - return get(path, ReadOnlyConfigurationSection.class); - } - - public Object get(String path) { - return internalMap.get(path); - } - - public Object get(String path, Object def) { - return internalMap.getOrDefault(path, def); - } - - public T get(String path, Class type) { - Object obj = get(path); - - if (obj == null) { - return null; - } - - return type.cast(obj); - } - - public T get(String path, Class type, T def) { - Object obj = get(path, def); - - if (obj == null) { - return null; - } - - return type.cast(obj); - } - - public String getString(String path) { - return get(path, String.class); - } - - public String getString(String path, String def) { - return get(path, String.class, def); - } - - public int getInt(String path) { - return get(path, Integer.class, 0); - } - - public int getInt(String path, int def) { - return get(path, Integer.class, def); - } - - public double getDouble(String path) { - return get(path, Double.class, 0d); - } - - public double getDouble(String path, double def) { - return get(path, Double.class, def); - } - - public boolean getBoolean(String path) { - return get(path, Boolean.class, false); - } - - public boolean getBoolean(String path, boolean def) { - return get(path, Boolean.class, def); - } - - public long getLong(String path) { - return get(path, Long.class, 0L); - } - - public long getLong(String path, long def) { - return get(path, Long.class, def); - } - - public float getFloat(String path) { - return get(path, Float.class, 0f); - } - -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/impl/AbstractDatabaseConfiguration.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/impl/AbstractDatabaseConfiguration.java deleted file mode 100644 index 8debf10..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/impl/AbstractDatabaseConfiguration.java +++ /dev/null @@ -1,53 +0,0 @@ -package me.illusion.skyblockcore.common.config.impl; - -import me.illusion.skyblockcore.common.config.AbstractConfiguration; -import me.illusion.skyblockcore.common.config.ReadOnlyConfigurationSection; -import me.illusion.skyblockcore.common.database.SkyblockDatabase; -import me.illusion.skyblockcore.common.database.SkyblockDatabaseSetup; -import me.illusion.skyblockcore.common.platform.SkyblockPlatform; - -/** - * Represents an abstract version of a database configuration. This provides the ability to get the properties of a database, as well as the ability to get the - * preferred database type and filter out unsupported databases. - * - * @param The database type. - */ -public abstract class AbstractDatabaseConfiguration extends AbstractConfiguration implements SkyblockDatabaseSetup { - - private boolean supportsFileBased = true; - - protected AbstractDatabaseConfiguration(SkyblockPlatform platform, String fileName) { - super(platform, fileName); - } - - public ReadOnlyConfigurationSection getProperties(String databaseType) { - return getConfiguration().getSection(databaseType); - } - - public String getFallback(String databaseType) { - ReadOnlyConfigurationSection databaseSection = getProperties(databaseType); - - if (databaseSection == null) { - return null; - } - - return databaseSection.getString("fallback"); - } - - public String getPreferredDatabase() { - return getConfiguration().getString("preferred"); - } - - public boolean supportsFileBased() { - return supportsFileBased; - } - - public void setSupportsFileBased(boolean supportsFileBased) { // This can be set by the network type - this.supportsFileBased = supportsFileBased; - } - - public boolean isSupported(T database) { - return !database.isFileBased() || supportsFileBased(); // If the database is file based, we must also support file based databases - } - -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/impl/SkyblockCacheDatabasesFile.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/impl/SkyblockCacheDatabasesFile.java deleted file mode 100644 index 6a1606c..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/impl/SkyblockCacheDatabasesFile.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.illusion.skyblockcore.common.config.impl; - -import me.illusion.skyblockcore.common.database.cache.SkyblockCacheDatabase; -import me.illusion.skyblockcore.common.platform.SkyblockPlatform; - -/** - * Represents the cache database configuration file. - */ -public class SkyblockCacheDatabasesFile extends AbstractDatabaseConfiguration { - - public SkyblockCacheDatabasesFile(SkyblockPlatform platform) { - super(platform, "cache-database.yml"); - } - - @Override - public Class getDatabaseClass() { - return SkyblockCacheDatabase.class; - } -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/impl/SkyblockDatabasesFile.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/impl/SkyblockDatabasesFile.java deleted file mode 100644 index 4d27b7d..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/impl/SkyblockDatabasesFile.java +++ /dev/null @@ -1,20 +0,0 @@ -package me.illusion.skyblockcore.common.config.impl; - -import me.illusion.skyblockcore.common.database.fetching.SkyblockFetchingDatabase; -import me.illusion.skyblockcore.common.platform.SkyblockPlatform; - -/** - * Represents the database configuration file. - */ -public class SkyblockDatabasesFile extends AbstractDatabaseConfiguration { - - public SkyblockDatabasesFile(SkyblockPlatform platform) { - super(platform, "database.yml"); - } - - - @Override - public Class getDatabaseClass() { - return SkyblockFetchingDatabase.class; - } -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/section/ConfigurationSection.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/section/ConfigurationSection.java new file mode 100644 index 0000000..57687fa --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/section/ConfigurationSection.java @@ -0,0 +1,48 @@ +package me.illusion.skyblockcore.common.config.section; + +import java.util.Collection; + +public interface ConfigurationSection { + + boolean contains(String path); + + Object get(String path); + + Object get(String path, Object def); + + T get(String path, Class type); + + T get(String path, Class type, T def); + + String getString(String path); + + String getString(String path, String def); + + int getInt(String path); + + int getInt(String path, int def); + + double getDouble(String path); + + double getDouble(String path, double def); + + boolean getBoolean(String path); + + boolean getBoolean(String path, boolean def); + + long getLong(String path); + + long getLong(String path, long def); + + float getFloat(String path); + + float getFloat(String path, float def); + + ConfigurationSection getSection(String path); + + Collection getKeys(); + + + ConfigurationSection cloneWithName(String name); + +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/section/MemoryConfigurationSection.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/section/MemoryConfigurationSection.java new file mode 100644 index 0000000..f456589 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/section/MemoryConfigurationSection.java @@ -0,0 +1,187 @@ +package me.illusion.skyblockcore.common.config.section; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Represents a read-only configuration section. This is similar to bukkit's system, but it is not tied to bukkit, and can be adapted to other systems, like + * json + */ +public class MemoryConfigurationSection implements ConfigurationSection { + + protected final Map internalMap; + private final String name; + + public MemoryConfigurationSection(String name, Map internalMap) { + this.name = name; + this.internalMap = internalMap; + } + + public MemoryConfigurationSection() { + this.name = ""; + this.internalMap = new ConcurrentHashMap<>(); + } + + public MemoryConfigurationSection(Map internalMap) { + this.name = ""; + this.internalMap = internalMap; + } + + public String getName() { + return name; + } + + public boolean contains(String path) { + return internalMap.containsKey(path); + } + + public boolean isSet(String path) { + return contains(path); + } + + public ConfigurationSection getSection(String path) { + return get(path, ConfigurationSection.class); + } + + public Object get(String path) { + int index = path.indexOf('.'); + + if (index != -1) { + String key = path.substring(0, index); + path = path.substring(index + 1); + + ConfigurationSection section = getSection(key); + + if (section == null) { + return null; + } + + return section.get(path); + } + + return internalMap.get(path); + } + + public Object get(String path, Object def) { + Object obj = get(path); + + if (obj == null) { + return def; + } + + return obj; + } + + public T get(String path, Class type) { + Object obj = get(path); + + if (obj == null) { + return null; + } + + if (type.isInstance(obj) || type.isAssignableFrom(obj.getClass())) { + return type.cast(obj); + } + + throw new IllegalArgumentException("Cannot cast " + obj.getClass().getName() + " to " + type.getName() + " at " + path + " in " + name); + } + + public T get(String path, Class type, T def) { + Object obj = get(path, def); + + if (obj == null) { + return def; + } + + if (type.isInstance(obj) || type.isAssignableFrom(obj.getClass())) { + return type.cast(obj); + } + + throw new IllegalArgumentException("Cannot cast " + obj.getClass().getName() + " to " + type.getName()); + } + + public String getString(String path) { + return get(path, String.class); + } + + public String getString(String path, String def) { + return get(path, String.class, def); + } + + public Number getNumber(String path) { + return get(path, Number.class); + } + + public Number getNumber(String path, Number def) { + return get(path, Number.class, def); + } + + public int getInt(String path) { + return getNumber(path).intValue(); + } + + public int getInt(String path, int def) { + return getNumber(path, def).intValue(); + } + + public double getDouble(String path) { + return getNumber(path).doubleValue(); + } + + public double getDouble(String path, double def) { + return getNumber(path, def).doubleValue(); + } + + public boolean getBoolean(String path) { + return get(path, Boolean.class, false); + } + + public boolean getBoolean(String path, boolean def) { + return get(path, Boolean.class, def); + } + + public long getLong(String path) { + return getNumber(path).longValue(); + } + + public long getLong(String path, long def) { + return getNumber(path, def).longValue(); + } + + public float getFloat(String path) { + return (float) getDouble(path); + } + + public float getFloat(String path, float def) { + return (float) getDouble(path, def); + } + + public Collection getKeys() { + return new ArrayList<>(internalMap.keySet()); + } + + @Override + public ConfigurationSection cloneWithName(String name) { + return new MemoryConfigurationSection(name, new ConcurrentHashMap<>(internalMap)); + } + + public List getStringList(String path) { + return get(path, List.class, new ArrayList<>()); + } + + public boolean isSection(String path) { + return get(path) instanceof ConfigurationSection; + } + + @Override + public String toString() { + return "ReadOnlyConfigurationSection{" + + "internalMap=" + internalMap + + ", name='" + name + '\'' + + '}'; + } +} + diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/section/WritableConfigurationSection.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/section/WritableConfigurationSection.java new file mode 100644 index 0000000..4efd0e4 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/config/section/WritableConfigurationSection.java @@ -0,0 +1,59 @@ +package me.illusion.skyblockcore.common.config.section; + +import java.io.File; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import me.illusion.skyblockcore.common.config.ConfigurationProvider; + +public class WritableConfigurationSection extends MemoryConfigurationSection { + + private final File file; + private final ConfigurationProvider provider; + + public WritableConfigurationSection(ConfigurationProvider provider, String fileName) { + this.provider = provider; + this.file = new File(provider.getDataFolder(), fileName); + } + + public WritableConfigurationSection(String name, Map internalMap, File file, ConfigurationProvider provider) { + super(name, internalMap); + this.file = file; + this.provider = provider; + } + + public WritableConfigurationSection(File file, ConfigurationProvider provider) { + this.file = file; + this.provider = provider; + } + + public WritableConfigurationSection(Map internalMap, File file, ConfigurationProvider provider) { + super(internalMap); + this.file = file; + this.provider = provider; + } + + public void addDefault(String path, Object value) { + if (contains(path)) { + return; + } + + internalMap.put(path, value); + } + + public void set(String path, Object value) { + if (value instanceof ConfigurationSection section) { + value = section.cloneWithName(path); + } + + internalMap.put(path, value); + } + + public void save() { + provider.saveConfiguration(this, file); + } + + @Override + public ConfigurationSection cloneWithName(String name) { + return new WritableConfigurationSection(name, new ConcurrentHashMap<>(internalMap), file, provider); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/AbstractSkyblockDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/AbstractSkyblockDatabase.java new file mode 100644 index 0000000..a605676 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/AbstractSkyblockDatabase.java @@ -0,0 +1,67 @@ +package me.illusion.skyblockcore.common.database; + +import com.google.common.collect.Sets; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import me.illusion.skyblockcore.common.config.section.ConfigurationSection; +import me.illusion.skyblockcore.common.platform.SkyblockPlatform; + +public abstract class AbstractSkyblockDatabase implements SkyblockDatabase { + + private final Set tags = Sets.newConcurrentHashSet(); + private final Set> futures = Sets.newConcurrentHashSet(); + + private ConfigurationSection properties; // Only initialized on the enable method + + public void addTag(SkyblockDatabaseTag tag) { + tags.add(tag); + } + + @Override + public ConfigurationSection getProperties() { + return properties; + } + + protected void setProperties(ConfigurationSection properties) { + this.properties = properties; + } + + @Override + public Collection getTags() { + return Collections.unmodifiableSet(tags); + } + + @Override + public CompletableFuture flush() { + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + } + + protected CompletableFuture addFuture(CompletableFuture future) { + futures.add(future); + return future.whenComplete((v, e) -> futures.remove(future)); + } + + protected CompletableFuture associate(Supplier supplier) { + return addFuture(CompletableFuture.supplyAsync(supplier)); + } + + protected CompletableFuture associate(Runnable runnable) { + return addFuture(CompletableFuture.runAsync(runnable)); + } + + protected CompletableFuture associate(CompletableFuture future) { + return addFuture(future); + } + + @Override + public CompletableFuture enable(SkyblockPlatform platform, ConfigurationSection properties) { + return enable(properties); + } + + public CompletableFuture enable(ConfigurationSection properties) { + throw new IllegalStateException("You must override at least one enable method!"); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/SkyblockDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/SkyblockDatabase.java index 4214788..5ebc8bd 100644 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/SkyblockDatabase.java +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/SkyblockDatabase.java @@ -1,36 +1,26 @@ package me.illusion.skyblockcore.common.database; +import java.util.Collection; import java.util.concurrent.CompletableFuture; -import me.illusion.skyblockcore.common.config.ReadOnlyConfigurationSection; +import me.illusion.skyblockcore.common.config.section.ConfigurationSection; +import me.illusion.skyblockcore.common.platform.SkyblockPlatform; -/** - * This interface represents a template for all databases. Do not implement this interface directly, instead implement one of the sub-interfaces. - */ public interface SkyblockDatabase { - /** - * Obtains the name of this database - * - * @return The name - */ String getName(); - /** - * Enables the database - * - * @param properties The properties, such as the host, port, username, password, etc. - * @return A future - */ - CompletableFuture enable(ReadOnlyConfigurationSection properties); - - /** - * Flushes the database, this is called when the server is shutting down - * - * @return A future which completes when the database is flushed - */ + ConfigurationSection getProperties(); + + CompletableFuture enable(SkyblockPlatform platform, ConfigurationSection properties); + CompletableFuture flush(); - boolean isFileBased(); + CompletableFuture wipe(); + + Collection getTags(); + default boolean hasTag(SkyblockDatabaseTag tag) { + return getTags().contains(tag); + } } diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/SkyblockDatabaseRegistry.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/SkyblockDatabaseRegistry.java deleted file mode 100644 index a15ca32..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/SkyblockDatabaseRegistry.java +++ /dev/null @@ -1,229 +0,0 @@ -package me.illusion.skyblockcore.common.database; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Level; -import java.util.logging.Logger; -import me.illusion.skyblockcore.common.config.ReadOnlyConfigurationSection; -import me.illusion.skyblockcore.common.database.cache.SkyblockCacheDatabase; -import me.illusion.skyblockcore.common.database.cache.redis.MemorySkyblockCache; -import me.illusion.skyblockcore.common.database.cache.redis.RedisSkyblockCache; -import me.illusion.skyblockcore.common.database.fetching.SkyblockFetchingDatabase; -import me.illusion.skyblockcore.common.database.fetching.mongo.MongoSkyblockDatabase; -import me.illusion.skyblockcore.common.database.fetching.sql.impl.MariaDBSkyblockDatabase; -import me.illusion.skyblockcore.common.database.fetching.sql.impl.MySQLSkyblockDatabase; -import me.illusion.skyblockcore.common.database.fetching.sql.impl.PostgresSkyblockDatabase; -import me.illusion.skyblockcore.common.database.fetching.sql.impl.SQLiteSkyblockDatabase; -import me.illusion.skyblockcore.common.platform.SkyblockPlatform; - -/** - * This class is responsible for registering and managing all skyblock databases. - */ -public class SkyblockDatabaseRegistry { - - private final Map databases = new ConcurrentHashMap<>(); - private final Map, String> chosenDatabases = new ConcurrentHashMap<>(); - private final Logger logger; - - public SkyblockDatabaseRegistry(SkyblockPlatform platform) { - this.logger = platform.getLogger(); - registerDefaultDatabases(platform); - } - - /** - * Registers a skyblock database - * - * @param database The database to register - */ - public void register(SkyblockDatabase database) { - String name = database.getName(); - - System.out.println("Registering database " + name + " of type " + database.getClass().getSimpleName()); - databases.put(name, database); - } - - /** - * Gets a skyblock database by name - * - * @param name The name of the database - * @return The database, or null if it does not exist - */ - public SkyblockDatabase get(String name) { - System.out.println("Getting database " + name); - return databases.get(name); - } - - /** - * Gets the currently chosen database - * - * @return The chosen database - */ - public SkyblockFetchingDatabase getChosenDatabase() { - return getChosenDatabase(SkyblockFetchingDatabase.class); - } - - /** - * Gets the currently chosen database - * - * @return The chosen database - */ - public SkyblockCacheDatabase getChosenCacheDatabase() { - return getChosenDatabase(SkyblockCacheDatabase.class); - } - - /** - * Gets the currently chosen database - * - * @return The chosen database - */ - public T getChosenDatabase(Class databaseClass) { - String name = chosenDatabases.get(databaseClass); - - if (name == null) { - return null; - } - - SkyblockDatabase database = get(name); - - if (database == null || !databaseClass.isAssignableFrom(database.getClass())) { - return null; - } - - return databaseClass.cast(database); - } - - /** - * Registers the default databases provided by the SkyblockCore plugin - * - * @param platform The platform to register the databases for - */ - private void registerDefaultDatabases(SkyblockPlatform platform) { - System.out.println("Registering default databases"); - // non-sql databases - register(new MongoSkyblockDatabase()); - - // sql remote databases - register(new MariaDBSkyblockDatabase()); - register(new MySQLSkyblockDatabase()); - register(new PostgresSkyblockDatabase()); - - // sql local databases - register(new SQLiteSkyblockDatabase(platform.getDataFolder())); - - // cache databases - register(new RedisSkyblockCache()); - - System.out.println("MEMORY MAN"); - register(new MemorySkyblockCache()); - } - - /** - * Tries to enable the preferred database, and if it fails, tries to enable the fallback - * - * @param setup The setup to use - * @return A completable future that completes when the database is enabled - */ - public CompletableFuture tryEnableMultiple(SkyblockDatabaseSetup... setup) { - CompletableFuture future = new CompletableFuture<>(); - List> futures = new ArrayList<>(); - - // Not a fan of this dance, but the idea is that if any of the databases fail to enable, we want to complete the future with false - - for (SkyblockDatabaseSetup databaseSetup : setup) { - futures.add(tryEnable(databaseSetup).thenApply(success -> { - if (!Boolean.TRUE.equals(success) && !future.isDone()) { - System.out.println("Failed to enable database " + databaseSetup.getPreferredDatabase() + ", disabling plugin..."); - future.complete(false); - } - - return success; - })); - } - - CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenRun(() -> { - if (!future.isDone()) { - future.complete(true); - } - }); - - return future; - } - - public CompletableFuture tryEnable(SkyblockDatabaseSetup setup) { - String preferred = setup.getPreferredDatabase(); - - SkyblockDatabase database = get(preferred); - - if (database == null) { - System.out.println("Database " + preferred + " is null, trying fallback"); - return tryEnableFallback(setup, setup.getFallback(preferred)).exceptionally(error -> { - warning("Failed to enable database {0}, disabling plugin...", preferred); - error.printStackTrace(); - return false; - }); - } - - return tryEnableFallback(setup, preferred).exceptionally(error -> { - warning("Failed to enable database {0}, disabling plugin...", preferred); - error.printStackTrace(); - return false; - }); - } - - /** - * Tries to enable a database, and if it fails, tries to enable the fallback - * - * @param setup The setup to use - * @param type The database type to try to enable - * @param The internal database type, such as SkyblockCacheDatabase - * @return A completable future that completes when the database is enabled - */ - private CompletableFuture tryEnableFallback(SkyblockDatabaseSetup setup, String type) { - if (type == null) { // If the fallback is null, we can't do anything, so we just return false - System.out.println("Type is null"); - return CompletableFuture.completedFuture(false); - } - - SkyblockDatabase database = databases.get(type); - String fallback = setup.getFallback(type); - - if (database == null) { // If the database specified doesn't exist, we try the fallback - warning("Failed to find database {0}, attempting fallback..", type); - return tryEnableFallback(setup, fallback); - } - - Class clazz = setup.getDatabaseClass(); - - if (!clazz.isAssignableFrom(database.getClass()) || !setup.isSupported(clazz.cast(database))) { - warning("Failed to enable database {0}, attempting fallback..", type); - return tryEnableFallback(setup, fallback); // If the database is file based, and the setup doesn't support file based databases, we try the fallback - } - - ReadOnlyConfigurationSection properties = setup.getProperties(type); - - if (properties == null) { // If there are no properties to load from, we try the fallback - warning("Failed to find properties for {0}, attempting fallback..", type); - return tryEnableFallback(setup, fallback); - } - - return database.enable(properties) - .thenCompose(success -> { // We try to enable the database, and if it fails, we try the fallback until there is no fallback - if (Boolean.TRUE.equals(success)) { - logger.info("Successfully enabled database " + type); - chosenDatabases.put(clazz, type); - return CompletableFuture.completedFuture(true); - } - - warning("Failed to enable database {0}, attempting fallback..", type); - return tryEnableFallback(setup, fallback); - }); - } - - private void warning(String message, Object... objects) { - logger.log(Level.WARNING, message, objects); - } - -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/SkyblockDatabaseSetup.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/SkyblockDatabaseSetup.java deleted file mode 100644 index bf827bf..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/SkyblockDatabaseSetup.java +++ /dev/null @@ -1,57 +0,0 @@ -package me.illusion.skyblockcore.common.database; - -import me.illusion.skyblockcore.common.config.ReadOnlyConfigurationSection; - -/** - * Represents a setup for a skyblock database. This class is used to load the database. - * - * @param The sub-interface of the database - */ -public interface SkyblockDatabaseSetup { - - /** - * Gets whether the setuo supports file based databases - * - * @return TRUE if we should load file based databases, FALSE otherwise - */ - boolean supportsFileBased(); - - /** - * Gets the properties for a database type, this is used for loading the database - * - * @param databaseType The database type - * @return The properties - */ - ReadOnlyConfigurationSection getProperties(String databaseType); - - /** - * Gets the fallback database type - * - * @param databaseType The database type - * @return The fallback database type - */ - String getFallback(String databaseType); - - /** - * Gets the preferred database type - * - * @return The preferred database type - */ - String getPreferredDatabase(); - - /** - * Checks whether the setup supports the database, there are some databases that are not supported by some structures - * - * @param database The database - * @return TRUE if the setup supports the database, FALSE otherwise - */ - boolean isSupported(T database); - - /** - * Obtains the sub-interface of the database - * - * @return The class - */ - Class getDatabaseClass(); - -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/SkyblockDatabaseTag.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/SkyblockDatabaseTag.java new file mode 100644 index 0000000..1eb207b --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/SkyblockDatabaseTag.java @@ -0,0 +1,23 @@ +package me.illusion.skyblockcore.common.database; + +public enum SkyblockDatabaseTag { + + // Database type + + FETCHING, + CACHE, + METRICS, + + // Implementation details + + FILE_BASED, + SQL, + NO_SQL, + + // Connection type + + REMOTE, + LOCAL, + + +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/AbstractCacheDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/AbstractCacheDatabase.java new file mode 100644 index 0000000..0732a5f --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/AbstractCacheDatabase.java @@ -0,0 +1,11 @@ +package me.illusion.skyblockcore.common.database.cache; + +import me.illusion.skyblockcore.common.database.AbstractSkyblockDatabase; +import me.illusion.skyblockcore.common.database.SkyblockDatabaseTag; + +public abstract class AbstractCacheDatabase extends AbstractSkyblockDatabase implements SkyblockCache { + + protected AbstractCacheDatabase() { + addTag(SkyblockDatabaseTag.CACHE); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/SkyblockCache.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/SkyblockCache.java new file mode 100644 index 0000000..0c47f3e --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/SkyblockCache.java @@ -0,0 +1,10 @@ +package me.illusion.skyblockcore.common.database.cache; + +import java.util.concurrent.CompletableFuture; +import me.illusion.skyblockcore.common.database.SkyblockDatabase; + +public interface SkyblockCache extends SkyblockDatabase { + + CompletableFuture unloadServer(); + +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/SkyblockCacheDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/SkyblockCacheDatabase.java deleted file mode 100644 index 8559666..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/SkyblockCacheDatabase.java +++ /dev/null @@ -1,72 +0,0 @@ -package me.illusion.skyblockcore.common.database.cache; - -import java.util.Collection; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import me.illusion.skyblockcore.common.database.SkyblockDatabase; - -/** - * This interface represents a template for all caching databases. A caching database is responsible for caching the data that is currently in use, such as what - * servers have what islands. - */ -public interface SkyblockCacheDatabase extends SkyblockDatabase { - - /** - * Fetches the server id that has ownership of the island - * - * @param islandId The island's id - * @return A future of the server id, may be null - */ - CompletableFuture getIslandServer(UUID islandId); - - /** - * Updates the server id that has ownership of the island - * - * @param islandId The island's id - * @param serverId The server's id - * @return A future - */ - CompletableFuture updateIslandServer(UUID islandId, String serverId); - - /** - * Removes the island from the cache, this is called when the island is unloaded - * - * @param islandId The island's id - * @return A future which completes when the island is removed from the cache. - */ - CompletableFuture removeIsland(UUID islandId); - - /** - * Removes a server from the cache, which removes all islands that are owned by the server - * - * @param serverId The server's id - * @return A future which completes when the server is removed from the cache. - */ - CompletableFuture removeServer(String serverId); - - /** - * Gets all the islands that are owned by the server - * - * @param serverId The server's id - * @return A future of the islands - */ - CompletableFuture> getIslands(String serverId); - - /** - * Gets all the islands that are owned by all servers - * - * @return A future of the islands - */ - CompletableFuture>> getAllIslands(); - - /** - * Indicates whether or not the database is file based. Some network structures may refuse to load in this occasion. - * - * @return TRUE if the database is file based, FALSE otherwise - */ - default boolean isFileBased() { - return false; - } - -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/redis/AbstractRedisCacheDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/redis/AbstractRedisCacheDatabase.java new file mode 100644 index 0000000..502d943 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/redis/AbstractRedisCacheDatabase.java @@ -0,0 +1,64 @@ +package me.illusion.skyblockcore.common.database.cache.redis; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; +import me.illusion.skyblockcore.common.config.section.ConfigurationSection; +import me.illusion.skyblockcore.common.database.SkyblockDatabaseTag; +import me.illusion.skyblockcore.common.database.cache.AbstractCacheDatabase; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; + +public abstract class AbstractRedisCacheDatabase extends AbstractCacheDatabase { + + private JedisPool pool; + private String password; + + protected AbstractRedisCacheDatabase() { + addTag(SkyblockDatabaseTag.REMOTE); + } + + protected CompletableFuture query(Function function) { + return associate(() -> { + try (Jedis jedis = getJedis()) { + return function.apply(jedis); + } + }); + } + + protected CompletableFuture update(Consumer consumer) { + return associate(() -> { + try (Jedis jedis = getJedis()) { + consumer.accept(jedis); + } + }); + } + + protected Jedis getJedis() { + Jedis jedis = pool.getResource(); + + if (password != null) { + jedis.auth(password); + } + + return jedis; + } + + @Override + public CompletableFuture enable(ConfigurationSection properties) { + setProperties(properties); + + String host = properties.getString("host"); + int port = properties.getInt("port"); + this.password = properties.getString("password"); + + pool = new JedisPool(host, port); + + return query(jedis -> jedis.ping().equalsIgnoreCase("PONG")); + } + + @Override + public String getName() { + return "redis"; + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/redis/MemorySkyblockCache.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/redis/MemorySkyblockCache.java deleted file mode 100644 index 9df1fc7..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/redis/MemorySkyblockCache.java +++ /dev/null @@ -1,77 +0,0 @@ -package me.illusion.skyblockcore.common.database.cache.redis; - -import java.util.Collection; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import me.illusion.skyblockcore.common.config.ReadOnlyConfigurationSection; -import me.illusion.skyblockcore.common.database.cache.SkyblockCacheDatabase; - -/** - * Represents an in-memory implementation of a SkyblockCacheDatabase. This should not be used for anything other than a "simple" network structure. - */ -public class MemorySkyblockCache implements SkyblockCacheDatabase { - - private final Map islandServers = new ConcurrentHashMap<>(); - - @Override - public String getName() { - return "memory"; - } - - @Override - public CompletableFuture enable(ReadOnlyConfigurationSection properties) { - return CompletableFuture.completedFuture(true); - } - - @Override - public CompletableFuture flush() { - return CompletableFuture.completedFuture(null); - } - - @Override - public CompletableFuture getIslandServer(UUID islandId) { - return CompletableFuture.completedFuture(islandServers.get(islandId)); - } - - @Override - public CompletableFuture updateIslandServer(UUID islandId, String serverId) { - return CompletableFuture.runAsync(() -> islandServers.put(islandId, serverId)); - } - - @Override - public CompletableFuture removeIsland(UUID islandId) { - return CompletableFuture.runAsync(() -> islandServers.remove(islandId)); - } - - @Override - public CompletableFuture removeServer(String serverId) { - return CompletableFuture.runAsync(() -> islandServers.values().removeIf(serverId::equals)); - } - - @Override - public CompletableFuture> getIslands(String serverId) { - Set islands = ConcurrentHashMap.newKeySet(); - - for (Map.Entry entry : islandServers.entrySet()) { - if (entry.getValue().equals(serverId)) { - islands.add(entry.getKey()); - } - } - - return CompletableFuture.completedFuture(islands); - } - - @Override - public CompletableFuture>> getAllIslands() { - Map> islands = new ConcurrentHashMap<>(); - - for (Map.Entry entry : islandServers.entrySet()) { - islands.computeIfAbsent(entry.getValue(), key -> ConcurrentHashMap.newKeySet()).add(entry.getKey()); - } - - return CompletableFuture.completedFuture(islands); - } -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/redis/RedisSkyblockCache.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/redis/RedisSkyblockCache.java deleted file mode 100644 index 4f3f202..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/cache/redis/RedisSkyblockCache.java +++ /dev/null @@ -1,119 +0,0 @@ -package me.illusion.skyblockcore.common.database.cache.redis; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; -import java.util.function.Function; -import me.illusion.skyblockcore.common.communication.redis.RedisController; -import me.illusion.skyblockcore.common.config.ReadOnlyConfigurationSection; -import me.illusion.skyblockcore.common.database.cache.SkyblockCacheDatabase; -import redis.clients.jedis.Jedis; - -/** - * A redis implementation of {@link SkyblockCacheDatabase} - */ -public class RedisSkyblockCache implements SkyblockCacheDatabase { - - private final Set> futures = ConcurrentHashMap.newKeySet(); - private RedisController controller; - - private static final String ISLAND_SERVERS_KEY = "island-servers"; - - @Override - public String getName() { - return "redis"; - } - - @Override - public CompletableFuture enable(ReadOnlyConfigurationSection properties) { - return CompletableFuture.supplyAsync(() -> { - String host = properties.getString("host", "localhost"); - int port = properties.getInt("port", 6379); - String password = properties.getString("password"); - boolean ssl = properties.getBoolean("ssl", false); - - controller = new RedisController(host, port, password, ssl); - - return controller.isValid(); - }); - } - - @Override - public CompletableFuture getIslandServer(UUID islandId) { - return associate(jedis -> jedis.hget(ISLAND_SERVERS_KEY, islandId.toString())); - } - - @Override - public CompletableFuture updateIslandServer(UUID islandId, String serverId) { - return associateTask(jedis -> jedis.hset(ISLAND_SERVERS_KEY, islandId.toString(), serverId)); - } - - @Override - public CompletableFuture removeServer(String serverId) { - return associateTask(jedis -> jedis.hdel(ISLAND_SERVERS_KEY, serverId)); - } - - @Override - public CompletableFuture> getIslands(String serverId) { - return associate(jedis -> jedis.hkeys(ISLAND_SERVERS_KEY)).thenApply(strings -> { - List islands = new ArrayList<>(); - - for (String string : strings) { - islands.add(UUID.fromString(string)); - } - - return islands; - }); - } - - @Override - public CompletableFuture>> getAllIslands() { - return associate(jedis -> jedis.hgetAll(ISLAND_SERVERS_KEY)).thenApply(stringMap -> { - Map> map = new ConcurrentHashMap<>(); - - for (Map.Entry entry : stringMap.entrySet()) { - String islandId = entry.getKey(); - String serverId = entry.getValue(); - - map.computeIfAbsent(serverId, k -> new ArrayList<>()).add(UUID.fromString(islandId)); - } - - return map; - }); - } - - @Override - public CompletableFuture removeIsland(UUID islandId) { - return associateTask(jedis -> jedis.hdel(ISLAND_SERVERS_KEY, islandId.toString())); - } - - @Override - public CompletableFuture flush() { - return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); - } - - private CompletableFuture associate(Function function) { - CompletableFuture future = controller.supply(function); - - futures.add(future); - future.whenComplete((aVoid, throwable) -> futures.remove(future)); - - return future; - } - - private CompletableFuture associateTask(Consumer consumer) { - CompletableFuture future = controller.borrow(consumer); - - futures.add(future); - future.whenComplete((aVoid, throwable) -> futures.remove(future)); - - return future; - } - -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/SkyblockFetchingDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/SkyblockFetchingDatabase.java deleted file mode 100644 index dba3fc0..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/SkyblockFetchingDatabase.java +++ /dev/null @@ -1,111 +0,0 @@ -package me.illusion.skyblockcore.common.database.fetching; - -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.function.Function; -import me.illusion.skyblockcore.common.data.IslandData; -import me.illusion.skyblockcore.common.database.SkyblockDatabase; - -/** - * This interface represents a template for all fetching databases. A fetching database is responsible for fetching the actual data from the database. - */ -public interface SkyblockFetchingDatabase extends SkyblockDatabase { - - /** - * Fetches the island id of a player - * - * @param profileId The profile's id - * @return The island id - */ - CompletableFuture fetchIslandId(UUID profileId); - - /** - * Fetches the island data of an island - * - * @param islandId The island's id - * @return The island data - */ - CompletableFuture fetchIslandData(UUID islandId); - - /** - * Saves the island data - * - * @param data The island data - * @return A future - */ - CompletableFuture saveIslandData(IslandData data); - - /** - * Deletes the island data - * - * @param islandId The island's id - * @return A future - */ - CompletableFuture deleteIslandData(UUID islandId); - - /** - * Fetches the profile id of a player - * - * @param playerId The player's id - * @return The profile id - */ - CompletableFuture getProfileId(UUID playerId); - - /** - * Sets the profile id of a player - * - * @param playerId The player's id - * @param profileId The profile's id - * @return A future - */ - CompletableFuture setProfileId(UUID playerId, UUID profileId); - - /** - * Deletes the island data of a player - * - * @param profileId The player's id - * @return A future - */ - default CompletableFuture deletePlayerIsland(UUID profileId) { - return compose(fetchIslandId(profileId), this::deleteIslandData); - } - - /** - * Fetches the island data of a player - * - * @param profileId The player's id - * @return The island data - */ - default CompletableFuture fetchPlayerIsland(UUID profileId) { - return compose(fetchIslandId(profileId), this::fetchIslandData); - } - - /** - * Composes a future, returning null if the value is null - * - * @param future The future - * @param function The function - * @param The type of the first future - * @param The return type of the second future - * @return The composed future - */ - private CompletableFuture compose(CompletableFuture future, Function> function) { - return future.thenCompose(value -> { - if (value == null) { - return CompletableFuture.completedFuture(null); - } - - return function.apply(value); - }); - } - - /** - * Checks if the database is file based, meaning it is not a remote database and is not supported by complex networks - * - * @return If the database is file based - */ - default boolean isFileBased() { - return false; - } - -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/mongo/MongoSkyblockDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/mongo/MongoSkyblockDatabase.java deleted file mode 100644 index 1167ead..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/mongo/MongoSkyblockDatabase.java +++ /dev/null @@ -1,192 +0,0 @@ -package me.illusion.skyblockcore.common.database.fetching.mongo; - -import com.mongodb.ConnectionString; -import com.mongodb.MongoClientSettings; -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; -import com.mongodb.client.MongoCollection; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Supplier; -import me.illusion.skyblockcore.common.config.ReadOnlyConfigurationSection; -import me.illusion.skyblockcore.common.data.IslandData; -import me.illusion.skyblockcore.common.database.fetching.SkyblockFetchingDatabase; -import me.illusion.skyblockcore.common.database.fetching.mongo.codec.MongoIslandDataCodec; -import me.illusion.skyblockcore.common.database.fetching.mongo.codec.MongoUUIDCodec; -import org.bson.Document; -import org.bson.codecs.configuration.CodecRegistries; -import org.bson.codecs.configuration.CodecRegistry; - -/** - * The mongodb implementation of {@link SkyblockFetchingDatabase}. - */ -public class MongoSkyblockDatabase implements SkyblockFetchingDatabase { - - private final Set> futures = ConcurrentHashMap.newKeySet(); - - private MongoClient mongoClient; - - private MongoCollection islandDataCollection; // Island ID : Island Data - private MongoCollection islandIdCollection; // Profile ID : Island ID - private MongoCollection profileIdCollection; // Player ID : Profile ID - - @Override - public String getName() { - return "mongodb"; - } - - @Override - public CompletableFuture enable(ReadOnlyConfigurationSection properties) { - return associate(() -> { - String connectionString = properties.getString("connection-string"); - - if (connectionString == null) { - String ip = properties.getString("ip"); - int port = properties.getInt("port"); - String authsource = properties.getString("auth-source", "admin"); - String username = properties.getString("username"); - String password = properties.getString("password"); - boolean ssl = properties.getBoolean("ssl", false); - - connectionString = createConnectionString(ip, port, authsource, username, password, ssl); - } - - String database = properties.getString("database", "skyblock"); - String collectionName = properties.getString("collection", "islands"); - - CodecRegistry codecs = CodecRegistries.fromCodecs( - MongoIslandDataCodec.INSTANCE, - MongoUUIDCodec.INSTANCE - ); - - try { - MongoClientSettings settings = MongoClientSettings.builder() - .applyConnectionString(new ConnectionString(connectionString)) - .codecRegistry(codecs) - .build(); - - mongoClient = MongoClients.create(settings); - - islandDataCollection = mongoClient.getDatabase(database) - .getCollection(collectionName, IslandData.class); // if the collection doesn't exist, it will be created - islandIdCollection = mongoClient.getDatabase(database).getCollection("island_ids", UUID.class); - profileIdCollection = mongoClient.getDatabase(database).getCollection("profile_ids", UUID.class); - - // validate the session - mongoClient.listDatabaseNames().first(); // throws an exception if the connection is invalid - return true; - } catch (Exception expected) { // catching MongoException doesn't work for some reason - return false; - } - }); - } - - @Override - public CompletableFuture fetchIslandId(UUID profileId) { - return associate(() -> islandIdCollection.find(ownerId(profileId)).first()); - } - - @Override - public CompletableFuture fetchIslandData(UUID islandId) { - return associate(() -> islandDataCollection.find(islandId(islandId)).first()); - } - - @Override - public CompletableFuture saveIslandData(IslandData data) { - return associate(() -> { - islandDataCollection.replaceOne(islandId(data.getIslandId()), data); - - // Let's also set the island id to the owner - islandIdCollection.replaceOne(ownerId(data.getOwnerId()), data.getIslandId()); - }); - } - - @Override - public CompletableFuture deleteIslandData(UUID islandId) { - return associate(() -> { - Document filter = islandId(islandId); - islandDataCollection.deleteOne(filter); - islandIdCollection.deleteOne(filter); - }); - } - - @Override - public CompletableFuture getProfileId(UUID playerId) { - return associate(() -> profileIdCollection.find(playerId(playerId)).first()); - } - - @Override - public CompletableFuture setProfileId(UUID playerId, UUID profileId) { - return associate((Runnable) () -> profileIdCollection.replaceOne(playerId(playerId), profileId)); - } - - private CompletableFuture associate(Supplier supplier) { - CompletableFuture future = CompletableFuture.supplyAsync(supplier); - - future.thenRun(() -> futures.remove(future)); - future.exceptionally(throwable -> { - throwable.printStackTrace(); - return null; - }); - - futures.add(future); - return future; - } - - private CompletableFuture associate(Runnable runnable) { - CompletableFuture future = CompletableFuture.runAsync(runnable); - - future.thenRun(() -> futures.remove(future)); - future.exceptionally(throwable -> { - throwable.printStackTrace(); - return null; - }); - - futures.add(future); - return future; - } - - - @Override - public CompletableFuture flush() { - return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); - } - - private String createConnectionString(String ip, int port, String authsource, String username, String password, boolean ssl) { - StringBuilder builder = new StringBuilder(); - builder.append("mongodb://"); - if (username != null && !username.isEmpty()) { - builder.append(username); - if (password != null && !password.isEmpty()) { - builder.append(":").append(password); - } - builder.append("@"); - } - - builder.append(ip).append(":").append(port); - - if (authsource != null && !authsource.isEmpty()) { - builder.append("/?authSource=").append(authsource); - } - - if (ssl) { - builder.append("&ssl=true"); - } - - return builder.toString(); - } - - private Document islandId(UUID islandId) { - return new Document("islandId", islandId); - } - - private Document playerId(UUID playerId) { - return new Document("playerId", playerId); - } - - private Document ownerId(UUID ownerId) { - return new Document("ownerId", ownerId); - } -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/AbstractRemoteSQLDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/AbstractRemoteSQLDatabase.java deleted file mode 100644 index 1ee5c01..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/AbstractRemoteSQLDatabase.java +++ /dev/null @@ -1,34 +0,0 @@ -package me.illusion.skyblockcore.common.database.fetching.sql; - -import java.sql.Connection; -import java.sql.SQLException; -import me.illusion.skyblockcore.common.config.ReadOnlyConfigurationSection; - -/** - * Represents a remote SQL database. - */ -public abstract class AbstractRemoteSQLDatabase extends AbstractSQLSkyblockDatabase { - - protected String host; - protected int port; - protected String database; - protected String username; - protected String password; - - @Override - protected boolean enableDriver(ReadOnlyConfigurationSection properties) { - host = properties.getString("host", "localhost"); - port = properties.getInt("port", 3306); - database = properties.getString("database", "skyblock"); - username = properties.getString("username", "root"); - password = properties.getString("password", "password"); - - try (Connection connection = createConnection()) { - return connection != null && connection.isValid(5); - } catch (SQLException e) { - return false; - } - } - - -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/AbstractSQLSkyblockDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/AbstractSQLSkyblockDatabase.java deleted file mode 100644 index ed33f86..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/AbstractSQLSkyblockDatabase.java +++ /dev/null @@ -1,269 +0,0 @@ -package me.illusion.skyblockcore.common.database.fetching.sql; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.EnumMap; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; -import me.illusion.skyblockcore.common.config.ReadOnlyConfigurationSection; -import me.illusion.skyblockcore.common.data.IslandData; -import me.illusion.skyblockcore.common.database.fetching.SkyblockFetchingDatabase; - -/** - * The abstract sql implementation of {@link SkyblockFetchingDatabase}. Certain methods are left abstract to allow for different implementations, as queries may - * differ. For example, Postgres uses BYTEA for binary data, while MySQL uses BLOB. - */ -public abstract class AbstractSQLSkyblockDatabase implements SkyblockFetchingDatabase { - - private final Set> futures = ConcurrentHashMap.newKeySet(); - private final AtomicReference connectionReference = new AtomicReference<>(); - - @Override - public CompletableFuture enable(ReadOnlyConfigurationSection properties) { - return associate(() -> enableDriver(properties)).thenCompose(unused -> createTables()); - } - - @Override - public CompletableFuture fetchIslandId(UUID profileId) { - return associate(() -> { - String query = getQueries().get(SkyblockSQLQuery.FETCH_ISLAND_ID); - - try (Connection connection = getConnection(); PreparedStatement statement = connection.prepareStatement(query)) { - statement.setString(1, profileId.toString()); - - ResultSet set = statement.executeQuery(); - - if (set.next()) { - return UUID.fromString(set.getString("island_id")); - } - } catch (Exception ex) { - ex.printStackTrace(); - } - - return null; - }); - } - - @Override - public CompletableFuture fetchIslandData(UUID islandId) { - return associate(() -> { - String query = getQueries().get(SkyblockSQLQuery.FETCH_ISLAND_DATA); - - try (Connection connection = getConnection(); PreparedStatement statement = connection.prepareStatement(query)) { - statement.setString(1, islandId.toString()); - - ResultSet set = statement.executeQuery(); - - if (!(set.next())) { - return null; - } - - String id = set.getString("island_id"); - String ownerId = set.getString("owner_id"); - - return new IslandData(UUID.fromString(id), UUID.fromString(ownerId)); - } catch (Exception ex) { - ex.printStackTrace(); - } - - return null; - }); - } - - @Override - public CompletableFuture saveIslandData(IslandData data) { - return associate(() -> { - String query1 = getQueries().get(SkyblockSQLQuery.SAVE_ISLAND_DATA); - String query2 = getQueries().get(SkyblockSQLQuery.SAVE_ISLAND_ID); - - try (Connection connection = getConnection(); PreparedStatement statement = connection.prepareStatement( - query1); PreparedStatement statement2 = connection.prepareStatement(query2)) { - statement.setString(1, data.getIslandId().toString()); - statement.setString(2, data.getOwnerId().toString()); - - statement2.setString(1, data.getOwnerId().toString()); - statement2.setString(2, data.getIslandId().toString()); - - statement.execute(); - statement2.execute(); - } catch (Exception ex) { - ex.printStackTrace(); - } - }); - } - - @Override - public CompletableFuture deleteIslandData(UUID islandId) { - return associate(() -> { - String query1 = getQueries().get(SkyblockSQLQuery.DELETE_ISLAND_DATA); - String query2 = getQueries().get(SkyblockSQLQuery.DELETE_ISLAND_ID); - - try (Connection connection = getConnection(); PreparedStatement statement = connection.prepareStatement( - query1); PreparedStatement statement2 = connection.prepareStatement(query2)) { - statement.setString(1, islandId.toString()); - statement2.setString(1, islandId.toString()); - - statement.execute(); - statement2.execute(); - } catch (Exception ex) { - ex.printStackTrace(); - } - }); - } - - @Override - public CompletableFuture setProfileId(UUID playerId, UUID profileId) { - return associate(() -> { - String query = getQueries().get(SkyblockSQLQuery.SAVE_PLAYER_PROFILE); - - try (Connection connection = getConnection(); PreparedStatement statement = connection.prepareStatement(query)) { - statement.setString(1, profileId.toString()); - statement.setString(2, playerId.toString()); - - statement.execute(); - } catch (Exception ex) { - ex.printStackTrace(); - } - }); - } - - @Override - public CompletableFuture getProfileId(UUID playerId) { - return associate(() -> { - String query = getQueries().get(SkyblockSQLQuery.FETCH_PLAYER_PROFILE); - - try (Connection connection = getConnection(); PreparedStatement statement = connection.prepareStatement(query)) { - statement.setString(1, playerId.toString()); - - ResultSet set = statement.executeQuery(); - - if (set.next()) { - return UUID.fromString(set.getString("profile_id")); - } - } catch (Exception ex) { - ex.printStackTrace(); - } - - return null; - }); - } - - @Override - public CompletableFuture flush() { - return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); - } - - private CompletableFuture createTables() { - return associate(() -> { - String query = getQueries().get(SkyblockSQLQuery.CREATE_ISLAND_DATA_TABLE); - String query2 = getQueries().get(SkyblockSQLQuery.CREATE_ISLAND_ID_TABLE); - String query3 = getQueries().get(SkyblockSQLQuery.CREATE_PROFILE_TABLE); - - try (Connection connection = getConnection()) { - return createTable(connection, query) && createTable(connection, query2) && createTable(connection, query3); - } catch (Exception ex) { - ex.printStackTrace(); - return false; - } - }); - } - - private boolean createTable(Connection connection, String query) { - try (PreparedStatement statement = connection.prepareStatement(query)) { - statement.execute(); - } catch (Exception ex) { - ex.printStackTrace(); - return false; - } - - return true; - } - - /** - * Gets the queries for this database. The key is the query type, the value is the query itself. - * - * @return The queries for this database. - */ - protected abstract Map getQueries(); - - /** - * Creates a connection to the database. This method is called when the connection is invalid or null. - * - * @return The connection to the database. - */ - protected abstract Connection createConnection(); - - /** - * Enables the driver for this database. - * - * @param properties The properties for this database. - * @return If the driver was enabled successfully. - */ - protected abstract boolean enableDriver(ReadOnlyConfigurationSection properties); - - /** - * Gets the connection to the database. If the connection is invalid or null, it will create a new one. - * - * @return The connection to the database. - */ - protected Connection getConnection() { - Connection connection = connectionReference.get(); - - try { - if (connection == null || !connection.isValid(5)) { - connection = createConnection(); - } - } catch (SQLException ignored) { - // The exception is thrown if the field passed in isValid is less than 0, which is not the case here - } - - return connection; - } - - private CompletableFuture associate(Supplier supplier) { - return registerFuture(CompletableFuture.supplyAsync(supplier)); - } - - private CompletableFuture associate(Runnable runnable) { - return registerFuture(CompletableFuture.runAsync(runnable)); - } - - private CompletableFuture registerFuture(CompletableFuture future) { - future.thenRun(() -> futures.remove(future)); - future.exceptionally(throwable -> { - throwable.printStackTrace(); - return null; - }); - - futures.add(future); - return future; - } - - protected Map of(Object... objects) { - Map map = new EnumMap<>(SkyblockSQLQuery.class); - - for (int index = 0; index < objects.length; index += 2) { - Object key = objects[index]; - Object value = objects[index + 1]; - - if (!(key instanceof SkyblockSQLQuery)) { - throw new IllegalArgumentException("Key must be of type SkyblockSQLQuery"); - } - - if (!(value instanceof String)) { - throw new IllegalArgumentException("Value must be of type String"); - } - - map.put((SkyblockSQLQuery) key, (String) value); - } - - return map; - } -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/SkyblockSQLQuery.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/SkyblockSQLQuery.java deleted file mode 100644 index 935c8e5..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/SkyblockSQLQuery.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.illusion.skyblockcore.common.database.fetching.sql; - -/** - * This enum is used for storing all the SQL queries that are used in the plugin. - */ -public enum SkyblockSQLQuery { - - FETCH_ISLAND_ID, // Fetch an island id from a player's uuid - FETCH_ISLAND_DATA, // Fetch all the island data from an island id - - DELETE_ISLAND_DATA, // Deletes all the island data associated with an island id - DELETE_ISLAND_ID, // Deletes all the island id associated with a player's uuid - - SAVE_ISLAND_DATA, // Saves all the island data associated with an island id - SAVE_ISLAND_ID, // Saves all the island id associated with a player's uuid - - FETCH_PLAYER_PROFILE, // Fetches a player's profile id - SAVE_PLAYER_PROFILE, // Saves a player's profile id - - CREATE_ISLAND_DATA_TABLE, // Creates the island data table - CREATE_ISLAND_ID_TABLE, // Creates the island id table - CREATE_PROFILE_TABLE // Creates the profile table -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/impl/MariaDBSkyblockDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/impl/MariaDBSkyblockDatabase.java deleted file mode 100644 index 6fe3ac6..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/impl/MariaDBSkyblockDatabase.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.illusion.skyblockcore.common.database.fetching.sql.impl; - -import java.sql.Connection; -import java.sql.DriverManager; -import me.illusion.skyblockcore.common.database.fetching.sql.AbstractSQLSkyblockDatabase; - -/** - * The mariadb implementation of {@link AbstractSQLSkyblockDatabase} - */ -public class MariaDBSkyblockDatabase extends MySQLSkyblockDatabase { // Same queries as MySQL - - @Override - protected Connection createConnection() { - try { - Class.forName("org.mariadb.jdbc.Driver"); - return DriverManager.getConnection("jdbc:mariadb://" + host + ":" + port + "/" + database, username, password); - } catch (Exception expected) { // The driver will throw an exception if it fails to connect - return null; - } - } - - - @Override - public String getName() { - return "mariadb"; - } -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/impl/MySQLSkyblockDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/impl/MySQLSkyblockDatabase.java deleted file mode 100644 index 102df90..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/impl/MySQLSkyblockDatabase.java +++ /dev/null @@ -1,58 +0,0 @@ -package me.illusion.skyblockcore.common.database.fetching.sql.impl; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.util.Map; -import me.illusion.skyblockcore.common.database.fetching.sql.AbstractRemoteSQLDatabase; -import me.illusion.skyblockcore.common.database.fetching.sql.AbstractSQLSkyblockDatabase; -import me.illusion.skyblockcore.common.database.fetching.sql.SkyblockSQLQuery; - -/** - * The mysql implementation of {@link AbstractSQLSkyblockDatabase} - */ -public class MySQLSkyblockDatabase extends AbstractRemoteSQLDatabase { - - private static final String FETCH_ISLAND_ID = "SELECT island_id FROM skyblock_ids WHERE owner_id = ?"; - private static final String FETCH_ISLAND_DATA = "SELECT * FROM skyblock_data WHERE island_id = ?"; - private static final String DELETE_ISLAND_DATA = "DELETE FROM skyblock_data WHERE island_id"; // Remove island data with island id - private static final String DELETE_ISLAND_ID = "DELETE FROM skyblock_ids WHERE island_id"; // Remove island id with island id - private static final String SAVE_ISLAND_DATA = "INSERT INTO skyblock_data (island_id, owner_id) VALUES (?, ?) ON DUPLICATE KEY UPDATE owner_id = ?"; - private static final String SAVE_ISLAND_ID = "INSERT INTO skyblock_ids (owner_id, island_id) VALUES (?, ?) ON DUPLICATE KEY UPDATE island_id = ?"; - private static final String FETCH_PROFILE_ID = "SELECT profile_id FROM skyblock_profiles WHERE owner_id = ?"; - private static final String SAVE_PROFILE_ID = "INSERT INTO skyblock_profiles (owner_id, profile_id) VALUES (?, ?) ON DUPLICATE KEY UPDATE profile_id = ?"; - private static final String CREATE_ISLAND_DATA_TABLE = "CREATE TABLE IF NOT EXISTS skyblock_data (island_id VARCHAR(36) PRIMARY KEY, owner_id VARCHAR(36))"; - private static final String CREATE_ISLAND_ID_TABLE = "CREATE TABLE IF NOT EXISTS skyblock_ids (owner_id VARCHAR(36) PRIMARY KEY, island_id VARCHAR(36))"; - private static final String CREATE_PROFILE_TABLE = "CREATE TABLE IF NOT EXISTS skyblock_profiles (owner_id VARCHAR(36) PRIMARY KEY, profile_id VARCHAR(36))"; - - @Override - protected Map getQueries() { - return of( - SkyblockSQLQuery.FETCH_ISLAND_ID, FETCH_ISLAND_ID, - SkyblockSQLQuery.FETCH_ISLAND_DATA, FETCH_ISLAND_DATA, - SkyblockSQLQuery.DELETE_ISLAND_DATA, DELETE_ISLAND_DATA, - SkyblockSQLQuery.DELETE_ISLAND_ID, DELETE_ISLAND_ID, - SkyblockSQLQuery.SAVE_ISLAND_DATA, SAVE_ISLAND_DATA, - SkyblockSQLQuery.SAVE_ISLAND_ID, SAVE_ISLAND_ID, - SkyblockSQLQuery.FETCH_PLAYER_PROFILE, FETCH_PROFILE_ID, - SkyblockSQLQuery.SAVE_PLAYER_PROFILE, SAVE_PROFILE_ID, - SkyblockSQLQuery.CREATE_ISLAND_DATA_TABLE, CREATE_ISLAND_DATA_TABLE, - SkyblockSQLQuery.CREATE_ISLAND_ID_TABLE, CREATE_ISLAND_ID_TABLE, - SkyblockSQLQuery.CREATE_PROFILE_TABLE, CREATE_PROFILE_TABLE - ); - } - - @Override - protected Connection createConnection() { - try { - Class.forName("com.mysql.jdbc.Driver"); // This isn't needed in JDBC 4.0+ (Java 6+), but old versions of spigot are stinky - return DriverManager.getConnection("jdbc:mysql://" + host + ":" + port + "/" + database, username, password); - } catch (Exception expected) { // The driver will throw an exception if it fails to connect - return null; - } - } - - @Override - public String getName() { - return "mysql"; - } -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/impl/PostgresSkyblockDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/impl/PostgresSkyblockDatabase.java deleted file mode 100644 index 7b2c9b2..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/impl/PostgresSkyblockDatabase.java +++ /dev/null @@ -1,58 +0,0 @@ -package me.illusion.skyblockcore.common.database.fetching.sql.impl; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.util.Map; -import me.illusion.skyblockcore.common.database.fetching.sql.AbstractRemoteSQLDatabase; -import me.illusion.skyblockcore.common.database.fetching.sql.AbstractSQLSkyblockDatabase; -import me.illusion.skyblockcore.common.database.fetching.sql.SkyblockSQLQuery; - -/** - * The postgres implementation of {@link AbstractSQLSkyblockDatabase} - */ -public class PostgresSkyblockDatabase extends AbstractRemoteSQLDatabase { - - private static final String FETCH_ISLAND_ID = "SELECT island_id FROM island_id WHERE owner_id = ?"; - private static final String FETCH_ISLAND_DATA = "SELECT * FROM island_data WHERE island_id = ?"; - private static final String DELETE_ISLAND_DATA = "DELETE FROM island_data WHERE island_id = ?"; - private static final String DELETE_ISLAND_ID = "DELETE FROM island_id WHERE island_id = ?"; - private static final String SAVE_ISLAND_DATA = "INSERT INTO island_data (island_id, owner_id) VALUES (?, ?) ON CONFLICT (island_id) DO UPDATE SET owner_id = ?"; - private static final String SAVE_ISLAND_ID = "INSERT INTO island_id (owner_id, island_id) VALUES (?, ?) ON CONFLICT (owner_id) DO UPDATE SET island_id = ?"; - private static final String FETCH_PLAYER_PROFILE = "SELECT profile_id FROM profile WHERE player_id = ?"; - private static final String SAVE_PLAYER_PROFILE = "INSERT INTO profile (player_id, profile_id) VALUES (?, ?) ON CONFLICT (player_id) DO UPDATE SET profile_id = ?"; - private static final String CREATE_ISLAND_DATA_TABLE = "CREATE TABLE IF NOT EXISTS island_data (island_id VARCHAR(36) PRIMARY KEY, owner_id VARCHAR(36))"; - private static final String CREATE_ISLAND_ID_TABLE = "CREATE TABLE IF NOT EXISTS island_id (owner_id VARCHAR(36) PRIMARY KEY, island_id VARCHAR(36))"; - private static final String CREATE_PROFILE_TABLE = "CREATE TABLE IF NOT EXISTS profile (owner_id VARCHAR(36) PRIMARY KEY, profile_id VARCHAR(36))"; - - @Override - protected Map getQueries() { - return of( - SkyblockSQLQuery.FETCH_ISLAND_ID, FETCH_ISLAND_ID, - SkyblockSQLQuery.FETCH_ISLAND_DATA, FETCH_ISLAND_DATA, - SkyblockSQLQuery.DELETE_ISLAND_DATA, DELETE_ISLAND_DATA, - SkyblockSQLQuery.DELETE_ISLAND_ID, DELETE_ISLAND_ID, - SkyblockSQLQuery.SAVE_ISLAND_DATA, SAVE_ISLAND_DATA, - SkyblockSQLQuery.SAVE_ISLAND_ID, SAVE_ISLAND_ID, - SkyblockSQLQuery.FETCH_PLAYER_PROFILE, FETCH_PLAYER_PROFILE, - SkyblockSQLQuery.SAVE_PLAYER_PROFILE, SAVE_PLAYER_PROFILE, - SkyblockSQLQuery.CREATE_ISLAND_DATA_TABLE, CREATE_ISLAND_DATA_TABLE, - SkyblockSQLQuery.CREATE_ISLAND_ID_TABLE, CREATE_ISLAND_ID_TABLE, - SkyblockSQLQuery.CREATE_PROFILE_TABLE, CREATE_PROFILE_TABLE - ); - } - - @Override - protected Connection createConnection() { - try { - Class.forName("org.postgresql.Driver"); - return DriverManager.getConnection("jdbc:postgresql://" + host + ":" + port + "/" + database, username, password); - } catch (Exception expected) { // The driver will throw an exception if it fails to connect - return null; - } - } - - @Override - public String getName() { - return "postgres"; - } -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/impl/SQLiteSkyblockDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/impl/SQLiteSkyblockDatabase.java deleted file mode 100644 index 5c3a3f0..0000000 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/sql/impl/SQLiteSkyblockDatabase.java +++ /dev/null @@ -1,84 +0,0 @@ -package me.illusion.skyblockcore.common.database.fetching.sql.impl; - -import java.io.File; -import java.sql.Connection; -import java.sql.DriverManager; -import java.util.Map; -import me.illusion.skyblockcore.common.config.ReadOnlyConfigurationSection; -import me.illusion.skyblockcore.common.database.fetching.sql.AbstractSQLSkyblockDatabase; -import me.illusion.skyblockcore.common.database.fetching.sql.SkyblockSQLQuery; - -/** - * The sqlite implementation of {@link AbstractSQLSkyblockDatabase} - */ -public class SQLiteSkyblockDatabase extends AbstractSQLSkyblockDatabase { - - private static final String FETCH_ISLAND_ID = "SELECT island_id FROM skyblock_ids WHERE owner_id = ?"; - private static final String FETCH_ISLAND_DATA = "SELECT * FROM skyblock_data WHERE island_id = ?"; - private static final String DELETE_ISLAND_DATA = "DELETE FROM skyblock_data WHERE island_id"; - private static final String DELETE_ISLAND_ID = "DELETE FROM skyblock_ids WHERE island_id"; - private static final String SAVE_ISLAND_DATA = "INSERT OR REPLACE INTO skyblock_data (island_id, owner_id) VALUES (?, ?)"; - private static final String SAVE_ISLAND_ID = "INSERT OR REPLACE INTO skyblock_ids (owner_id, island_id) VALUES (?, ?)"; - private static final String FETCH_PLAYER_PROFILE = "SELECT profile_id FROM skyblock_profiles WHERE player_id = ?"; - private static final String SAVE_PLAYER_PROFILE = "INSERT OR REPLACE INTO skyblock_profiles (player_id, profile_id) VALUES (?, ?)"; - private static final String CREATE_ISLAND_DATA_TABLE = "CREATE TABLE IF NOT EXISTS skyblock_data (island_id VARCHAR(36) PRIMARY KEY, owner_id VARCHAR(36))"; - private static final String CREATE_ISLAND_ID_TABLE = "CREATE TABLE IF NOT EXISTS skyblock_ids (owner_id VARCHAR(36) PRIMARY KEY, island_id VARCHAR(36))"; - private static final String CREATE_PROFILE_TABLE = "CREATE TABLE IF NOT EXISTS skyblock_profiles (player_id VARCHAR(36) PRIMARY KEY, profile_id VARCHAR(36))"; - - private final File dataFolder; - private File databaseFile; - - public SQLiteSkyblockDatabase(File dataFolder) { - this.dataFolder = dataFolder; - } - - @Override - protected Map getQueries() { - return of( - SkyblockSQLQuery.FETCH_ISLAND_ID, FETCH_ISLAND_ID, - SkyblockSQLQuery.FETCH_ISLAND_DATA, FETCH_ISLAND_DATA, - SkyblockSQLQuery.DELETE_ISLAND_DATA, DELETE_ISLAND_DATA, - SkyblockSQLQuery.DELETE_ISLAND_ID, DELETE_ISLAND_ID, - SkyblockSQLQuery.SAVE_ISLAND_DATA, SAVE_ISLAND_DATA, - SkyblockSQLQuery.SAVE_ISLAND_ID, SAVE_ISLAND_ID, - SkyblockSQLQuery.FETCH_PLAYER_PROFILE, FETCH_PLAYER_PROFILE, - SkyblockSQLQuery.SAVE_PLAYER_PROFILE, SAVE_PLAYER_PROFILE, - SkyblockSQLQuery.CREATE_ISLAND_DATA_TABLE, CREATE_ISLAND_DATA_TABLE, - SkyblockSQLQuery.CREATE_ISLAND_ID_TABLE, CREATE_ISLAND_ID_TABLE, - SkyblockSQLQuery.CREATE_PROFILE_TABLE, CREATE_PROFILE_TABLE - ); - } - - @Override - protected Connection createConnection() { - try { - Class.forName("org.sqlite.JDBC"); - return DriverManager.getConnection("jdbc:sqlite:" + databaseFile.getAbsolutePath()); - } catch (Exception expected) { // The driver will throw an exception if it fails to connect - return null; - } - } - - @Override - protected boolean enableDriver(ReadOnlyConfigurationSection properties) { - String fileName = properties.getString("file-name", "database"); - - databaseFile = new File(dataFolder, fileName + ".db"); - - try (Connection connection = getConnection()) { - return connection.isValid(5); - } catch (Exception expected) { - return false; - } - } - - @Override - public String getName() { - return "sqlite"; - } - - @Override - public boolean isFileBased() { - return true; - } -} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/AbstractPersistenceDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/AbstractPersistenceDatabase.java new file mode 100644 index 0000000..ed56b95 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/AbstractPersistenceDatabase.java @@ -0,0 +1,11 @@ +package me.illusion.skyblockcore.common.database.persistence; + +import me.illusion.skyblockcore.common.database.AbstractSkyblockDatabase; +import me.illusion.skyblockcore.common.database.SkyblockDatabaseTag; + +public abstract class AbstractPersistenceDatabase extends AbstractSkyblockDatabase { + + protected AbstractPersistenceDatabase() { + addTag(SkyblockDatabaseTag.FETCHING); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/mongo/MongoPersistenceDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/mongo/MongoPersistenceDatabase.java new file mode 100644 index 0000000..7e6e492 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/mongo/MongoPersistenceDatabase.java @@ -0,0 +1,114 @@ +package me.illusion.skyblockcore.common.database.persistence.mongo; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.ReplaceOptions; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import me.illusion.skyblockcore.common.config.section.ConfigurationSection; +import me.illusion.skyblockcore.common.database.SkyblockDatabaseTag; +import me.illusion.skyblockcore.common.database.persistence.AbstractPersistenceDatabase; +import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecRegistries; +import org.bson.codecs.configuration.CodecRegistry; + +public abstract class MongoPersistenceDatabase extends AbstractPersistenceDatabase { + + protected static final ReplaceOptions UPSERT = new ReplaceOptions().upsert(true); + + protected MongoClient client; + protected MongoDatabase database; + + protected String collectionName; + + protected MongoPersistenceDatabase() { + addTag(SkyblockDatabaseTag.NO_SQL); + addTag(SkyblockDatabaseTag.REMOTE); + } + + @Override + public String getName() { + return "mongo"; + } + + @Override + public CompletableFuture enable(ConfigurationSection properties) { + setProperties(properties); + + return associate(() -> { + String connectionString = properties.getString("connection-string"); + + if (connectionString == null) { + String ip = properties.getString("ip"); + int port = properties.getInt("port"); + String authsource = properties.getString("auth-source", "admin"); + String username = properties.getString("username"); + String password = properties.getString("password"); + boolean ssl = properties.getBoolean("ssl", false); + + connectionString = createConnectionString(ip, port, authsource, username, password, ssl); + } + + String databaseName = properties.getString("database", getDefaultDatabase()); + collectionName = properties.getString("collection", getDefaultCollection()); + + CodecRegistry registry = CodecRegistries.fromRegistries( + CodecRegistries.fromCodecs(getCodecs()) + ); + + try { + client = MongoClients.create(connectionString); + database = client.getDatabase(databaseName).withCodecRegistry(registry); + + initializeCollections(); + return true; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } + }); + } + + @Override + public CompletableFuture wipe() { + return associate(() -> database.getCollection(collectionName).drop()); + } + + protected String getDefaultDatabase() { + return "skyblock"; + } + + protected abstract String getDefaultCollection(); + + protected List> getCodecs() { + return Collections.emptyList(); + } + + protected abstract void initializeCollections(); + + private String createConnectionString(String ip, int port, String authsource, String username, String password, boolean ssl) { + StringBuilder builder = new StringBuilder(); + builder.append("mongodb://"); + if (username != null && !username.isEmpty()) { + builder.append(username); + if (password != null && !password.isEmpty()) { + builder.append(":").append(password); + } + builder.append("@"); + } + + builder.append(ip).append(":").append(port); + + if (authsource != null && !authsource.isEmpty()) { + builder.append("/?authSource=").append(authsource); + } + + if (ssl) { + builder.append("&ssl=true"); + } + + return builder.toString(); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/mongo/codec/MongoUUIDCodec.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/mongo/MongoUUIDCodec.java similarity index 92% rename from SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/mongo/codec/MongoUUIDCodec.java rename to SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/mongo/MongoUUIDCodec.java index de372d7..893de11 100644 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/mongo/codec/MongoUUIDCodec.java +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/mongo/MongoUUIDCodec.java @@ -1,4 +1,4 @@ -package me.illusion.skyblockcore.common.database.fetching.mongo.codec; +package me.illusion.skyblockcore.common.database.persistence.mongo; import java.util.UUID; import org.bson.BsonBinary; diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/sql/AbstractRemoteSQLPersistenceDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/sql/AbstractRemoteSQLPersistenceDatabase.java new file mode 100644 index 0000000..2165a80 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/sql/AbstractRemoteSQLPersistenceDatabase.java @@ -0,0 +1,29 @@ +package me.illusion.skyblockcore.common.database.persistence.sql; + +import java.util.concurrent.CompletableFuture; +import me.illusion.skyblockcore.common.config.section.ConfigurationSection; +import me.illusion.skyblockcore.common.database.SkyblockDatabaseTag; + +public abstract class AbstractRemoteSQLPersistenceDatabase extends AbstractSQLPersistenceDatabase { + + protected String host; + protected int port; + protected String database; + protected String username; + protected String password; + + public AbstractRemoteSQLPersistenceDatabase() { + addTag(SkyblockDatabaseTag.REMOTE); + } + + @Override + public CompletableFuture enable(ConfigurationSection properties) { + host = properties.getString("host"); + port = properties.getInt("port"); + database = properties.getString("database"); + username = properties.getString("username"); + password = properties.getString("password"); + + return super.enable(properties); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/sql/AbstractSQLPersistenceDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/sql/AbstractSQLPersistenceDatabase.java new file mode 100644 index 0000000..617c6f8 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/sql/AbstractSQLPersistenceDatabase.java @@ -0,0 +1,160 @@ +package me.illusion.skyblockcore.common.database.persistence.sql; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import lombok.SneakyThrows; +import me.illusion.skyblockcore.common.config.section.ConfigurationSection; +import me.illusion.skyblockcore.common.database.SkyblockDatabaseTag; +import me.illusion.skyblockcore.common.database.persistence.AbstractPersistenceDatabase; +import me.illusion.skyblockcore.common.database.sql.functional.ResultSetFunction; +import me.illusion.skyblockcore.common.database.sql.object.StatementObject; + +public abstract class AbstractSQLPersistenceDatabase extends AbstractPersistenceDatabase { + + private final AtomicReference connectionReference = new AtomicReference<>(); + + protected AbstractSQLPersistenceDatabase() { + addTag(SkyblockDatabaseTag.SQL); + } + + @Override + public CompletableFuture enable(ConfigurationSection properties) { + setProperties(properties); + + return associate(() -> { + Connection connection = getConnection(); + boolean valid = validateConnection(connection); + + if (!valid) { + return false; + } + + createTables(); + return true; + }); + } + + protected ResultSet runQuery(String query, List list) { + try (PreparedStatement statement = getConnection().prepareStatement(query)) { + for (int index = 0; index < list.size(); index++) { + list.get(index).applyTo(statement, index + 1); + } + + return statement.executeQuery(); + } catch (Exception ex) { + ex.printStackTrace(); + } + + return null; + } + + protected CompletableFuture runQueryAsync(String query, List list, ResultSetFunction function) { + return associate(() -> { + ResultSet set = runQuery(query, list); + + if (set == null) { + return null; + } + + try { + return function.apply(set); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + try { + set.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + }); + } + + protected void runUpdate(String query, Consumer consumer) { + try (PreparedStatement statement = getConnection().prepareStatement(query)) { + consumer.accept(statement); + statement.executeUpdate(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + protected void runUpdate(String query, StatementObject... objects) { + this.runUpdate(query, statement -> { + for (int index = 0; index < objects.length; index++) { + try { + objects[index].applyTo(statement, index + 1); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + }); + } + + protected void runUpdate(String query) { + this.runUpdate(query, statement -> { + }); + } + + protected CompletableFuture runUpdateAsync(String query, Consumer consumer) { + return associate(() -> { + try (PreparedStatement statement = getConnection().prepareStatement(query)) { + consumer.accept(statement); + statement.executeUpdate(); + } catch (Exception ex) { + ex.printStackTrace(); + } + }); + } + + protected CompletableFuture runUpdateAsync(String query, StatementObject... objects) { + return runUpdateAsync(query, statement -> { + for (int index = 0; index < objects.length; index++) { + try { + objects[index].applyTo(statement, index + 1); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + }); + } + + @SneakyThrows // This will never throw under normal circumstances + protected Connection getConnection() { + Connection current = connectionReference.get(); + + if (!validateConnection(current)) { + current = createConnection(); + connectionReference.set(current); + } + + return current; + } + + private boolean validateConnection(Connection connection) { + if (connection == null) { + return false; + } + + try { + return connection.isValid(1); + } catch (Exception ignored) { + // The driver will throw an exception if the parameter passed is below 0. This is not a problem. + return false; + } + } + + protected abstract Connection createConnection(); + + protected abstract Collection getTables(); + + protected abstract void createTables(); + +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/sql/impl/MySQLPersistenceDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/sql/impl/MySQLPersistenceDatabase.java new file mode 100644 index 0000000..85c615e --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/sql/impl/MySQLPersistenceDatabase.java @@ -0,0 +1,43 @@ +package me.illusion.skyblockcore.common.database.persistence.sql.impl; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.util.Properties; +import java.util.concurrent.CompletableFuture; +import me.illusion.skyblockcore.common.database.persistence.sql.AbstractRemoteSQLPersistenceDatabase; + +public abstract class MySQLPersistenceDatabase extends AbstractRemoteSQLPersistenceDatabase { + + @Override + public String getName() { + return "mysql"; + } + + @Override + protected Connection createConnection() { + try { + Class.forName("com.mysql.jdbc.Driver"); + + Properties properties = new Properties(); + + properties.setProperty("user", username); + properties.setProperty("password", password); + properties.setProperty("useSSL", "false"); + properties.setProperty("autoReconnect", "true"); + + return DriverManager.getConnection("jdbc:mysql://" + host + ":" + port + "/" + database, properties); + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } + + @Override + public CompletableFuture wipe() { + return associate(() -> { + for (String table : getTables()) { + runUpdate("DROP TABLE IF EXISTS " + table); + } + }); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/sql/impl/SQLitePersistenceDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/sql/impl/SQLitePersistenceDatabase.java new file mode 100644 index 0000000..12d99ac --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/persistence/sql/impl/SQLitePersistenceDatabase.java @@ -0,0 +1,56 @@ +package me.illusion.skyblockcore.common.database.persistence.sql.impl; + +import java.io.File; +import java.sql.Connection; +import java.sql.DriverManager; +import java.util.concurrent.CompletableFuture; +import me.illusion.skyblockcore.common.config.section.ConfigurationSection; +import me.illusion.skyblockcore.common.database.persistence.sql.AbstractSQLPersistenceDatabase; +import me.illusion.skyblockcore.common.platform.SkyblockPlatform; +import me.illusion.skyblockcore.common.utilities.file.IOUtils; + +public abstract class SQLitePersistenceDatabase extends AbstractSQLPersistenceDatabase { + + private File file; + + @Override + public CompletableFuture enable(SkyblockPlatform platform, ConfigurationSection properties) { + String name = properties.getString("name"); + File folder = platform.getConfigurationProvider().getDataFolder(); + + return associate(() -> { + File file = new File(folder, name + ".db"); + + if (!file.exists()) { + IOUtils.createFile(file); + } + + this.file = file; + }).thenCompose(value -> super.enable(properties)); + } + + @Override + public String getName() { + return "sqlite"; + } + + @Override + public CompletableFuture wipe() { + return associate(() -> { + for (String table : getTables()) { + runUpdate("DROP TABLE IF EXISTS " + table); + } + }); + } + + @Override + protected Connection createConnection() { + try { + Class.forName("org.sqlite.JDBC"); + return DriverManager.getConnection("jdbc:sqlite:" + file.getAbsolutePath()); + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/registry/RegisteredDatabase.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/registry/RegisteredDatabase.java new file mode 100644 index 0000000..d2b5f79 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/registry/RegisteredDatabase.java @@ -0,0 +1,56 @@ +package me.illusion.skyblockcore.common.database.registry; + +import me.illusion.skyblockcore.common.database.SkyblockDatabase; + +public class RegisteredDatabase { + + private final SkyblockDatabaseProvider provider; + private final String name; + + private SkyblockDatabase database; + private boolean enabled; + private boolean attemptedLoad; + + public RegisteredDatabase(SkyblockDatabaseProvider provider, String name) { + this.provider = provider; + this.name = name; + } + + public SkyblockDatabase getDatabase() { + if (database == null) { + database = provider.getDatabase(name); + } + + return database; + } + + public void setSpecifiedType(String type) { + if (database == null) { + database = provider.getDatabase(type); + } + } + + public String getName() { + return name; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public SkyblockDatabaseProvider getProvider() { + return provider; + } + + public boolean hasAttemptedLoad() { + return attemptedLoad; + } + + public void setAttemptedLoad(boolean attemptedLoad) { + this.attemptedLoad = attemptedLoad; + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/registry/SkyblockDatabaseCredentialRegistry.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/registry/SkyblockDatabaseCredentialRegistry.java new file mode 100644 index 0000000..d8b486b --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/registry/SkyblockDatabaseCredentialRegistry.java @@ -0,0 +1,100 @@ +package me.illusion.skyblockcore.common.database.registry; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; +import java.util.concurrent.ConcurrentHashMap; +import me.illusion.skyblockcore.common.config.section.ConfigurationSection; + +public class SkyblockDatabaseCredentialRegistry { + + private final Map dependencies = new ConcurrentHashMap<>(); + private final Map credentialsMap = new ConcurrentHashMap<>(); + + private final Map> adjacencyList = new ConcurrentHashMap<>(); + + + public void registerCredentials(String name, ConfigurationSection credentials) { + if (credentialsMap.containsKey(name)) { + throw new IllegalStateException("Credentials " + name + " already registered, names are shared between files."); + } + + credentialsMap.put(name, credentials); + } + + public void registerDependency(String name, String dependency) { + if (dependencies.containsKey(name)) { + throw new IllegalStateException("Dependency " + name + " already registered"); + } + + dependencies.put(name, dependency); + adjacencyList.computeIfAbsent(dependency, s -> new ArrayList<>()).add(name); + } + + public Object get(String name) { + String dependency = dependencies.get(name); + + if (dependency != null) { + return get(dependency); + } + + return credentialsMap.get(name); + } + + public ConfigurationSection getCredentials(String name) { + if (name == null) { + return null; + } + + ConfigurationSection section = credentialsMap.get(name); + + if (section == null) { + return getCredentials(dependencies.get(name)); + } + + return section; + } + + public void checkCyclicDependencies() { + Set visited = new HashSet<>(); + Set currentlyChecking = new HashSet<>(); + Stack cyclicPath = new Stack<>(); + + for (String vertex : adjacencyList.keySet()) { + if (hasCyclicDependency(vertex, visited, currentlyChecking, cyclicPath)) { + throw new IllegalStateException("Cyclic dependency found: " + String.join(" -> ", cyclicPath)); + } + } + } + + private boolean hasCyclicDependency(String vertex, Set visited, Set currentlyChecking, Stack cyclicPath) { + if (currentlyChecking.contains(vertex)) { + cyclicPath.push(vertex); + return true; // Cyclic dependency found + } + + if (visited.contains(vertex)) { + return false; // Already visited, no cycle + } + + visited.add(vertex); + currentlyChecking.add(vertex); + cyclicPath.push(vertex); + + List neighbors = adjacencyList.get(vertex); + if (neighbors != null) { + for (String neighbor : neighbors) { + if (hasCyclicDependency(neighbor, visited, currentlyChecking, cyclicPath)) { + return true; // Cyclic dependency found + } + } + } + + currentlyChecking.remove(vertex); + cyclicPath.pop(); + return false; + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/registry/SkyblockDatabaseProvider.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/registry/SkyblockDatabaseProvider.java new file mode 100644 index 0000000..692c02b --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/registry/SkyblockDatabaseProvider.java @@ -0,0 +1,24 @@ +package me.illusion.skyblockcore.common.database.registry; + +import java.util.Map; +import me.illusion.skyblockcore.common.config.section.ConfigurationSection; +import me.illusion.skyblockcore.common.database.SkyblockDatabase; +import me.illusion.skyblockcore.common.database.registry.provider.MappedDatabaseProvider; + +public interface SkyblockDatabaseProvider { + + static SkyblockDatabaseProvider of(Map map) { + return new MappedDatabaseProvider(map); + } + + static SkyblockDatabaseProvider of(SkyblockDatabase... databases) { + return new MappedDatabaseProvider(databases); + } + + SkyblockDatabase getDatabase(String name); + + default ConfigurationSection getDefaultConfiguration() { + return null; + } + +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/registry/SkyblockDatabaseRegistry.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/registry/SkyblockDatabaseRegistry.java new file mode 100644 index 0000000..d04435d --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/registry/SkyblockDatabaseRegistry.java @@ -0,0 +1,281 @@ +package me.illusion.skyblockcore.common.database.registry; + +import com.google.common.collect.Sets; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import me.illusion.skyblockcore.common.config.section.ConfigurationSection; +import me.illusion.skyblockcore.common.config.section.WritableConfigurationSection; +import me.illusion.skyblockcore.common.database.SkyblockDatabase; +import me.illusion.skyblockcore.common.database.cache.SkyblockCache; +import me.illusion.skyblockcore.common.platform.SkyblockPlatform; +import me.illusion.skyblockcore.common.storage.SkyblockStorage; +import me.illusion.skyblockcore.common.storage.cache.redis.MemorySkyblockIslandCache; +import me.illusion.skyblockcore.common.storage.cache.redis.RedisSkyblockIslandCache; +import me.illusion.skyblockcore.common.storage.island.mongo.MongoIslandStorage; +import me.illusion.skyblockcore.common.storage.island.sql.MySQLIslandStorage; +import me.illusion.skyblockcore.common.storage.island.sql.SQLiteIslandStorage; +import me.illusion.skyblockcore.common.storage.profiles.mongo.MongoProfileStorage; +import me.illusion.skyblockcore.common.storage.profiles.sql.MySQLProfileStorage; +import me.illusion.skyblockcore.common.storage.profiles.sql.SQLiteProfileStorage; + +public class SkyblockDatabaseRegistry { + + private final SkyblockDatabaseCredentialRegistry credentialRegistry = new SkyblockDatabaseCredentialRegistry(); + private final Collection> futures = Sets.newConcurrentHashSet(); + + private final Map registeredDatabases = new ConcurrentHashMap<>(); + + private final SkyblockPlatform platform; + + public SkyblockDatabaseRegistry(SkyblockPlatform platform) { + this.platform = platform; + + registerDefaults(); + } + + private void registerDefaults() { + register("island", SkyblockDatabaseProvider.of( + new MongoIslandStorage(), + new MySQLIslandStorage(), + new SQLiteIslandStorage() + )); + + register("profile", SkyblockDatabaseProvider.of( + new MongoProfileStorage(), + new MySQLProfileStorage(), + new SQLiteProfileStorage() + )); + + register("island-cache", SkyblockDatabaseProvider.of( + new RedisSkyblockIslandCache(), + new MemorySkyblockIslandCache() + )); + } + + // --- CORE LOGIC --- + + public void register(String name, SkyblockDatabaseProvider provider) { + registeredDatabases.put(name, new RegisteredDatabase(provider, name)); + tryLoad(registeredDatabases.get(name)); + } + + public T getCache(Class clazz) { + for (RegisteredDatabase registeredDatabase : registeredDatabases.values()) { + SkyblockDatabase database = registeredDatabase.getDatabase(); + + if (clazz.isInstance(database)) { + return clazz.cast(database); + } + } + + return null; + } + + public > T getStorage(Class clazz) { + for (RegisteredDatabase registeredDatabase : registeredDatabases.values()) { + SkyblockDatabase database = registeredDatabase.getDatabase(); + + if (clazz.isInstance(database) || clazz.isAssignableFrom(database.getClass())) { + return clazz.cast(database); + } + } + + warn("Failed to find storage of type {0}", clazz.getSimpleName()); + return null; + } + + public > T getStorage(String name) { + SkyblockDatabase database = getDatabase(name); + + if (database == null) { + return null; + } + + if (database instanceof SkyblockStorage) { + return (T) database; + } + + return null; + } + + public SkyblockDatabase getDatabase(String name) { + RegisteredDatabase registeredDatabase = registeredDatabases.get(name); + + if (registeredDatabase == null) { + return null; + } + + return registeredDatabase.getDatabase(); + } + + public CompletableFuture loadPossible(ConfigurationSection section) { + loadSection(section); + credentialRegistry.checkCyclicDependencies(); + + Set> temp = new HashSet<>(); + Collection keys = section.getKeys(); + + for (RegisteredDatabase registeredDatabase : registeredDatabases.values()) { + if (registeredDatabase.isEnabled()) { + continue; + } + + if(keys.contains(registeredDatabase.getName())) { + registeredDatabase.setAttemptedLoad(true); + } + + + temp.add(tryLoad(registeredDatabase)); + } + + return CompletableFuture.allOf(temp.toArray(new CompletableFuture[0])); + } + + private CompletableFuture tryLoad(RegisteredDatabase registeredDatabase) { + if (registeredDatabase.isEnabled()) { + warn("Database {0} is already enabled", registeredDatabase.getName()); + return CompletableFuture.completedFuture(true); + } + + Object credentials = credentialRegistry.get(registeredDatabase.getName()); + + if (credentials == null) { + if (!registeredDatabase.hasAttemptedLoad()) { // The database credentials haven't been loaded yet + return CompletableFuture.completedFuture(false); + } + + warn("Database {0} has no credentials", registeredDatabase.getName()); + return CompletableFuture.completedFuture(false); + } + + ConfigurationSection config; + + if (credentials instanceof ConfigurationSection) { + config = (ConfigurationSection) credentials; + } else { + config = credentialRegistry.getCredentials((String) credentials); + } + + if (config == null) { + warn("Database {0} has no matching credentials", registeredDatabase.getName()); + + config = registeredDatabase.getProvider().getDefaultConfiguration(); + + if (config == null) { + return CompletableFuture.completedFuture(false); + } + + warn("Using default configuration for {0}", registeredDatabase.getName()); + + if (config instanceof WritableConfigurationSection writable) { + writable.set(registeredDatabase.getName(), config); + + warn("Wrote credentials to default configuration for {0}", registeredDatabase.getName()); + writable.save(); + } + } + + String type = config.getString("type"); + + if (type == null) { + warn("Database {0} has no type", registeredDatabase.getName()); + return CompletableFuture.completedFuture(false); + } + + registeredDatabase.setSpecifiedType(type); + SkyblockDatabase database = registeredDatabase.getDatabase(); + + if (database == null) { + warn("Database {0} has no matching database", registeredDatabase.getName()); + return CompletableFuture.completedFuture(false); + } + + return addFuture(database.enable(platform, config).thenApply((v) -> { + if (v) { + registeredDatabase.setEnabled(true); + } else { + warn("Database {0} failed to enable", registeredDatabase.getName()); + } + + return true; + })); + + } + + private void loadSection(ConfigurationSection section) { + for (String key : section.getKeys()) { + Object obj = section.get(key); + + if (obj instanceof ConfigurationSection) { + credentialRegistry.registerCredentials(key, (ConfigurationSection) obj); + continue; + } + + String value = String.valueOf(obj); + credentialRegistry.registerDependency(key, value); + } + } + + public boolean areAllLoaded() { + for (RegisteredDatabase registeredDatabase : registeredDatabases.values()) { + if (!registeredDatabase.isEnabled()) { + warn("Database {0} is not enabled", registeredDatabase.getName()); + return false; + } + } + + return true; + } + + public CompletableFuture finishLoading() { + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenApply((v) -> areAllLoaded()); + } + + public CompletableFuture shutdown() { + for (RegisteredDatabase registeredDatabase : registeredDatabases.values()) { + if (!registeredDatabase.isEnabled()) { + continue; + } + + addFuture(registeredDatabase.getDatabase().flush()); + } + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + } + + private void warn(String message, Object... args) { + platform.getLogger().log(Level.WARNING, message, args); + } + + private CompletableFuture addFuture(CompletableFuture future) { + futures.add(future); + return future.whenComplete((v, e) -> futures.remove(future)); + } +} + +// Structure: + /* + global: # This doesn't have to be global, it can be anything + type: mysql # Name to get on the provider + host: localhost + port: 3306 + database: skyblock + username: root + + islands: global # This can be anything, as long as it is registered + profiles: global + + bank: + type: mongo + host: localhost + port: 27017 + database: skyblock + username: root + */ + +// Check for cyclic dependencies \ No newline at end of file diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/registry/provider/MappedDatabaseProvider.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/registry/provider/MappedDatabaseProvider.java new file mode 100644 index 0000000..4cc3493 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/registry/provider/MappedDatabaseProvider.java @@ -0,0 +1,31 @@ +package me.illusion.skyblockcore.common.database.registry.provider; + +import com.google.common.collect.ImmutableMap; +import java.util.HashMap; +import java.util.Map; +import me.illusion.skyblockcore.common.database.SkyblockDatabase; +import me.illusion.skyblockcore.common.database.registry.SkyblockDatabaseProvider; + +public class MappedDatabaseProvider implements SkyblockDatabaseProvider { + + private final Map map; + + public MappedDatabaseProvider(Map map) { + this.map = ImmutableMap.copyOf(map); + } + + public MappedDatabaseProvider(SkyblockDatabase... databases) { + Map temp = new HashMap<>(); + + for (SkyblockDatabase database : databases) { + temp.put(database.getName(), database); + } + + this.map = ImmutableMap.copyOf(temp); + } + + @Override + public SkyblockDatabase getDatabase(String name) { + return map.get(name); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/sql/functional/ResultSetConsumer.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/sql/functional/ResultSetConsumer.java new file mode 100644 index 0000000..c719560 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/sql/functional/ResultSetConsumer.java @@ -0,0 +1,9 @@ +package me.illusion.skyblockcore.common.database.sql.functional; + +import java.sql.ResultSet; + +@FunctionalInterface +public interface ResultSetConsumer { + + void accept(ResultSet resultSet) throws Exception; +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/sql/functional/ResultSetFunction.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/sql/functional/ResultSetFunction.java new file mode 100644 index 0000000..a268c25 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/sql/functional/ResultSetFunction.java @@ -0,0 +1,10 @@ +package me.illusion.skyblockcore.common.database.sql.functional; + +import java.sql.ResultSet; + +@FunctionalInterface +public interface ResultSetFunction { + + T apply(ResultSet resultSet) throws Exception; + +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/sql/object/ObjectStatementObject.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/sql/object/ObjectStatementObject.java new file mode 100644 index 0000000..0005b97 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/sql/object/ObjectStatementObject.java @@ -0,0 +1,18 @@ +package me.illusion.skyblockcore.common.database.sql.object; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public class ObjectStatementObject implements StatementObject { + + private final Object object; + + public ObjectStatementObject(Object object) { + this.object = object; + } + + @Override + public void applyTo(PreparedStatement statement, int index) throws SQLException { + statement.setObject(index, object); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/sql/object/StatementObject.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/sql/object/StatementObject.java new file mode 100644 index 0000000..3afdeb3 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/sql/object/StatementObject.java @@ -0,0 +1,11 @@ +package me.illusion.skyblockcore.common.database.sql.object; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public interface StatementObject { // Class made because of how jdbc likes to throw exceptions everywhere resulting in a lot of try-catch blocks + + void applyTo(PreparedStatement statement, int index) throws SQLException; + + +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/sql/object/StringStatementObject.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/sql/object/StringStatementObject.java new file mode 100644 index 0000000..aa5641c --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/sql/object/StringStatementObject.java @@ -0,0 +1,18 @@ +package me.illusion.skyblockcore.common.database.sql.object; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public class StringStatementObject implements StatementObject { + + private final String value; + + public StringStatementObject(String value) { + this.value = value; + } + + @Override + public void applyTo(PreparedStatement statement, int index) throws SQLException { + statement.setString(index, value); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/platform/SkyblockPlatform.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/platform/SkyblockPlatform.java index 7f1debc..bfe408c 100644 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/platform/SkyblockPlatform.java +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/platform/SkyblockPlatform.java @@ -1,13 +1,11 @@ package me.illusion.skyblockcore.common.platform; -import java.io.File; import java.util.logging.Logger; import me.illusion.skyblockcore.common.command.audience.SkyblockAudience; import me.illusion.skyblockcore.common.command.manager.SkyblockCommandManager; import me.illusion.skyblockcore.common.config.ConfigurationProvider; import me.illusion.skyblockcore.common.config.SkyblockMessagesFile; -import me.illusion.skyblockcore.common.config.impl.SkyblockDatabasesFile; -import me.illusion.skyblockcore.common.database.SkyblockDatabaseRegistry; +import me.illusion.skyblockcore.common.database.registry.SkyblockDatabaseRegistry; import me.illusion.skyblockcore.common.event.manager.SkyblockEventManager; /** @@ -23,13 +21,6 @@ public interface SkyblockPlatform { */ Logger getLogger(); - /** - * Gets the data folder for the platform - * - * @return The data folder - */ - File getDataFolder(); - /** * Gets the database registry for the platform * @@ -58,13 +49,6 @@ public interface SkyblockPlatform { */ SkyblockCommandManager getCommandManager(); - /** - * Gets the database setup for the platform. Certain parts of the platform may require a specific database setup, and access to this is required. - * - * @return The database setup - */ - SkyblockDatabasesFile getDatabasesFile(); - /** * Gets the messages file for the platform. Each platform may have its own messages file, and access to this is required. * diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/SkyblockStorage.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/SkyblockStorage.java new file mode 100644 index 0000000..afe7a22 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/SkyblockStorage.java @@ -0,0 +1,10 @@ +package me.illusion.skyblockcore.common.storage; + +import java.util.concurrent.CompletableFuture; +import me.illusion.skyblockcore.common.database.SkyblockDatabase; + +public interface SkyblockStorage extends SkyblockDatabase { + + CompletableFuture migrateTo(T other); + +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/cache/SkyblockIslandCache.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/cache/SkyblockIslandCache.java new file mode 100644 index 0000000..ccdd42e --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/cache/SkyblockIslandCache.java @@ -0,0 +1,21 @@ +package me.illusion.skyblockcore.common.storage.cache; + +import java.util.Collection; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import me.illusion.skyblockcore.common.database.cache.SkyblockCache; + +public interface SkyblockIslandCache extends SkyblockCache { + + CompletableFuture>> getAllIslands(); + + CompletableFuture getIslandServer(UUID islandId); + + CompletableFuture setServer(UUID islandId, String serverId); + + CompletableFuture removeIsland(UUID islandId); + + CompletableFuture removeServer(String serverId); + +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/cache/redis/MemorySkyblockIslandCache.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/cache/redis/MemorySkyblockIslandCache.java new file mode 100644 index 0000000..eb40044 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/cache/redis/MemorySkyblockIslandCache.java @@ -0,0 +1,80 @@ +package me.illusion.skyblockcore.common.storage.cache.redis; + +import java.util.Collection; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import me.illusion.skyblockcore.common.config.section.ConfigurationSection; +import me.illusion.skyblockcore.common.database.AbstractSkyblockDatabase; +import me.illusion.skyblockcore.common.database.SkyblockDatabaseTag; +import me.illusion.skyblockcore.common.storage.cache.SkyblockIslandCache; + +public class MemorySkyblockIslandCache extends AbstractSkyblockDatabase implements SkyblockIslandCache { + + private final Map> islands = new ConcurrentHashMap<>(); + + public MemorySkyblockIslandCache() { + addTag(SkyblockDatabaseTag.LOCAL); + addTag(SkyblockDatabaseTag.CACHE); + } + + @Override + public CompletableFuture enable(ConfigurationSection properties) { + setProperties(properties); + return CompletableFuture.completedFuture(true); // Prevent any exceptions regarding not overriding the enable method + } + + @Override + public String getName() { + return "memory"; + } + + @Override + public CompletableFuture wipe() { + islands.clear(); + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture unloadServer() { + return wipe(); + } + + @Override + public CompletableFuture>> getAllIslands() { + return CompletableFuture.completedFuture(islands); + } + + @Override + public CompletableFuture getIslandServer(UUID islandId) { + for (Map.Entry> entry : islands.entrySet()) { + if (entry.getValue().contains(islandId)) { + return CompletableFuture.completedFuture(entry.getKey()); + } + } + + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture setServer(UUID islandId, String serverId) { + islands.computeIfAbsent(serverId, k -> ConcurrentHashMap.newKeySet()).add(islandId); + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture removeIsland(UUID islandId) { + for (Map.Entry> entry : islands.entrySet()) { + entry.getValue().remove(islandId); + } + + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture removeServer(String serverId) { + islands.remove(serverId); + return CompletableFuture.completedFuture(null); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/cache/redis/RedisSkyblockIslandCache.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/cache/redis/RedisSkyblockIslandCache.java new file mode 100644 index 0000000..5213668 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/cache/redis/RedisSkyblockIslandCache.java @@ -0,0 +1,78 @@ +package me.illusion.skyblockcore.common.storage.cache.redis; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import me.illusion.skyblockcore.common.database.cache.redis.AbstractRedisCacheDatabase; +import me.illusion.skyblockcore.common.storage.cache.SkyblockIslandCache; + +public class RedisSkyblockIslandCache extends AbstractRedisCacheDatabase implements SkyblockIslandCache { + + @Override + public CompletableFuture wipe() { + return update(jedis -> { + jedis.hdel("islands", jedis.hkeys("islands").toArray(new String[0])); + }); + } + + @Override + public CompletableFuture unloadServer() { + return update(jedis -> { + jedis.hdel("islands", jedis.hkeys("islands").toArray(new String[0])); + }); + } + + @Override + public CompletableFuture>> getAllIslands() { + return query(jedis -> { + Map islands = jedis.hgetAll("islands"); + Map> map = new HashMap<>(); + + for (Map.Entry entry : islands.entrySet()) { + String serverId = entry.getValue(); + UUID islandId = UUID.fromString(entry.getKey()); + + map.computeIfAbsent(serverId, k -> new ArrayList<>()).add(islandId); + } + + return map; + }); + } + + @Override + public CompletableFuture getIslandServer(UUID islandId) { + return query(jedis -> { + return jedis.hget("islands", islandId.toString()); + }); + } + + @Override + public CompletableFuture setServer(UUID islandId, String serverId) { + return update(jedis -> { + jedis.hset("islands", islandId.toString(), serverId); + }); + } + + @Override + public CompletableFuture removeIsland(UUID islandId) { + return update(jedis -> { + jedis.hdel("islands", islandId.toString()); + }); + } + + @Override + public CompletableFuture removeServer(String serverId) { + return update(jedis -> { + Map islands = jedis.hgetAll("islands"); + + for (Map.Entry entry : islands.entrySet()) { + if (entry.getValue().equals(serverId)) { + jedis.hdel("islands", entry.getKey()); + } + } + }); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/island/SkyblockIslandStorage.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/island/SkyblockIslandStorage.java new file mode 100644 index 0000000..c3d74f7 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/island/SkyblockIslandStorage.java @@ -0,0 +1,28 @@ +package me.illusion.skyblockcore.common.storage.island; + +import java.util.Collection; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import me.illusion.skyblockcore.common.data.IslandData; +import me.illusion.skyblockcore.common.storage.SkyblockStorage; + +public interface SkyblockIslandStorage extends SkyblockStorage { + + CompletableFuture getIslandId(UUID profileId); + + CompletableFuture getIslandData(UUID islandId); + + CompletableFuture saveIslandData(IslandData data); + + CompletableFuture deleteIslandData(UUID islandId); + + // For migration purposes + + CompletableFuture> getAllIslandData(); + + CompletableFuture saveAllIslandData(Collection data); + + default CompletableFuture migrateTo(SkyblockIslandStorage other) { + return getAllIslandData().thenCompose(other::saveAllIslandData); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/island/mongo/MongoIslandStorage.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/island/mongo/MongoIslandStorage.java new file mode 100644 index 0000000..b3d2ece --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/island/mongo/MongoIslandStorage.java @@ -0,0 +1,84 @@ +package me.illusion.skyblockcore.common.storage.island.mongo; + +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Filters; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import me.illusion.skyblockcore.common.data.IslandData; +import me.illusion.skyblockcore.common.database.persistence.mongo.MongoPersistenceDatabase; +import me.illusion.skyblockcore.common.database.persistence.mongo.MongoUUIDCodec; +import me.illusion.skyblockcore.common.storage.island.SkyblockIslandStorage; +import me.illusion.skyblockcore.common.storage.island.mongo.codec.MongoIslandDataCodec; +import org.bson.codecs.Codec; + +public class MongoIslandStorage extends MongoPersistenceDatabase implements SkyblockIslandStorage { + + public static final String PROFILE_ID = "profileId"; + public static final String ISLAND_ID = "islandId"; + + private MongoCollection islandIdCollection; // Profile ID : Island ID + private MongoCollection islandDataCollection; // Island ID : Island Data + + @Override + public CompletableFuture getIslandId(UUID profileId) { + return associate(() -> islandIdCollection.find(Filters.eq(PROFILE_ID, profileId)).first()); + } + + @Override + public CompletableFuture getIslandData(UUID islandId) { + return associate(() -> islandDataCollection.find(Filters.eq(ISLAND_ID, islandId)).first()); + } + + @Override + public CompletableFuture saveIslandData(IslandData data) { + return associate(() -> { + islandIdCollection.replaceOne(Filters.eq(PROFILE_ID, data.getOwnerId()), data.getIslandId(), UPSERT); + islandDataCollection.replaceOne(Filters.eq(ISLAND_ID, data.getIslandId()), data, UPSERT); + }); + } + + @Override + public CompletableFuture deleteIslandData(UUID islandId) { + return associate(() -> { + islandIdCollection.deleteOne(Filters.eq(ISLAND_ID, islandId)); + islandDataCollection.deleteOne(Filters.eq(ISLAND_ID, islandId)); + }); + } + + @Override + public CompletableFuture> getAllIslandData() { + return associate(() -> islandDataCollection.find().into(new ArrayList<>())); + } + + @Override + public CompletableFuture saveAllIslandData(Collection data) { + return associate(() -> { + for (IslandData islandData : data) { + islandIdCollection.replaceOne(Filters.eq(PROFILE_ID, islandData.getOwnerId()), islandData.getIslandId(), UPSERT); + islandDataCollection.replaceOne(Filters.eq(ISLAND_ID, islandData.getIslandId()), islandData, UPSERT); + } + }); + } + + @Override + protected String getDefaultCollection() { + return "islands"; + } + + @Override + protected List> getCodecs() { + return List.of( + MongoIslandDataCodec.INSTANCE, + MongoUUIDCodec.INSTANCE + ); + } + + @Override + protected void initializeCollections() { + islandDataCollection = database.getCollection(collectionName, IslandData.class); + islandIdCollection = database.getCollection(collectionName, UUID.class); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/mongo/codec/MongoIslandDataCodec.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/island/mongo/codec/MongoIslandDataCodec.java similarity index 74% rename from SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/mongo/codec/MongoIslandDataCodec.java rename to SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/island/mongo/codec/MongoIslandDataCodec.java index f0415cb..7d9ca07 100644 --- a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/database/fetching/mongo/codec/MongoIslandDataCodec.java +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/island/mongo/codec/MongoIslandDataCodec.java @@ -1,7 +1,8 @@ -package me.illusion.skyblockcore.common.database.fetching.mongo.codec; +package me.illusion.skyblockcore.common.storage.island.mongo.codec; import java.util.UUID; import me.illusion.skyblockcore.common.data.IslandData; +import me.illusion.skyblockcore.common.storage.island.mongo.MongoIslandStorage; import org.bson.BsonBinary; import org.bson.BsonReader; import org.bson.BsonWriter; @@ -23,8 +24,8 @@ private MongoIslandDataCodec() { public IslandData decode(BsonReader reader, DecoderContext decoderContext) { reader.readStartDocument(); - UUID islandId = readUUID("islandId", reader); - UUID ownerId = readUUID("ownerId", reader); + UUID islandId = readUUID(MongoIslandStorage.ISLAND_ID, reader); + UUID ownerId = readUUID(MongoIslandStorage.PROFILE_ID, reader); reader.readEndDocument(); @@ -35,8 +36,8 @@ public IslandData decode(BsonReader reader, DecoderContext decoderContext) { public void encode(BsonWriter writer, IslandData value, EncoderContext encoderContext) { writer.writeStartDocument(); - writeUUID("islandId", value.getIslandId(), writer); - writeUUID("ownerId", value.getOwnerId(), writer); + writeUUID(MongoIslandStorage.ISLAND_ID, value.getIslandId(), writer); + writeUUID(MongoIslandStorage.PROFILE_ID, value.getOwnerId(), writer); writer.writeEndDocument(); } diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/island/sql/MySQLIslandStorage.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/island/sql/MySQLIslandStorage.java new file mode 100644 index 0000000..aec047e --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/island/sql/MySQLIslandStorage.java @@ -0,0 +1,123 @@ +package me.illusion.skyblockcore.common.storage.island.sql; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import me.illusion.skyblockcore.common.data.IslandData; +import me.illusion.skyblockcore.common.database.persistence.sql.impl.MySQLPersistenceDatabase; +import me.illusion.skyblockcore.common.database.sql.object.StringStatementObject; +import me.illusion.skyblockcore.common.storage.island.SkyblockIslandStorage; + +public class MySQLIslandStorage extends MySQLPersistenceDatabase implements SkyblockIslandStorage { // TODO: De-duplicate the SQL storages + + private static final String PROFILES_TABLE = "island_profiles"; + private static final String ISLANDS_TABLE = "island_data"; + + private static final String CREATE_PROFILES_TABLE = "CREATE TABLE IF NOT EXISTS " + PROFILES_TABLE + " (" + + "profile_id VARCHAR(36) NOT NULL," + + "island_id VARCHAR(36) NOT NULL," + + "PRIMARY KEY (profile_id)" + + ");"; + + private static final String CREATE_ISLANDS_TABLE = "CREATE TABLE IF NOT EXISTS " + ISLANDS_TABLE + " (" + + "island_id VARCHAR(36) NOT NULL," + + "owner_id VARCHAR(36) NOT NULL," + + "PRIMARY KEY (island_id)" + + ");"; + + private static final String GET_ISLAND_ID = "SELECT island_id FROM " + PROFILES_TABLE + " WHERE profile_id = ?;"; + private static final String GET_ISLAND_DATA = "SELECT * FROM " + ISLANDS_TABLE + " WHERE island_id = ?;"; + private static final String SAVE_ISLAND_DATA = + "INSERT INTO " + ISLANDS_TABLE + " (island_id, owner_id) VALUES (?, ?) ON DUPLICATE KEY UPDATE owner_id = ?;"; + private static final String DELETE_ISLAND_DATA = "DELETE FROM " + ISLANDS_TABLE + " WHERE island_id = ?;"; + private static final String GET_ALL_ISLAND_DATA = "SELECT * FROM " + ISLANDS_TABLE + ";"; + private static final String SAVE_ALL_ISLAND_DATA = + "INSERT INTO " + ISLANDS_TABLE + " (island_id, owner_id) VALUES (?, ?) ON DUPLICATE KEY UPDATE owner_id = ?;"; + + @Override + public CompletableFuture getIslandId(UUID profileId) { + return runQueryAsync(GET_ISLAND_ID, List.of(new StringStatementObject(profileId.toString())), resultSet -> { + if (resultSet.next()) { + return UUID.fromString(resultSet.getString("island_id")); + } + + return null; + }); + } + + @Override + public CompletableFuture getIslandData(UUID islandId) { + return runQueryAsync(GET_ISLAND_DATA, List.of(new StringStatementObject(islandId.toString())), resultSet -> { + if (resultSet.next()) { + return new IslandData( + UUID.fromString(resultSet.getString("island_id")), + UUID.fromString(resultSet.getString("owner_id")) + ); + } + + return null; + }); + } + + @Override + public CompletableFuture saveIslandData(IslandData data) { + return runUpdateAsync(SAVE_ISLAND_DATA, + new StringStatementObject(data.getIslandId().toString()), + new StringStatementObject(data.getOwnerId().toString()), + new StringStatementObject(data.getOwnerId().toString()) + ); + } + + @Override + public CompletableFuture deleteIslandData(UUID islandId) { + return runUpdateAsync(DELETE_ISLAND_DATA, new StringStatementObject(islandId.toString())); + } + + @Override + public CompletableFuture> getAllIslandData() { + return runQueryAsync(GET_ALL_ISLAND_DATA, List.of(), resultSet -> { + try { + Collection data = new ArrayList<>(); + + while (resultSet.next()) { + data.add(new IslandData( + UUID.fromString(resultSet.getString("island_id")), + UUID.fromString(resultSet.getString("owner_id")) + )); + } + + return data; + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + }); + } + + @Override + public CompletableFuture saveAllIslandData(Collection data) { + return associate(() -> { + for (IslandData islandData : data) { + runUpdate(SAVE_ALL_ISLAND_DATA, + new StringStatementObject(islandData.getIslandId().toString()), + new StringStatementObject(islandData.getOwnerId().toString()), + new StringStatementObject(islandData.getOwnerId().toString()) + ); + } + }); + } + + @Override + protected Collection getTables() { + return List.of(PROFILES_TABLE, ISLANDS_TABLE); + } + + @Override + protected void createTables() { + runUpdate(CREATE_PROFILES_TABLE); + runUpdate(CREATE_ISLANDS_TABLE); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/island/sql/SQLiteIslandStorage.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/island/sql/SQLiteIslandStorage.java new file mode 100644 index 0000000..b940c46 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/island/sql/SQLiteIslandStorage.java @@ -0,0 +1,123 @@ +package me.illusion.skyblockcore.common.storage.island.sql; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import me.illusion.skyblockcore.common.data.IslandData; +import me.illusion.skyblockcore.common.database.persistence.sql.impl.SQLitePersistenceDatabase; +import me.illusion.skyblockcore.common.database.sql.object.StringStatementObject; +import me.illusion.skyblockcore.common.storage.island.SkyblockIslandStorage; + +public class SQLiteIslandStorage extends SQLitePersistenceDatabase implements SkyblockIslandStorage { + + private static final String PROFILES_TABLE = "island_profiles"; + private static final String ISLANDS_TABLE = "island_data"; + + private static final String CREATE_PROFILES_TABLE = "CREATE TABLE IF NOT EXISTS " + PROFILES_TABLE + " (" + + "profile_id VARCHAR(36) NOT NULL," + + "island_id VARCHAR(36) NOT NULL," + + "PRIMARY KEY (profile_id)" + + ");"; + + private static final String CREATE_ISLANDS_TABLE = "CREATE TABLE IF NOT EXISTS " + ISLANDS_TABLE + " (" + + "island_id VARCHAR(36) NOT NULL," + + "owner_id VARCHAR(36) NOT NULL," + + "PRIMARY KEY (island_id)" + + ");"; + + private static final String GET_ISLAND_ID = "SELECT island_id FROM " + PROFILES_TABLE + " WHERE profile_id = ?;"; + private static final String GET_ISLAND_DATA = "SELECT * FROM " + ISLANDS_TABLE + " WHERE island_id = ?;"; + private static final String SAVE_ISLAND_DATA = + "INSERT INTO " + ISLANDS_TABLE + " (island_id, owner_id) VALUES (?, ?) ON DUPLICATE KEY UPDATE owner_id = ?;"; + private static final String DELETE_ISLAND_DATA = "DELETE FROM " + ISLANDS_TABLE + " WHERE island_id = ?;"; + private static final String GET_ALL_ISLAND_DATA = "SELECT * FROM " + ISLANDS_TABLE + ";"; + private static final String SAVE_ALL_ISLAND_DATA = + "INSERT INTO " + ISLANDS_TABLE + " (island_id, owner_id) VALUES (?, ?) ON DUPLICATE KEY UPDATE owner_id = ?;"; + + @Override + public CompletableFuture getIslandId(UUID profileId) { + return runQueryAsync(GET_ISLAND_ID, List.of(new StringStatementObject(profileId.toString())), resultSet -> { + if (resultSet.next()) { + return UUID.fromString(resultSet.getString("island_id")); + } + + return null; + }); + } + + @Override + public CompletableFuture getIslandData(UUID islandId) { + return runQueryAsync(GET_ISLAND_DATA, List.of(new StringStatementObject(islandId.toString())), resultSet -> { + if (resultSet.next()) { + return new IslandData( + UUID.fromString(resultSet.getString("island_id")), + UUID.fromString(resultSet.getString("owner_id")) + ); + } + + return null; + }); + } + + @Override + public CompletableFuture saveIslandData(IslandData data) { + return runUpdateAsync(SAVE_ISLAND_DATA, + new StringStatementObject(data.getIslandId().toString()), + new StringStatementObject(data.getOwnerId().toString()), + new StringStatementObject(data.getOwnerId().toString()) + ); + } + + @Override + public CompletableFuture deleteIslandData(UUID islandId) { + return runUpdateAsync(DELETE_ISLAND_DATA, new StringStatementObject(islandId.toString())); + } + + @Override + public CompletableFuture> getAllIslandData() { + return runQueryAsync(GET_ALL_ISLAND_DATA, List.of(), resultSet -> { + try { + Collection data = new ArrayList<>(); + + while (resultSet.next()) { + data.add(new IslandData( + UUID.fromString(resultSet.getString("island_id")), + UUID.fromString(resultSet.getString("owner_id")) + )); + } + + return data; + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + }); + } + + @Override + public CompletableFuture saveAllIslandData(Collection data) { + return associate(() -> { + for (IslandData islandData : data) { + runUpdate(SAVE_ALL_ISLAND_DATA, + new StringStatementObject(islandData.getIslandId().toString()), + new StringStatementObject(islandData.getOwnerId().toString()), + new StringStatementObject(islandData.getOwnerId().toString()) + ); + } + }); + } + + @Override + protected Collection getTables() { + return List.of(PROFILES_TABLE, ISLANDS_TABLE); + } + + @Override + protected void createTables() { + runUpdate(CREATE_PROFILES_TABLE); + runUpdate(CREATE_ISLANDS_TABLE); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/profiles/SkyblockProfileStorage.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/profiles/SkyblockProfileStorage.java new file mode 100644 index 0000000..b043db4 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/profiles/SkyblockProfileStorage.java @@ -0,0 +1,21 @@ +package me.illusion.skyblockcore.common.storage.profiles; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import me.illusion.skyblockcore.common.storage.SkyblockStorage; + +public interface SkyblockProfileStorage extends SkyblockStorage { + + CompletableFuture getProfileId(UUID playerId); + + CompletableFuture setProfileId(UUID playerId, UUID profileId); + + CompletableFuture> getAllProfileIds(); + + CompletableFuture setAllProfileIds(Map profileIds); + + default CompletableFuture migrateTo(SkyblockProfileStorage other) { + return getAllProfileIds().thenCompose(other::setAllProfileIds); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/profiles/mongo/MongoProfileStorage.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/profiles/mongo/MongoProfileStorage.java new file mode 100644 index 0000000..47e59ad --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/profiles/mongo/MongoProfileStorage.java @@ -0,0 +1,67 @@ +package me.illusion.skyblockcore.common.storage.profiles.mongo; + +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Filters; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import me.illusion.skyblockcore.common.database.persistence.mongo.MongoPersistenceDatabase; +import me.illusion.skyblockcore.common.database.persistence.mongo.MongoUUIDCodec; +import me.illusion.skyblockcore.common.storage.profiles.SkyblockProfileStorage; +import org.bson.codecs.Codec; + +public class MongoProfileStorage extends MongoPersistenceDatabase implements SkyblockProfileStorage { + + private MongoCollection profileIdCollection; // Player ID : Profile ID + + @Override + protected String getDefaultCollection() { + return "profiles"; + } + + @Override + protected void initializeCollections() { + profileIdCollection = database.getCollection("profileIds", UUID.class); + } + + @Override + protected List> getCodecs() { + return List.of(MongoUUIDCodec.INSTANCE); + } + + @Override + public CompletableFuture getProfileId(UUID playerId) { + return associate(() -> profileIdCollection.find(Filters.eq(playerId)).first()); + } + + @Override + public CompletableFuture setProfileId(UUID playerId, UUID profileId) { + return associate(() -> { + profileIdCollection.replaceOne(Filters.eq(playerId), profileId, UPSERT); // Needs braces due to UpdateResult + }); + } + + @Override + public CompletableFuture> getAllProfileIds() { + return associate(() -> { + Map map = new ConcurrentHashMap<>(); + + for (UUID playerId : profileIdCollection.find()) { + map.put(playerId, profileIdCollection.find(Filters.eq(playerId)).first()); + } + + return map; + }); + } + + @Override + public CompletableFuture setAllProfileIds(Map profileIds) { + return associate(() -> { + for (Map.Entry entry : profileIds.entrySet()) { + profileIdCollection.replaceOne(Filters.eq(entry.getKey()), entry.getValue(), UPSERT); + } + }); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/profiles/sql/MySQLProfileStorage.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/profiles/sql/MySQLProfileStorage.java new file mode 100644 index 0000000..0228d69 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/profiles/sql/MySQLProfileStorage.java @@ -0,0 +1,77 @@ +package me.illusion.skyblockcore.common.storage.profiles.sql; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import me.illusion.skyblockcore.common.database.persistence.sql.impl.MySQLPersistenceDatabase; +import me.illusion.skyblockcore.common.database.sql.object.StringStatementObject; +import me.illusion.skyblockcore.common.storage.profiles.SkyblockProfileStorage; + +public class MySQLProfileStorage extends MySQLPersistenceDatabase implements SkyblockProfileStorage { + + private static final String TABLE = "skyblock_profiles"; + + private static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE + "(player_id VARCHAR(36) PRIMARY KEY, profile_id VARCHAR(36))"; + private static final String GET_PROFILE_ID = "SELECT profile_id FROM " + TABLE + " WHERE player_id = ?"; + private static final String SET_PROFILE_ID = "INSERT INTO " + TABLE + " (player_id, profile_id) VALUES (?, ?) ON DUPLICATE KEY UPDATE profile_id = ?"; + private static final String GET_ALL_PROFILE_IDS = "SELECT * FROM " + TABLE; + + + @Override + protected Collection getTables() { + return List.of(TABLE); + } + + @Override + protected void createTables() { + runUpdate(CREATE_TABLE); + } + + @Override + public CompletableFuture getProfileId(UUID playerId) { + return runQueryAsync(GET_PROFILE_ID, List.of(new StringStatementObject(playerId.toString())), resultSet -> { + if (resultSet.next()) { + return UUID.fromString(resultSet.getString("profile_id")); + } else { + return null; + } + }); + } + + @Override + public CompletableFuture setProfileId(UUID playerId, UUID profileId) { + return runUpdateAsync(SET_PROFILE_ID, + new StringStatementObject(playerId.toString()), + new StringStatementObject(profileId.toString()), + new StringStatementObject(profileId.toString()) + ); + } + + @Override + public CompletableFuture> getAllProfileIds() { + return runQueryAsync(GET_ALL_PROFILE_IDS, List.of(), resultSet -> { + Map map = new ConcurrentHashMap<>(); + + while (resultSet.next()) { + map.put(UUID.fromString(resultSet.getString("player_id")), UUID.fromString(resultSet.getString("profile_id"))); + } + + return map; + }); + } + + @Override + public CompletableFuture setAllProfileIds(Map profileIds) { + List> futures = new ArrayList<>(); + + for (Map.Entry entry : profileIds.entrySet()) { + futures.add(setProfileId(entry.getKey(), entry.getValue())); + } + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/profiles/sql/SQLiteProfileStorage.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/profiles/sql/SQLiteProfileStorage.java new file mode 100644 index 0000000..29d460a --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/storage/profiles/sql/SQLiteProfileStorage.java @@ -0,0 +1,76 @@ +package me.illusion.skyblockcore.common.storage.profiles.sql; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import me.illusion.skyblockcore.common.database.persistence.sql.impl.SQLitePersistenceDatabase; +import me.illusion.skyblockcore.common.database.sql.object.StringStatementObject; +import me.illusion.skyblockcore.common.storage.profiles.SkyblockProfileStorage; + +public class SQLiteProfileStorage extends SQLitePersistenceDatabase implements SkyblockProfileStorage { + + private static final String TABLE = "skyblock_profiles"; + + private static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE + "(player_id VARCHAR(36) PRIMARY KEY, profile_id VARCHAR(36))"; + private static final String GET_PROFILE_ID = "SELECT profile_id FROM " + TABLE + " WHERE player_id = ?"; + private static final String SET_PROFILE_ID = "INSERT INTO " + TABLE + " (player_id, profile_id) VALUES (?, ?) ON DUPLICATE KEY UPDATE profile_id = ?"; + private static final String GET_ALL_PROFILE_IDS = "SELECT * FROM " + TABLE; + + @Override + protected Collection getTables() { + return List.of(TABLE); + } + + @Override + protected void createTables() { + runUpdate(CREATE_TABLE); + } + + @Override + public CompletableFuture getProfileId(UUID playerId) { + return runQueryAsync(GET_PROFILE_ID, List.of(new StringStatementObject(playerId.toString())), resultSet -> { + if (resultSet.next()) { + return UUID.fromString(resultSet.getString("profile_id")); + } else { + return null; + } + }); + } + + @Override + public CompletableFuture setProfileId(UUID playerId, UUID profileId) { + return runUpdateAsync(SET_PROFILE_ID, + new StringStatementObject(playerId.toString()), + new StringStatementObject(profileId.toString()), + new StringStatementObject(profileId.toString()) + ); + } + + @Override + public CompletableFuture> getAllProfileIds() { + return runQueryAsync(GET_ALL_PROFILE_IDS, List.of(), resultSet -> { + Map map = new ConcurrentHashMap<>(); + + while (resultSet.next()) { + map.put(UUID.fromString(resultSet.getString("player_id")), UUID.fromString(resultSet.getString("profile_id"))); + } + + return map; + }); + } + + @Override + public CompletableFuture setAllProfileIds(Map profileIds) { + List> futures = new ArrayList<>(); + + for (Map.Entry entry : profileIds.entrySet()) { + futures.add(setProfileId(entry.getKey(), entry.getValue())); + } + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + } +} diff --git a/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/utilities/file/IOUtils.java b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/utilities/file/IOUtils.java new file mode 100644 index 0000000..3faaf21 --- /dev/null +++ b/SkyblockCore-Common/src/main/java/me/illusion/skyblockcore/common/utilities/file/IOUtils.java @@ -0,0 +1,97 @@ +package me.illusion.skyblockcore.common.utilities.file; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collections; +import java.util.function.Consumer; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; +import me.illusion.skyblockcore.common.platform.SkyblockPlatform; + +public final class IOUtils { + + public static final int BUFFER_SIZE = 1024; + + private IOUtils() { + + } + + public static void traverseAndLoad(File folder, Consumer consumer) { + if (folder == null || !folder.exists() || !folder.isDirectory()) { + return; + } + + for (File file : folder.listFiles()) { + if (file.isDirectory()) { + traverseAndLoad(file, consumer); + } else { + consumer.accept(file); + } + } + } + + public static void createFile(File file) { + if (file == null) { + return; + } + + if (!file.exists()) { + try { + file.getParentFile().mkdirs(); + file.createNewFile(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + public static void copyFolder(SkyblockPlatform platform, File jarFile, String folderPath) { + File dataFolder = platform.getConfigurationProvider().getDataFolder(); + File destination = new File(dataFolder, folderPath); + + if (!destination.exists()) { + destination.mkdirs(); + } + + try (JarFile jar = new JarFile(jarFile)) { + for (ZipEntry entry : Collections.list(jar.entries())) { + String name = entry.getName(); + + if (!name.startsWith(folderPath)) { + continue; + } + + File file = new File(dataFolder, name); + + if (file.exists()) { // Don't overwrite + continue; + } + + if (entry.isDirectory()) { + file.mkdirs(); + continue; + } + + IOUtils.copy(jar.getInputStream(entry), file); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void copy(InputStream in, File file) throws IOException { + IOUtils.createFile(file); + + try (OutputStream out = new FileOutputStream(file)) { + byte[] buffer = new byte[BUFFER_SIZE]; + int length; + + while ((length = in.read(buffer)) > 0) { + out.write(buffer, 0, length); + } + } + } +} diff --git a/SkyblockCore-Proxy/src/main/java/me/illusion/skyblockcore/proxy/command/PlaySkyblockCommand.java b/SkyblockCore-Proxy/src/main/java/me/illusion/skyblockcore/proxy/command/PlaySkyblockCommand.java index edb50d5..5a99a63 100644 --- a/SkyblockCore-Proxy/src/main/java/me/illusion/skyblockcore/proxy/command/PlaySkyblockCommand.java +++ b/SkyblockCore-Proxy/src/main/java/me/illusion/skyblockcore/proxy/command/PlaySkyblockCommand.java @@ -5,7 +5,8 @@ import me.illusion.skyblockcore.common.command.audience.SkyblockAudience; import me.illusion.skyblockcore.common.command.manager.SkyblockCommandManager; import me.illusion.skyblockcore.common.config.SkyblockMessagesFile; -import me.illusion.skyblockcore.common.database.fetching.SkyblockFetchingDatabase; +import me.illusion.skyblockcore.common.storage.island.SkyblockIslandStorage; +import me.illusion.skyblockcore.common.storage.profiles.SkyblockProfileStorage; import me.illusion.skyblockcore.proxy.SkyblockProxyPlatform; import me.illusion.skyblockcore.proxy.audience.SkyblockProxyPlayerAudience; @@ -25,6 +26,7 @@ public PlaySkyblockCommand(SkyblockProxyPlatform platform) { commandManager.newCommand("play-skyblock") .audience(SkyblockProxyPlayerAudience.class) + .permission("skyblockproxy.command.play") .handler((player, context) -> { UUID playerId = player.getUniqueId(); @@ -42,14 +44,15 @@ public PlaySkyblockCommand(SkyblockProxyPlatform platform) { } private CompletableFuture fetchIslandId(UUID playerId) { - SkyblockFetchingDatabase database = platform.getDatabaseRegistry().getChosenDatabase(); + SkyblockProfileStorage profileStorage = platform.getDatabaseRegistry().getStorage(SkyblockProfileStorage.class); + SkyblockIslandStorage islandStorage = platform.getDatabaseRegistry().getStorage(SkyblockIslandStorage.class); - return database.getProfileId(playerId).thenCompose(uuid -> { + return profileStorage.getProfileId(playerId).thenCompose(uuid -> { if (uuid == null) { return CompletableFuture.completedFuture(null); } - return database.fetchIslandId(uuid); + return islandStorage.getIslandId(uuid); }); } diff --git a/SkyblockCore-Proxy/src/main/java/me/illusion/skyblockcore/proxy/matchmaking/data/AbstractSkyblockServerMatchmaker.java b/SkyblockCore-Proxy/src/main/java/me/illusion/skyblockcore/proxy/matchmaking/data/AbstractSkyblockServerMatchmaker.java index 28bb2bc..3c1c059 100644 --- a/SkyblockCore-Proxy/src/main/java/me/illusion/skyblockcore/proxy/matchmaking/data/AbstractSkyblockServerMatchmaker.java +++ b/SkyblockCore-Proxy/src/main/java/me/illusion/skyblockcore/proxy/matchmaking/data/AbstractSkyblockServerMatchmaker.java @@ -6,7 +6,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import me.illusion.skyblockcore.common.database.cache.SkyblockCacheDatabase; +import me.illusion.skyblockcore.common.storage.cache.SkyblockIslandCache; import me.illusion.skyblockcore.proxy.SkyblockProxyPlatform; import me.illusion.skyblockcore.proxy.instance.ProxyServerData; import me.illusion.skyblockcore.proxy.matchmaking.comparator.ServerDataComparator; @@ -18,11 +18,11 @@ public abstract class AbstractSkyblockServerMatchmaker implements SkyblockServerMatchmaker { private final ServerDataComparator comparator; - private final SkyblockCacheDatabase cacheDatabase; + private final SkyblockIslandCache cacheDatabase; protected AbstractSkyblockServerMatchmaker(SkyblockProxyPlatform platform) { this.comparator = platform.getMatchmakerComparatorRegistry().getDefaultComparator(); - this.cacheDatabase = platform.getDatabaseRegistry().getChosenCacheDatabase(); + this.cacheDatabase = platform.getDatabaseRegistry().getCache(SkyblockIslandCache.class); } @Override diff --git a/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/island/AbstractIslandManager.java b/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/island/AbstractIslandManager.java index 2d0b3eb..a4709a9 100644 --- a/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/island/AbstractIslandManager.java +++ b/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/island/AbstractIslandManager.java @@ -6,8 +6,8 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import me.illusion.skyblockcore.common.data.IslandData; -import me.illusion.skyblockcore.common.database.fetching.SkyblockFetchingDatabase; import me.illusion.skyblockcore.common.platform.SkyblockPlatform; +import me.illusion.skyblockcore.common.storage.island.SkyblockIslandStorage; import me.illusion.skyblockcore.server.SkyblockServerPlatform; import me.illusion.skyblockcore.server.player.SkyblockPlayerManager; import me.illusion.skyblockcore.server.util.SkyblockLocation; @@ -22,14 +22,14 @@ public abstract class AbstractIslandManager implements SkyblockIslandManager { protected final Set> pending = ConcurrentHashMap.newKeySet(); - protected final SkyblockFetchingDatabase database; + protected final SkyblockIslandStorage database; protected final SkyblockPlayerManager playerManager; protected final SkyblockPlatform platform; protected AbstractIslandManager(SkyblockServerPlatform platform) { this.platform = platform; - this.database = platform.getDatabaseRegistry().getChosenDatabase(); + this.database = platform.getDatabaseRegistry().getStorage(SkyblockIslandStorage.class); this.playerManager = platform.getPlayerManager(); } @@ -47,7 +47,7 @@ public CompletableFuture loadPlayerIsland(UUID profileId, String return CompletableFuture.completedFuture(cached); } - return register(database.fetchPlayerIsland(profileId).thenCompose(id -> { + return register(database.getIslandId(profileId).thenCompose(id -> { if (id == null) { return createIsland(fallback, profileId); } @@ -70,7 +70,7 @@ public CompletableFuture getIslandData(UUID profileId) { return CompletableFuture.completedFuture(cached.getData()); } - return register(database.fetchPlayerIsland(profileId)); + return register(database.getIslandId(profileId).thenCompose(database::getIslandData)); } /** @@ -81,7 +81,7 @@ public CompletableFuture getIslandData(UUID profileId) { */ @Override public CompletableFuture loadIsland(UUID islandId) { - return register(database.fetchIslandData(islandId).thenCompose(this::loadIsland)); + return register(database.getIslandData(islandId).thenCompose(this::loadIsland)); } diff --git a/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/SkyblockNetworkRegistryImpl.java b/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/SkyblockNetworkRegistryImpl.java index 19db40b..3094940 100644 --- a/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/SkyblockNetworkRegistryImpl.java +++ b/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/SkyblockNetworkRegistryImpl.java @@ -4,7 +4,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; -import me.illusion.skyblockcore.common.config.ReadOnlyConfigurationSection; +import me.illusion.skyblockcore.common.config.section.ConfigurationSection; import me.illusion.skyblockcore.server.SkyblockServerPlatform; /** @@ -16,7 +16,7 @@ public class SkyblockNetworkRegistryImpl implements SkyblockNetworkRegistry { private final Map structures = new ConcurrentHashMap<>(); private final SkyblockServerPlatform platform; - private final ReadOnlyConfigurationSection config; + private final ConfigurationSection config; private String desiredStructure; private boolean loaded = false; diff --git a/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/complex/ComplexSkyblockNetwork.java b/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/complex/ComplexSkyblockNetwork.java index d76f353..bd4d5bd 100644 --- a/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/complex/ComplexSkyblockNetwork.java +++ b/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/complex/ComplexSkyblockNetwork.java @@ -1,11 +1,14 @@ package me.illusion.skyblockcore.server.network.complex; +import java.util.Collection; +import java.util.List; import lombok.Getter; import me.illusion.skyblockcore.common.communication.packet.PacketManager; import me.illusion.skyblockcore.common.config.SkyblockMessagesFile; -import me.illusion.skyblockcore.common.database.cache.SkyblockCacheDatabase; -import me.illusion.skyblockcore.common.database.fetching.SkyblockFetchingDatabase; +import me.illusion.skyblockcore.common.database.SkyblockDatabaseTag; import me.illusion.skyblockcore.common.event.manager.SkyblockEventManager; +import me.illusion.skyblockcore.common.storage.cache.SkyblockIslandCache; +import me.illusion.skyblockcore.common.storage.island.SkyblockIslandStorage; import me.illusion.skyblockcore.server.SkyblockServerPlatform; import me.illusion.skyblockcore.server.island.SkyblockIslandManager; import me.illusion.skyblockcore.server.network.SkyblockNetworkStructure; @@ -25,9 +28,13 @@ @Getter public class ComplexSkyblockNetwork implements SkyblockNetworkStructure { + private static final Collection DISALLOWED_TAGS = List.of( + SkyblockDatabaseTag.LOCAL + ); + private final SkyblockServerPlatform platform; - private SkyblockFetchingDatabase database; + private SkyblockIslandStorage database; private CommunicationsHandler communicationsHandler; private ComplexNetworkConfiguration configuration; @@ -38,12 +45,14 @@ public ComplexSkyblockNetwork(SkyblockServerPlatform platform) { @Override public void load() { - platform.getDatabasesFile().setSupportsFileBased(false); // We don't support file-based databases, as they are instance-specific + // platform.getDatabasesFile().setSupportsFileBased(false); // We don't support file-based databases, as they are instance-specific } @Override public void enable() { - database = platform.getDatabaseRegistry().getChosenDatabase(); + checkSetup(); + + database = platform.getDatabaseRegistry().getStorage(SkyblockIslandStorage.class); configuration = new ComplexNetworkConfiguration(platform); registerListeners(); @@ -63,6 +72,22 @@ public String getName() { // Main startup logic + private void checkSetup() { + SkyblockIslandStorage storage = platform.getDatabaseRegistry().getStorage(SkyblockIslandStorage.class); + + if (storage == null) { + throw new IllegalStateException("No island storage found"); + } + + for (SkyblockDatabaseTag tag : DISALLOWED_TAGS) { + if (storage.hasTag(tag)) { + throw new IllegalStateException( + "Incompatible database of type " + database.getName() + " found. Make sure the database chosen does not match any of the following tags: " + + DISALLOWED_TAGS); + } + } + } + private void registerListeners() { new ComplexPlayerJoinListener(this); new ComplexIslandLoadListener(this); @@ -85,8 +110,8 @@ public SkyblockIslandManager getIslandManager() { return platform.getIslandManager(); } - public SkyblockCacheDatabase getCacheDatabase() { - return platform.getDatabaseRegistry().getChosenCacheDatabase(); + public SkyblockIslandCache getCacheDatabase() { + return platform.getDatabaseRegistry().getCache(SkyblockIslandCache.class); } public SkyblockEventManager getEventManager() { diff --git a/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/complex/command/ComplexIslandCommand.java b/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/complex/command/ComplexIslandCommand.java index c6f0833..a1ca348 100644 --- a/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/complex/command/ComplexIslandCommand.java +++ b/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/complex/command/ComplexIslandCommand.java @@ -28,7 +28,7 @@ public ComplexIslandCommand(ComplexSkyblockNetwork network) { return; } - network.getDatabase().fetchIslandId(player.getUniqueId()).thenAccept(islandId -> { + network.getDatabase().getIslandId(player.getSelectedProfileId()).thenAccept(islandId -> { if (islandId == null) { messages.sendMessage(player, "no-island-loaded"); return; diff --git a/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/complex/communication/CommunicationsHandler.java b/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/complex/communication/CommunicationsHandler.java index 7ce447f..a1c59fe 100644 --- a/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/complex/communication/CommunicationsHandler.java +++ b/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/complex/communication/CommunicationsHandler.java @@ -3,8 +3,8 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import me.illusion.skyblockcore.common.communication.packet.PacketManager; -import me.illusion.skyblockcore.common.database.cache.SkyblockCacheDatabase; -import me.illusion.skyblockcore.common.database.fetching.SkyblockFetchingDatabase; +import me.illusion.skyblockcore.common.storage.cache.SkyblockIslandCache; +import me.illusion.skyblockcore.common.storage.island.SkyblockIslandStorage; import me.illusion.skyblockcore.server.island.SkyblockIsland; import me.illusion.skyblockcore.server.network.complex.ComplexSkyblockNetwork; import me.illusion.skyblockcore.server.network.complex.communication.packet.request.PacketRequestIslandTeleport; @@ -18,8 +18,8 @@ public class CommunicationsHandler { // Potential problem: If an island is reque private final PacketManager packetManager; private final String serverId; - private final SkyblockCacheDatabase cacheDatabase; - private final SkyblockFetchingDatabase database; + private final SkyblockIslandCache cacheDatabase; + private final SkyblockIslandStorage database; private final ComplexSkyblockNetwork network; @@ -59,7 +59,7 @@ public CompletableFuture getIslandServer(UUID islandId) { * @return A future containing the result of the update */ public CompletableFuture updateIslandServer(UUID islandId, String serverId) { - return cacheDatabase.updateIslandServer(islandId, serverId); + return cacheDatabase.setServer(islandId, serverId); } /** diff --git a/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/complex/listener/ComplexPlayerJoinListener.java b/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/complex/listener/ComplexPlayerJoinListener.java index ffe668b..f9f852b 100644 --- a/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/complex/listener/ComplexPlayerJoinListener.java +++ b/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/network/complex/listener/ComplexPlayerJoinListener.java @@ -36,7 +36,7 @@ private void handle(SkyblockPlayerJoinEvent event) { // We try to fetch the island id, and see if we can load it. If we can, we load it. network.getDatabase() - .fetchIslandId(profileId) // Fetch the island id + .getIslandId(profileId) // Fetch the island id .thenCompose(islandId -> network.getCommunicationsHandler().canLoad(islandId)) // Check if we can load the island .thenAccept(allowed -> { // If we can load the island, we load it. if (Boolean.TRUE.equals(allowed)) { // CF returns a boxed boolean, let's prevent null pointer exceptions. diff --git a/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/player/AbstractSkyblockPlayerManager.java b/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/player/AbstractSkyblockPlayerManager.java index 62aa95e..e3cbdb8 100644 --- a/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/player/AbstractSkyblockPlayerManager.java +++ b/SkyblockCore-Server/src/main/java/me/illusion/skyblockcore/server/player/AbstractSkyblockPlayerManager.java @@ -6,8 +6,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import lombok.Setter; -import me.illusion.skyblockcore.common.database.fetching.SkyblockFetchingDatabase; import me.illusion.skyblockcore.common.platform.SkyblockPlatform; +import me.illusion.skyblockcore.common.storage.profiles.SkyblockProfileStorage; import me.illusion.skyblockcore.server.event.player.SkyblockPlayerJoinEvent; import me.illusion.skyblockcore.server.event.player.SkyblockPlayerQuitEvent; @@ -17,7 +17,7 @@ public abstract class AbstractSkyblockPlayerManager implements SkyblockPlayerManager { protected final SkyblockPlatform platform; - protected final SkyblockFetchingDatabase database; + protected final SkyblockProfileStorage database; private final Map profileMap = new ConcurrentHashMap<>(); // Player ID -> Profile ID private final Map playerIdMap = new ConcurrentHashMap<>(); @@ -27,7 +27,7 @@ public abstract class AbstractSkyblockPlayerManager implements SkyblockPlayerMan protected AbstractSkyblockPlayerManager(SkyblockPlatform platform) { this.platform = platform; - this.database = platform.getDatabaseRegistry().getChosenDatabase(); + this.database = platform.getDatabaseRegistry().getStorage(SkyblockProfileStorage.class); } // Player management stuff diff --git a/SkyblockCore-Spigot/build.gradle b/SkyblockCore-Spigot/build.gradle index 7a8811a..31cdc47 100644 --- a/SkyblockCore-Spigot/build.gradle +++ b/SkyblockCore-Spigot/build.gradle @@ -56,7 +56,7 @@ dependencies { compileOnly('me.clip:placeholderapi:2.11.4') compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.2.15") - implementation("me.illusion:cosmos-plugin:1.0") + compileOnly("me.illusion:cosmos-plugin:1.0") implementation(project(":SkyblockCore-Common")) implementation(project(":SkyblockCore-Server")) diff --git a/SkyblockCore-Spigot/src/main/java/me/illusion/skyblockcore/spigot/SkyblockSpigotPlugin.java b/SkyblockCore-Spigot/src/main/java/me/illusion/skyblockcore/spigot/SkyblockSpigotPlugin.java index 4767306..09bebf8 100644 --- a/SkyblockCore-Spigot/src/main/java/me/illusion/skyblockcore/spigot/SkyblockSpigotPlugin.java +++ b/SkyblockCore-Spigot/src/main/java/me/illusion/skyblockcore/spigot/SkyblockSpigotPlugin.java @@ -1,17 +1,18 @@ package me.illusion.skyblockcore.spigot; +import java.io.File; import lombok.Getter; import me.illusion.cosmos.CosmosPlugin; import me.illusion.skyblockcore.common.command.audience.SkyblockAudience; import me.illusion.skyblockcore.common.command.manager.SkyblockCommandManager; import me.illusion.skyblockcore.common.config.ConfigurationProvider; import me.illusion.skyblockcore.common.config.SkyblockMessagesFile; -import me.illusion.skyblockcore.common.config.impl.SkyblockCacheDatabasesFile; -import me.illusion.skyblockcore.common.config.impl.SkyblockDatabasesFile; -import me.illusion.skyblockcore.common.database.SkyblockDatabaseRegistry; +import me.illusion.skyblockcore.common.database.registry.SkyblockDatabaseRegistry; import me.illusion.skyblockcore.common.event.impl.SkyblockPlatformEnabledEvent; import me.illusion.skyblockcore.common.event.manager.SkyblockEventManager; import me.illusion.skyblockcore.common.event.manager.SkyblockEventManagerImpl; +import me.illusion.skyblockcore.common.platform.SkyblockPlatform; +import me.illusion.skyblockcore.common.utilities.file.IOUtils; import me.illusion.skyblockcore.server.SkyblockServerPlatform; import me.illusion.skyblockcore.server.island.SkyblockIslandManager; import me.illusion.skyblockcore.server.network.SkyblockNetworkRegistry; @@ -28,6 +29,7 @@ import me.illusion.skyblockcore.spigot.island.IslandManagerImpl; import me.illusion.skyblockcore.spigot.player.SkyblockBukkitPlayerManager; import org.bukkit.Bukkit; +import org.bukkit.plugin.ServicePriority; import org.bukkit.plugin.java.JavaPlugin; /** @@ -42,8 +44,6 @@ public class SkyblockSpigotPlugin extends JavaPlugin implements SkyblockServerPl // Server-platform specific things - private SkyblockDatabasesFile databasesFile; - private SkyblockCacheDatabasesFile cacheDatabasesFile; private SkyblockMessagesFile messagesFile; private ConfigurationProvider configurationProvider; @@ -55,6 +55,11 @@ public class SkyblockSpigotPlugin extends JavaPlugin implements SkyblockServerPl private SkyblockPlayerManager playerManager; private SkyblockCommandManager commandManager; + @Override + public void onLoad() { + Bukkit.getServicesManager().register(SkyblockPlatform.class, this, this, ServicePriority.Normal); + } + @Override public void onEnable() { System.out.println("Loading configuration provider"); @@ -64,8 +69,6 @@ public void onEnable() { networkRegistry = new SkyblockNetworkRegistryImpl(this); System.out.println("Loading configuration files"); - databasesFile = new SkyblockDatabasesFile(this); - cacheDatabasesFile = new SkyblockCacheDatabasesFile(this); messagesFile = new SkyblockMessagesFile(this, "server-messages"); System.out.println("Loading database & grid"); @@ -80,7 +83,7 @@ public void onEnable() { registerNetworks(); System.out.println("Finishing loading"); - Bukkit.getScheduler().runTask(this, this::finishLoading); + Bukkit.getScheduler().runTask(this, this::load); } @Override @@ -96,12 +99,10 @@ public void onDisable() { islandManager.flush().join(); } - - databaseRegistry.getChosenDatabase().flush().join(); - databaseRegistry.getChosenCacheDatabase().flush().join(); + databaseRegistry.shutdown().join(); } - private void finishLoading() { + private void load() { System.out.println("Loading networks"); networkRegistry.load(); @@ -109,28 +110,39 @@ private void finishLoading() { initCosmos(); System.out.println("Enabling databases"); - databaseRegistry.tryEnableMultiple(databasesFile, cacheDatabasesFile).thenAccept(success -> { - if (Boolean.FALSE.equals(success)) { // The future returns a boxed boolean - getLogger().severe("Failed to enable databases, disabling plugin..."); - Bukkit.getPluginManager().disablePlugin(this); + loadDatabases(); + databaseRegistry.finishLoading().thenAccept(this::finishLoading); + } + + private void loadDatabases() { + File databasesFolder = new File(getDataFolder(), "databases"); + + IOUtils.copyFolder(this, getFile(), databasesFolder.getName()); + + IOUtils.traverseAndLoad(databasesFolder, file -> { + if (!file.getName().endsWith(".yml")) { return; } - System.out.println("Enabling island manager"); - playerManager = new SkyblockBukkitPlayerManager(this); - islandManager = new IslandManagerImpl(this); - - networkRegistry.enable(); + databaseRegistry.loadPossible(configurationProvider.loadConfiguration(file)); + }); + } - commandManager.syncCommands(); - eventManager.callEvent(new SkyblockPlatformEnabledEvent(this)); - }).exceptionally(throwable -> { + private void finishLoading(boolean databasesLoaded) { + if (!databasesLoaded) { getLogger().severe("Failed to enable databases, disabling plugin..."); - throwable.printStackTrace(); Bukkit.getPluginManager().disablePlugin(this); - return null; - }); + return; + } + + System.out.println("Enabling island manager"); + playerManager = new SkyblockBukkitPlayerManager(this); + islandManager = new IslandManagerImpl(this); + + networkRegistry.enable(); + commandManager.syncCommands(); + eventManager.callEvent(new SkyblockPlatformEnabledEvent(this)); } /** diff --git a/SkyblockCore-Spigot/src/main/java/me/illusion/skyblockcore/spigot/config/BukkitConfigurationProvider.java b/SkyblockCore-Spigot/src/main/java/me/illusion/skyblockcore/spigot/config/BukkitConfigurationProvider.java index d54a391..d7239f6 100644 --- a/SkyblockCore-Spigot/src/main/java/me/illusion/skyblockcore/spigot/config/BukkitConfigurationProvider.java +++ b/SkyblockCore-Spigot/src/main/java/me/illusion/skyblockcore/spigot/config/BukkitConfigurationProvider.java @@ -3,9 +3,11 @@ import java.io.File; import me.illusion.cosmos.utilities.storage.YMLBase; import me.illusion.skyblockcore.common.config.ConfigurationProvider; -import me.illusion.skyblockcore.common.config.ReadOnlyConfigurationSection; +import me.illusion.skyblockcore.common.config.section.ConfigurationSection; import me.illusion.skyblockcore.spigot.SkyblockSpigotPlugin; import me.illusion.skyblockcore.spigot.utilities.config.BukkitConfigurationAdapter; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; /** * Bukkit implementation of {@link ConfigurationProvider}. @@ -24,8 +26,20 @@ public File getDataFolder() { } @Override - public ReadOnlyConfigurationSection loadConfiguration(File file) { + public ConfigurationSection loadConfiguration(File file) { YMLBase base = new YMLBase(plugin, file, true); - return BukkitConfigurationAdapter.adapt(base.getConfiguration()); + return BukkitConfigurationAdapter.adapt(file, this, base.getConfiguration()); + } + + @Override + public void saveConfiguration(ConfigurationSection section, File file) { + FileConfiguration configuration = new YamlConfiguration(); + BukkitConfigurationAdapter.writeTo(section, configuration); + + try { + configuration.save(file); + } catch (Exception e) { + e.printStackTrace(); + } } } diff --git a/SkyblockCore-Spigot/src/main/java/me/illusion/skyblockcore/spigot/utilities/config/BukkitConfigurationAdapter.java b/SkyblockCore-Spigot/src/main/java/me/illusion/skyblockcore/spigot/utilities/config/BukkitConfigurationAdapter.java index bd75c69..042ebdb 100644 --- a/SkyblockCore-Spigot/src/main/java/me/illusion/skyblockcore/spigot/utilities/config/BukkitConfigurationAdapter.java +++ b/SkyblockCore-Spigot/src/main/java/me/illusion/skyblockcore/spigot/utilities/config/BukkitConfigurationAdapter.java @@ -1,13 +1,15 @@ package me.illusion.skyblockcore.spigot.utilities.config; +import java.io.File; import java.util.Map; -import me.illusion.skyblockcore.common.config.ReadOnlyConfigurationSection; +import me.illusion.skyblockcore.common.config.ConfigurationProvider; +import me.illusion.skyblockcore.common.config.section.WritableConfigurationSection; import org.bukkit.configuration.ConfigurationSection; /** * A utility class for adapting Bukkit configuration classes to common configuration classes * - * @see ReadOnlyConfigurationSection + * @see ConfigurationSection */ public final class BukkitConfigurationAdapter { @@ -16,28 +18,46 @@ private BukkitConfigurationAdapter() { } /** - * Adapt a {@link ConfigurationSection} to a {@link ReadOnlyConfigurationSection}, flattening the section + * Adapt a {@link ConfigurationSection} to a {@link ConfigurationSection}, flattening the section * * @param section The section to adapt * @return The adapted section */ - public static ReadOnlyConfigurationSection adapt(ConfigurationSection section) { + public static me.illusion.skyblockcore.common.config.section.ConfigurationSection adapt(File file, ConfigurationProvider provider, + ConfigurationSection section) { if (section == null) { return null; } - Map map = section.getValues(true); + Map map = section.getValues(false); for (Map.Entry entry : map.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); if (value instanceof ConfigurationSection) { - map.put(key, adapt((ConfigurationSection) value)); + map.put(key, adapt(file, provider, (ConfigurationSection) value)); } } - return new ReadOnlyConfigurationSection(section.getName(), map); + return new WritableConfigurationSection(map, file, provider); + } + + public static void writeTo(me.illusion.skyblockcore.common.config.section.ConfigurationSection skyblockSection, ConfigurationSection bukkitSection) { + for (String key : skyblockSection.getKeys()) { + Object value = skyblockSection.get(key); + + if (value instanceof me.illusion.skyblockcore.common.config.section.ConfigurationSection) { + ConfigurationSection section = bukkitSection.createSection(key); + writeTo((me.illusion.skyblockcore.common.config.section.ConfigurationSection) value, section); + } else { + bukkitSection.set(key, value); + } + } } } + +/* +{mongo=ReadOnlyConfigurationSection{internalMap={host=localhost, port=27017, database=island, username=root, password=12345}, name='mongo'}, island-cache=ReadOnlyConfigurationSection{internalMap={type=memory}, name='island-cache'}, sqlite=ReadOnlyConfigurationSection{internalMap={name=island-storage/database}, name='sqlite'}, island=ReadOnlyConfigurationSection{internalMap={type=sqlite}, name='island'}, profile=ReadOnlyConfigurationSection{internalMap={type=sqlite}, name='profile'}, remote-sql=ReadOnlyConfigurationSection{internalMap={host=localhost, port=3306, database=island, username=root, password=12345}, name='remote-sql'}, redis=ReadOnlyConfigurationSection{internalMap={host=localhost, port=6379, password=12345}, name='redis'}} + */ \ No newline at end of file diff --git a/SkyblockCore-Spigot/src/main/resources/cache-database.yml b/SkyblockCore-Spigot/src/main/resources/cache-database.yml deleted file mode 100644 index 1ec17e0..0000000 --- a/SkyblockCore-Spigot/src/main/resources/cache-database.yml +++ /dev/null @@ -1,12 +0,0 @@ -# This is the preferred database type. There are a few reasons why it may default to a fallback: -# - The database type is not supported by the server (MySQL as a cache database) -# - The database type is not registered -# - The database configuration is invalid (invalid credentials) -# If all fallbacks fail, the plugin will disable. This is a feature. -preferred: redis - -redis: - host: localhost - port: 6379 - password: "" - ssl: false \ No newline at end of file diff --git a/SkyblockCore-Spigot/src/main/resources/database.yml b/SkyblockCore-Spigot/src/main/resources/database.yml deleted file mode 100644 index b39ed94..0000000 --- a/SkyblockCore-Spigot/src/main/resources/database.yml +++ /dev/null @@ -1,26 +0,0 @@ -# This is the preferred database type. There are a few reasons why it may default to a fallback: -# - The database type is not supported by the server (SQLite on a complex network) -# - The database type is not registered (invalid type) -# - The database configuration is invalid (invalid credentials) -# If all fallbacks fail, the plugin will disable. This is a feature. -preferred: mongodb - -mongodb: - fallback: mysql # If this fails to enable, default to mysql - - ip: localhost - port: 27017 - auth-source: admin - username: root - password: password - ssl: false - - database: skyblock - collection: skyblock_data # There is a second island-ids collection - -mysql: - host: localhost - port: 3306 - username: root - password: password - database: skyblock \ No newline at end of file diff --git a/SkyblockCore-Spigot/src/main/resources/databases/cache.yml b/SkyblockCore-Spigot/src/main/resources/databases/cache.yml new file mode 100644 index 0000000..efc7bb6 --- /dev/null +++ b/SkyblockCore-Spigot/src/main/resources/databases/cache.yml @@ -0,0 +1,13 @@ +# This is a separate file for the cache database credentials +# The whole reason why this file exists is so that we can be organized and split priorities +# You can add as many files as you want on this folder, and as many databases as you want on each file + +redis: + type: redis + host: localhost + port: 6379 + password: 12345 + +island-cache: + type: memory # Redis/memory available by default + diff --git a/SkyblockCore-Spigot/src/main/resources/databases/persistence.yml b/SkyblockCore-Spigot/src/main/resources/databases/persistence.yml new file mode 100644 index 0000000..995f59a --- /dev/null +++ b/SkyblockCore-Spigot/src/main/resources/databases/persistence.yml @@ -0,0 +1,19 @@ +# This is a separate file for the cache database credentials +# The whole reason why this file exists is so that we can be organized and split priorities +# You can add as many files as you want on this folder, and as many databases as you want on each file + +# Sample credentials format: + +global: + type: sqlite # postgres/mysql/mariadb/sqlite/mongo available by default + name: island-storage/database # .db extension is added automatically + +# mysql: +# host: localhost +# port: 3306 +# database: island +# username: root +# password: 12345 + +island: global +profile: global diff --git a/build.gradle b/build.gradle index 66ee568..ae367b2 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ plugins { id 'java' + id "com.github.johnrengelman.shadow" version "8.1.1" } group = 'me.illusion' @@ -11,6 +12,15 @@ repositories { } dependencies { + + // shadowjar all modules + implementation(project(":SkyblockCore-Common")) + implementation(project(":SkyblockCore-Proxy")) + implementation(project(":SkyblockCore-Server")) + implementation(project(":SkyblockCore-Spigot")) { + exclude group: 'me.illusion', module: 'cosmos-plugin' + } + implementation(project(":SkyblockCore-BungeeCord")) } def targetJavaVersion = 17 @@ -36,4 +46,6 @@ processResources { filesMatching('plugin.yml') { expand props } -} \ No newline at end of file +} + +shadowJar.dependsOn(processResources) \ No newline at end of file