Cleanup & document a number of Bukkit impl classes

This commit is contained in:
Luck 2017-07-24 14:32:10 +01:00
parent 845367e847
commit e42cc101cc
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
21 changed files with 1027 additions and 702 deletions

View File

@ -206,7 +206,11 @@ public class BukkitListener implements Listener {
final Player player = e.getPlayer();
// Remove the custom permissible
Injector.unInject(player, true, true);
try {
Injector.unInject(player, true, true);
} catch (Exception ex) {
ex.printStackTrace();
}
// Handle auto op
if (plugin.getConfiguration().get(ConfigKeys.AUTO_OP)) {

View File

@ -32,6 +32,7 @@ import me.lucko.luckperms.api.Logger;
import me.lucko.luckperms.api.LuckPermsApi;
import me.lucko.luckperms.api.PlatformType;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.bukkit.messaging.BungeeMessagingService;
import me.lucko.luckperms.bukkit.messaging.LilyPadMessagingService;
@ -39,7 +40,7 @@ import me.lucko.luckperms.bukkit.model.ChildPermissionProvider;
import me.lucko.luckperms.bukkit.model.DefaultsProvider;
import me.lucko.luckperms.bukkit.model.Injector;
import me.lucko.luckperms.bukkit.model.LPPermissible;
import me.lucko.luckperms.bukkit.vault.VaultHook;
import me.lucko.luckperms.bukkit.vault.VaultHookManager;
import me.lucko.luckperms.common.api.ApiHandler;
import me.lucko.luckperms.common.api.ApiProvider;
import me.lucko.luckperms.common.caching.handlers.CachedStateManager;
@ -111,7 +112,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private long startTime;
private LPBukkitScheduler scheduler;
private BukkitCommand commandManager;
private VaultHook vaultHook = null;
private VaultHookManager vaultHookManager = null;
private LuckPermsConfiguration configuration;
private UserManager userManager;
private GroupManager groupManager;
@ -288,13 +289,26 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
calculatorFactory = new BukkitCalculatorFactory(this);
cachedStateManager = new CachedStateManager(this);
contextManager = new ContextManager<>();
contextManager = new ContextManager<Player>() {
@Override
public Contexts formContexts(Player player, ImmutableContextSet contextSet) {
return new Contexts(
contextSet,
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
player.isOp()
);
}
};
worldCalculator = new WorldCalculator(this);
contextManager.registerCalculator(worldCalculator);
StaticCalculator<Player> staticCalculator = new StaticCalculator<>(getConfiguration());
contextManager.registerCalculator(staticCalculator);
contextManager.registerStaticCalculator(staticCalculator);
contextManager.registerCalculator(staticCalculator, true);
// Provide vault support
tryVaultHook(false);
@ -368,7 +382,12 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
verboseHandler.setShutdown(true);
for (Player player : getServer().getOnlinePlayers()) {
Injector.unInject(player, false, false);
try {
Injector.unInject(player, false, false);
} catch (Exception e) {
e.printStackTrace();
}
if (getConfiguration().get(ConfigKeys.AUTO_OP)) {
player.setOp(false);
}
@ -395,8 +414,8 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
ApiHandler.unregisterProvider();
getServer().getServicesManager().unregisterAll(this);
if (vaultHook != null) {
vaultHook.unhook(this);
if (vaultHookManager != null) {
vaultHookManager.unhook(this);
}
getLog().info("Shutting down internal scheduler...");
@ -408,7 +427,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
// Null everything
ignoringLogs = null;
vaultHook = null;
vaultHookManager = null;
configuration = null;
userManager = null;
groupManager = null;
@ -434,18 +453,18 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
}
public void tryVaultHook(boolean force) {
if (vaultHook != null) {
if (vaultHookManager != null) {
return; // already hooked
}
try {
if (force || getServer().getPluginManager().isPluginEnabled("Vault")) {
vaultHook = new VaultHook();
vaultHook.hook(this);
vaultHookManager = new VaultHookManager();
vaultHookManager.hook(this);
getLog().info("Registered Vault permission & chat hook.");
}
} catch (Exception e) {
vaultHook = null;
vaultHookManager = null;
getLog().severe("Error occurred whilst hooking into Vault.");
e.printStackTrace();
}
@ -531,15 +550,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
if (player == null) {
return null;
}
return new Contexts(
getContextManager().getApplicableContext(player),
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
player.isOp()
);
return contextManager.getApplicableContexts(player);
}
@Override
@ -645,7 +656,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
@Override
public LinkedHashMap<String, Object> getExtraInfo() {
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("Vault Enabled", vaultHook != null);
map.put("Vault Enabled", vaultHookManager != null);
map.put("Bukkit Defaults count", defaultsProvider.size());
map.put("Bukkit Child Permissions count", childPermissionProvider.getPermissions().size());
return map;

View File

@ -36,6 +36,9 @@ import org.bukkit.permissions.PermissionAttachmentInfo;
import java.util.Map;
import java.util.function.Supplier;
/**
* Permission Processor for permissions set to a player via permission attachments.
*/
@AllArgsConstructor
public class AttachmentProcessor implements PermissionProcessor {

View File

@ -36,6 +36,9 @@ import me.lucko.luckperms.common.calculators.PermissionProcessor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Permission Processor for Bukkits "child" permission system.
*/
@RequiredArgsConstructor
public class ChildProcessor implements PermissionProcessor {
private final ChildPermissionProvider provider;

View File

@ -36,6 +36,9 @@ import org.bukkit.permissions.Permission;
import java.util.Map;
/**
* Permission Processor for Bukkits "default" permission system.
*/
@AllArgsConstructor
public class DefaultsProcessor implements PermissionProcessor {
private final boolean isOp;

View File

@ -33,6 +33,9 @@ import net.kyori.text.serializer.ComponentSerializer;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
/**
* Sends a json message to a command sender.
*/
public class MessageHandler {
private final BukkitJsonMessageHandler bukkitHandler;
private final SpigotJsonMessageHandler spigotHandler;

View File

@ -32,52 +32,71 @@ import com.google.common.collect.Maps;
import org.bukkit.Bukkit;
import org.bukkit.permissions.Permission;
import org.bukkit.plugin.PluginManager;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Holds child permissions registered on the platform.
*
* The data stored in this class is pulled from the data in {@link PluginManager#getPermissions()}.
*
* The former method is not thread safe, so we populate this class when the server starts to get all of the data
* in a form which is easily queryable & thread safe.
*
* The data is resolved early, so the represented child permissions are a "deep" lookup of permissions.
*/
public class ChildPermissionProvider {
// in the format: permission+value ===> children (a map of child permissions)
@Getter
private ImmutableMap<Map.Entry<String, Boolean>, ImmutableMap<String, Boolean>> permissions = ImmutableMap.of();
public void setup() {
ImmutableMap.Builder<Map.Entry<String, Boolean>, ImmutableMap<String, Boolean>> permissions = ImmutableMap.builder();
// iterate all permissions registered on the platform & resolve.
for (Permission permission : Bukkit.getServer().getPluginManager().getPermissions()) {
// handle true
Map<String, Boolean> trueChildren = new HashMap<>();
resolveChildren(trueChildren, Collections.singletonMap(permission.getName(), true), false);
trueChildren.remove(permission.getName(), true);
if (!trueChildren.isEmpty()) {
permissions.put(Maps.immutableEntry(permission.getName().toLowerCase(), true), ImmutableMap.copyOf(trueChildren));
}
// handle false
Map<String, Boolean> falseChildren = new HashMap<>();
resolveChildren(falseChildren, Collections.singletonMap(permission.getName(), false), false);
falseChildren.remove(permission.getName(), false);
if (!falseChildren.isEmpty()) {
permissions.put(Maps.immutableEntry(permission.getName().toLowerCase(), false), ImmutableMap.copyOf(falseChildren));
}
resolve(permissions, permission, true);
resolve(permissions, permission, false);
}
this.permissions = permissions.build();
}
private static void resolve(ImmutableMap.Builder<Map.Entry<String, Boolean>, ImmutableMap<String, Boolean>> accumulator, Permission permission, boolean value) {
// accumulator for the child permissions being looked up
Map<String, Boolean> children = new HashMap<>();
// resolve children for the permission, so pass a map containing just the permission being looked up.
resolveChildren(children, Collections.singletonMap(permission.getName(), value), false);
// remove self
children.remove(permission.getName(), value);
// only register the children if there are any.
if (!children.isEmpty()) {
accumulator.put(Maps.immutableEntry(permission.getName().toLowerCase(), value), ImmutableMap.copyOf(children));
}
}
private static void resolveChildren(Map<String, Boolean> accumulator, Map<String, Boolean> children, boolean invert) {
// iterate through the current known children.
// the first time this method is called for a given permission, the children map will contain only the permission itself.
for (Map.Entry<String, Boolean> e : children.entrySet()) {
if (accumulator.containsKey(e.getKey())) {
continue; // Prevent infinite loops
}
Permission perm = Bukkit.getServer().getPluginManager().getPermission(e.getKey());
// xor the value using the parent (bukkit logic, not mine)
boolean value = e.getValue() ^ invert;
String lName = e.getKey().toLowerCase();
accumulator.put(lName, value);
accumulator.put(e.getKey().toLowerCase(), value);
// lookup any deeper children & resolve if present
Permission perm = Bukkit.getServer().getPluginManager().getPermission(e.getKey());
if (perm != null) {
resolveChildren(accumulator, perm.getChildren(), !value);
}

View File

@ -33,49 +33,50 @@ import me.lucko.luckperms.api.Tristate;
import org.bukkit.Bukkit;
import org.bukkit.permissions.Permission;
import org.bukkit.plugin.PluginManager;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Holds default permissions registered on the platform.
*
* The data stored in this class is pulled from the data in {@link PluginManager#getDefaultPermissions(boolean)}.
*
* The former method is not thread safe, so we populate this class when the server starts to get all of the data
* in a form which is easily queryable & thread safe.
*
* The {@link DummyPermissible}s are registered with Bukkit, so we can listen for any
* changes to default permissions.
*/
public class DefaultsProvider {
// defaults for opped players
@Getter
private Map<String, Boolean> opDefaults = ImmutableMap.of();
private final DummyPermissible opDummy = new DummyPermissible(this::refreshOp);
// defaults for non-opped players
@Getter
private Map<String, Boolean> nonOpDefaults = ImmutableMap.of();
private final DummyPermissible nonOpDummy = new DummyPermissible(this::refreshNonOp);
/**
* Refreshes the data in this provider.
*/
public void refresh() {
refreshOp();
refreshNonOp();
}
private void refreshOp() {
unregisterDefaults(opDefaults, opDummy);
Map<String, Boolean> builder = new HashMap<>();
calculateDefaults(builder, opDummy, true);
opDefaults = ImmutableMap.copyOf(builder);
}
private void refreshNonOp() {
unregisterDefaults(nonOpDefaults, nonOpDummy);
Map<String, Boolean> builder = new HashMap<>();
calculateDefaults(builder, nonOpDummy, false);
nonOpDefaults = ImmutableMap.copyOf(builder);
}
public void close() {
unregisterDefaults(opDefaults, opDummy);
unregisterDefaults(nonOpDefaults, nonOpDummy);
}
/**
* Queries whether a given permission should be granted by default.
*
* @param permission the permission to query
* @param isOp if the player is op
* @return a tristate result
*/
public Tristate hasDefault(String permission, boolean isOp) {
Map<String, Boolean> map = isOp ? opDefaults : nonOpDefaults;
@ -83,44 +84,94 @@ public class DefaultsProvider {
return b == null ? Tristate.UNDEFINED : Tristate.fromBoolean(b);
}
/**
* Gets the number of default permissions held by the provider.
*
* @return the number of permissions held
*/
public int size() {
return opDefaults.size() + nonOpDefaults.size();
}
private static void unregisterDefaults(Map<String, Boolean> map, DummyPermissible p) {
/**
* Refreshes the op data in this provider.
*/
private void refreshOp() {
unregisterDefaults(opDefaults, opDummy, true);
Map<String, Boolean> builder = new HashMap<>();
calculateDefaults(builder, opDummy, true);
opDefaults = ImmutableMap.copyOf(builder);
}
/**
* Refreshes the non op data in this provider.
*/
private void refreshNonOp() {
unregisterDefaults(nonOpDefaults, nonOpDummy, false);
Map<String, Boolean> builder = new HashMap<>();
calculateDefaults(builder, nonOpDummy, false);
nonOpDefaults = ImmutableMap.copyOf(builder);
}
/**
* Unregisters the dummy permissibles with Bukkit.
*/
public void close() {
unregisterDefaults(opDefaults, opDummy, true);
unregisterDefaults(nonOpDefaults, nonOpDummy, false);
}
/**
* Unregisters defaults for a given permissible.
*
* @param map the map of current defaults
* @param p the permissible
*/
private static void unregisterDefaults(Map<String, Boolean> map, DummyPermissible p, boolean op) {
Set<String> perms = map.keySet();
for (String name : perms) {
Bukkit.getServer().getPluginManager().unsubscribeFromPermission(name, p);
}
Bukkit.getServer().getPluginManager().unsubscribeFromDefaultPerms(false, p);
Bukkit.getServer().getPluginManager().unsubscribeFromDefaultPerms(true, p);
Bukkit.getServer().getPluginManager().unsubscribeFromDefaultPerms(op, p);
}
private static void calculateDefaults(Map<String, Boolean> map, DummyPermissible p, boolean op) {
Set<Permission> defaults = Bukkit.getServer().getPluginManager().getDefaultPermissions(op);
Bukkit.getServer().getPluginManager().subscribeToDefaultPerms(op, p);
Set<Permission> defaults = Bukkit.getServer().getPluginManager().getDefaultPermissions(op);
for (Permission perm : defaults) {
String name = perm.getName().toLowerCase();
map.put(name, true);
Bukkit.getServer().getPluginManager().subscribeToPermission(name, p);
// register defaults for any children too
calculateChildPermissions(map, p, perm.getChildren(), false);
}
}
private static void calculateChildPermissions(Map<String, Boolean> map, DummyPermissible p, Map<String, Boolean> children, boolean invert) {
private static void calculateChildPermissions(Map<String, Boolean> accumulator, DummyPermissible p, Map<String, Boolean> children, boolean invert) {
for (Map.Entry<String, Boolean> e : children.entrySet()) {
Permission perm = Bukkit.getServer().getPluginManager().getPermission(e.getKey());
boolean value = e.getValue() ^ invert;
String lName = e.getKey().toLowerCase();
if (accumulator.containsKey(e.getKey())) {
continue; // Prevent infinite loops
}
map.put(lName, value);
// xor the value using the parent (bukkit logic, not mine)
boolean value = e.getValue() ^ invert;
accumulator.put(e.getKey().toLowerCase(), value);
Bukkit.getServer().getPluginManager().subscribeToPermission(e.getKey(), p);
// lookup any deeper children & resolve if present
Permission perm = Bukkit.getServer().getPluginManager().getPermission(e.getKey());
if (perm != null) {
calculateChildPermissions(map, p, perm.getChildren(), !value);
calculateChildPermissions(accumulator, p, perm.getChildren(), !value);
}
}
}

View File

@ -40,31 +40,49 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* Injects a {@link LPPermissible} into a {@link Player}
* Injects a {@link LPPermissible} into a {@link Player}.
*
* This allows LuckPerms to directly intercept permission checks and take over all handling of
* checks made by plugins.
*/
@SuppressWarnings("unchecked")
@UtilityClass
public class Injector {
private static final Map<UUID, LPPermissible> INJECTED_PERMISSIBLES = new ConcurrentHashMap<>();
private static Field humanEntityField;
private static Field permissibleAttachmentsField;
private static Throwable cachedThrowable = null;
/**
* All permission checks made on standard Bukkit objects are effectively proxied to a
* {@link PermissibleBase} object, held as a parameter on the object.
*
* This field is where the permissible is stored on a HumanEntity.
*/
private static Field humanEntityPermissibleField;
/**
* The field where attachments are stored on a permissible base.
*/
private static Field permissibleBaseAttachmentsField;
private static Throwable cachedThrowable = null;
static {
try {
// Catch all. If this setup doesn't fully complete without
// exceptions, then the Injector will not work.
// Try to load the permissible field.
try {
// craftbukkit
humanEntityField = ReflectionUtil.obcClass("entity.CraftHumanEntity").getDeclaredField("perm");
humanEntityField.setAccessible(true);
humanEntityPermissibleField = ReflectionUtil.obcClass("entity.CraftHumanEntity").getDeclaredField("perm");
humanEntityPermissibleField.setAccessible(true);
} catch (Exception e) {
// glowstone
humanEntityField = Class.forName("net.glowstone.entity.GlowHumanEntity").getDeclaredField("permissions");
humanEntityField.setAccessible(true);
humanEntityPermissibleField = Class.forName("net.glowstone.entity.GlowHumanEntity").getDeclaredField("permissions");
humanEntityPermissibleField.setAccessible(true);
}
permissibleAttachmentsField = PermissibleBase.class.getDeclaredField("attachments");
permissibleAttachmentsField.setAccessible(true);
// Try to load the attachments field.
permissibleBaseAttachmentsField = PermissibleBase.class.getDeclaredField("attachments");
permissibleBaseAttachmentsField.setAccessible(true);
} catch (Throwable t) {
cachedThrowable = t;
@ -72,82 +90,107 @@ public class Injector {
}
}
public static boolean inject(Player player, LPPermissible lpPermissible) {
/**
* Injects a {@link LPPermissible} into a {@link Player}.
*
* @param player the player to inject into
* @param newPermissible the permissible to inject
* @throws Exception propagates any exceptions which were thrown during injection
*/
public static void inject(Player player, LPPermissible newPermissible) throws Exception {
// make sure the class inited without errors, otherwise, print a trace
if (cachedThrowable != null) {
cachedThrowable.printStackTrace();
return false;
throw new RuntimeException("Injector did not init successfully.", cachedThrowable);
}
try {
PermissibleBase existing = (PermissibleBase) humanEntityField.get(player);
if (existing instanceof LPPermissible) {
// uh oh
throw new IllegalStateException("LPPermissible already injected into player " + player.toString());
}
// get the existing PermissibleBase held by the player
PermissibleBase oldPermissible = (PermissibleBase) humanEntityPermissibleField.get(player);
// Move attachments over from the old permissible.
List<PermissionAttachment> attachments = (List<PermissionAttachment>) permissibleAttachmentsField.get(existing);
lpPermissible.addAttachments(attachments);
attachments.clear();
existing.clearPermissions();
lpPermissible.getActive().set(true);
lpPermissible.recalculatePermissions(false);
lpPermissible.setOldPermissible(existing);
lpPermissible.updateSubscriptionsAsync();
humanEntityField.set(player, lpPermissible);
INJECTED_PERMISSIBLES.put(player.getUniqueId(), lpPermissible);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
// seems we have already injected into this player.
if (oldPermissible instanceof LPPermissible) {
throw new IllegalStateException("LPPermissible already injected into player " + player.toString());
}
// Move attachments over from the old permissible
//noinspection unchecked
List<PermissionAttachment> attachments = (List<PermissionAttachment>) permissibleBaseAttachmentsField.get(oldPermissible);
newPermissible.addAttachments(attachments);
attachments.clear();
oldPermissible.clearPermissions();
// Setup the new permissible
newPermissible.getActive().set(true);
newPermissible.recalculatePermissions(false);
newPermissible.setOldPermissible(oldPermissible);
newPermissible.updateSubscriptionsAsync();
// inject the new instance
humanEntityPermissibleField.set(player, newPermissible);
// register the injection with the map
INJECTED_PERMISSIBLES.put(player.getUniqueId(), newPermissible);
}
public static boolean unInject(Player player, boolean dummy, boolean unsubscribe) {
/**
* Uninjects a {@link LPPermissible} from a {@link Player}.
*
* @param player the player to uninject from
* @param dummy if the replacement permissible should be a dummy.
* @param unsubscribe if the extracted permissible should unsubscribe itself. see {@link SubscriptionManager}.
* @throws Exception propagates any exceptions which were thrown during uninjection
*/
public static void unInject(Player player, boolean dummy, boolean unsubscribe) throws Exception {
// make sure the class inited without errors, otherwise, print a trace
if (cachedThrowable != null) {
cachedThrowable.printStackTrace();
return false;
throw new RuntimeException("Injector did not init successfully.", cachedThrowable);
}
try {
PermissibleBase permissible = (PermissibleBase) humanEntityField.get(player);
if (permissible instanceof LPPermissible) {
// gets the players current permissible.
PermissibleBase permissible = (PermissibleBase) humanEntityPermissibleField.get(player);
permissible.clearPermissions();
// only uninject if the permissible was a luckperms one.
if (permissible instanceof LPPermissible) {
LPPermissible lpPermissible = ((LPPermissible) permissible);
if (unsubscribe) {
((LPPermissible) permissible).unsubscribeFromAllAsync();
}
// clear all permissions
lpPermissible.clearPermissions();
((LPPermissible) permissible).getActive().set(false);
if (dummy) {
humanEntityField.set(player, new DummyPermissibleBase());
} else {
LPPermissible lpp = ((LPPermissible) permissible);
List<PermissionAttachment> attachments = lpp.getAttachments();
PermissibleBase newPb = lpp.getOldPermissible();
if (newPb == null) {
newPb = new PermissibleBase(player);
}
List<PermissionAttachment> newAttachments = (List<PermissionAttachment>) permissibleAttachmentsField.get(newPb);
newAttachments.addAll(attachments);
attachments.clear();
humanEntityField.set(player, newPb);
}
// try to unsubscribe
if (unsubscribe) {
lpPermissible.unsubscribeFromAllAsync();
}
// set to inactive
lpPermissible.getActive().set(false);
// handle the replacement permissible.
if (dummy) {
// just inject a dummy class. this is used when we know the player is about to quit the server.
humanEntityPermissibleField.set(player, new DummyPermissibleBase());
} else {
// otherwise, inject the permissible they had when we first injected.
List<PermissionAttachment> lpAttachments = lpPermissible.getAttachments();
PermissibleBase newPb = lpPermissible.getOldPermissible();
if (newPb == null) {
newPb = new PermissibleBase(player);
}
//noinspection unchecked
List<PermissionAttachment> newPbAttachments = (List<PermissionAttachment>) permissibleBaseAttachmentsField.get(newPb);
newPbAttachments.addAll(lpAttachments);
lpAttachments.clear();
humanEntityPermissibleField.set(player, newPb);
}
INJECTED_PERMISSIBLES.remove(player.getUniqueId());
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
INJECTED_PERMISSIBLES.remove(player.getUniqueId());
}
public static LPPermissible getPermissible(UUID uuid) {

View File

@ -45,9 +45,10 @@ import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.permissions.PermissionRemovedExecutor;
import org.bukkit.plugin.Plugin;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -57,24 +58,47 @@ import java.util.logging.Level;
import java.util.stream.Collectors;
/**
* Modified PermissibleBase for LuckPerms
* PermissibleBase for LuckPerms.
*
* This class overrides all methods defined in PermissibleBase, and provides custom handling
* from LuckPerms.
*
* This means that all permission checks made for a player are handled directly by the plugin.
* Method behaviour is retained, but alternate implementation is used.
*
* "Hot" method calls, (namely #hasPermission) are significantly faster than the base implementation.
*
* This class is **thread safe**. This means that when LuckPerms is installed on the server,
* is is safe to call Player#hasPermission asynchronously.
*/
@Getter
public class LPPermissible extends PermissibleBase {
// the LuckPerms user this permissible references.
private final User user;
// the player this permissible is injected into.
private final Player parent;
// the luckperms plugin instance
private final LPBukkitPlugin plugin;
// the subscription manager, handling the players permission subscriptions.
private final SubscriptionManager subscriptions;
// the players previous permissible. (the one they had before this one was injected)
@Setter
private PermissibleBase oldPermissible = null;
// if the permissible is currently active.
private final AtomicBoolean active = new AtomicBoolean(false);
// Attachment stuff.
// the permissions registered by PermissionAttachments.
// stored in this format, as that's what is used by #getEffectivePermissions
private final Map<String, PermissionAttachmentInfo> attachmentPermissions = new ConcurrentHashMap<>();
private final List<PermissionAttachment> attachments = Collections.synchronizedList(new LinkedList<>());
// the attachments hooked onto the permissible.
private final List<PermissionAttachment> attachments = Collections.synchronizedList(new ArrayList<>());
public LPPermissible(@NonNull Player parent, @NonNull User user, @NonNull LPBukkitPlugin plugin) {
super(parent);
@ -84,6 +108,49 @@ public class LPPermissible extends PermissibleBase {
this.subscriptions = new SubscriptionManager(this);
}
@Override
public boolean isPermissionSet(@NonNull String permission) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission);
return ts != Tristate.UNDEFINED || Permission.DEFAULT_PERMISSION.getValue(isOp());
}
@Override
public boolean isPermissionSet(@NonNull Permission permission) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission.getName());
if (ts != Tristate.UNDEFINED) {
return true;
}
if (!plugin.getConfiguration().get(ConfigKeys.APPLY_BUKKIT_DEFAULT_PERMISSIONS)) {
return Permission.DEFAULT_PERMISSION.getValue(isOp());
} else {
return permission.getDefault().getValue(isOp());
}
}
@Override
public boolean hasPermission(@NonNull String permission) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission);
return ts != Tristate.UNDEFINED ? ts.asBoolean() : Permission.DEFAULT_PERMISSION.getValue(isOp());
}
@Override
public boolean hasPermission(@NonNull Permission permission) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(permission.getName());
if (ts != Tristate.UNDEFINED) {
return ts.asBoolean();
}
if (!plugin.getConfiguration().get(ConfigKeys.APPLY_BUKKIT_DEFAULT_PERMISSIONS)) {
return Permission.DEFAULT_PERMISSION.getValue(isOp());
} else {
return permission.getDefault().getValue(isOp());
}
}
/**
* Updates the players subscriptions asynchronously
*/
public void updateSubscriptionsAsync() {
if (!active.get()) {
return;
@ -92,18 +159,20 @@ public class LPPermissible extends PermissibleBase {
plugin.doAsync(this::updateSubscriptions);
}
/**
* Updates the players subscriptions
*/
public void updateSubscriptions() {
if (!active.get()) {
return;
}
UserCache cache = user.getUserData();
if (cache == null) {
return;
}
// calculate their "active" permissions
Set<String> ent = new HashSet<>(cache.getPermissionData(calculateContexts()).getImmutableBacking().keySet());
// include defaults, if enabled.
if (plugin.getConfiguration().get(ConfigKeys.APPLY_BUKKIT_DEFAULT_PERMISSIONS)) {
if (parent.isOp()) {
ent.addAll(plugin.getDefaultsProvider().getOpDefaults().keySet());
@ -115,28 +184,37 @@ public class LPPermissible extends PermissibleBase {
subscriptions.subscribe(ent);
}
/**
* Unsubscribes from all permissions asynchronously
*/
public void unsubscribeFromAllAsync() {
plugin.doAsync(this::unsubscribeFromAll);
}
/**
* Unsubscribes from all permissions
*/
public void unsubscribeFromAll() {
subscriptions.subscribe(Collections.emptySet());
}
public void addAttachments(List<PermissionAttachment> attachments) {
/**
* Adds attachments to this permissible.
*
* @param attachments the attachments to add
*/
public void addAttachments(Collection<PermissionAttachment> attachments) {
this.attachments.addAll(attachments);
}
/**
* Obtains a {@link Contexts} instance for the player.
* Values are determined using the plugins ContextManager.
*
* @return the calculated contexts for the player.
*/
public Contexts calculateContexts() {
return new Contexts(
plugin.getContextManager().getApplicableContext(parent),
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
parent.isOp()
);
return plugin.getContextManager().getApplicableContexts(parent);
}
@Override
@ -144,46 +222,6 @@ public class LPPermissible extends PermissibleBase {
parent.setOp(value);
}
@Override
public boolean isPermissionSet(@NonNull String name) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(name);
return ts != Tristate.UNDEFINED || Permission.DEFAULT_PERMISSION.getValue(isOp());
}
@Override
public boolean isPermissionSet(@NonNull Permission perm) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(perm.getName());
if (ts != Tristate.UNDEFINED) {
return true;
}
if (!plugin.getConfiguration().get(ConfigKeys.APPLY_BUKKIT_DEFAULT_PERMISSIONS)) {
return Permission.DEFAULT_PERMISSION.getValue(isOp());
} else {
return perm.getDefault().getValue(isOp());
}
}
@Override
public boolean hasPermission(@NonNull String name) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(name);
return ts != Tristate.UNDEFINED ? ts.asBoolean() : Permission.DEFAULT_PERMISSION.getValue(isOp());
}
@Override
public boolean hasPermission(@NonNull Permission perm) {
Tristate ts = user.getUserData().getPermissionData(calculateContexts()).getPermissionValue(perm.getName());
if (ts != Tristate.UNDEFINED) {
return ts.asBoolean();
}
if (!plugin.getConfiguration().get(ConfigKeys.APPLY_BUKKIT_DEFAULT_PERMISSIONS)) {
return Permission.DEFAULT_PERMISSION.getValue(isOp());
} else {
return perm.getDefault().getValue(isOp());
}
}
@Override
public Set<PermissionAttachmentInfo> getEffectivePermissions() {
Set<PermissionAttachmentInfo> perms = new HashSet<>();

View File

@ -40,11 +40,27 @@ import me.lucko.luckperms.common.utils.ExtractedContexts;
import net.milkbowl.vault.chat.Chat;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.Map;
/**
* LuckPerms Vault Chat implementation
* All user lookups are cached.
* An implementation of the Vault {@link Chat} API using LuckPerms.
*
* Methods which change the state of data objects are likely to return immediately.
*
* LuckPerms is a multithreaded permissions plugin, and some actions require considerable
* time to execute. (database queries, re-population of caches, etc) In these cases, the
* methods will return immediately and the change will be executed asynchronously.
*
* Users of the Vault API expect these methods to be "main thread friendly", so unfortunately,
* we have to favour so called "performance" for consistency. The Vault API really wasn't designed
* with database backed permission plugins in mind. :(
*
* The methods which query offline players will explicitly FAIL if the corresponding player is not online.
* We cannot risk blocking the main thread to load in their data. Again, this is due to crap Vault
* design. There is nothing I can do about it.
*/
public class VaultChatHook extends Chat {
private final VaultPermissionHook perms;
@ -61,37 +77,209 @@ public class VaultChatHook extends Chat {
public boolean isEnabled() {
return perms.isEnabled();
}
private User getUser(String username) {
Player player = Bukkit.getPlayerExact(username);
return player == null ? null : perms.getPlugin().getUserManager().getIfLoaded(perms.getPlugin().getUuidCache().getUUID(player.getUniqueId()));
}
private void setMeta(PermissionHolder holder, String world, String node, String value) {
String finalWorld = perms.isIgnoreWorld() ? null : world;
if (holder == null || node.equals("")) return;
@Override
public String getPlayerPrefix(String world, @NonNull String player) {
final User user = getUser(player);
return getUserChatMeta(user, ChatMetaType.PREFIX, world);
}
perms.log("Setting meta: '" + node + "' for " + holder.getObjectName() + " on world " + world + ", server " + perms.getServer());
@Override
public void setPlayerPrefix(String world, @NonNull String player, @NonNull String prefix) {
final User user = getUser(player);
setChatMeta(user, ChatMetaType.PREFIX, prefix, world);
}
perms.getScheduler().execute(() -> {
holder.removeIf(n -> n.isMeta() && n.getMeta().getKey().equals(node));
@Override
public String getPlayerSuffix(String world, @NonNull String player) {
final User user = getUser(player);
return getUserChatMeta(user, ChatMetaType.SUFFIX, world);
}
Node.Builder metaNode = NodeFactory.makeMetaNode(node, value).setValue(true);
if (!perms.getServer().equalsIgnoreCase("global")) {
metaNode.setServer(perms.getServer());
}
if (finalWorld != null && !finalWorld.equals("") && !finalWorld.equals("global")) {
metaNode.setWorld(finalWorld);
}
@Override
public void setPlayerSuffix(String world, @NonNull String player, @NonNull String suffix) {
final User user = getUser(player);
setChatMeta(user, ChatMetaType.SUFFIX, suffix, world);
}
@Override
public String getGroupPrefix(String world, @NonNull String group) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
return getGroupChatMeta(g, ChatMetaType.PREFIX, world);
}
@Override
public void setGroupPrefix(String world, @NonNull String group, @NonNull String prefix) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
setChatMeta(g, ChatMetaType.PREFIX, prefix, world);
}
@Override
public String getGroupSuffix(String world, @NonNull String group) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
return getGroupChatMeta(g, ChatMetaType.SUFFIX, world);
}
@Override
public void setGroupSuffix(String world, @NonNull String group, @NonNull String suffix) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
setChatMeta(g, ChatMetaType.SUFFIX, suffix, world);
}
@Override
public int getPlayerInfoInteger(String world, @NonNull String player, @NonNull String node, int defaultValue) {
final User user = getUser(player);
try {
return Integer.parseInt(getUserMeta(user, node, world, String.valueOf(defaultValue)));
} catch (NumberFormatException e) {
return defaultValue;
}
}
@Override
public void setPlayerInfoInteger(String world, @NonNull String player, @NonNull String node, int value) {
final User user = getUser(player);
setMeta(user, node, String.valueOf(value), world);
}
@Override
public int getGroupInfoInteger(String world, @NonNull String group, @NonNull String node, int defaultValue) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
try {
return Integer.parseInt(getGroupMeta(g, node, world, String.valueOf(defaultValue)));
} catch (NumberFormatException e) {
return defaultValue;
}
}
@Override
public void setGroupInfoInteger(String world, @NonNull String group, @NonNull String node, int value) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
setMeta(g, node, String.valueOf(value), world);
}
@Override
public double getPlayerInfoDouble(String world, @NonNull String player, @NonNull String node, double defaultValue) {
final User user = getUser(player);
try {
return Double.parseDouble(getUserMeta(user, node, world, String.valueOf(defaultValue)));
} catch (NumberFormatException e) {
return defaultValue;
}
}
@Override
public void setPlayerInfoDouble(String world, @NonNull String player, @NonNull String node, double value) {
final User user = getUser(player);
setMeta(user, node, String.valueOf(value), world);
}
@Override
public double getGroupInfoDouble(String world, @NonNull String group, @NonNull String node, double defaultValue) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
try {
return Double.parseDouble(getGroupMeta(g, node, world, String.valueOf(defaultValue)));
} catch (NumberFormatException e) {
return defaultValue;
}
}
@Override
public void setGroupInfoDouble(String world, @NonNull String group, @NonNull String node, double value) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
setMeta(g, node, String.valueOf(value), world);
}
@Override
public boolean getPlayerInfoBoolean(String world, @NonNull String player, @NonNull String node, boolean defaultValue) {
final User user = getUser(player);
String s = getUserMeta(user, node, world, String.valueOf(defaultValue));
if (!s.equalsIgnoreCase("true") && !s.equalsIgnoreCase("false")) {
return defaultValue;
}
return Boolean.parseBoolean(s);
}
@Override
public void setPlayerInfoBoolean(String world, @NonNull String player, @NonNull String node, boolean value) {
final User user = getUser(player);
setMeta(user, node, String.valueOf(value), world);
}
@Override
public boolean getGroupInfoBoolean(String world, @NonNull String group, @NonNull String node, boolean defaultValue) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
String s = getGroupMeta(g, node, world, String.valueOf(defaultValue));
if (!s.equalsIgnoreCase("true") && !s.equalsIgnoreCase("false")) {
return defaultValue;
}
return Boolean.parseBoolean(s);
}
@Override
public void setGroupInfoBoolean(String world, @NonNull String group, @NonNull String node, boolean value) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
setMeta(g, node, String.valueOf(value), world);
}
@Override
public String getPlayerInfoString(String world, @NonNull String player, @NonNull String node, String defaultValue) {
final User user = getUser(player);
return getUserMeta(user, node, world, defaultValue);
}
@Override
public void setPlayerInfoString(String world, @NonNull String player, @NonNull String node, String value) {
final User user = getUser(player);
setMeta(user, node, value, world);
}
@Override
public String getGroupInfoString(String world, @NonNull String group, @NonNull String node, String defaultValue) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
return getGroupMeta(g, node, world, defaultValue);
}
@Override
public void setGroupInfoString(String world, @NonNull String group, @NonNull String node, String value) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
setMeta(g, node, value, world);
}
private void setMeta(PermissionHolder holder, String key, String value, String world) {
if (holder == null || key.isEmpty()) {
return;
}
String finalWorld = perms.correctWorld(world);
perms.log("Setting meta: '" + key + "' for " + holder.getObjectName() + " on world " + world + ", server " + perms.getServer());
perms.getExecutor().execute(() -> {
holder.removeIf(n -> n.isMeta() && n.getMeta().getKey().equals(key));
Node.Builder metaNode = NodeFactory.makeMetaNode(key, value).setValue(true);
metaNode.setServer(perms.getServer());
metaNode.setWorld(finalWorld);
holder.setPermission(metaNode.build());
perms.save(holder);
perms.holderSave(holder);
});
}
private void setChatMeta(ChatMetaType type, PermissionHolder holder, String value, String world) {
String finalWorld = perms.isIgnoreWorld() ? null : world;
if (holder == null) return;
if (value.equals("")) return;
private void setChatMeta(PermissionHolder holder, ChatMetaType type, String value, String world) {
if (holder == null || value.equals("")) {
return;
}
String finalWorld = perms.correctWorld(world);
perms.log("Setting " + type.name().toLowerCase() + " for " + holder.getObjectName() + " on world " + world + ", server " + perms.getServer());
perms.getScheduler().execute(() -> {
perms.getExecutor().execute(() -> {
// remove all prefixes/suffixes directly set on the user/group
holder.removeIf(type::matches);
@ -101,32 +289,32 @@ public class VaultChatHook extends Chat {
.mapToInt(e -> e).max().orElse(0) + 10;
Node.Builder chatMetaNode = NodeFactory.makeChatMetaNode(type, priority, value);
if (!perms.getServer().equalsIgnoreCase("global")) {
chatMetaNode.setServer(perms.getServer());
}
if (finalWorld != null && !finalWorld.equals("") && !finalWorld.equals("global")) {
chatMetaNode.setWorld(finalWorld);
}
chatMetaNode.setServer(perms.getServer());
chatMetaNode.setWorld(finalWorld);
holder.setPermission(chatMetaNode.build());
perms.save(holder);
perms.holderSave(holder);
});
}
private String getUserMeta(User user, String world, String node, String defaultValue) {
if (user == null) return defaultValue;
world = perms.isIgnoreWorld() ? null : world;
private String getUserMeta(User user, String node, String world, String defaultValue) {
if (user == null) {
return defaultValue;
}
world = perms.correctWorld(world);
perms.log("Getting meta: '" + node + "' for user " + user.getFriendlyName() + " on world " + world + ", server " + perms.getServer());
String ret = user.getUserData().getMetaData(perms.createContextForWorldLookup(perms.getPlugin().getPlayer(user), world)).getMeta().get(node);
return ret != null ? ret : defaultValue;
}
private String getUserChatMeta(ChatMetaType type, User user, String world) {
if (user == null) return "";
world = perms.isIgnoreWorld() ? null : world;
private String getUserChatMeta(User user, ChatMetaType type, String world) {
if (user == null) {
return "";
}
world = perms.correctWorld(world);
perms.log("Getting " + type.name().toLowerCase() + " for user " + user.getFriendlyName() + " on world " + world + ", server " + perms.getServer());
MetaData data = user.getUserData().getMetaData(perms.createContextForWorldLookup(perms.getPlugin().getPlayer(user), world));
@ -134,10 +322,12 @@ public class VaultChatHook extends Chat {
return ret != null ? ret : "";
}
private String getGroupMeta(Group group, String world, String node, String defaultValue) {
world = perms.isIgnoreWorld() ? null : world;
if (group == null || node.equals("")) return defaultValue;
private String getGroupMeta(Group group, String node, String world, String defaultValue) {
if (group == null || node.equals("")) {
return defaultValue;
}
world = perms.correctWorld(world);
perms.log("Getting meta: '" + node + "' for group " + group.getName() + " on world " + world + ", server " + perms.getServer());
for (Node n : group.mergePermissionsToList()) {
@ -154,10 +344,12 @@ public class VaultChatHook extends Chat {
return defaultValue;
}
private String getGroupChatMeta(ChatMetaType type, Group group, String world) {
world = perms.isIgnoreWorld() ? null : world;
if (group == null) return "";
private String getGroupChatMeta(Group group, ChatMetaType type, String world) {
if (group == null) {
return "";
}
world = perms.correctWorld(world);
perms.log("Getting " + type.name().toLowerCase() + " for group " + group + " on world " + world + ", server " + perms.getServer());
int priority = Integer.MIN_VALUE;
@ -179,172 +371,4 @@ public class VaultChatHook extends Chat {
return meta != null ? meta : "";
}
@Override
public String getPlayerPrefix(String world, @NonNull String player) {
final User user = perms.getPlugin().getUserManager().getByUsername(player);
return getUserChatMeta(ChatMetaType.PREFIX, user, world);
}
@Override
public void setPlayerPrefix(String world, @NonNull String player, @NonNull String prefix) {
final User user = perms.getPlugin().getUserManager().getByUsername(player);
setChatMeta(ChatMetaType.PREFIX, user, prefix, world);
}
@Override
public String getPlayerSuffix(String world, @NonNull String player) {
final User user = perms.getPlugin().getUserManager().getByUsername(player);
return getUserChatMeta(ChatMetaType.SUFFIX, user, world);
}
@Override
public void setPlayerSuffix(String world, @NonNull String player, @NonNull String suffix) {
final User user = perms.getPlugin().getUserManager().getByUsername(player);
setChatMeta(ChatMetaType.SUFFIX, user, suffix, world);
}
@Override
public String getGroupPrefix(String world, @NonNull String group) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
return getGroupChatMeta(ChatMetaType.PREFIX, g, world);
}
@Override
public void setGroupPrefix(String world, @NonNull String group, @NonNull String prefix) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
setChatMeta(ChatMetaType.PREFIX, g, prefix, world);
}
@Override
public String getGroupSuffix(String world, @NonNull String group) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
return getGroupChatMeta(ChatMetaType.SUFFIX, g, world);
}
@Override
public void setGroupSuffix(String world, @NonNull String group, @NonNull String suffix) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
setChatMeta(ChatMetaType.SUFFIX, g, suffix, world);
}
@Override
public int getPlayerInfoInteger(String world, @NonNull String player, @NonNull String node, int defaultValue) {
final User user = perms.getPlugin().getUserManager().getByUsername(player);
try {
return Integer.parseInt(getUserMeta(user, world, node, String.valueOf(defaultValue)));
} catch (NumberFormatException e) {
return defaultValue;
}
}
@Override
public void setPlayerInfoInteger(String world, @NonNull String player, @NonNull String node, int value) {
final User user = perms.getPlugin().getUserManager().getByUsername(player);
setMeta(user, world, node, String.valueOf(value));
}
@Override
public int getGroupInfoInteger(String world, @NonNull String group, @NonNull String node, int defaultValue) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
try {
return Integer.parseInt(getGroupMeta(g, world, node, String.valueOf(defaultValue)));
} catch (NumberFormatException e) {
return defaultValue;
}
}
@Override
public void setGroupInfoInteger(String world, @NonNull String group, @NonNull String node, int value) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
setMeta(g, world, node, String.valueOf(value));
}
@Override
public double getPlayerInfoDouble(String world, @NonNull String player, @NonNull String node, double defaultValue) {
final User user = perms.getPlugin().getUserManager().getByUsername(player);
try {
return Double.parseDouble(getUserMeta(user, world, node, String.valueOf(defaultValue)));
} catch (NumberFormatException e) {
return defaultValue;
}
}
@Override
public void setPlayerInfoDouble(String world, @NonNull String player, @NonNull String node, double value) {
final User user = perms.getPlugin().getUserManager().getByUsername(player);
setMeta(user, world, node, String.valueOf(value));
}
@Override
public double getGroupInfoDouble(String world, @NonNull String group, @NonNull String node, double defaultValue) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
try {
return Double.parseDouble(getGroupMeta(g, world, node, String.valueOf(defaultValue)));
} catch (NumberFormatException e) {
return defaultValue;
}
}
@Override
public void setGroupInfoDouble(String world, @NonNull String group, @NonNull String node, double value) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
setMeta(g, world, node, String.valueOf(value));
}
@Override
public boolean getPlayerInfoBoolean(String world, @NonNull String player, @NonNull String node, boolean defaultValue) {
final User user = perms.getPlugin().getUserManager().getByUsername(player);
String s = getUserMeta(user, world, node, String.valueOf(defaultValue));
if (!s.equalsIgnoreCase("true") && !s.equalsIgnoreCase("false")) {
return defaultValue;
}
return Boolean.parseBoolean(s);
}
@Override
public void setPlayerInfoBoolean(String world, @NonNull String player, @NonNull String node, boolean value) {
final User user = perms.getPlugin().getUserManager().getByUsername(player);
setMeta(user, world, node, String.valueOf(value));
}
@Override
public boolean getGroupInfoBoolean(String world, @NonNull String group, @NonNull String node, boolean defaultValue) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
String s = getGroupMeta(g, world, node, String.valueOf(defaultValue));
if (!s.equalsIgnoreCase("true") && !s.equalsIgnoreCase("false")) {
return defaultValue;
}
return Boolean.parseBoolean(s);
}
@Override
public void setGroupInfoBoolean(String world, @NonNull String group, @NonNull String node, boolean value) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
setMeta(g, world, node, String.valueOf(value));
}
@Override
public String getPlayerInfoString(String world, @NonNull String player, @NonNull String node, String defaultValue) {
final User user = perms.getPlugin().getUserManager().getByUsername(player);
return getUserMeta(user, world, node, defaultValue);
}
@Override
public void setPlayerInfoString(String world, @NonNull String player, @NonNull String node, String value) {
final User user = perms.getPlugin().getUserManager().getByUsername(player);
setMeta(user, world, node, value);
}
@Override
public String getGroupInfoString(String world, @NonNull String group, @NonNull String node, String defaultValue) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
return getGroupMeta(g, world, node, defaultValue);
}
@Override
public void setGroupInfoString(String world, @NonNull String group, @NonNull String node, String value) {
final Group g = perms.getPlugin().getGroupManager().getIfLoaded(group);
setMeta(g, world, node, value);
}
}

View File

@ -36,12 +36,12 @@ import java.util.concurrent.Executor;
/**
* Sequential executor for Vault modifications
*/
public class VaultScheduler implements Runnable, Executor {
public class VaultExecutor implements Runnable, Executor {
private BukkitTask task = null;
private final List<Runnable> tasks = new ArrayList<>();
public VaultScheduler(LPBukkitPlugin plugin) {
public VaultExecutor(LPBukkitPlugin plugin) {
task = plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, this, 1L, 1L);
}
@ -60,15 +60,21 @@ public class VaultScheduler implements Runnable, Executor {
return;
}
toRun = new ArrayList<>();
toRun = new ArrayList<>(tasks.size());
toRun.addAll(tasks);
tasks.clear();
}
toRun.forEach(Runnable::run);
for (Runnable runnable : toRun) {
try {
runnable.run();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void cancelTask() {
public void close() {
if (task != null) {
task.cancel();
task = null;

View File

@ -39,10 +39,16 @@ import org.bukkit.plugin.ServicesManager;
* Handles hooking with the Vault API
*/
@Getter
public class VaultHook {
public class VaultHookManager {
private VaultChatHook chatHook = null;
private VaultPermissionHook permissionHook = null;
/**
* Registers the LuckPerms implementation of {@link Permission} and {@link Chat} with
* the service manager.
*
* @param plugin the plugin
*/
public void hook(LPBukkitPlugin plugin) {
try {
if (permissionHook == null) {
@ -62,19 +68,31 @@ public class VaultHook {
}
}
/**
* Unregisters the LuckPerms Vault hooks, if present.
*
* @param plugin the plugin
*/
public void unhook(LPBukkitPlugin plugin) {
final ServicesManager sm = plugin.getServer().getServicesManager();
if (permissionHook != null) {
sm.unregister(Permission.class, permissionHook);
permissionHook.getScheduler().cancelTask();
permissionHook.getExecutor().close();
permissionHook = null;
}
if (chatHook != null) {
sm.unregister(Chat.class, chatHook);
chatHook = null;
}
}
/**
* Gets if the Vault classes are registered.
*
* @return true if hooked
*/
public boolean isHooked() {
return permissionHook != null && chatHook != null;
}

View File

@ -45,37 +45,52 @@ import me.lucko.luckperms.common.utils.ExtractedContexts;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* LuckPerms Vault Permission implementation
* Most lookups are cached.
* An implementation of the Vault {@link Permission} API using LuckPerms.
*
* Methods which change the state of data objects are likely to return immediately.
*
* LuckPerms is a multithreaded permissions plugin, and some actions require considerable
* time to execute. (database queries, re-population of caches, etc) In these cases, the
* methods will return immediately and the change will be executed asynchronously.
*
* Users of the Vault API expect these methods to be "main thread friendly", so unfortunately,
* we have to favour so called "performance" for consistency. The Vault API really wasn't designed
* with database backed permission plugins in mind. :(
*
* The methods which query offline players will explicitly FAIL if the corresponding player is not online.
* We cannot risk blocking the main thread to load in their data. Again, this is due to crap Vault
* design. There is nothing I can do about it.
*/
@Getter
public class VaultPermissionHook extends Permission {
// the plugin instance
private LPBukkitPlugin plugin;
private VaultScheduler scheduler;
private final String name = "LuckPerms";
private Function<String, String> worldCorrectionFunction = s -> isIgnoreWorld() ? null : s;
// an executor for Vault modifications.
private VaultExecutor executor;
public VaultPermissionHook(LPBukkitPlugin plugin) {
this.plugin = plugin;
this.scheduler = new VaultScheduler(plugin);
super.plugin = plugin;
super.plugin = JavaPlugin.getProvidingPlugin(Permission.class);
this.executor = new VaultExecutor(plugin);
}
public void log(String s) {
if (plugin.getConfiguration().get(ConfigKeys.VAULT_DEBUG)) {
plugin.getLog().info("[VAULT] " + s);
}
@Override
public String getName() {
return "LuckPerms";
}
@Override
@ -88,149 +103,122 @@ public class VaultPermissionHook extends Permission {
return true;
}
/**
* Generic method to add a permission to a holder
*
* @param world the world to add in
* @param holder the holder to add the permission to
* @param permission the permission to add
*/
private CompletableFuture<Void> add(String world, PermissionHolder holder, String permission) {
return CompletableFuture.runAsync(() -> {
DataMutateResult result;
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
result = holder.setPermission(NodeFactory.make(permission, true, getServer(), world));
} else {
result = holder.setPermission(NodeFactory.make(permission, true, getServer()));
}
if (result.asBoolean()) {
save(holder);
}
}, scheduler);
@Override
public boolean hasGroupSupport() {
return true;
}
/**
* Generic method to remove a permission from a holder
*
* @param world the world to remove in
* @param holder the holder to remove the permission from
* @param permission the permission to remove
*/
private CompletableFuture<Void> remove(String world, PermissionHolder holder, String permission) {
return CompletableFuture.runAsync(() -> {
DataMutateResult result;
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
result = holder.unsetPermission(NodeFactory.make(permission, getServer(), world));
} else {
result = holder.unsetPermission(NodeFactory.make(permission, getServer()));
}
if (result.asBoolean()) {
save(holder);
}
}, scheduler);
@Override
public String[] getGroups() {
return plugin.getGroupManager().getAll().keySet().toArray(new String[0]);
}
/**
* Utility method to asynchronously save a user or group
*
* @param holder the holder instance
*/
public void save(PermissionHolder holder) {
if (holder instanceof User) {
User u = (User) holder;
plugin.getStorage().saveUser(u).thenRunAsync(() -> u.getRefreshBuffer().request(), plugin.getScheduler().async());
}
if (holder instanceof Group) {
Group g = (Group) holder;
plugin.getStorage().saveGroup(g).thenRunAsync(() -> plugin.getUpdateTaskBuffer().request(), plugin.getScheduler().async());
}
@Override
public boolean has(@NonNull CommandSender sender, @NonNull String permission) {
return sender.hasPermission(permission);
}
public Contexts createContextForWorldSet(String world) {
MutableContextSet context = MutableContextSet.create();
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
context.add("world", world.toLowerCase());
}
context.add("server", getServer());
return new Contexts(context, isIncludeGlobal(), true, true, true, true, false);
}
public Contexts createContextForWorldLookup(String world) {
MutableContextSet context = MutableContextSet.create();
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
context.add("world", world.toLowerCase());
}
context.add("server", getServer());
context.addAll(plugin.getConfiguration().getContextsFile().getStaticContexts());
return new Contexts(context, isIncludeGlobal(), true, true, true, true, false);
}
public Contexts createContextForWorldLookup(Player player, String world) {
MutableContextSet context = MutableContextSet.create();
// use player context
if (player != null) {
ImmutableContextSet applicableContext = plugin.getContextManager().getApplicableContext(player);
context.addAll(applicableContext);
} else {
// at least given them the static context defined for this instance
context.addAll(plugin.getConfiguration().getContextsFile().getStaticContexts());
}
// worlds & servers get set depending on the config setting
context.removeAll("world");
context.removeAll("server");
// add the vault settings
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
context.add("world", world.toLowerCase());
}
context.add("server", getServer());
return new Contexts(context, isIncludeGlobal(), true, true, true, true, false);
@Override
public boolean has(@NonNull Player player, @NonNull String permission) {
return player.hasPermission(permission);
}
@Override
public boolean playerHas(String world, @NonNull String player, @NonNull String permission) {
world = worldCorrectionFunction.apply(world);
return playerHas(world, Bukkit.getPlayerExact(player), permission);
}
@Override
public boolean playerHas(String world, @NonNull OfflinePlayer player, @NonNull String permission) {
return playerHas(world, player.getPlayer(), permission);
}
private boolean playerHas(String world, Player player, String permission) {
world = correctWorld(world);
log("Checking if player " + player + " has permission: " + permission + " on world " + world + ", server " + getServer());
User user = plugin.getUserManager().getByUsername(player);
if (user == null) return false;
if (player == null) {
return false;
}
User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(player.getUniqueId()));
if (user == null) {
return false;
}
// Effectively fallback to the standard Bukkit #hasPermission check.
return user.getUserData().getPermissionData(createContextForWorldLookup(plugin.getPlayer(user), world)).getPermissionValue(permission).asBoolean();
return user.getUserData().getPermissionData(createContextForWorldLookup(player, world)).getPermissionValue(permission).asBoolean();
}
@Override
public boolean playerAdd(String world, @NonNull String player, @NonNull String permission) {
world = worldCorrectionFunction.apply(world);
return playerAdd(world, Bukkit.getPlayerExact(player), permission);
}
@SuppressWarnings("deprecation")
@Override
public boolean playerAdd(World world, @NonNull String player, @NonNull String permission) {
return playerAdd(world == null ? null : world.getName(), Bukkit.getPlayerExact(player), permission);
}
@Override
public boolean playerAdd(String world, @NonNull OfflinePlayer player, @NonNull String permission) {
return playerAdd(world, player.getPlayer(), permission);
}
private boolean playerAdd(String world, Player player, String permission) {
world = correctWorld(world);
log("Adding permission to player " + player + ": '" + permission + "' on world " + world + ", server " + getServer());
final User user = plugin.getUserManager().getByUsername(player);
if (user == null) return false;
if (player == null) {
return false;
}
add(world, user, permission);
final User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(player.getUniqueId()));
if (user == null) {
return false;
}
holderAddPermission(user, permission, world);
return true;
}
@Override
public boolean playerRemove(String world, @NonNull String player, @NonNull String permission) {
world = worldCorrectionFunction.apply(world);
return playerRemove(world, Bukkit.getPlayerExact(player), permission);
}
@Override
public boolean playerRemove(String world, @NonNull OfflinePlayer player, @NonNull String permission) {
return playerRemove(world, player.getPlayer(), permission);
}
@SuppressWarnings("deprecation")
@Override
public boolean playerRemove(World world, @NonNull String player, @NonNull String permission) {
return playerRemove(world == null ? null : world.getName(), Bukkit.getPlayerExact(player), permission);
}
private boolean playerRemove(String world, Player player, String permission) {
world = correctWorld(world);
log("Removing permission from player " + player + ": '" + permission + "' on world " + world + ", server " + getServer());
final User user = plugin.getUserManager().getByUsername(player);
if (user == null) return false;
if (player == null) {
return false;
}
remove(world, user, permission);
final User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(player.getUniqueId()));
if (user == null) {
return false;
}
holderRemovePermission(user, permission, world);
return true;
}
@Override
public boolean groupHas(String world, @NonNull String groupName, @NonNull String permission) {
world = worldCorrectionFunction.apply(world);
world = correctWorld(world);
log("Checking if group " + groupName + " has permission: " + permission + " on world " + world + ", server " + getServer());
final Group group = plugin.getGroupManager().getIfLoaded(groupName);
@ -243,105 +231,123 @@ public class VaultPermissionHook extends Permission {
@Override
public boolean groupAdd(String world, @NonNull String groupName, @NonNull String permission) {
world = worldCorrectionFunction.apply(world);
world = correctWorld(world);
log("Adding permission to group " + groupName + ": '" + permission + "' on world " + world + ", server " + getServer());
final Group group = plugin.getGroupManager().getIfLoaded(groupName);
if (group == null) return false;
add(world, group, permission);
holderAddPermission(group, permission, world);
return true;
}
@Override
public boolean groupRemove(String world, @NonNull String groupName, @NonNull String permission) {
world = worldCorrectionFunction.apply(world);
world = correctWorld(world);
log("Removing permission from group " + groupName + ": '" + permission + "' on world " + world + ", server " + getServer());
final Group group = plugin.getGroupManager().getIfLoaded(groupName);
if (group == null) return false;
remove(world, group, permission);
holderRemovePermission(group, permission, world);
return true;
}
@Override
public boolean playerInGroup(String world, @NonNull String player, @NonNull String group) {
world = worldCorrectionFunction.apply(world);
log("Checking if player " + player + " is in group: " + group + " on world " + world + ", server " + getServer());
public boolean playerInGroup(String world, String player, @NonNull String group) {
return playerHas(world, player, "group." + group);
}
final User user = plugin.getUserManager().getByUsername(player);
if (user == null) return false;
String w = world; // screw effectively final
return user.getNodes().values().stream()
.filter(Node::isGroupNode)
.filter(n -> n.shouldApplyWithContext(createContextForWorldLookup(plugin.getPlayer(user), w).getContexts()))
.map(Node::getGroupName)
.anyMatch(s -> s.equalsIgnoreCase(group));
@SuppressWarnings("deprecation")
@Override
public boolean playerInGroup(World world, String player, @NonNull String group) {
return playerHas(world, player, "group." + group);
}
@Override
public boolean playerAddGroup(String world, @NonNull String player, @NonNull String groupName) {
world = worldCorrectionFunction.apply(world);
log("Adding player " + player + " to group: '" + groupName + "' on world " + world + ", server " + getServer());
final User user = plugin.getUserManager().getByUsername(player);
if (user == null) return false;
final Group group = plugin.getGroupManager().getIfLoaded(groupName);
if (group == null) return false;
String w = world;
scheduler.execute(() -> {
DataMutateResult result;
if (w != null && !w.equals("") && !w.equalsIgnoreCase("global")) {
result = user.setInheritGroup(group, ImmutableContextSet.of("server", getServer(), "world", w));
} else {
result = user.setInheritGroup(group, ImmutableContextSet.singleton("server", getServer()));
}
if (result.asBoolean()) {
save(user);
}
});
return true;
public boolean playerInGroup(String world, OfflinePlayer player, @NonNull String group) {
return playerHas(world, player, "group." + group);
}
@Override
public boolean playerRemoveGroup(String world, @NonNull String player, @NonNull String groupName) {
world = worldCorrectionFunction.apply(world);
log("Removing player " + player + " from group: '" + groupName + "' on world " + world + ", server " + getServer());
public boolean playerInGroup(Player player, @NonNull String group) {
return playerHas(player, "group." + group);
}
final User user = plugin.getUserManager().getByUsername(player);
if (user == null) return false;
private boolean checkGroupExists(String group) {
return plugin.getGroupManager().isLoaded(group);
}
final Group group = plugin.getGroupManager().getIfLoaded(groupName);
if (group == null) return false;
@Override
public boolean playerAddGroup(String world, String player, @NonNull String group) {
return checkGroupExists(group) && playerAdd(world, player, "group." + group);
}
String w = world;
scheduler.execute(() -> {
DataMutateResult result;
if (w != null && !w.equals("") && !w.equalsIgnoreCase("global")) {
result = user.unsetInheritGroup(group, ImmutableContextSet.of("server", getServer(), "world", w));
} else {
result = user.unsetInheritGroup(group, ImmutableContextSet.singleton("server", getServer()));
}
@SuppressWarnings("deprecation")
@Override
public boolean playerAddGroup(World world, String player, @NonNull String group) {
return checkGroupExists(group) && playerAdd(world, player, "group." + group);
}
if (result.asBoolean()) {
save(user);
}
});
return true;
@Override
public boolean playerAddGroup(String world, OfflinePlayer player, @NonNull String group) {
return checkGroupExists(group) && playerAdd(world, player, "group." + group);
}
@Override
public boolean playerAddGroup(Player player, @NonNull String group) {
return checkGroupExists(group) && playerAdd(player, "group." + group);
}
@Override
public boolean playerRemoveGroup(String world, String player, @NonNull String group) {
return checkGroupExists(group) && playerRemove(world, player, "group." + group);
}
@SuppressWarnings("deprecation")
@Override
public boolean playerRemoveGroup(World world, String player, @NonNull String group) {
return checkGroupExists(group) && playerRemove(world, player, "group." + group);
}
@Override
public boolean playerRemoveGroup(String world, OfflinePlayer player, @NonNull String group) {
return checkGroupExists(group) && playerRemove(world, player, "group." + group);
}
@Override
public boolean playerRemoveGroup(Player player, @NonNull String group) {
return checkGroupExists(group) && playerRemove(player, "group." + group);
}
@Override
public String[] getPlayerGroups(String world, @NonNull String player) {
world = worldCorrectionFunction.apply(world);
return getPlayerGroups(world, Bukkit.getPlayerExact(player));
}
@SuppressWarnings("deprecation")
@Override
public String[] getPlayerGroups(World world, @NonNull String player) {
return getPlayerGroups(world == null ? null : world.getName(), Bukkit.getPlayerExact(player));
}
@Override
public String[] getPlayerGroups(String world, @NonNull OfflinePlayer player) {
return getPlayerGroups(world, player.getPlayer());
}
private String[] getPlayerGroups(String world, Player player) {
world = correctWorld(world);
log("Getting groups of player: " + player + ", on world " + world + ", server " + getServer());
User user = plugin.getUserManager().getByUsername(player);
if (user == null) return new String[0];
if (player == null) {
return new String[0];
}
User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(player.getUniqueId()));
if (user == null) {
return new String[0];
}
String w = world; // screw effectively final
return user.getNodes().values().stream()
@ -353,9 +359,29 @@ public class VaultPermissionHook extends Permission {
@Override
public String getPrimaryGroup(String world, @NonNull String player) {
world = worldCorrectionFunction.apply(world);
return getPrimaryGroup(world, Bukkit.getPlayerExact(player));
}
@SuppressWarnings("deprecation")
@Override
public String getPrimaryGroup(World world, @NonNull String player) {
return getPrimaryGroup(world == null ? null : world.getName(), Bukkit.getPlayerExact(player));
}
@Override
public String getPrimaryGroup(String world, @NonNull OfflinePlayer player) {
return getPrimaryGroup(world, player.getPlayer());
}
private String getPrimaryGroup(String world, Player player) {
world = correctWorld(world);
log("Getting primary group of player: " + player);
final User user = plugin.getUserManager().getByUsername(player);
if (player == null) {
return null;
}
final User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(player.getUniqueId()));
if (user == null) {
return null;
@ -428,16 +454,102 @@ public class VaultPermissionHook extends Permission {
return plugin.getConfiguration().get(ConfigKeys.GROUP_NAME_REWRITES).getOrDefault(g, g);
}
@Override
public String[] getGroups() {
return plugin.getGroupManager().getAll().keySet().toArray(new String[0]);
public void log(String s) {
if (plugin.getConfiguration().get(ConfigKeys.VAULT_DEBUG)) {
plugin.getLog().info("[VAULT] " + s);
}
}
@Override
public boolean hasGroupSupport() {
return true;
String correctWorld(String world) {
return isIgnoreWorld() ? null : world;
}
// utility methods for modifying the state of PermissionHolders
private void holderAddPermission(PermissionHolder holder, String permission, String world) {
executor.execute(() -> {
DataMutateResult result;
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
result = holder.setPermission(NodeFactory.make(permission, true, getServer(), world));
} else {
result = holder.setPermission(NodeFactory.make(permission, true, getServer()));
}
if (result.asBoolean()) {
holderSave(holder);
}
});
}
private void holderRemovePermission(PermissionHolder holder, String permission, String world) {
executor.execute(() -> {
DataMutateResult result;
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
result = holder.unsetPermission(NodeFactory.make(permission, getServer(), world));
} else {
result = holder.unsetPermission(NodeFactory.make(permission, getServer()));
}
if (result.asBoolean()) {
holderSave(holder);
}
});
}
public void holderSave(PermissionHolder holder) {
if (holder instanceof User) {
User u = (User) holder;
plugin.getStorage().saveUser(u).thenRunAsync(() -> u.getRefreshBuffer().request(), plugin.getScheduler().async());
}
if (holder instanceof Group) {
Group g = (Group) holder;
plugin.getStorage().saveGroup(g).thenRunAsync(() -> plugin.getUpdateTaskBuffer().request(), plugin.getScheduler().async());
}
}
// helper methods to build Contexts instances for different world/server combinations
public Contexts createContextForWorldSet(String world) {
MutableContextSet context = MutableContextSet.create();
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
context.add("world", world.toLowerCase());
}
context.add("server", getServer());
return new Contexts(context, isIncludeGlobal(), true, true, true, true, false);
}
public Contexts createContextForWorldLookup(String world) {
MutableContextSet context = MutableContextSet.create();
if (world != null && !world.equals("") && !world.equalsIgnoreCase("global")) {
context.add("world", world.toLowerCase());
}
context.add("server", getServer());
context.addAll(plugin.getConfiguration().getContextsFile().getStaticContexts());
return new Contexts(context, isIncludeGlobal(), true, true, true, true, false);
}
public Contexts createContextForWorldLookup(@NonNull Player player, String world) {
MutableContextSet context = MutableContextSet.create();
// use player context
ImmutableContextSet applicableContext = plugin.getContextManager().getApplicableContext(player);
context.addAll(applicableContext);
// worlds & servers get set depending on the config setting
context.removeAll("world");
context.removeAll("server");
// add the vault settings
if (world != null && !world.isEmpty() && !world.equalsIgnoreCase("global")) {
context.add("world", world.toLowerCase());
}
context.add("server", getServer());
return new Contexts(context, isIncludeGlobal(), true, true, true, true, false);
}
// helper methods to just pull values from the config.
String getServer() {
return plugin.getConfiguration().get(ConfigKeys.VAULT_SERVER);
}

View File

@ -150,16 +150,7 @@ public class BungeeListener implements Listener {
return;
}
Contexts contexts = new Contexts(
plugin.getContextManager().getApplicableContext(player),
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
false
);
Contexts contexts = plugin.getContextManager().getApplicableContexts(player);
Tristate result = user.getUserData().getPermissionData(contexts).getPermissionValue(e.getPermission());
if (result == Tristate.UNDEFINED && plugin.getConfiguration().get(ConfigKeys.APPLY_BUNGEE_CONFIG_PERMISSIONS)) {
return; // just use the result provided by the proxy when the event was created
@ -182,16 +173,7 @@ public class BungeeListener implements Listener {
return;
}
Contexts contexts = new Contexts(
plugin.getContextManager().getApplicableContext(player),
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
false
);
Contexts contexts = plugin.getContextManager().getApplicableContexts(player);
Tristate result = user.getUserData().getPermissionData(contexts).getPermissionValue(e.getPermission());
if (result == Tristate.UNDEFINED && plugin.getConfiguration().get(ConfigKeys.APPLY_BUNGEE_CONFIG_PERMISSIONS)) {
return; // just use the result provided by the proxy when the event was created
@ -216,16 +198,7 @@ public class BungeeListener implements Listener {
return;
}
Contexts contexts = new Contexts(
set.makeImmutable(),
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
false
);
Contexts contexts = plugin.getContextManager().formContexts(e.getPlayer(), set.makeImmutable());
user.getUserData().preCalculate(contexts);
});
}

View File

@ -31,6 +31,7 @@ import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.Logger;
import me.lucko.luckperms.api.PlatformType;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.bungee.messaging.BungeeMessagingService;
import me.lucko.luckperms.bungee.messaging.RedisBungeeMessagingService;
import me.lucko.luckperms.bungee.util.RedisBungeeUtil;
@ -231,13 +232,26 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
calculatorFactory = new BungeeCalculatorFactory(this);
cachedStateManager = new CachedStateManager(this);
contextManager = new ContextManager<>();
contextManager = new ContextManager<ProxiedPlayer>() {
@Override
public Contexts formContexts(ProxiedPlayer player, ImmutableContextSet contextSet) {
return new Contexts(
contextSet,
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
false
);
}
};
BackendServerCalculator serverCalculator = new BackendServerCalculator(this);
contextManager.registerCalculator(serverCalculator);
StaticCalculator<ProxiedPlayer> staticCalculator = new StaticCalculator<>(getConfiguration());
contextManager.registerCalculator(staticCalculator);
contextManager.registerStaticCalculator(staticCalculator);
contextManager.registerCalculator(staticCalculator, true);
// register with the LP API
apiProvider = new ApiProvider(this);
@ -338,15 +352,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
if (player == null) {
return null;
}
return new Contexts(
getContextManager().getApplicableContext(player),
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
false
);
return contextManager.getApplicableContexts(player);
}
@Override
@ -384,19 +390,11 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
@Override
public Set<Contexts> getPreProcessContexts(boolean op) {
Set<ContextSet> c = new HashSet<>();
Set<ImmutableContextSet> c = new HashSet<>();
c.add(ContextSet.empty());
c.add(ContextSet.singleton("server", getConfiguration().get(ConfigKeys.SERVER)));
return c.stream()
.map(set -> new Contexts(
set,
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
false
))
.map(set -> contextManager.formContexts(null, set))
.collect(Collectors.toSet());
}
}

View File

@ -27,7 +27,9 @@ package me.lucko.luckperms.common.contexts;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.RemovalListener;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.context.ContextCalculator;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.api.context.MutableContextSet;
@ -36,20 +38,31 @@ import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
public class ContextManager<T> {
public abstract class ContextManager<T> {
private final List<ContextCalculator<T>> calculators = new CopyOnWriteArrayList<>();
private final List<ContextCalculator<?>> staticCalculators = new CopyOnWriteArrayList<>();
private final LoadingCache<T, ImmutableContextSet> cache = Caffeine.newBuilder()
private final LoadingCache<T, ImmutableContextSet> activeContextCache = Caffeine.newBuilder()
.weakKeys()
.expireAfterWrite(50L, TimeUnit.MILLISECONDS)
.removalListener((RemovalListener<T, ImmutableContextSet>) (t, contextSet, removalCause) -> invalidateContextsCache(t))
.build(t -> calculateApplicableContext(t, MutableContextSet.create()).makeImmutable());
private final LoadingCache<T, Contexts> contextsCache = Caffeine.newBuilder()
.weakKeys()
.build(t -> formContexts(t, getApplicableContext(t)));
public ImmutableContextSet getApplicableContext(T subject) {
return cache.get(subject);
return activeContextCache.get(subject);
}
public Contexts getApplicableContexts(T subject) {
return contextsCache.get(subject);
}
public abstract Contexts formContexts(T t, ImmutableContextSet contextSet);
private MutableContextSet calculateApplicableContext(T subject, MutableContextSet accumulator) {
for (ContextCalculator<T> calculator : calculators) {
try {
@ -62,13 +75,21 @@ public class ContextManager<T> {
return accumulator;
}
public void registerCalculator(ContextCalculator<T> calculator) {
// calculators registered first should have priority (and be checked last.)
calculators.add(0, calculator);
private void invalidateContextsCache(T t) {
contextsCache.invalidate(t);
}
public void registerStaticCalculator(ContextCalculator<?> calculator) {
staticCalculators.add(0, calculator);
public void registerCalculator(ContextCalculator<T> calculator) {
registerCalculator(calculator, false);
}
public void registerCalculator(ContextCalculator<T> calculator, boolean isStatic) {
// calculators registered first should have priority (and be checked last.)
calculators.add(0, calculator);
if (isStatic) {
staticCalculators.add(0, calculator);
}
}
public ImmutableContextSet getStaticContexts() {
@ -80,7 +101,7 @@ public class ContextManager<T> {
}
public void invalidateCache(T subject){
cache.invalidate(subject);
activeContextCache.invalidate(subject);
}
public int getCalculatorsSize() {

View File

@ -68,7 +68,7 @@ public class User extends PermissionHolder implements Identifiable<UserIdentifie
* The users data cache instance, if present.
*/
@Getter
private final UserCache userData;
private final UserCache userData = new UserCache(this);
@Getter
private BufferedRequest<Void> refreshBuffer = new BufferedRequest<Void>(1000L, r -> getPlugin().doAsync(r)) {
@ -87,7 +87,6 @@ public class User extends PermissionHolder implements Identifiable<UserIdentifie
this.uuid = uuid;
this.primaryGroup = plugin.getConfiguration().get(ConfigKeys.PRIMARY_GROUP_CALCULATION).apply(this);
this.userData = new UserCache(this);
getPlugin().getApiProvider().getEventFactory().handleUserCacheLoad(this, userData);
}
@ -97,7 +96,6 @@ public class User extends PermissionHolder implements Identifiable<UserIdentifie
setName(name, false);
this.primaryGroup = plugin.getConfiguration().get(ConfigKeys.PRIMARY_GROUP_CALCULATION).apply(this);
this.userData = new UserCache(this);
getPlugin().getApiProvider().getEventFactory().handleUserCacheLoad(this, userData);
}

View File

@ -32,6 +32,7 @@ import com.google.inject.Inject;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.LuckPermsApi;
import me.lucko.luckperms.api.PlatformType;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.common.api.ApiHandler;
import me.lucko.luckperms.common.api.ApiProvider;
import me.lucko.luckperms.common.caching.handlers.CachedStateManager;
@ -274,12 +275,25 @@ public class LPSpongePlugin implements LuckPermsPlugin {
calculatorFactory = new SpongeCalculatorFactory(this);
cachedStateManager = new CachedStateManager(this);
contextManager = new ContextManager<>();
contextManager = new ContextManager<Subject>() {
@Override
public Contexts formContexts(Subject subject, ImmutableContextSet contextSet) {
return new Contexts(
contextSet,
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
false
);
}
};
contextManager.registerCalculator(new WorldCalculator(this));
StaticCalculator<Subject> staticCalculator = new StaticCalculator<>(getConfiguration());
contextManager.registerCalculator(staticCalculator);
contextManager.registerStaticCalculator(staticCalculator);
contextManager.registerCalculator(staticCalculator, true);
// register the PermissionService with Sponge
getLog().info("Registering PermissionService...");
@ -424,15 +438,7 @@ public class LPSpongePlugin implements LuckPermsPlugin {
if (player == null) {
return null;
}
return new Contexts(
getContextManager().getApplicableContext(player),
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
false
);
return contextManager.getApplicableContexts(player);
}
@Override

View File

@ -190,7 +190,7 @@ public class SpongeUser extends User {
@Override
public ImmutableContextSet getActiveContextSet() {
try (Timing ignored = plugin.getTimings().time(LPTiming.USER_GET_ACTIVE_CONTEXTS)) {
return plugin.getContextManager().getApplicableContext(this.sponge()).makeImmutable();
return plugin.getContextManager().getApplicableContext(this.sponge());
}
}
}

View File

@ -41,7 +41,6 @@ import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.common.caching.UserCache;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.core.model.Group;
import me.lucko.luckperms.common.core.model.User;
import me.lucko.luckperms.common.utils.Predicates;
@ -253,15 +252,7 @@ public class LuckPermsService implements LPPermissionService {
@Override
public Contexts calculateContexts(ImmutableContextSet contextSet) {
return new Contexts(
contextSet,
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_PERMS),
plugin.getConfiguration().get(ConfigKeys.INCLUDING_GLOBAL_WORLD_PERMS),
true,
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_GROUPS),
plugin.getConfiguration().get(ConfigKeys.APPLYING_GLOBAL_WORLD_GROUPS),
false
);
return plugin.getContextManager().formContexts(null, contextSet);
}
@Override