Support multiple plugins loaded from the same JAR

This commit is contained in:
Andrew Steinborn
2021-05-15 23:42:54 -04:00
parent 3579fa644b
commit a155f91dfd
4 changed files with 62 additions and 51 deletions

View File

@@ -49,12 +49,10 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -100,7 +98,7 @@ public class VelocityPluginManager implements PluginManager {
p -> p.toFile().isFile() && p.toString().endsWith(".jar"))) {
for (Path path : stream) {
try {
found.add(loader.loadPluginDescription(path));
found.addAll(loader.loadPluginCandidates(path));
} catch (Exception e) {
logger.error("Unable to load plugin {}", path, e);
}
@@ -160,7 +158,7 @@ public class VelocityPluginManager implements PluginManager {
}
try {
PluginDescription realPlugin = loader.loadPlugin(candidate);
PluginDescription realPlugin = loader.materializePlugin(candidate);
VelocityPluginContainer container = new VelocityPluginContainer(realPlugin);
pluginContainers.put(container, loader.createModule(container));
loadedPluginsById.put(candidate.id(), container);

View File

@@ -21,15 +21,16 @@ import com.google.inject.Module;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import java.nio.file.Path;
import java.util.Collection;
/**
* This interface is used for loading plugins.
*/
public interface PluginLoader {
PluginDescription loadPluginDescription(Path source) throws Exception;
Collection<PluginDescription> loadPluginCandidates(Path source) throws Exception;
PluginDescription loadPlugin(PluginDescription source) throws Exception;
PluginDescription materializePlugin(PluginDescription source) throws Exception;
/**
* Creates a {@link Module} for the provided {@link PluginContainer}

View File

@@ -31,16 +31,23 @@ import com.velocitypowered.proxy.plugin.PluginClassLoader;
import com.velocitypowered.proxy.plugin.loader.PluginLoader;
import com.velocitypowered.proxy.plugin.loader.VelocityPluginContainer;
import com.velocitypowered.proxy.plugin.loader.VelocityPluginDescription;
import io.leangen.geantyref.TypeToken;
import java.io.BufferedInputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
@@ -49,27 +56,28 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public class JavaPluginLoader implements PluginLoader {
private final Path baseDirectory;
private final Map<URI, PluginClassLoader> classLoaders = new HashMap<>();
public JavaPluginLoader(ProxyServer server, Path baseDirectory) {
this.baseDirectory = baseDirectory;
}
@Override
public PluginDescription loadPluginDescription(Path source) throws Exception {
SerializedPluginDescription serialized = getSerializedPluginInfo(source);
if (serialized == null) {
throw new InvalidPluginException("Did not find a valid velocity-plugin.json.");
public Collection<PluginDescription> loadPluginCandidates(Path source) throws Exception {
List<SerializedPluginDescription> serialized = getSerializedPluginInfo(source);
if (serialized.isEmpty()) {
throw new InvalidPluginException("Did not find a valid velocity-plugin-info.json.");
}
if (!SerializedPluginDescription.ID_PATTERN.matcher(serialized.getId()).matches()) {
throw new InvalidPluginException("Plugin ID '" + serialized.getId() + "' is invalid.");
List<PluginDescription> candidates = new ArrayList<>();
for (SerializedPluginDescription description : serialized) {
candidates.add(createCandidateDescription(description, source));
}
return createCandidateDescription(serialized, source);
return candidates;
}
@Override
public PluginDescription loadPlugin(PluginDescription source) throws Exception {
public PluginDescription materializePlugin(PluginDescription source) throws Exception {
if (!(source instanceof JavaVelocityPluginDescriptionCandidate)) {
throw new IllegalArgumentException("Description provided isn't of the Java plugin loader");
}
@@ -79,11 +87,15 @@ public class JavaPluginLoader implements PluginLoader {
throw new IllegalStateException("JAR path not provided.");
}
URL pluginJarUrl = jarFilePath.toUri().toURL();
PluginClassLoader loader = AccessController.doPrivileged(
(PrivilegedAction<PluginClassLoader>) () -> new PluginClassLoader(new URL[]{pluginJarUrl},
source));
loader.addToClassloaders();
URI pluginJarUri = jarFilePath.toUri();
URL pluginJarUrl = pluginJarUri.toURL();
PluginClassLoader loader = this.classLoaders.computeIfAbsent(pluginJarUri, (uri) -> {
PluginClassLoader classLoader = AccessController.doPrivileged(
(PrivilegedAction<PluginClassLoader>) () -> new PluginClassLoader(new URL[]{pluginJarUrl},
source));
classLoader.addToClassloaders();
return classLoader;
});
JavaVelocityPluginDescriptionCandidate candidate =
(JavaVelocityPluginDescriptionCandidate) source;
@@ -130,32 +142,43 @@ public class JavaPluginLoader implements PluginLoader {
((VelocityPluginContainer) container).setInstance(instance);
}
private @Nullable SerializedPluginDescription getSerializedPluginInfo(Path source)
private List<SerializedPluginDescription> getSerializedPluginInfo(Path source)
throws Exception {
boolean foundOldVelocityPlugin = false;
boolean foundBungeeBukkitPluginFile = false;
try (JarInputStream in = new JarInputStream(
new BufferedInputStream(Files.newInputStream(source)))) {
JarEntry entry;
while ((entry = in.getNextJarEntry()) != null) {
if (entry.getName().equals("velocity-plugin.json")) {
if (entry.getName().equals("velocity-plugin-info.json")) {
try (Reader pluginInfoReader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
return VelocityServer.GENERAL_GSON.fromJson(pluginInfoReader,
SerializedPluginDescription.class);
new TypeToken<List<SerializedPluginDescription>>() {}.getType());
}
}
if (entry.getName().equals("velocity-plugin.json")) {
foundOldVelocityPlugin = true;
}
if (entry.getName().equals("plugin.yml") || entry.getName().equals("bungee.yml")) {
foundBungeeBukkitPluginFile = true;
}
}
if (foundOldVelocityPlugin) {
throw new InvalidPluginException("The plugin file " + source.getFileName() + " appears to "
+ "be developed for an older version of Velocity. Please obtain a newer version of the "
+ "plugin.");
}
if (foundBungeeBukkitPluginFile) {
throw new InvalidPluginException("The plugin file " + source.getFileName() + " appears to "
+ "be a Bukkit or BungeeCord plugin. Velocity does not support Bukkit or BungeeCord "
+ "plugins.");
}
return null;
return List.of();
}
}