From 2a39ddb03e5a25dafaa3949859e2860ee3650656 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 15 May 2021 08:16:17 -0400 Subject: [PATCH] Add support for resolving dependencies that require a version range --- .../SerializedPluginDescription.java | 26 ++++--- .../api/network/NetworkEndpoint.java | 29 ++++++++ .../api/plugin/Dependency.java | 9 +++ .../api/plugin/PluginDescription.java | 4 +- .../api/plugin/meta/PluginDependency.java | 12 ++-- .../api/proxy/ProxyServer.java | 9 +++ .../api/proxy/config/ProxyConfig.java | 5 +- .../api/util/ProxyVersion.java | 6 +- proxy/build.gradle | 9 ++- .../com/velocitypowered/proxy/Metrics.java | 2 +- .../velocitypowered/proxy/VelocityServer.java | 8 ++- .../command/builtin/VelocityCommand.java | 14 ++-- .../client/StatusSessionHandler.java | 4 +- .../proxy/network/ConnectionManager.java | 13 +++- .../proxy/network/Endpoint.java | 12 +++- .../proxy/network/PluginMessageUtil.java | 2 +- .../proxy/plugin/PluginClassLoader.java | 11 ++- .../proxy/plugin/VelocityPluginManager.java | 51 ++++++++++++-- .../loader/VelocityPluginDescription.java | 8 +-- .../plugin/loader/java/JavaPluginLoader.java | 5 +- .../java/JavaVelocityPluginDescription.java | 2 +- ...avaVelocityPluginDescriptionCandidate.java | 2 +- .../plugin/util/ProxyPluginContainer.java | 67 +++++++++++++++++++ .../util/PluginDependencyUtilsTest.java | 6 +- 24 files changed, 258 insertions(+), 58 deletions(-) create mode 100644 api/src/main/java/com/velocitypowered/api/network/NetworkEndpoint.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/plugin/util/ProxyPluginContainer.java diff --git a/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/SerializedPluginDescription.java b/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/SerializedPluginDescription.java index a085f4127..e571b84bf 100644 --- a/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/SerializedPluginDescription.java +++ b/annotation-processor/src/main/java/com/velocitypowered/annotationprocessor/SerializedPluginDescription.java @@ -36,7 +36,7 @@ public final class SerializedPluginDescription { // @Nullable is used here to make GSON skip these in the serialized file private final String id; private final @Nullable String name; - private final @Nullable String version; + private final String version; private final @Nullable String description; private final @Nullable String url; private final @Nullable List authors; @@ -44,13 +44,12 @@ public final class SerializedPluginDescription { private final String main; private SerializedPluginDescription(String id, String name, String version, String description, - String url, - List authors, List dependencies, String main) { + String url, List authors, List dependencies, String main) { Preconditions.checkNotNull(id, "id"); Preconditions.checkArgument(ID_PATTERN.matcher(id).matches(), "id is not valid"); this.id = id; this.name = Strings.emptyToNull(name); - this.version = Strings.emptyToNull(version); + this.version = Preconditions.checkNotNull(version, "version"); this.description = Strings.emptyToNull(description); this.url = Strings.emptyToNull(url); this.authors = authors == null || authors.isEmpty() ? ImmutableList.of() : authors; @@ -62,7 +61,8 @@ public final class SerializedPluginDescription { static SerializedPluginDescription from(Plugin plugin, String qualifiedName) { List dependencies = new ArrayList<>(); for (com.velocitypowered.api.plugin.Dependency dependency : plugin.dependencies()) { - dependencies.add(new Dependency(dependency.id(), dependency.optional())); + dependencies.add(new Dependency(dependency.id(), dependency.version(), + dependency.optional())); } return new SerializedPluginDescription(plugin.id(), plugin.name(), plugin.version(), plugin.description(), plugin.url(), @@ -78,7 +78,7 @@ public final class SerializedPluginDescription { return name; } - public @Nullable String getVersion() { + public String getVersion() { return version; } @@ -143,10 +143,12 @@ public final class SerializedPluginDescription { public static final class Dependency { private final String id; + private final String version; private final boolean optional; - public Dependency(String id, boolean optional) { + public Dependency(String id, String version, boolean optional) { this.id = id; + this.version = version; this.optional = optional; } @@ -154,6 +156,10 @@ public final class SerializedPluginDescription { return id; } + public String getVersion() { + return version; + } + public boolean isOptional() { return optional; } @@ -167,19 +173,19 @@ public final class SerializedPluginDescription { return false; } Dependency that = (Dependency) o; - return optional == that.optional - && Objects.equals(id, that.id); + return optional == that.optional && id.equals(that.id) && version.equals(that.version); } @Override public int hashCode() { - return Objects.hash(id, optional); + return Objects.hash(id, version, optional); } @Override public String toString() { return "Dependency{" + "id='" + id + '\'' + + ", version='" + version + '\'' + ", optional=" + optional + '}'; } diff --git a/api/src/main/java/com/velocitypowered/api/network/NetworkEndpoint.java b/api/src/main/java/com/velocitypowered/api/network/NetworkEndpoint.java new file mode 100644 index 000000000..170a3a570 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/network/NetworkEndpoint.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.network; + +import java.net.SocketAddress; + +/** + * Represents a network listener for the proxy. + */ +public interface NetworkEndpoint { + /** + * The type. + * + * @return the type + */ + ListenerType type(); + + /** + * The address the listener is listening on. + * + * @return the address + */ + SocketAddress address(); +} diff --git a/api/src/main/java/com/velocitypowered/api/plugin/Dependency.java b/api/src/main/java/com/velocitypowered/api/plugin/Dependency.java index 03412ec16..f4f6960ea 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/Dependency.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/Dependency.java @@ -26,6 +26,15 @@ public @interface Dependency { */ String id(); + /** + * The required version of the dependency. This should be in an NPM-compatible versioning format, + * which you can figure from npm's SemVer checker. If + * not specified, this assumes any version is acceptable. + * + * @return the version requirement + */ + String version() default "*"; + /** * Whether or not the dependency is not required to enable this plugin. By default this is * {@code false}, meaning that the dependency is required to enable this plugin. diff --git a/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java b/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java index 183dd506f..07c74ff62 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/PluginDescription.java @@ -52,8 +52,8 @@ public interface PluginDescription { * @return a String with the plugin version, may be null * @see Plugin#version() */ - default @Nullable String version() { - return null; + default String version() { + return ""; } /** diff --git a/api/src/main/java/com/velocitypowered/api/plugin/meta/PluginDependency.java b/api/src/main/java/com/velocitypowered/api/plugin/meta/PluginDependency.java index fbb343a63..9b0f4ab0a 100644 --- a/api/src/main/java/com/velocitypowered/api/plugin/meta/PluginDependency.java +++ b/api/src/main/java/com/velocitypowered/api/plugin/meta/PluginDependency.java @@ -20,7 +20,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public final class PluginDependency { private final String id; - private final @Nullable String version; + private final String version; private final boolean optional; @@ -30,10 +30,10 @@ public final class PluginDependency { * @param version an optional version * @param optional whether or not this dependency is optional */ - public PluginDependency(String id, @Nullable String version, boolean optional) { + public PluginDependency(String id, String version, boolean optional) { this.id = checkNotNull(id, "id"); checkArgument(!id.isEmpty(), "id cannot be empty"); - this.version = emptyToNull(version); + this.version = checkNotNull(version, "version"); this.optional = optional; } @@ -47,11 +47,11 @@ public final class PluginDependency { } /** - * Returns the version this {@link PluginDependency} should match. + * Returns the version this {@link PluginDependency} should match in NPM SemVer range format. * - * @return a String with the plugin version, may be {@code null} + * @return a String with the plugin version, may be empty if no version requirement is present */ - public @Nullable String version() { + public String version() { return version; } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java index 2d0efbcbb..38490200e 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/ProxyServer.java @@ -11,6 +11,7 @@ import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.ConsoleCommandSource; import com.velocitypowered.api.event.EventManager; +import com.velocitypowered.api.network.NetworkEndpoint; import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.proxy.config.ProxyConfig; import com.velocitypowered.api.proxy.connection.Player; @@ -179,4 +180,12 @@ public interface ProxyServer extends Audience { * @return the proxy version */ ProxyVersion version(); + + /** + * Returns all the endpoints the proxy is listening on. This collection is immutable. + * + * @return all the endpoints the proxy is listening on + */ + Collection endpoints(); + } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/config/ProxyConfig.java b/api/src/main/java/com/velocitypowered/api/proxy/config/ProxyConfig.java index 9ae00593d..946d1f355 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/config/ProxyConfig.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/config/ProxyConfig.java @@ -7,6 +7,7 @@ package com.velocitypowered.api.proxy.config; +import com.google.common.annotations.Beta; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.util.Favicon; import java.util.List; @@ -14,8 +15,10 @@ import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; /** - * Exposes certain proxy configuration information that plugins may use. + * Exposes certain proxy configuration information that plugins may use. Note that this interface + * is in constant flux and should never be considered stable. */ +@Beta public interface ProxyConfig { /** diff --git a/api/src/main/java/com/velocitypowered/api/util/ProxyVersion.java b/api/src/main/java/com/velocitypowered/api/util/ProxyVersion.java index 22f0e571f..a84ba5153 100644 --- a/api/src/main/java/com/velocitypowered/api/util/ProxyVersion.java +++ b/api/src/main/java/com/velocitypowered/api/util/ProxyVersion.java @@ -33,15 +33,15 @@ public final class ProxyVersion { this.version = Preconditions.checkNotNull(version, "version"); } - public String getName() { + public String name() { return name; } - public String getVendor() { + public String vendor() { return vendor; } - public String getVersion() { + public String version() { return version; } diff --git a/proxy/build.gradle b/proxy/build.gradle index 3e6703aea..86bfc4b22 100644 --- a/proxy/build.gradle +++ b/proxy/build.gradle @@ -25,10 +25,14 @@ test { jar { manifest { - def buildNumber = System.getenv("BUILD_NUMBER") ?: "unknown" + def buildNumber = System.getenv("BUILD_NUMBER") def version if (project.version.endsWith("-SNAPSHOT")) { - version = "${project.version} (git-${project.ext.getCurrentShortRevision()}-b${buildNumber})" + if (buildNumber != null) { + version = "${project.version}+g${project.ext.getCurrentShortRevision()}-b${buildNumber}" + } else { + version = "${project.version}+g${project.ext.getCurrentShortRevision()}" + } } else { version = "${project.version}" } @@ -86,6 +90,7 @@ dependencies { implementation 'org.lanternpowered:lmbda:2.0.0-SNAPSHOT' implementation 'com.github.ben-manes.caffeine:caffeine:2.8.8' + implementation 'com.vdurmont:semver4j:3.1.0' compileOnly 'com.github.spotbugs:spotbugs-annotations:4.1.2' diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java b/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java index f1996d55c..b0794b1e4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java @@ -118,7 +118,7 @@ public class Metrics { () -> server.configuration().isOnlineMode() ? "online" : "offline") ); metrics.addCustomChart(new SimplePie("velocity_version", - () -> server.version().getVersion())); + () -> server.version().version())); metrics.addCustomChart(new DrilldownPie("java_version", () -> { Map> map = new HashMap<>(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 1545fc6fc..6090c11f7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -26,6 +26,7 @@ import com.velocitypowered.api.event.EventManager; import com.velocitypowered.api.event.lifecycle.ProxyInitializeEventImpl; import com.velocitypowered.api.event.lifecycle.ProxyReloadEventImpl; import com.velocitypowered.api.event.lifecycle.ProxyShutdownEventImpl; +import com.velocitypowered.api.network.NetworkEndpoint; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginManager; @@ -184,6 +185,11 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { return new ProxyVersion(implName, implVendor, implVersion); } + @Override + public Collection endpoints() { + return this.cm.endpoints(); + } + @Override public VelocityCommandManager commandManager() { return commandManager; @@ -195,7 +201,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { @EnsuresNonNull({"serverKeyPair", "eventManager", "console", "cm", "configuration"}) void start() { - logger.info("Booting up {} {}...", version().getName(), version().getVersion()); + logger.info("Booting up {} {}...", version().name(), version().version()); console.setupStreams(); registerTranslations(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java index 23ff976d6..0d4933516 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java @@ -217,19 +217,19 @@ public class VelocityCommand implements SimpleCommand { ProxyVersion version = server.version(); - Component velocity = Component.text().content(version.getName() + " ") + Component velocity = Component.text().content(version.name() + " ") .decoration(TextDecoration.BOLD, true) .color(VELOCITY_COLOR) - .append(Component.text(version.getVersion()).decoration(TextDecoration.BOLD, false)) + .append(Component.text(version.version()).decoration(TextDecoration.BOLD, false)) .build(); Component copyright = Component .translatable("velocity.command.version-copyright", - Component.text(version.getVendor()), - Component.text(version.getName())); + Component.text(version.vendor()), + Component.text(version.name())); source.sendMessage(velocity); source.sendMessage(copyright); - if (version.getName().equals("Velocity")) { + if (version.name().equals("Velocity")) { TextComponent embellishment = Component.text() .append(Component.text().content("velocitypowered.com") .color(NamedTextColor.GREEN) @@ -383,8 +383,8 @@ public class VelocityCommand implements SimpleCommand { BoundRequestBuilder request = httpClient.preparePost("https://dump.velocitypowered.com/documents"); request.setHeader("Content-Type", "text/plain"); - request.addHeader("User-Agent", server.version().getName() + "/" - + server.version().getVersion()); + request.addHeader("User-Agent", server.version().name() + "/" + + server.version().version()); request.setBody( InformationUtils.toHumanReadableString(dump).getBytes(StandardCharsets.UTF_8)); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java index ad2e74b70..ac9c803d7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java @@ -25,6 +25,7 @@ import com.velocitypowered.api.proxy.connection.InboundConnection; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerPing; import com.velocitypowered.api.util.ModInfo; +import com.velocitypowered.api.util.ProxyVersion; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.config.PingPassthroughMode; import com.velocitypowered.proxy.config.VelocityConfiguration; @@ -75,9 +76,10 @@ public class StatusSessionHandler implements MinecraftSessionHandler { private ServerPing constructLocalPing(ProtocolVersion version) { VelocityConfiguration configuration = server.configuration(); + ProxyVersion proxyVersion = server.version(); return new ServerPing( new ServerPing.Version(version.protocol(), - "Velocity " + ProtocolVersion.SUPPORTED_VERSION_STRING), + proxyVersion.name() + " " + ProtocolVersion.SUPPORTED_VERSION_STRING), new ServerPing.Players(server.countConnectedPlayers(), configuration.getShowMaxPlayers(), ImmutableList.of()), configuration.getMotd(), diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java index a93cf0a52..814768d3e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java @@ -24,6 +24,7 @@ import com.google.common.base.Preconditions; import com.velocitypowered.api.event.lifecycle.network.ListenerBoundEventImpl; import com.velocitypowered.api.event.lifecycle.network.ListenerClosedEventImpl; import com.velocitypowered.api.network.ListenerType; +import com.velocitypowered.api.network.NetworkEndpoint; import com.velocitypowered.natives.util.Natives; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.network.pipeline.GS4QueryHandler; @@ -39,7 +40,9 @@ import io.netty.channel.epoll.EpollChannelOption; import io.netty.util.concurrent.GlobalEventExecutor; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -87,7 +90,7 @@ public final class ConnectionManager { this.resolver = new SeparatePoolInetNameResolver(GlobalEventExecutor.INSTANCE); this.httpClient = asyncHttpClient(config() .setEventLoopGroup(this.workerGroup) - .setUserAgent(server.version().getName() + "/" + server.version().getVersion()) + .setUserAgent(server.version().name() + "/" + server.version().version()) .addRequestFilter(new RequestFilter() { @Override public FilterContext filter(FilterContext ctx) { @@ -205,7 +208,7 @@ public final class ConnectionManager { // Fire proxy close event to notify plugins of socket close. We block since plugins // should have a chance to be notified before the server stops accepting connections. - server.eventManager().fire(new ListenerClosedEventImpl(oldBind, endpoint.getType())).join(); + server.eventManager().fire(new ListenerClosedEventImpl(oldBind, endpoint.type())).join(); Channel serverChannel = endpoint.getChannel(); @@ -224,7 +227,7 @@ public final class ConnectionManager { // Fire proxy close event to notify plugins of socket close. We block since plugins // should have a chance to be notified before the server stops accepting connections. - server.eventManager().fire(new ListenerClosedEventImpl(address, endpoint.getType())).join(); + server.eventManager().fire(new ListenerClosedEventImpl(address, endpoint.type())).join(); try { LOGGER.info("Closing endpoint {}", address); @@ -253,4 +256,8 @@ public final class ConnectionManager { public ChannelInitializerHolder getBackendChannelInitializer() { return this.backendChannelInitializer; } + + public Collection endpoints() { + return List.copyOf(this.endpoints.values()); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/Endpoint.java b/proxy/src/main/java/com/velocitypowered/proxy/network/Endpoint.java index af453e636..a54a9eccf 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/Endpoint.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/Endpoint.java @@ -19,12 +19,14 @@ package com.velocitypowered.proxy.network; import com.google.common.base.Preconditions; import com.velocitypowered.api.network.ListenerType; +import com.velocitypowered.api.network.NetworkEndpoint; import io.netty.channel.Channel; +import java.net.SocketAddress; /** * Represents a listener endpoint. */ -public final class Endpoint { +public final class Endpoint implements NetworkEndpoint { private final Channel channel; private final ListenerType type; @@ -37,7 +39,13 @@ public final class Endpoint { return channel; } - public ListenerType getType() { + @Override + public ListenerType type() { return type; } + + @Override + public SocketAddress address() { + return this.channel.localAddress(); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/PluginMessageUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/network/PluginMessageUtil.java index 8db301011..b87e460a9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/PluginMessageUtil.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/PluginMessageUtil.java @@ -153,7 +153,7 @@ public final class PluginMessageUtil { checkArgument(isMcBrand(message), "message is not a brand plugin message"); String currentBrand = readBrandMessage(message.content()); - String rewrittenBrand = String.format("%s (%s)", currentBrand, version.getName()); + String rewrittenBrand = String.format("%s (%s)", currentBrand, version.name()); ByteBuf rewrittenBuf = Unpooled.buffer(); if (protocolVersion.gte(ProtocolVersion.MINECRAFT_1_8)) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java index bf590338d..2124c4c27 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java @@ -17,6 +17,7 @@ package com.velocitypowered.proxy.plugin; +import com.velocitypowered.api.plugin.PluginDescription; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; @@ -33,8 +34,11 @@ public class PluginClassLoader extends URLClassLoader { ClassLoader.registerAsParallelCapable(); } - public PluginClassLoader(URL[] urls) { + private final PluginDescription description; + + public PluginClassLoader(URL[] urls, PluginDescription description) { super(urls); + this.description = description; } public void addToClassloaders() { @@ -82,4 +86,9 @@ public class PluginClassLoader extends URLClassLoader { throw new ClassNotFoundException(name); } + + @Override + public String toString() { + return "plugin " + this.description.name(); + } } 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 20c6d98ea..f878bf94e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java @@ -25,6 +25,9 @@ import com.google.common.base.MoreObjects; import com.google.inject.AbstractModule; import com.google.inject.Module; import com.google.inject.name.Names; +import com.vdurmont.semver4j.Semver; +import com.vdurmont.semver4j.Semver.SemverType; +import com.vdurmont.semver4j.SemverException; import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.event.EventManager; import com.velocitypowered.api.plugin.PluginContainer; @@ -36,6 +39,7 @@ import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.plugin.loader.VelocityPluginContainer; import com.velocitypowered.proxy.plugin.loader.java.JavaPluginLoader; import com.velocitypowered.proxy.plugin.util.PluginDependencyUtils; +import com.velocitypowered.proxy.plugin.util.ProxyPluginContainer; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.nio.file.DirectoryStream; @@ -44,6 +48,7 @@ import java.nio.file.Path; 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; @@ -64,6 +69,9 @@ public class VelocityPluginManager implements PluginManager { public VelocityPluginManager(VelocityServer server) { this.server = checkNotNull(server, "server"); + + // Register ourselves as a plugin + this.registerPlugin(ProxyPluginContainer.VELOCITY); } private void registerPlugin(PluginContainer plugin) { @@ -106,17 +114,48 @@ public class VelocityPluginManager implements PluginManager { List sortedPlugins = PluginDependencyUtils.sortCandidates(found); - Set loadedPluginsById = new HashSet<>(); + Map loadedPluginsById = new HashMap<>(this.plugins); Map pluginContainers = new LinkedHashMap<>(); // Now load the plugins pluginLoad: for (PluginDescription candidate : sortedPlugins) { // Verify dependencies for (PluginDependency dependency : candidate.dependencies()) { - if (!dependency.optional() && !loadedPluginsById.contains(dependency.id())) { - logger.error("Can't load plugin {} due to missing dependency {}", candidate.id(), - dependency.id()); - continue pluginLoad; + final PluginContainer dependencyContainer = loadedPluginsById.get(dependency.id()); + if (dependencyContainer == null) { + if (dependency.optional()) { + logger.warn("Plugin {} has an optional dependency {} that is not available", + candidate.id(), dependency.id()); + } else { + logger.error("Can't load plugin {} due to missing dependency {}", + candidate.id(), dependency.id()); + continue pluginLoad; + } + } else { + String requiredRange = dependency.version(); + if (!requiredRange.isEmpty()) { + try { + Semver dependencyCandidateVersion = new Semver( + dependencyContainer.description().version(), SemverType.NPM); + if (!dependencyCandidateVersion.satisfies(requiredRange)) { + if (dependency.optional()) { + logger.error( + "Can't load plugin {} due to incompatible dependency {} {} (you have {})", + candidate.id(), dependency.id(), requiredRange, + dependencyContainer.description().version()); + continue pluginLoad; + } else { + logger.warn( + "Plugin {} has an optional dependency on {} {}, but you have {}", + candidate.id(), dependency.id(), requiredRange, + dependencyContainer.description().version()); + } + } + } catch (SemverException exception) { + logger.warn("Can't check dependency of {} for the proper version of {}," + + " assuming they are compatible", candidate.id(), dependency.id()); + } + } } } @@ -124,7 +163,7 @@ public class VelocityPluginManager implements PluginManager { PluginDescription realPlugin = loader.loadPlugin(candidate); VelocityPluginContainer container = new VelocityPluginContainer(realPlugin); pluginContainers.put(container, loader.createModule(container)); - loadedPluginsById.add(realPlugin.id()); + loadedPluginsById.put(candidate.id(), container); } catch (Exception e) { logger.error("Can't create module for plugin {}", candidate.id(), e); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java index 901605c06..4372f948c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/VelocityPluginDescription.java @@ -34,7 +34,7 @@ public class VelocityPluginDescription implements PluginDescription { private final String id; private final @Nullable String name; - private final @Nullable String version; + private final String version; private final @Nullable String description; private final @Nullable String url; private final List authors; @@ -52,13 +52,13 @@ public class VelocityPluginDescription implements PluginDescription { * @param dependencies the dependencies for this plugin * @param source the original source for the plugin */ - public VelocityPluginDescription(String id, @Nullable String name, @Nullable String version, + public VelocityPluginDescription(String id, @Nullable String name, String version, @Nullable String description, @Nullable String url, @Nullable List authors, Collection dependencies, @Nullable Path source) { this.id = checkNotNull(id, "id"); this.name = Strings.emptyToNull(name); - this.version = Strings.emptyToNull(version); + this.version = checkNotNull(version, "version"); this.description = Strings.emptyToNull(description); this.url = Strings.emptyToNull(url); this.authors = authors == null ? ImmutableList.of() : ImmutableList.copyOf(authors); @@ -77,7 +77,7 @@ public class VelocityPluginDescription implements PluginDescription { } @Override - public @Nullable String version() { + public String version() { return version; } 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 4bf3db055..e5fff7e45 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 @@ -81,7 +81,8 @@ public class JavaPluginLoader implements PluginLoader { URL pluginJarUrl = jarFilePath.toUri().toURL(); PluginClassLoader loader = AccessController.doPrivileged( - (PrivilegedAction) () -> new PluginClassLoader(new URL[]{pluginJarUrl})); + (PrivilegedAction) () -> new PluginClassLoader(new URL[]{pluginJarUrl}, + source)); loader.addToClassloaders(); JavaVelocityPluginDescriptionCandidate candidate = @@ -200,7 +201,7 @@ public class JavaPluginLoader implements PluginLoader { SerializedPluginDescription.Dependency dependency) { return new PluginDependency( dependency.getId(), - null, // TODO Implement version matching in dependency annotation + dependency.getVersion(), dependency.isOptional() ); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java index 3b67e3d2c..92a69852a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescription.java @@ -30,7 +30,7 @@ class JavaVelocityPluginDescription extends VelocityPluginDescription { private final Class mainClass; - JavaVelocityPluginDescription(String id, @Nullable String name, @Nullable String version, + JavaVelocityPluginDescription(String id, @Nullable String name, String version, @Nullable String description, @Nullable String url, @Nullable List authors, Collection dependencies, @Nullable Path source, Class mainClass) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescriptionCandidate.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescriptionCandidate.java index 4e4aa306a..b4f6e4499 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescriptionCandidate.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/loader/java/JavaVelocityPluginDescriptionCandidate.java @@ -30,7 +30,7 @@ class JavaVelocityPluginDescriptionCandidate extends VelocityPluginDescription { private final String mainClass; - JavaVelocityPluginDescriptionCandidate(String id, @Nullable String name, @Nullable String version, + JavaVelocityPluginDescriptionCandidate(String id, @Nullable String name, String version, @Nullable String description, @Nullable String url, @Nullable List authors, Collection dependencies, Path source, String mainClass) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/util/ProxyPluginContainer.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/util/ProxyPluginContainer.java new file mode 100644 index 000000000..57a38c9bb --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/util/ProxyPluginContainer.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.plugin.util; + +import com.google.common.base.MoreObjects; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginDescription; +import com.velocitypowered.proxy.VelocityServer; +import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class ProxyPluginContainer implements PluginContainer { + + public static final PluginContainer VELOCITY = new ProxyPluginContainer(); + + private final PluginDescription description = new PluginDescription() { + @Override + public String id() { + return "velocity"; + } + + @Override + public String name() { + final Package pkg = VelocityServer.class.getPackage(); + return MoreObjects.firstNonNull(pkg.getImplementationTitle(), "Velocity"); + } + + @Override + public String version() { + final Package pkg = VelocityServer.class.getPackage(); + return MoreObjects.firstNonNull(pkg.getImplementationVersion(), ""); + } + + @Override + public List authors() { + final Package pkg = VelocityServer.class.getPackage(); + final String vendor = MoreObjects.firstNonNull(pkg.getImplementationVendor(), + "Velocity Contributors"); + return List.of(vendor); + } + }; + + @Override + public PluginDescription description() { + return this.description; + } + + @Override + public @Nullable Object instance() { + return null; + } +} diff --git a/proxy/src/test/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtilsTest.java b/proxy/src/test/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtilsTest.java index aff2ae73f..44505ec29 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtilsTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtilsTest.java @@ -33,11 +33,11 @@ class PluginDependencyUtilsTest { private static final PluginDescription NO_DEPENDENCY = testDescription("trivial"); private static final PluginDescription NO_DEPENDENCY_2 = testDescription("trivial2"); private static final PluginDescription HAS_DEPENDENCY_1 = testDescription("dependent1", - new PluginDependency("trivial", null, false)); + new PluginDependency("trivial", "", false)); private static final PluginDescription HAS_DEPENDENCY_2 = testDescription("dependent2", - new PluginDependency("dependent1", null, false)); + new PluginDependency("dependent1", "", false)); private static final PluginDescription HAS_DEPENDENCY_3 = testDescription("dependent3", - new PluginDependency("trivial", null, false)); + new PluginDependency("trivial", "", false)); private static final PluginDescription CIRCULAR_DEPENDENCY_1 = testDescription("circle", new PluginDependency("oval", "", false));