1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2026-01-06 15:42:03 +00:00

Added a config system for database implementations

This commit is contained in:
Tim203
2021-01-17 15:37:50 +01:00
parent d88eb9aa63
commit f17d8c590a
7 changed files with 219 additions and 18 deletions

View File

@@ -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 {
}

View File

@@ -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 <T> 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 extends DatabaseConfig> T loadAs(Class<T> 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;
}
}
}

View File

@@ -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 extends DatabaseConfig> T getConfig(Class<T> configClass) {
return configLoader.loadAs(configClass);
}
@Override
public void stop() {
executorService.shutdown();

View File

@@ -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<? extends PlayerLink> 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<? extends PlayerLink>) classLoader.loadClass(mainClassName);
Class<? extends PlayerLink> mainClass =
(Class<? extends PlayerLink>) 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;
}
}

View File

@@ -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;
}

View File

@@ -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.<br> 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();
}
}