diff --git a/bungee/src/main/java/me/lucko/luckperms/bungee/BungeeListener.java b/bungee/src/main/java/me/lucko/luckperms/bungee/BungeeListener.java index 25788359f..b676d964a 100644 --- a/bungee/src/main/java/me/lucko/luckperms/bungee/BungeeListener.java +++ b/bungee/src/main/java/me/lucko/luckperms/bungee/BungeeListener.java @@ -37,6 +37,7 @@ import net.md_5.bungee.api.event.PlayerDisconnectEvent; import net.md_5.bungee.api.event.PostLoginEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.event.EventHandler; +import net.md_5.bungee.event.EventPriority; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -83,7 +84,7 @@ public class BungeeListener extends AbstractListener implements Listener { e.setHasPermission(user.getUserData().getPermissionData(contexts).getPermissionValue(e.getPermission()).asBoolean()); } - @EventHandler + @EventHandler(priority = EventPriority.LOWEST) public void onPlayerLogin(LoginEvent e) { /* Delay the login here, as we want to cache UUID data before the player is connected to a backend bukkit server. This means that a player will have the same UUID across the network, even if parts of the network are running in diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/MetaCache.java b/common/src/main/java/me/lucko/luckperms/common/caching/MetaCache.java index a3320ce6c..4c102f576 100644 --- a/common/src/main/java/me/lucko/luckperms/common/caching/MetaCache.java +++ b/common/src/main/java/me/lucko/luckperms/common/caching/MetaCache.java @@ -24,142 +24,67 @@ package me.lucko.luckperms.common.caching; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedMap; -import lombok.RequiredArgsConstructor; -import me.lucko.luckperms.api.Contexts; -import me.lucko.luckperms.api.LocalizedNode; -import me.lucko.luckperms.api.Node; +import lombok.Getter; +import lombok.NoArgsConstructor; import me.lucko.luckperms.api.caching.MetaData; -import me.lucko.luckperms.api.context.MutableContextSet; -import java.util.*; +import java.util.Map; +import java.util.SortedMap; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Holds a user's cached meta for a given context */ -@RequiredArgsConstructor +@NoArgsConstructor public class MetaCache implements MetaData { - private final Contexts contexts; + private final ReadWriteLock lock = new ReentrantReadWriteLock(); - private final SortedMap prefixes = new TreeMap<>(Comparator.reverseOrder()); - private final SortedMap suffixes = new TreeMap<>(Comparator.reverseOrder()); + @Getter + private Map meta = ImmutableMap.of(); - private final Map meta = new HashMap<>(); + @Getter + private SortedMap prefixes = ImmutableSortedMap.of(); - public void loadMeta(SortedSet nodes) { - invalidateCache(); + @Getter + private SortedMap suffixes = ImmutableSortedMap.of(); - MutableContextSet contexts = MutableContextSet.fromSet(this.contexts.getContexts()); - String server = contexts.getValues("server").stream().findAny().orElse(null); - String world = contexts.getValues("world").stream().findAny().orElse(null); - contexts.removeAll("server"); - contexts.removeAll("world"); - - for (LocalizedNode ln : nodes) { - Node n = ln.getNode(); - - if (!n.getValue()) { - continue; - } - - if (!n.isMeta() && !n.isPrefix() && !n.isSuffix()) { - continue; - } - - if (!n.shouldApplyOnServer(server, this.contexts.isIncludeGlobal(), false)) { - continue; - } - - if (!n.shouldApplyOnWorld(world, this.contexts.isIncludeGlobalWorld(), false)) { - continue; - } - - if (!n.shouldApplyWithContext(contexts, false)) { - continue; - } - - if (n.isPrefix()) { - Map.Entry value = n.getPrefix(); - synchronized (this.prefixes) { - if (!this.prefixes.containsKey(value.getKey())) { - this.prefixes.put(value.getKey(), value.getValue()); - } - } - continue; - } - - if (n.isSuffix()) { - Map.Entry value = n.getSuffix(); - synchronized (this.suffixes) { - if (!this.suffixes.containsKey(value.getKey())) { - this.suffixes.put(value.getKey(), value.getValue()); - } - } - continue; - } - - if (n.isMeta()) { - Map.Entry meta = n.getMeta(); - synchronized (this.meta) { - if (!this.meta.containsKey(meta.getKey())) { - this.meta.put(meta.getKey(), meta.getValue()); - } - } - } - } - } - - private void invalidateCache() { - synchronized (meta) { - meta.clear(); - } - synchronized (prefixes) { - prefixes.clear(); - } - synchronized (suffixes) { - suffixes.clear(); - } - } - - @Override - public Map getMeta() { - synchronized (meta) { - return ImmutableMap.copyOf(meta); - } - } - - @Override - public SortedMap getPrefixes() { - synchronized (prefixes) { - return ImmutableSortedMap.copyOfSorted(prefixes); - } - } - - @Override - public SortedMap getSuffixes() { - synchronized (suffixes) { - return ImmutableSortedMap.copyOfSorted(suffixes); + public void loadMeta(MetaHolder meta) { + lock.writeLock().lock(); + try { + this.meta = ImmutableMap.copyOf(meta.getMeta()); + this.prefixes = ImmutableSortedMap.copyOfSorted(meta.getPrefixes()); + this.suffixes = ImmutableSortedMap.copyOfSorted(meta.getSuffixes()); + } finally { + lock.writeLock().unlock(); } } @Override public String getPrefix() { - synchronized (prefixes) { + lock.readLock().lock(); + try { if (prefixes.isEmpty()) { return null; } return prefixes.get(prefixes.firstKey()); + } finally { + lock.readLock().unlock(); } } @Override public String getSuffix() { - synchronized (suffixes) { + lock.readLock().lock(); + try { if (suffixes.isEmpty()) { return null; } return suffixes.get(suffixes.firstKey()); + } finally { + lock.readLock().unlock(); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/MetaHolder.java b/common/src/main/java/me/lucko/luckperms/common/caching/MetaHolder.java new file mode 100644 index 000000000..0fdf76ab7 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/caching/MetaHolder.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2016 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.caching; + +import lombok.Getter; +import lombok.ToString; + +import java.util.*; + +/** + * Holds temporary mutable meta whilst this object is passed up the inheritance tree to accumulate meta from parents + */ +@Getter +@ToString +public class MetaHolder { + + private final Map meta = new HashMap<>(); + private final SortedMap prefixes = new TreeMap<>(Comparator.reverseOrder()); + private final SortedMap suffixes = new TreeMap<>(Comparator.reverseOrder()); + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/UserCache.java b/common/src/main/java/me/lucko/luckperms/common/caching/UserCache.java index 78ae47102..290bda287 100644 --- a/common/src/main/java/me/lucko/luckperms/common/caching/UserCache.java +++ b/common/src/main/java/me/lucko/luckperms/common/caching/UserCache.java @@ -78,7 +78,7 @@ public class UserCache implements UserData { @Override public ListenableFuture reload(Contexts contexts, MetaCache oldData) { - oldData.loadMeta(user.getAllNodes(null, contexts)); + oldData.loadMeta(user.accumulateMeta(null, null, contexts)); return Futures.immediateFuture(oldData); } }); @@ -102,8 +102,8 @@ public class UserCache implements UserData { @Override public MetaCache calculateMeta(@NonNull Contexts contexts) { - MetaCache data = new MetaCache(contexts); - data.loadMeta(user.getAllNodes(null, contexts)); + MetaCache data = new MetaCache(); + data.loadMeta(user.accumulateMeta(null, null, contexts)); return data; } diff --git a/common/src/main/java/me/lucko/luckperms/common/core/PermissionHolder.java b/common/src/main/java/me/lucko/luckperms/common/core/PermissionHolder.java index 1f1db374e..7af8da960 100644 --- a/common/src/main/java/me/lucko/luckperms/common/core/PermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/core/PermissionHolder.java @@ -38,6 +38,7 @@ import me.lucko.luckperms.api.event.events.*; import me.lucko.luckperms.common.LuckPermsPlugin; import me.lucko.luckperms.common.api.internal.GroupLink; import me.lucko.luckperms.common.api.internal.PermissionHolderLink; +import me.lucko.luckperms.common.caching.MetaHolder; import me.lucko.luckperms.common.commands.Util; import me.lucko.luckperms.common.groups.Group; import me.lucko.luckperms.common.utils.Cache; @@ -308,6 +309,98 @@ public abstract class PermissionHolder { return all; } + private static void accumulateMetaNode(Node n, MetaHolder holder) { + if (n.isPrefix()) { + Map.Entry value = n.getPrefix(); + if (!holder.getPrefixes().containsKey(value.getKey())) { + holder.getPrefixes().put(value.getKey(), value.getValue()); + } + } + + if (n.isSuffix()) { + Map.Entry value = n.getSuffix(); + if (!holder.getSuffixes().containsKey(value.getKey())) { + holder.getSuffixes().put(value.getKey(), value.getValue()); + } + } + + if (n.isMeta()) { + Map.Entry meta = n.getMeta(); + if (!holder.getMeta().containsKey(meta.getKey())) { + holder.getMeta().put(meta.getKey(), meta.getValue()); + } + } + } + + public MetaHolder accumulateMeta(MetaHolder holder, List excludedGroups, Contexts context) { + if (holder == null) { + holder = new MetaHolder(); + } + + if (excludedGroups == null) { + excludedGroups = new ArrayList<>(); + } + + excludedGroups.add(getObjectName().toLowerCase()); + + MutableContextSet contexts = MutableContextSet.fromSet(context.getContexts()); + String server = contexts.getValues("server").stream().findAny().orElse(null); + String world = contexts.getValues("world").stream().findAny().orElse(null); + contexts.removeAll("server"); + contexts.removeAll("world"); + + SortedSet all = new TreeSet<>((SortedSet) getPermissions(true)); + for (LocalizedNode ln : all) { + Node n = ln.getNode(); + + if (!n.getValue()) continue; + if (!n.isMeta() && !n.isPrefix() && !n.isSuffix()) continue; + if (!n.shouldApplyOnServer(server, context.isIncludeGlobal(), false)) continue; + if (!n.shouldApplyOnWorld(world, context.isIncludeGlobalWorld(), false)) continue; + if (!n.shouldApplyWithContext(contexts, false)) continue; + + accumulateMetaNode(n, holder); + } + + Set parents = all.stream() + .map(LocalizedNode::getNode) + .filter(Node::getValue) + .filter(Node::isGroupNode) + .collect(Collectors.toSet()); + + parents.removeIf(node -> + !node.shouldApplyOnServer(server, context.isApplyGlobalGroups(), plugin.getConfiguration().isApplyingRegex()) || + !node.shouldApplyOnWorld(world, context.isApplyGlobalWorldGroups(), plugin.getConfiguration().isApplyingRegex()) || + !node.shouldApplyWithContext(contexts, false) + ); + + TreeSet> sortedParents = new TreeSet<>(Util.getMetaComparator().reversed()); + Map weights = plugin.getConfiguration().getGroupWeights(); + for (Node node : parents) { + if (weights.containsKey(node.getGroupName().toLowerCase())) { + sortedParents.add(Maps.immutableEntry(weights.get(node.getGroupName().toLowerCase()), node)); + } else { + sortedParents.add(Maps.immutableEntry(0, node)); + } + } + + for (Map.Entry e : sortedParents) { + Node parent = e.getValue(); + Group group = plugin.getGroupManager().get(parent.getGroupName()); + if (group == null) { + continue; + } + + if (excludedGroups.contains(group.getObjectName().toLowerCase())) { + continue; + } + + group.accumulateMeta(holder, excludedGroups, context); + } + + return holder; + } + /** * Gets all of the nodes that this holder has (and inherits), given the context * @param context the context for this request