fixes issue with bbox reload command

https://github.com/BentoBoxWorld/BentoBox/issues/731

Issue was that classes were not being fully removed from class loaders
and commands needed to be unregistered from Bukkit. For the latter,
reflection was required to obtain the knownCommand map and change it
because there is no Bukkit API to unregister commands.
This commit is contained in:
tastybento 2019-06-07 17:37:16 -07:00
parent 9da79f3bc2
commit 9b8c8f6bc8
8 changed files with 82 additions and 93 deletions

View File

@ -21,6 +21,7 @@ import java.net.URLClassLoader;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set;
/** /**
* Loads addons and sets up permissions * Loads addons and sets up permissions
@ -155,4 +156,11 @@ public class AddonClassLoader extends URLClassLoader {
return addon; return addon;
} }
/**
* @return class list
*/
public Set<String> getClasses() {
return classes.keySet();
}
} }

View File

@ -1,15 +1,13 @@
package world.bentobox.bentobox.commands; package world.bentobox.bentobox.commands;
import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.user.User;
/** /**
* Reloads settings, addons and localization. * Reloads settings, addons and localization.
* *
@ -36,7 +34,7 @@ public class BentoBoxReloadCommand extends ConfirmableCommand {
@Override @Override
public boolean execute(User user, String label, List<String> args) { public boolean execute(User user, String label, List<String> args) {
if (args.isEmpty()) { if (args.isEmpty()) {
this.askConfirmation(user, () -> { this.askConfirmation(user, user.getTranslation("commands.bentobox.reload.warning"), () -> {
// Reload settings // Reload settings
getPlugin().loadSettings(); getPlugin().loadSettings();
user.sendMessage("commands.bentobox.reload.settings-reloaded"); user.sendMessage("commands.bentobox.reload.settings-reloaded");
@ -49,18 +47,6 @@ public class BentoBoxReloadCommand extends ConfirmableCommand {
getPlugin().getLocalesManager().reloadLanguages(); getPlugin().getLocalesManager().reloadLanguages();
user.sendMessage("commands.bentobox.reload.locales-reloaded"); user.sendMessage("commands.bentobox.reload.locales-reloaded");
}); });
} else if (args.size() == 1) {
Optional<Addon> addon = getPlugin().getAddonsManager().getAddonByName(args.get(0));
if (!addon.isPresent()) {
user.sendMessage("commands.bentobox.reload.unknown-addon");
return false;
}
this.askConfirmation(user, () -> {
user.sendMessage("commands.bentobox.reload.addon", TextVariables.NAME, addon.get().getDescription().getName());
addon.ifPresent(getPlugin().getAddonsManager()::reloadAddon);
user.sendMessage("commands.bentobox.reload.addon-reloaded", TextVariables.NAME, addon.get().getDescription().getName());
});
} else { } else {
showHelp(this, user); showHelp(this, user);
} }

View File

@ -1,27 +1,9 @@
package world.bentobox.bentobox.managers; package world.bentobox.bentobox.managers;
import org.bukkit.Bukkit;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.generator.ChunkGenerator;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.addons.AddonClassLoader;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.addons.exceptions.InvalidAddonFormatException;
import world.bentobox.bentobox.api.configuration.ConfigObject;
import world.bentobox.bentobox.api.events.addon.AddonEvent;
import world.bentobox.bentobox.database.objects.DataObject;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@ -35,6 +17,26 @@ import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.generator.ChunkGenerator;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.addons.Addon.State;
import world.bentobox.bentobox.api.addons.AddonClassLoader;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.addons.exceptions.InvalidAddonFormatException;
import world.bentobox.bentobox.api.configuration.ConfigObject;
import world.bentobox.bentobox.api.events.addon.AddonEvent;
import world.bentobox.bentobox.commands.BentoBoxCommand;
import world.bentobox.bentobox.database.objects.DataObject;
/** /**
* @author tastybento, ComminQ * @author tastybento, ComminQ
*/ */
@ -213,19 +215,29 @@ public class AddonsManager {
*/ */
public void reloadAddons() { public void reloadAddons() {
disableAddons(); disableAddons();
// Reload BentoBox commands
new BentoBoxCommand();
loadAddons(); loadAddons();
enableAddons(); enableAddons();
} }
/** /**
* Reloads one addon * Disable all the enabled addons
* @param addon - addon
*/ */
public void reloadAddon(Addon addon) { public void disableAddons() {
Path p = addon.getFile().toPath(); if (!getEnabledAddons().isEmpty()) {
disable(addon); plugin.log("Disabling addons...");
loadAddon(p.toFile()); // Disable addons
enableAddon(addon); getEnabledAddons().forEach(this::disable);
plugin.log("Addons successfully disabled.");
}
// Unregister all commands
plugin.getCommandsManager().unregisterCommands();
// Clear all maps
listeners.clear();
addons.clear();
loaders.clear();
classes.clear();
} }
/** /**
@ -253,23 +265,6 @@ public class AddonsManager {
return data; return data;
} }
/**
* Disable all the enabled addons
*/
public void disableAddons() {
if (!getEnabledAddons().isEmpty()) {
plugin.log("Disabling addons...");
// Disable addons
getEnabledAddons().forEach(this::disable);
plugin.log("Addons successfully disabled.");
}
// Clear all maps
listeners.clear();
addons.clear();
loaders.clear();
classes.clear();
}
@NonNull @NonNull
public List<Addon> getAddons() { public List<Addon> getAddons() {
return addons; return addons;
@ -422,11 +417,9 @@ public class AddonsManager {
} }
// Clear loaders // Clear loaders
if (loaders.containsKey(addon)) { if (loaders.containsKey(addon)) {
try { loaders.get(addon).getClasses().forEach(classes::remove);
loaders.get(addon).close(); addon.setState(State.DISABLED);
} catch (IOException ignore) { loaders.remove(addon);
// Nothing
}
} }
// Remove it from the addons list // Remove it from the addons list

View File

@ -7,16 +7,19 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandMap; import org.bukkit.command.Command;
import org.bukkit.command.SimpleCommandMap;
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.commands.CompositeCommand;
public class CommandsManager { public class CommandsManager {
@NonNull @NonNull
private Map<@NonNull String, @NonNull CompositeCommand> commands = new HashMap<>(); private Map<@NonNull String, @NonNull CompositeCommand> commands = new HashMap<>();
private SimpleCommandMap commandMap;
public void registerCommand(@NonNull CompositeCommand command) { public void registerCommand(@NonNull CompositeCommand command) {
commands.put(command.getLabel(), command); commands.put(command.getLabel(), command);
@ -24,20 +27,39 @@ public class CommandsManager {
try{ try{
Field commandMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); Field commandMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap");
commandMapField.setAccessible(true); commandMapField.setAccessible(true);
CommandMap commandMap = (CommandMap) commandMapField.get(Bukkit.getServer()); commandMap = (SimpleCommandMap) commandMapField.get(Bukkit.getServer());
String commandPrefix = "bentobox"; String commandPrefix = "bentobox";
if (command.getAddon() != null) { if (command.getAddon() != null) {
commandPrefix = command.getAddon().getDescription().getName().toLowerCase(Locale.ENGLISH); commandPrefix = command.getAddon().getDescription().getName().toLowerCase(Locale.ENGLISH);
} }
if (!commandMap.register(commandPrefix, command)) {
commandMap.register(commandPrefix, command); BentoBox.getInstance().logError("Failed to register command " + commandPrefix + " " + command.getLabel());
}
} }
catch(Exception exception){ catch(Exception exception){
Bukkit.getLogger().severe("Bukkit server commandMap method is not there! This means no commands can be registered!"); Bukkit.getLogger().severe("Bukkit server commandMap method is not there! This means no commands can be registered!");
} }
} }
/**
* Unregisters all BentoBox registered commands with Bukkit
*/
public void unregisterCommands() {
// Use reflection to obtain the knownCommands in the commandMap
try {
@SuppressWarnings("unchecked")
Map<String, Command> knownCommands = (Map<String, Command>) commandMap.getClass().getMethod("getKnownCommands").invoke(commandMap);
knownCommands.values().removeIf(commands.values()::contains);
// Not sure if this is needed, but it clears out all references
commands.values().forEach(c -> c.unregister(commandMap));
// Zap everything
commands.clear();
} catch(Exception e){
Bukkit.getLogger().severe("Known commands reflection was not possible, BentoBox is now unstable, so restart server!");
}
}
/** /**
* Try to get a registered command. * Try to get a registered command.
* @param command - command string * @param command - command string

View File

@ -305,13 +305,13 @@ commands:
about: about:
description: "display copyright and license info" description: "display copyright and license info"
reload: reload:
parameters: "[addon]" description: "reloads BentoBox and all addons, settings and locales"
description: "reloads all addons, settings and locales or reloads an individual addon"
locales-reloaded: "&2Languages reloaded." locales-reloaded: "&2Languages reloaded."
addons-reloaded: "&2Addons reloaded." addons-reloaded: "&2Addons reloaded."
settings-reloaded: "&2Settings reloaded." settings-reloaded: "&2Settings reloaded."
addon: "&6Reloading &b[name]&2." addon: "&6Reloading &b[name]&2."
addon-reloaded: "&b[name] &2reloaded." addon-reloaded: "&b[name] &2reloaded."
warning: "&cWarning: Reloading may cause instability, so if you see errors afterwards, restart the server"
unknown-addon: "&2Unknown addon!" unknown-addon: "&2Unknown addon!"
version: version:
plugin-version: "&2BentoBox version: &3[version]" plugin-version: "&2BentoBox version: &3[version]"

View File

@ -300,8 +300,7 @@ commands:
about: about:
description: "mostra le info riguardo copyright e licenza" description: "mostra le info riguardo copyright e licenza"
reload: reload:
parameters: "[addon]" description: "ricarica gli addons, le impostazioni, i locali"
description: "ricarica gli addons, le impostazioni, i locali o ricarica un singolo addon"
locales-reloaded: "&2Linguaggi ricaricati." locales-reloaded: "&2Linguaggi ricaricati."
addons-reloaded: "&2Addons ricaricati." addons-reloaded: "&2Addons ricaricati."
settings-reloaded: "&2Impostazioni ricaricate." settings-reloaded: "&2Impostazioni ricaricate."

View File

@ -313,7 +313,6 @@ commands:
locales-reloaded: "&2Valodas faili pārlādēti." locales-reloaded: "&2Valodas faili pārlādēti."
addons-reloaded: "&2Papildinājumu pārlādēti." addons-reloaded: "&2Papildinājumu pārlādēti."
settings-reloaded: "&2Iestatījumi pārlādēti." settings-reloaded: "&2Iestatījumi pārlādēti."
parameters: '[addon]'
addon: '&6Pārlādē &b[name]&2.' addon: '&6Pārlādē &b[name]&2.'
addon-reloaded: '&b[name] &2pārlādēts.' addon-reloaded: '&b[name] &2pārlādēts.'
unknown-addon: '&2Nezināms papildinājums!' unknown-addon: '&2Nezināms papildinājums!'

View File

@ -29,7 +29,6 @@ import org.powermock.reflect.Whitebox;
import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.Addon; import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.addons.AddonDescription;
import world.bentobox.bentobox.database.objects.DataObject; import world.bentobox.bentobox.database.objects.DataObject;
@RunWith(PowerMockRunner.class) @RunWith(PowerMockRunner.class)
@ -105,23 +104,6 @@ public class AddonsManagerTest {
verify(plugin, never()).log("Disabling addons..."); verify(plugin, never()).log("Disabling addons...");
} }
/**
* Test method for {@link world.bentobox.bentobox.managers.AddonsManager#reloadAddon(world.bentobox.bentobox.api.addons.Addon)}.
*/
@Test
public void testReloadAddon() {
Addon addon = mock(Addon.class);
when(addon.isEnabled()).thenReturn(true);
File f = new File(plugin.getDataFolder(), "addons");
File file = new File(f, "addon.jar");
when(addon.getFile()).thenReturn(file);
AddonDescription desc = new AddonDescription.Builder("main", "addon-name", "1.0").build();
when(addon.getDescription()).thenReturn(desc);
am.reloadAddon(addon);
verify(plugin).log("Disabling addon-name...");
verify(addon).onDisable();
}
/** /**
* Test method for {@link world.bentobox.bentobox.managers.AddonsManager#getAddonByName(java.lang.String)}. * Test method for {@link world.bentobox.bentobox.managers.AddonsManager#getAddonByName(java.lang.String)}.
*/ */