diff --git a/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/ApiAnnotationProcessor.java b/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/ApiAnnotationProcessor.java index f1d6be68f..52975056d 100644 --- a/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/ApiAnnotationProcessor.java +++ b/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/ApiAnnotationProcessor.java @@ -28,6 +28,7 @@ import com.velocitypowered.api.plugin.Plugin; import java.io.BufferedWriter; import java.io.IOException; import java.io.Writer; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Set; @@ -56,9 +57,6 @@ import javax.tools.StandardLocation; @SupportedAnnotationTypes({PLUGIN_ANNOTATION_CLASS, SUBSCRIBE_ANNOTATION_CLASS}) public class ApiAnnotationProcessor extends AbstractProcessor { - private @Nullable String pluginClassFound; - private boolean warnedAboutMultiplePlugins; - @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); @@ -107,6 +105,7 @@ public class ApiAnnotationProcessor extends AbstractProcessor { } if (ProcessorUtils.contains(annotations, Plugin.class)) { + List found = new ArrayList<>(); for (Element element : roundEnv.getElementsAnnotatedWith(Plugin.class)) { if (element.getKind() != ElementKind.CLASS) { processingEnv.getMessager() @@ -117,17 +116,6 @@ public class ApiAnnotationProcessor extends AbstractProcessor { Name qualifiedName = ((TypeElement) element).getQualifiedName(); - if (Objects.equals(pluginClassFound, qualifiedName.toString())) { - if (!warnedAboutMultiplePlugins) { - processingEnv.getMessager() - .printMessage(Diagnostic.Kind.WARNING, - "Velocity does not yet currently support multiple plugins. " - + "We are using " + pluginClassFound + " for your plugin's main class."); - warnedAboutMultiplePlugins = true; - } - return false; - } - Plugin plugin = element.getAnnotation(Plugin.class); if (!SerializedPluginDescription.ID_PATTERN.matcher(plugin.id()).matches()) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, @@ -137,22 +125,23 @@ public class ApiAnnotationProcessor extends AbstractProcessor { return false; } - // All good, generate the velocity-plugin.json. + // All good, generate the velocity-plugin-info.json. SerializedPluginDescription description = SerializedPluginDescription .from(plugin, qualifiedName.toString()); - try { - FileObject object = processingEnv.getFiler() - .createResource(StandardLocation.CLASS_OUTPUT, "", "velocity-plugin.json"); - try (Writer writer = new BufferedWriter(object.openWriter())) { - new Gson().toJson(description, writer); - } - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, - "Wrote velocity-plugin.json to " + object.toUri().toString()); - pluginClassFound = qualifiedName.toString(); - } catch (IOException e) { - processingEnv.getMessager() - .printMessage(Diagnostic.Kind.ERROR, "Unable to generate plugin file"); + found.add(description); + } + + try { + FileObject object = processingEnv.getFiler() + .createResource(StandardLocation.CLASS_OUTPUT, "", "velocity-plugin-info.json"); + try (Writer writer = new BufferedWriter(object.openWriter())) { + new Gson().toJson(found, writer); } + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, + "Wrote velocity-plugin-info.json to " + object.toUri().toString()); + } catch (IOException e) { + processingEnv.getMessager() + .printMessage(Diagnostic.Kind.ERROR, "Unable to generate plugin file"); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java index f878bf94e..5a96e9d17 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java @@ -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); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/PluginLoader.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/PluginLoader.java index 5c1087221..d981bd355 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/PluginLoader.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/PluginLoader.java @@ -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 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} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaPluginLoader.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaPluginLoader.java index e5fff7e45..7e9fcd435 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaPluginLoader.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaPluginLoader.java @@ -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 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 loadPluginCandidates(Path source) throws Exception { + List 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 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) () -> 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) () -> 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 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>() {}.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(); } }