diff --git a/common/src/main/java/org/geysermc/floodgate/database/config/DatabaseConfig.java b/common/src/main/java/org/geysermc/floodgate/database/config/DatabaseConfig.java new file mode 100644 index 00000000..f789b398 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/database/config/DatabaseConfig.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.database.config; + +/** + * Base class for every database related configuration. + */ +public interface DatabaseConfig { +} diff --git a/common/src/main/java/org/geysermc/floodgate/database/config/DatabaseConfigLoader.java b/common/src/main/java/org/geysermc/floodgate/database/config/DatabaseConfigLoader.java new file mode 100644 index 00000000..76af4f71 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/database/config/DatabaseConfigLoader.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.database.config; + +import com.google.gson.JsonObject; +import com.google.inject.Inject; +import com.google.inject.name.Named; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.CustomClassLoaderConstructor; +import org.yaml.snakeyaml.introspector.BeanAccess; + +public class DatabaseConfigLoader { + private Yaml yaml; + + @Inject + @Named("dataDirectory") + private Path dataDirectory; + + @Inject + @Named("databaseName") + private String name; + + @Inject + @Named("databaseClassLoader") + private ClassLoader classLoader; + + @Inject + @Named("databaseInitData") + private JsonObject initData; + + @Inject + public void init() { + yaml = new Yaml(new CustomClassLoaderConstructor(classLoader)); + yaml.setBeanAccess(BeanAccess.FIELD); + } + + /** + * This will load the config if it already exists or will create the config from the default + * config file if it doesn't exist. + * + * @param configType the class to parse the config into + * @param type that extends the base DatabaseConfig class + * @return the config if successful or null if not. It'll return null if the database didn't + * provide a config or if there is no config present nor default config available or if an error + * occurred while executing this method. + */ + public T loadAs(Class configType) { + if (!initData.has("config")) { + return null; + } + + String configFile = initData.get("config").getAsString(); + Path configPath = dataDirectory.resolve(name).resolve(configFile); + + // return the existing config + if (Files.exists(configPath)) { + try (BufferedReader reader = Files.newBufferedReader(configPath)) { + return yaml.loadAs(reader, configType); + } catch (IOException exception) { + exception.printStackTrace(); + return null; + } + } + + // make directories + try { + Files.createDirectories(configPath.getParent()); + } catch (IOException exception) { + exception.printStackTrace(); + return null; + } + + // load default config resource + InputStream configStream = classLoader.getResourceAsStream(configFile); + if (configStream == null) { + return null; + } + + // copy resource and load config + try { + if (!configStream.markSupported()) { + Files.copy(configStream, configPath); + configStream.close(); + configStream = classLoader.getResourceAsStream(configFile); + return yaml.loadAs(configStream, configType); + } + + configStream.mark(Integer.MAX_VALUE); + Files.copy(configStream, configPath); + configStream.reset(); + return yaml.loadAs(configStream, configType); + } catch (IOException exception) { + exception.printStackTrace(); + return null; + } + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java b/common/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java index f2e54e36..c5e1322f 100644 --- a/common/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java +++ b/common/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java @@ -37,6 +37,8 @@ import org.geysermc.floodgate.api.link.LinkRequest; import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.config.FloodgateConfig; +import org.geysermc.floodgate.database.config.DatabaseConfig; +import org.geysermc.floodgate.database.config.DatabaseConfigLoader; public abstract class CommonPlayerLink implements PlayerLink { @Getter(AccessLevel.PROTECTED) @@ -54,6 +56,9 @@ public abstract class CommonPlayerLink implements PlayerLink { @Getter(AccessLevel.PROTECTED) private FloodgateApi api; + @Inject + private DatabaseConfigLoader configLoader; + @Inject private void init(FloodgateConfig config) { FloodgateConfig.PlayerLinkConfig linkConfig = config.getPlayerLink(); @@ -70,6 +75,10 @@ public abstract class CommonPlayerLink implements PlayerLink { return request.isRequestedPlayer(api.getPlayer(bedrockId)); } + public T getConfig(Class configClass) { + return configLoader.loadAs(configClass); + } + @Override public void stop() { executorService.shutdown(); diff --git a/common/src/main/java/org/geysermc/floodgate/link/PlayerLinkLoader.java b/common/src/main/java/org/geysermc/floodgate/link/PlayerLinkLoader.java index e98d0d94..c18be2d6 100644 --- a/common/src/main/java/org/geysermc/floodgate/link/PlayerLinkLoader.java +++ b/common/src/main/java/org/geysermc/floodgate/link/PlayerLinkLoader.java @@ -32,6 +32,7 @@ import com.google.gson.JsonObject; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Singleton; +import com.google.inject.name.Names; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -40,14 +41,16 @@ import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -import java.util.Locale; import java.util.stream.Collectors; import javax.inject.Named; import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.config.FloodgateConfig; +import org.geysermc.floodgate.util.Constants; +import org.geysermc.floodgate.util.Utils; @Singleton +@SuppressWarnings("unchecked") public final class PlayerLinkLoader { @Inject private Injector injector; @Inject private FloodgateConfig config; @@ -88,14 +91,16 @@ public final class PlayerLinkLoader { } Path implementationPath = files.get(0); + final String databaseName; // We only want to load one database implementation if (files.size() > 1) { boolean found = false; + databaseName = linkingConfig.getType(); - String type = linkingConfig.getType().toLowerCase(Locale.ROOT); + String expectedName = "floodgate-" + databaseName + "-database.jar"; for (Path path : files) { - if (path.getFileName().toString().toLowerCase(Locale.ROOT).contains(type)) { + if (expectedName.equalsIgnoreCase(path.getFileName().toString())) { implementationPath = path; found = true; } @@ -106,9 +111,19 @@ public final class PlayerLinkLoader { linkingConfig.getType()); return null; } + } else { + String name = implementationPath.getFileName().toString(); + if (!Utils.isValidDatabaseName(name)) { + logger.error("Found database {} but the name doesn't match {}", + name, Constants.DATABASE_NAME_FORMAT); + return null; + } + int firstSplit = name.indexOf('-') + 1; + databaseName = name.substring(firstSplit, name.indexOf('-', firstSplit)); } - Class mainClass; + boolean init = true; + try { URL pluginUrl = implementationPath.toUri().toURL(); @@ -116,37 +131,51 @@ public final class PlayerLinkLoader { new URL[]{pluginUrl}, PlayerLinkLoader.class.getClassLoader())) { String mainClassName; + JsonObject linkConfig; try (InputStream linkConfigStream = - classLoader.getResourceAsStream("config.json")) { + classLoader.getResourceAsStream("init.json")) { - requireNonNull(linkConfigStream, "Implementation should have a config"); + requireNonNull(linkConfigStream, "Implementation should have an init file"); - JsonObject linkConfig = new Gson().fromJson( + linkConfig = new Gson().fromJson( new InputStreamReader(linkConfigStream), JsonObject.class ); mainClassName = linkConfig.get("mainClass").getAsString(); } - mainClass = (Class) classLoader.loadClass(mainClassName); + Class mainClass = + (Class) classLoader.loadClass(mainClassName); + + init = false; + + Injector linkInjector = injector.createChildInjector(binder -> { + binder.bind(String.class) + .annotatedWith(Names.named("databaseName")) + .toInstance(databaseName); + binder.bind(ClassLoader.class).annotatedWith( + Names.named("databaseClassLoader")).toInstance(classLoader); + binder.bind(JsonObject.class) + .annotatedWith(Names.named("databaseInitData")) + .toInstance(linkConfig); + }); + + PlayerLink instance = linkInjector.getInstance(mainClass); + instance.load(); + return instance; } } catch (ClassCastException exception) { logger.error("The database implementation ({}) doesn't extend the PlayerLink class!", implementationPath.getFileName().toString(), exception); return null; } catch (Exception exception) { - logger.error("Error while loading database jar", exception); + if (init) { + logger.error("Error while initialising database jar", exception); + } else { + logger.error("Error while loading database jar", exception); + } return null; } - - try { - PlayerLink instance = injector.getInstance(mainClass); - instance.load(); - return instance; - } catch (Exception exception) { - logger.error("Error while initializing database jar", exception); - } - return null; } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/Constants.java b/common/src/main/java/org/geysermc/floodgate/util/Constants.java index 8b9e5a1d..cfe46429 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/Constants.java +++ b/common/src/main/java/org/geysermc/floodgate/util/Constants.java @@ -26,5 +26,6 @@ package org.geysermc.floodgate.util; public final class Constants { + public static final String DATABASE_NAME_FORMAT = "^floodgate-[a-zA-Z0-9_]{0,16}-database.jar$"; public static final int LOGIN_SUCCESS_PACKET_ID = 2; } diff --git a/common/src/main/java/org/geysermc/floodgate/util/Utils.java b/common/src/main/java/org/geysermc/floodgate/util/Utils.java index 91bf433f..bb4b2f5e 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/Utils.java +++ b/common/src/main/java/org/geysermc/floodgate/util/Utils.java @@ -43,6 +43,7 @@ import java.util.regex.Pattern; public class Utils { private static final Pattern NON_UNIQUE_PREFIX = Pattern.compile("^[a-zA-Z0-9_]{0,16}$"); + private static final Pattern DATABASE_NAME = Pattern.compile(Constants.DATABASE_NAME_FORMAT); /** * This method is used in Addons.
Most addons can be removed once the player associated to @@ -96,4 +97,8 @@ public class Utils { public static boolean isUniquePrefix(String prefix) { return !NON_UNIQUE_PREFIX.matcher(prefix).matches(); } + + public static boolean isValidDatabaseName(String databaseName) { + return DATABASE_NAME.matcher(databaseName).matches(); + } } diff --git a/database/sqlite/src/main/resources/config.json b/database/sqlite/src/main/resources/init.json similarity index 100% rename from database/sqlite/src/main/resources/config.json rename to database/sqlite/src/main/resources/init.json