Finishing Softreload utility
Dieser Commit ist enthalten in:
Ursprung
33decadee5
Commit
1f853ab61c
38
src/bungeepluginmanager/ModifiedPluginEventBus.java
Normale Datei
38
src/bungeepluginmanager/ModifiedPluginEventBus.java
Normale Datei
@ -0,0 +1,38 @@
|
||||
package bungeepluginmanager;
|
||||
|
||||
import net.md_5.bungee.api.event.AsyncEvent;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.event.EventBus;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
public final class ModifiedPluginEventBus extends EventBus {
|
||||
|
||||
private static final Set<AsyncEvent<?>> UNCOMPLETED_EVENTS = Collections.newSetFromMap(new WeakHashMap<>());
|
||||
private static final Object LOCK = new Object();
|
||||
|
||||
static void completeIntents(Plugin plugin) {
|
||||
synchronized (LOCK) {
|
||||
UNCOMPLETED_EVENTS.forEach(event -> {
|
||||
try {
|
||||
event.completeIntent(plugin);
|
||||
} catch (Exception error) {
|
||||
// Ignored
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void post(Object event) {
|
||||
if (event instanceof AsyncEvent) {
|
||||
synchronized (LOCK) {
|
||||
UNCOMPLETED_EVENTS.add((AsyncEvent<?>) event);
|
||||
}
|
||||
}
|
||||
super.post(event);
|
||||
}
|
||||
|
||||
}
|
175
src/bungeepluginmanager/PluginUtils.java
Normale Datei
175
src/bungeepluginmanager/PluginUtils.java
Normale Datei
@ -0,0 +1,175 @@
|
||||
package bungeepluginmanager;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.plugin.*;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public final class PluginUtils {
|
||||
private PluginUtils() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void unloadPlugin(Plugin plugin) {
|
||||
|
||||
PluginManager pluginManager = ProxyServer.getInstance().getPluginManager();
|
||||
ClassLoader pluginClassLoader = plugin.getClass().getClassLoader();
|
||||
|
||||
try {
|
||||
//call onDisable
|
||||
plugin.onDisable();
|
||||
//close all log handlers
|
||||
for (Handler handler : plugin.getLogger().getHandlers()) {
|
||||
handler.close();
|
||||
}
|
||||
} catch (Exception t) {
|
||||
severe("Exception disabling plugin", t, plugin.getDescription().getName());
|
||||
}
|
||||
|
||||
//unregister event handlers
|
||||
pluginManager.unregisterListeners(plugin);
|
||||
//unregister commands
|
||||
pluginManager.unregisterCommands(plugin);
|
||||
//cancel tasks
|
||||
ProxyServer.getInstance().getScheduler().cancel(plugin);
|
||||
//shutdown internal executor
|
||||
plugin.getExecutorService().shutdownNow();
|
||||
//stop all still active threads that belong to a plugin
|
||||
Thread.getAllStackTraces().keySet().stream()
|
||||
.filter(thread -> (thread.getClass().getClassLoader() == pluginClassLoader))
|
||||
.forEach(thread -> {
|
||||
try {
|
||||
thread.interrupt();
|
||||
thread.join(2000);
|
||||
if (thread.isAlive()) {
|
||||
throw new IllegalStateException("Thread " + thread.getName() + " still running");
|
||||
}
|
||||
} catch (Exception t) {
|
||||
severe("Failed to stop thread that belong to plugin", t, plugin.getDescription().getName());
|
||||
}
|
||||
});
|
||||
|
||||
//finish uncompleted intents
|
||||
ModifiedPluginEventBus.completeIntents(plugin);
|
||||
//remove commands that were registered by plugin not through normal means
|
||||
try {
|
||||
Map<String, Command> commandMap = ReflectionUtils.getFieldValue(pluginManager, "commandMap");
|
||||
if (checkReflectionNotNull(commandMap, "commandMap")) {
|
||||
commandMap.entrySet().removeIf(entry -> entry.getValue().getClass().getClassLoader() == pluginClassLoader);
|
||||
}
|
||||
} catch (Exception t) {
|
||||
severe("Failed to cleanup commandMap", t, plugin.getDescription().getName());
|
||||
}
|
||||
//cleanup internal listener and command maps from plugin refs
|
||||
try {
|
||||
Map<String, Plugin> pluginsMap = ReflectionUtils.getFieldValue(pluginManager, "plugins");
|
||||
Multimap<Plugin, Command> commands = ReflectionUtils.getFieldValue(pluginManager, "commandsByPlugin");
|
||||
Multimap<Plugin, Listener> listeners = ReflectionUtils.getFieldValue(pluginManager, "listenersByPlugin");
|
||||
|
||||
if (checkReflectionNotNull(pluginsMap, "plugin")
|
||||
&& checkReflectionNotNull(commands, "commandByPlugin")
|
||||
&& checkReflectionNotNull(listeners, "listenersByPlugin")) {
|
||||
pluginsMap.values().remove(plugin);
|
||||
commands.removeAll(plugin);
|
||||
listeners.removeAll(plugin);
|
||||
}
|
||||
} catch (Exception t) {
|
||||
severe("Failed to cleanup bungee internal maps from plugin refs", t, plugin.getDescription().getName());
|
||||
}
|
||||
//close classloader
|
||||
if (pluginClassLoader instanceof URLClassLoader) {
|
||||
try {
|
||||
((URLClassLoader) pluginClassLoader).close();
|
||||
} catch (Exception t) {
|
||||
severe("Failed to close the classloader for plugin", t, plugin.getDescription().getName());
|
||||
}
|
||||
}
|
||||
//remove classloader
|
||||
Set<PluginClassloader> allLoaders = ReflectionUtils.getStaticFieldValue(PluginClassloader.class, "allLoaders");
|
||||
if (checkReflectionNotNull(allLoaders, "allLoaders")) {
|
||||
allLoaders.remove(pluginClassLoader);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static boolean checkReflectionNotNull(Object obj, String field) {
|
||||
if (obj == null) {
|
||||
ProxyServer.getInstance().getLogger().log(Level.SEVERE, "Could not get field {} by reflections: are you using the latest version of BungeePluginManager?", field);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
public static boolean loadPlugin(File pluginFile) {
|
||||
|
||||
try (JarFile jar = new JarFile(pluginFile)) {
|
||||
|
||||
JarEntry pdf = jar.getJarEntry("bungee.yml");
|
||||
|
||||
if (pdf == null) {
|
||||
pdf = jar.getJarEntry("plugin.yml");
|
||||
}
|
||||
|
||||
try (InputStream in = jar.getInputStream(pdf)) {
|
||||
//load description
|
||||
PluginDescription desc = new Yaml().loadAs(in, PluginDescription.class);
|
||||
desc.setFile(pluginFile);
|
||||
//check depends
|
||||
HashSet<String> plugins = new HashSet<>();
|
||||
ProxyServer.getInstance().getPluginManager().getPlugins().forEach(plugin -> plugins.add(plugin.getDescription().getName()));
|
||||
for (String dependency : desc.getDepends()) {
|
||||
if (!plugins.contains(dependency)) {
|
||||
ProxyServer.getInstance().getLogger().log(Level.WARNING, "{0} (required by {1}) is unavailable", new Object[]{dependency, desc.getName()});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// do actual loading
|
||||
Class<?> main;
|
||||
try (URLClassLoader loader = new PluginClassloader(
|
||||
new URL[]{pluginFile.toURI().toURL()}
|
||||
)) {
|
||||
main = loader.loadClass(desc.getMain());
|
||||
Enumeration<JarEntry> entries = jar.entries();
|
||||
while(entries.hasMoreElements()){
|
||||
JarEntry entry = entries.nextElement();
|
||||
if(!entry.isDirectory() && entry.getName().endsWith(".class")){
|
||||
loader.loadClass(entry.getName().replace('/', '.').replace(".class", ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
Plugin clazz = (Plugin) main.getDeclaredConstructor().newInstance();
|
||||
|
||||
// reflection
|
||||
Map<String, Plugin> pluginsMap = ReflectionUtils.getFieldValue(ProxyServer.getInstance().getPluginManager(), "plugins");
|
||||
ReflectionUtils.invokeMethod(clazz, "init", ProxyServer.getInstance(), desc);
|
||||
|
||||
pluginsMap.put(desc.getName(), clazz);
|
||||
clazz.onLoad();
|
||||
clazz.onEnable();
|
||||
return true;
|
||||
}
|
||||
} catch (Exception t) {
|
||||
severe("Failed to load plugin", t, pluginFile.getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void severe(String message, Exception t, String pluginName) {
|
||||
ProxyServer.getInstance().getLogger().log(Level.SEVERE, message + " " + pluginName, t);
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
package de.steamwar.bungeecore;
|
||||
package bungeepluginmanager;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
final class ReflectionUtils {
|
||||
public final class ReflectionUtils {
|
||||
private ReflectionUtils() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
@ -24,7 +24,7 @@ final class ReflectionUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
static void setFieldValue(Object obj, String fieldName, Object value) {
|
||||
public static void setFieldValue(Object obj, String fieldName, Object value) {
|
||||
Class<?> clazz = obj.getClass();
|
||||
do {
|
||||
try {
|
@ -1,21 +1,14 @@
|
||||
package de.steamwar.bungeecore;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import bungeepluginmanager.ModifiedPluginEventBus;
|
||||
import bungeepluginmanager.PluginUtils;
|
||||
import bungeepluginmanager.ReflectionUtils;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.plugin.*;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.api.plugin.PluginManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class Persistent extends Plugin {
|
||||
|
||||
@ -26,110 +19,15 @@ public class Persistent extends Plugin {
|
||||
@Override
|
||||
public void onEnable(){
|
||||
instance = this;
|
||||
ReflectionUtils.setFieldValue(getProxy().getPluginManager(), "eventBus", new ModifiedPluginEventBus());
|
||||
getProxy().getPluginManager().registerCommand(this, new Command("softreload", "bungeecore.softreload"){
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
// Copied from https://www.spigotmc.org/resources/bungeepluginmanager-manage-your-bungee-plugin-at-runtime.63861/
|
||||
PluginManager pluginManager = getProxy().getPluginManager();
|
||||
Plugin bungeecore = pluginManager.getPlugin("BungeeCore");
|
||||
if(bungeecore != null) {
|
||||
ClassLoader pluginClassLoader = bungeecore.getClass().getClassLoader();
|
||||
|
||||
bungeecore.onDisable();
|
||||
for (Handler h : bungeecore.getLogger().getHandlers()) {
|
||||
h.close();
|
||||
}
|
||||
|
||||
pluginManager.unregisterCommands(bungeecore);
|
||||
pluginManager.unregisterListeners(bungeecore);
|
||||
getProxy().getScheduler().cancel(bungeecore);
|
||||
bungeecore.getExecutorService().shutdownNow();
|
||||
Thread.getAllStackTraces().keySet().stream()
|
||||
.filter(thread -> (thread.getClass().getClassLoader() == pluginClassLoader))
|
||||
.forEach(thread -> {
|
||||
try {
|
||||
thread.interrupt();
|
||||
thread.join(2000);
|
||||
if (thread.isAlive()) {
|
||||
throw new IllegalStateException("Thread " + thread.getName() + " still running");
|
||||
}
|
||||
} catch (Exception t) {
|
||||
getProxy().getLogger().log(Level.SEVERE, "Failed to stop thread that belong to plugin", t);
|
||||
}
|
||||
});
|
||||
//remove commands that were registered by plugin not through normal means
|
||||
try {
|
||||
Map<String, Command> commandMap = ReflectionUtils.getFieldValue(pluginManager, "commandMap");
|
||||
if (commandMap != null) {
|
||||
commandMap.entrySet().removeIf(entry -> entry.getValue().getClass().getClassLoader() == pluginClassLoader);
|
||||
}
|
||||
} catch (Exception t) {
|
||||
getLogger().log(Level.SEVERE, "Failed to cleanup commandMap", t);
|
||||
}
|
||||
try {
|
||||
Map<String, Plugin> pluginsMap = ReflectionUtils.getFieldValue(pluginManager, "plugins");
|
||||
Multimap<Plugin, Command> commands = ReflectionUtils.getFieldValue(pluginManager, "commandsByPlugin");
|
||||
Multimap<Plugin, Listener> listeners = ReflectionUtils.getFieldValue(pluginManager, "listenersByPlugin");
|
||||
|
||||
if (pluginsMap != null && commands != null && listeners != null) {
|
||||
pluginsMap.values().remove(bungeecore);
|
||||
commands.removeAll(bungeecore);
|
||||
listeners.removeAll(bungeecore);
|
||||
}
|
||||
} catch (Exception t) {
|
||||
getLogger().log(Level.SEVERE, "Failed to cleanup bungee internal maps from plugin refs", t);
|
||||
}
|
||||
if (pluginClassLoader instanceof URLClassLoader) {
|
||||
try {
|
||||
((URLClassLoader) pluginClassLoader).close();
|
||||
} catch (Exception t) {
|
||||
getLogger().log(Level.SEVERE, "Failed to close classloader", t);
|
||||
}
|
||||
}
|
||||
Set<PluginClassloader> allLoaders = ReflectionUtils.getStaticFieldValue(PluginClassloader.class, "allLoaders");
|
||||
if (allLoaders != null) {
|
||||
allLoaders.remove(pluginClassLoader);
|
||||
}
|
||||
}
|
||||
|
||||
File pluginFile = new File(getProxy().getPluginsFolder(), "BungeeCore.jar");
|
||||
try (JarFile jar = new JarFile(pluginFile)) {
|
||||
JarEntry pdf = jar.getJarEntry("plugin.yml");
|
||||
|
||||
try (InputStream in = jar.getInputStream(pdf)) {
|
||||
//load description
|
||||
PluginDescription desc = new Yaml().loadAs(in, PluginDescription.class);
|
||||
desc.setFile(pluginFile);
|
||||
//check depends
|
||||
HashSet<String> plugins = new HashSet<>();
|
||||
getProxy().getPluginManager().getPlugins().forEach(plugin -> plugins.add(plugin.getDescription().getName()));
|
||||
for (String dependency : desc.getDepends()) {
|
||||
if (!plugins.contains(dependency)) {
|
||||
getLogger().log(Level.WARNING, "{0} (required by {1}) is unavailable", new Object[]{dependency, desc.getName()});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// do actual loading
|
||||
Class<?> main;
|
||||
try (URLClassLoader loader = new PluginClassloader(new URL[]{
|
||||
pluginFile.toURI().toURL()
|
||||
})) {
|
||||
main = loader.loadClass(desc.getMain());
|
||||
}
|
||||
Plugin clazz = (Plugin) main.getDeclaredConstructor().newInstance();
|
||||
|
||||
// reflection
|
||||
Map<String, Plugin> pluginsMap = ReflectionUtils.getFieldValue(getProxy().getPluginManager(), "plugins");
|
||||
ReflectionUtils.invokeMethod(clazz, "init", getProxy(), desc);
|
||||
|
||||
pluginsMap.put(desc.getName(), clazz);
|
||||
clazz.onLoad();
|
||||
clazz.onEnable();
|
||||
}
|
||||
} catch (Exception t) {
|
||||
getLogger().log(Level.SEVERE, "Failed to load plugin", t);
|
||||
}
|
||||
PluginUtils.unloadPlugin(bungeecore);
|
||||
PluginUtils.loadPlugin(new File(getProxy().getPluginsFolder(), "BungeeCore.jar"));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -145,13 +43,13 @@ public class Persistent extends Plugin {
|
||||
public static void setChatPrefix(String prefix){
|
||||
chatPrefix = prefix;
|
||||
}
|
||||
public static String getLobbyServer() {
|
||||
static String getLobbyServer() {
|
||||
return lobbyServer;
|
||||
}
|
||||
public static String getPrefix(){
|
||||
static String getPrefix(){
|
||||
return chatPrefix;
|
||||
}
|
||||
public static Persistent getInstance() {
|
||||
static Persistent getInstance() {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
In neuem Issue referenzieren
Einen Benutzer sperren