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:
@@ -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 {
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user