Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions proxy/src/main/java/com/velocitypowered/proxy/ProxyOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@
import com.velocitypowered.proxy.util.AddressUtil;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import joptsimple.ValueConversionException;
import joptsimple.ValueConverter;
import joptsimple.util.PathConverter;
import joptsimple.util.PathProperties;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
Expand All @@ -43,6 +46,8 @@ public final class ProxyOptions {
private final @Nullable Boolean haproxy;
private final boolean ignoreConfigServers;
private final List<ServerInfo> servers;
private final List<Path> extraPluginJars;
private final List<Path> extraPluginDirectories;

ProxyOptions(final String[] args) {
final OptionParser parser = new OptionParser();
Expand All @@ -64,13 +69,27 @@ public final class ProxyOptions {
final OptionSpec<Void> ignoreConfigServers = parser.accepts("ignore-config-servers",
"Skip registering servers from the config file. "
+ "Useful in dynamic setups or with the --add-server flag.");
final OptionSpec<Path> pluginFiles = parser.acceptsAll(
Arrays.asList("add-plugin", "add-extra-plugin"),
"Load an additional plugin from the specified jar file.")
.withRequiredArg()
.withValuesConvertedBy(new PathConverter(PathProperties.FILE_EXISTING,
PathProperties.READABLE));
final OptionSpec<Path> pluginDirectories = parser.acceptsAll(
Arrays.asList("add-plugin-dir", "add-extra-plugin-dir"),
"Load plugins from an additional directory.")
.withRequiredArg()
.withValuesConvertedBy(new PathConverter(PathProperties.DIRECTORY_EXISTING,
PathProperties.READABLE));
final OptionSet set = parser.parse(args);

this.help = set.has(help);
this.port = port.value(set);
this.haproxy = haproxy.value(set);
this.servers = servers.values(set);
this.ignoreConfigServers = set.has(ignoreConfigServers);
this.extraPluginJars = pluginFiles.values(set);
this.extraPluginDirectories = pluginDirectories.values(set);

if (this.help) {
try {
Expand Down Expand Up @@ -101,6 +120,14 @@ public List<ServerInfo> getServers() {
return this.servers;
}

public List<Path> getExtraPluginJars() {
return this.extraPluginJars;
}

public List<Path> getExtraPluginDirectories() {
return this.extraPluginDirectories;
}

private static class ServerInfoConverter implements ValueConverter<ServerInfo> {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,15 +426,14 @@ private void loadPlugins() {

if (!pluginPath.toFile().exists()) {
Files.createDirectory(pluginPath);
} else {
if (!pluginPath.toFile().isDirectory()) {
logger.warn("Plugin location {} is not a directory, continuing without loading plugins",
pluginPath);
return;
}

pluginManager.loadPlugins(pluginPath);
} else if (!pluginPath.toFile().isDirectory()) {
logger.warn("Plugin location {} is not a directory, continuing without loading plugins",
pluginPath);
return;
}

pluginManager.loadPlugins(pluginPath, options.getExtraPluginDirectories(),
options.getExtraPluginJars());
} catch (Exception e) {
logger.error("Couldn't load plugins", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,40 +80,43 @@ public void registerPlugin(PluginContainer plugin) {
}

/**
* Loads all plugins from the specified {@code directory}.
* Loads all plugins found by scanning the given {@code directory} and
* {@code extraDirectories}, along with the individual plugin jars given in {@code extraJars}.
*
* @param directory the directory to load from
* @throws IOException if we could not open the directory
* @param directory the main directory to scan for plugin jars
* @param extraDirectories additional directories to scan for plugin jars
* @param extraJars individual plugin jars to load
* @throws IOException if we could not open one of the directories
*/
@SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE",
justification = "I looked carefully and there's no way SpotBugs is right.")
public void loadPlugins(Path directory) throws IOException {
checkNotNull(directory, "directory");
checkArgument(directory.toFile().isDirectory(), "provided path isn't a directory");

public void loadPlugins(Path directory, List<Path> extraDirectories, List<Path> extraJars)
throws IOException {
Map<String, PluginDescription> foundCandidates = new LinkedHashMap<>();
JavaPluginLoader loader = new JavaPluginLoader(server, directory);

try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory,
p -> p.toFile().isFile() && p.toString().endsWith(".jar"))) {
for (Path path : stream) {
try {
PluginDescription candidate = loader.loadCandidate(path);

// If we found a duplicate candidate (with the same ID), don't load it.
PluginDescription maybeExistingCandidate = foundCandidates.putIfAbsent(
candidate.getId(), candidate);

if (maybeExistingCandidate != null) {
logger.error("Refusing to load plugin at path {} since we already "
+ "loaded a plugin with the same ID {} from {}",
candidate.getSource().map(Objects::toString).orElse("<UNKNOWN>"),
candidate.getId(),
maybeExistingCandidate.getSource().map(Objects::toString).orElse("<UNKNOWN>"));
}
} catch (Throwable e) {
logger.error("Unable to load plugin {}", path, e);
List<Path> candidates = new ArrayList<>();
collectPluginJars(directory, candidates);
for (Path extraDirectory : extraDirectories) {
collectPluginJars(extraDirectory, candidates);
}
candidates.addAll(extraJars);

for (Path path : candidates) {
try {
PluginDescription candidate = loader.loadCandidate(path);

// If we found a duplicate candidate (with the same ID), don't load it.
PluginDescription maybeExistingCandidate = foundCandidates.putIfAbsent(
candidate.getId(), candidate);

if (maybeExistingCandidate != null) {
logger.error("Refusing to load plugin at path {} since we already "
+ "loaded a plugin with the same ID {} from {}",
candidate.getSource().map(Objects::toString).orElse("<UNKNOWN>"),
candidate.getId(),
maybeExistingCandidate.getSource().map(Objects::toString).orElse("<UNKNOWN>"));
}
} catch (Throwable e) {
logger.error("Unable to load plugin {}", path, e);
}
}

Expand Down Expand Up @@ -182,6 +185,17 @@ protected void configure() {
}
}

@SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE",
justification = "I looked carefully and there's no way SpotBugs is right.")
private static void collectPluginJars(Path directory, List<Path> candidates) throws IOException {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory,
p -> p.toFile().isFile() && p.toString().endsWith(".jar"))) {
for (Path path : stream) {
candidates.add(path);
}
}
}

@Override
public Optional<PluginContainer> fromInstance(Object instance) {
checkNotNull(instance, "instance");
Expand Down