diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java index a1724b200..6ac52995f 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java @@ -39,6 +39,7 @@ import me.lucko.luckperms.bukkit.model.DefaultsProvider; import me.lucko.luckperms.bukkit.vault.VaultHook; import me.lucko.luckperms.common.LuckPermsPlugin; import me.lucko.luckperms.common.api.ApiProvider; +import me.lucko.luckperms.common.caching.handlers.CachedStateManager; import me.lucko.luckperms.common.calculators.CalculatorFactory; import me.lucko.luckperms.common.commands.ConsecutiveExecutor; import me.lucko.luckperms.common.commands.sender.Sender; @@ -107,6 +108,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { private DefaultsProvider defaultsProvider; private ChildPermissionProvider childPermissionProvider; private LocaleManager localeManager; + private CachedStateManager cachedStateManager; private ContextManager contextManager; private WorldCalculator worldCalculator; private CalculatorFactory calculatorFactory; @@ -218,6 +220,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { importer = new Importer(commandManager); consecutiveExecutor = new ConsecutiveExecutor(commandManager); calculatorFactory = new BukkitCalculatorFactory(this); + cachedStateManager = new CachedStateManager(this); contextManager = new ContextManager<>(); worldCalculator = new WorldCalculator(this); diff --git a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java index 579a3f2d7..8c9fab8f7 100644 --- a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java +++ b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java @@ -32,6 +32,7 @@ import me.lucko.luckperms.api.context.ContextSet; import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.common.LuckPermsPlugin; import me.lucko.luckperms.common.api.ApiProvider; +import me.lucko.luckperms.common.caching.handlers.CachedStateManager; import me.lucko.luckperms.common.calculators.CalculatorFactory; import me.lucko.luckperms.common.commands.CommandManager; import me.lucko.luckperms.common.commands.ConsecutiveExecutor; @@ -89,6 +90,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin { private Importer importer; private ConsecutiveExecutor consecutiveExecutor; private LocaleManager localeManager; + private CachedStateManager cachedStateManager; private ContextManager contextManager; private CalculatorFactory calculatorFactory; private BufferedRequest updateTaskBuffer; @@ -164,6 +166,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin { importer = new Importer(commandManager); consecutiveExecutor = new ConsecutiveExecutor(commandManager); calculatorFactory = new BungeeCalculatorFactory(this); + cachedStateManager = new CachedStateManager(this); contextManager = new ContextManager<>(); BackendServerCalculator serverCalculator = new BackendServerCalculator(); diff --git a/common/src/main/java/me/lucko/luckperms/common/LuckPermsPlugin.java b/common/src/main/java/me/lucko/luckperms/common/LuckPermsPlugin.java index 9d0d8a699..0a66eaa8f 100644 --- a/common/src/main/java/me/lucko/luckperms/common/LuckPermsPlugin.java +++ b/common/src/main/java/me/lucko/luckperms/common/LuckPermsPlugin.java @@ -26,6 +26,7 @@ import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.Logger; import me.lucko.luckperms.api.PlatformType; import me.lucko.luckperms.common.api.ApiProvider; +import me.lucko.luckperms.common.caching.handlers.CachedStateManager; import me.lucko.luckperms.common.calculators.CalculatorFactory; import me.lucko.luckperms.common.commands.BaseCommand; import me.lucko.luckperms.common.commands.ConsecutiveExecutor; @@ -153,6 +154,13 @@ public interface LuckPermsPlugin { */ ContextManager getContextManager(); + /** + * Gets the cached state manager for the platform. + * + * @return the cached state manager + */ + CachedStateManager getCachedStateManager(); + /** * Gets the class responsible for constructing PermissionCalculators on this platform. * diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/handlers/CachedStateManager.java b/common/src/main/java/me/lucko/luckperms/common/caching/handlers/CachedStateManager.java new file mode 100644 index 000000000..94c0c8c3e --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/caching/handlers/CachedStateManager.java @@ -0,0 +1,125 @@ +/* + * 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.handlers; + +import lombok.RequiredArgsConstructor; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +import me.lucko.luckperms.common.LuckPermsPlugin; +import me.lucko.luckperms.common.core.model.PermissionHolder; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; + +/** + * Manages the cached state of all permission holders + */ +@RequiredArgsConstructor +public class CachedStateManager { + private static final Consumer INVALIDATE_CONSUMER = PermissionHolder::invalidateInheritanceCaches; + + private final LuckPermsPlugin plugin; + + // Group --> Groups that inherit from that group. (reverse relationship) + private final Multimap map = HashMultimap.create(); + private final ReentrantLock lock = new ReentrantLock(); + + /** + * Gets a set of holder names that inherit permissions (either directly or via other groups) + * from the given holder name + * + * @param holder the holder name to query for + * @return a set of inherited groups + */ + public Set getInheritances(HolderReference holder) { + Set set = new HashSet<>(); + set.add(holder); + + lock.lock(); + try { + while (true) { + Set clone = new HashSet<>(set); + + boolean work = false; + + for (HolderReference s : clone) { + if (set.addAll(map.get(s))) { + work = true; + } + } + + if (!work) { + break; + } + } + } finally { + lock.unlock(); + } + + set.remove(holder); + return set; + } + + /** + * Registers a holder and the groups they inherit from within this map. + * + * @param holder the holder to add + * @param inheritedGroups a list of groups the holder inherits from + */ + public void putAll(HolderReference holder, Set inheritedGroups) { + lock.lock(); + try { + map.entries().removeIf(entry -> entry.getValue().equals(holder)); + + for (HolderReference child : inheritedGroups) { + map.put(child, holder); + } + } finally { + lock.unlock(); + } + } + + /** + * Clears defined inheritances for the given holder name. + * + * @param holder the holder name to clear + */ + public void clear(HolderReference holder) { + lock.lock(); + try { + map.entries().removeIf(entry -> entry.getValue().equals(holder)); + } finally { + lock.unlock(); + } + } + + public void invalidateInheritances(HolderReference holder) { + Set toInvalidate = getInheritances(holder); + toInvalidate.forEach(hr -> hr.apply(plugin, INVALIDATE_CONSUMER)); + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/handlers/GroupReference.java b/common/src/main/java/me/lucko/luckperms/common/caching/handlers/GroupReference.java new file mode 100644 index 000000000..695b771b5 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/caching/handlers/GroupReference.java @@ -0,0 +1,53 @@ +/* + * 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.handlers; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import me.lucko.luckperms.common.LuckPermsPlugin; +import me.lucko.luckperms.common.core.model.Group; +import me.lucko.luckperms.common.core.model.PermissionHolder; + +import java.util.function.Consumer; + +@Getter +@AllArgsConstructor(staticName = "of") +public class GroupReference implements HolderReference { + + private final String id; + + @Override + public HolderType getType() { + return HolderType.GROUP; + } + + @Override + public void apply(LuckPermsPlugin plugin, Consumer consumer) { + Group group = plugin.getGroupManager().getIfLoaded(id); + if (group == null) return; + + consumer.accept(group); + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/handlers/HolderReference.java b/common/src/main/java/me/lucko/luckperms/common/caching/handlers/HolderReference.java new file mode 100644 index 000000000..7212dfa1f --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/caching/handlers/HolderReference.java @@ -0,0 +1,36 @@ +/* + * 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.handlers; + +import me.lucko.luckperms.common.LuckPermsPlugin; +import me.lucko.luckperms.common.core.model.PermissionHolder; +import me.lucko.luckperms.common.utils.Identifiable; + +import java.util.function.Consumer; + +public interface HolderReference extends Identifiable { + + HolderType getType(); + void apply(LuckPermsPlugin plugin, Consumer consumer); + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/handlers/HolderType.java b/common/src/main/java/me/lucko/luckperms/common/caching/handlers/HolderType.java new file mode 100644 index 000000000..0c17ce8d4 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/caching/handlers/HolderType.java @@ -0,0 +1,27 @@ +/* + * 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.handlers; + +public enum HolderType { + USER, GROUP +} diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/handlers/UserReference.java b/common/src/main/java/me/lucko/luckperms/common/caching/handlers/UserReference.java new file mode 100644 index 000000000..5476626b8 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/caching/handlers/UserReference.java @@ -0,0 +1,53 @@ +/* + * 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.handlers; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import me.lucko.luckperms.common.LuckPermsPlugin; +import me.lucko.luckperms.common.core.UserIdentifier; +import me.lucko.luckperms.common.core.model.PermissionHolder; +import me.lucko.luckperms.common.core.model.User; + +import java.util.function.Consumer; + +@Getter +@AllArgsConstructor(staticName = "of") +public class UserReference implements HolderReference { + private final UserIdentifier id; + + @Override + public HolderType getType() { + return HolderType.USER; + } + + @Override + public void apply(LuckPermsPlugin plugin, Consumer consumer) { + User user = plugin.getUserManager().getIfLoaded(id); + if (user == null) return; + + consumer.accept(user); + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/holder/ExportNodesHolder.java b/common/src/main/java/me/lucko/luckperms/common/caching/holder/ExportNodesHolder.java new file mode 100644 index 000000000..513522ca0 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/caching/holder/ExportNodesHolder.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.holder; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import me.lucko.luckperms.api.Contexts; + +@Getter +@ToString +@EqualsAndHashCode +@AllArgsConstructor(staticName = "of") +public class ExportNodesHolder { + + private final Contexts contexts; + private final Boolean lowerCase; + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/holder/GetAllNodesHolder.java b/common/src/main/java/me/lucko/luckperms/common/caching/holder/GetAllNodesHolder.java new file mode 100644 index 000000000..b9a8a07c9 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/caching/holder/GetAllNodesHolder.java @@ -0,0 +1,43 @@ +/* + * 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.holder; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import com.google.common.collect.ImmutableList; + +import me.lucko.luckperms.common.utils.ExtractedContexts; + +@Getter +@ToString +@EqualsAndHashCode +@AllArgsConstructor(staticName = "of") +public class GetAllNodesHolder { + + private final ImmutableList excludedGroups; + private final ExtractedContexts contexts; + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/core/model/Group.java b/common/src/main/java/me/lucko/luckperms/common/core/model/Group.java index adb6a3d46..d32f26d87 100644 --- a/common/src/main/java/me/lucko/luckperms/common/core/model/Group.java +++ b/common/src/main/java/me/lucko/luckperms/common/core/model/Group.java @@ -27,6 +27,8 @@ import lombok.Getter; import lombok.ToString; import me.lucko.luckperms.common.LuckPermsPlugin; +import me.lucko.luckperms.common.caching.handlers.GroupReference; +import me.lucko.luckperms.common.caching.handlers.HolderReference; import me.lucko.luckperms.common.utils.Identifiable; @ToString(of = {"name"}) @@ -62,4 +64,9 @@ public class Group extends PermissionHolder implements Identifiable { public String getFriendlyName() { return getDisplayName(); } + + @Override + public HolderReference toReference() { + return GroupReference.of(getId()); + } } diff --git a/common/src/main/java/me/lucko/luckperms/common/core/model/PermissionHolder.java b/common/src/main/java/me/lucko/luckperms/common/core/model/PermissionHolder.java index 4b1e5f9ee..8b1bd0313 100644 --- a/common/src/main/java/me/lucko/luckperms/common/core/model/PermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/core/model/PermissionHolder.java @@ -26,6 +26,10 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; @@ -44,6 +48,9 @@ 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.caching.handlers.HolderReference; +import me.lucko.luckperms.common.caching.holder.ExportNodesHolder; +import me.lucko.luckperms.common.caching.holder.GetAllNodesHolder; import me.lucko.luckperms.common.commands.utils.Util; import me.lucko.luckperms.common.core.InheritanceInfo; import me.lucko.luckperms.common.core.NodeBuilder; @@ -77,26 +84,6 @@ import java.util.stream.Collectors; @RequiredArgsConstructor(access = AccessLevel.PROTECTED) public abstract class PermissionHolder { - public static Map exportToLegacy(Set nodes) { - Map m = new HashMap<>(); - for (Node node : nodes) { - m.put(node.toSerializedNode(), node.getValue()); - } - return m; - } - - private static Node.Builder buildNode(String permission) { - return new NodeBuilder(permission); - } - - private static ImmutableLocalizedNode makeLocal(Node node, String location) { - return ImmutableLocalizedNode.of(node, location); - } - - private static Node makeNode(String s, Boolean b) { - return NodeFactory.fromSerialisedNode(s, b); - } - /** * The UUID of the user / name of the group. * Used to prevent circular inheritance issues @@ -110,25 +97,83 @@ public abstract class PermissionHolder { @Getter(AccessLevel.PROTECTED) private final LuckPermsPlugin plugin; + /** + * The holders persistent nodes + */ private final Set nodes = new HashSet<>(); + + /** + * The holders transient nodes + */ private final Set transientNodes = new HashSet<>(); + /** + * Lock used by Storage implementations to prevent concurrent read/writes + */ @Getter private final Lock ioLock = new ReentrantLock(); + + /* + * CACHES - cache the result of a number of methods in this class, until they are invalidated. + */ + + /* Internal Caches - only depend on the state of this instance. */ + private Cache> enduringCache = new Cache<>(() -> { synchronized (nodes) { return ImmutableSet.copyOf(nodes); } }); - private Cache> transientCache = new Cache<>(() -> { synchronized (transientNodes) { return ImmutableSet.copyOf(transientNodes); } }); + private Cache> cache = new Cache<>(this::cacheApply); + private Cache> mergedCache = new Cache<>(this::mergedCacheApply); - private Cache> cache = new Cache<>(() -> { + /* External Caches - may depend on the state of other instances. */ + + private LoadingCache> getAllNodesCache = CacheBuilder.newBuilder() + .build(new CacheLoader>() { + @Override + public SortedSet load(GetAllNodesHolder getAllNodesHolder) { + return getAllNodesCacheApply(getAllNodesHolder); + } + }); + private LoadingCache> getAllNodesFilteredCache = CacheBuilder.newBuilder() + .build(new CacheLoader>() { + @Override + public Set load(ExtractedContexts extractedContexts) throws Exception { + return getAllNodesFilteredApply(extractedContexts); + } + }); + private LoadingCache> exportNodesCache = CacheBuilder.newBuilder() + .build(new CacheLoader>() { + @Override + public Map load(ExportNodesHolder exportNodesHolder) throws Exception { + return exportNodesApply(exportNodesHolder); + } + }); + + + + /* Caching apply methods. Are just called by the caching instances to gather data about the instance. */ + + private void invalidateCache(boolean enduring) { + if (enduring) { + enduringCache.invalidate(); + } else { + transientCache.invalidate(); + } + cache.invalidate(); + mergedCache.invalidate(); + invalidateInheritanceCaches(); + plugin.getCachedStateManager().invalidateInheritances(toReference()); + } + + private ImmutableSortedSet cacheApply() { TreeSet combined = new TreeSet<>(PriorityComparator.reverse()); Set enduring = getNodes(); if (!enduring.isEmpty()) { @@ -160,9 +205,9 @@ public abstract class PermissionHolder { higherPriority.add(entry); } return ImmutableSortedSet.copyOfSorted(combined); - }); + } - private Cache> mergedCache = new Cache<>(() -> { + private ImmutableSortedSet mergedCacheApply() { TreeSet combined = new TreeSet<>(PriorityComparator.reverse()); Set enduring = getNodes(); if (!enduring.isEmpty()) { @@ -194,9 +239,137 @@ public abstract class PermissionHolder { higherPriority.add(entry); } return ImmutableSortedSet.copyOfSorted(combined); - }); + } + + public void invalidateInheritanceCaches() { + getAllNodesCache.invalidateAll(); + getAllNodesFilteredCache.invalidateAll(); + exportNodesCache.invalidateAll(); + } + + private SortedSet getAllNodesCacheApply(GetAllNodesHolder getAllNodesHolder) { + List excludedGroups = new ArrayList<>(getAllNodesHolder.getExcludedGroups()); + ExtractedContexts contexts = getAllNodesHolder.getContexts(); + + SortedSet all = new TreeSet<>((SortedSet) getPermissions(true)); + + excludedGroups.add(getObjectName().toLowerCase()); + + Set parents = all.stream() + .map(LocalizedNode::getNode) + .filter(Node::getValue) + .filter(Node::isGroupNode) + .collect(Collectors.toSet()); + + Contexts context = contexts.getContexts(); + String server = contexts.getServer(); + String world = contexts.getWorld(); + + parents.removeIf(node -> + !node.shouldApplyOnServer(server, context.isApplyGlobalGroups(), plugin.getConfiguration().isApplyingRegex()) || + !node.shouldApplyOnWorld(world, context.isApplyGlobalWorldGroups(), plugin.getConfiguration().isApplyingRegex()) || + !node.shouldApplyWithContext(contexts.getContextSet(), false) + ); + + TreeSet> sortedParents = new TreeSet<>(Util.META_COMPARATOR.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().getIfLoaded(parent.getGroupName()); + if (group == null) { + continue; + } + + if (excludedGroups.contains(group.getObjectName().toLowerCase())) { + continue; + } + + inherited: + for (LocalizedNode inherited : group.getAllNodes(excludedGroups, contexts)) { + for (LocalizedNode existing : all) { + if (existing.getNode().almostEquals(inherited.getNode())) { + continue inherited; + } + } + + all.add(inherited); + } + } + + return all; + } + + private Set getAllNodesFilteredApply(ExtractedContexts contexts) { + SortedSet allNodes; + + Contexts context = contexts.getContexts(); + String server = contexts.getServer(); + String world = contexts.getWorld(); + + if (context.isApplyGroups()) { + allNodes = getAllNodes(null, contexts); + } else { + allNodes = new TreeSet<>((SortedSet) getPermissions(true)); + } + + allNodes.removeIf(node -> + !node.shouldApplyOnServer(server, context.isIncludeGlobal(), plugin.getConfiguration().isApplyingRegex()) || + !node.shouldApplyOnWorld(world, context.isIncludeGlobalWorld(), plugin.getConfiguration().isApplyingRegex()) || + !node.shouldApplyWithContext(contexts.getContextSet(), false) + ); + + Set perms = ConcurrentHashMap.newKeySet(); + + all: + for (LocalizedNode ln : allNodes) { + // Force higher priority nodes to override + for (LocalizedNode alreadyIn : perms) { + if (ln.getNode().getPermission().equals(alreadyIn.getNode().getPermission())) { + continue all; + } + } + + perms.add(ln); + } + + return perms; + } + + private Map exportNodesApply(ExportNodesHolder exportNodesHolder) { + Contexts context = exportNodesHolder.getContexts(); + Boolean lowerCase = exportNodesHolder.getLowerCase(); + + Map perms = new HashMap<>(); + + for (LocalizedNode ln : getAllNodesFiltered(ExtractedContexts.generate(context))) { + Node node = ln.getNode(); + + perms.put(lowerCase ? node.getPermission().toLowerCase() : node.getPermission(), node.getValue()); + + if (plugin.getConfiguration().isApplyingShorthand()) { + List sh = node.resolveShorthand(); + if (!sh.isEmpty()) { + sh.stream().map(s -> lowerCase ? s.toLowerCase() : s) + .filter(s -> !perms.containsKey(s)) + .forEach(s -> perms.put(s, node.getValue())); + } + } + } + + return ImmutableMap.copyOf(perms); + } + public abstract String getFriendlyName(); + public abstract HolderReference toReference(); public Set getNodes() { return enduringCache.get(); @@ -238,16 +411,6 @@ public abstract class PermissionHolder { invalidateCache(false); } - private void invalidateCache(boolean enduring) { - if (enduring) { - enduringCache.invalidate(); - } else { - transientCache.invalidate(); - } - cache.invalidate(); - mergedCache.invalidate(); - } - /** * Combines and returns this holders nodes in a priority order. * @@ -257,6 +420,104 @@ public abstract class PermissionHolder { return mergeTemp ? mergedCache.get() : cache.get(); } + /** + * Resolves inherited nodes and returns them + * + * @param excludedGroups a list of groups to exclude + * @param contexts context to decide if groups should be applied + * @return a set of nodes + */ + public SortedSet getAllNodes(List excludedGroups, ExtractedContexts contexts) { + return getAllNodesCache.getUnchecked(GetAllNodesHolder.of(excludedGroups == null ? ImmutableList.of() : ImmutableList.copyOf(excludedGroups), contexts)); + } + + /** + * Gets all of the nodes that this holder has (and inherits), given the context + * + * @param contexts the context for this request + * @return a map of permissions + */ + public Set getAllNodesFiltered(ExtractedContexts contexts) { + return getAllNodesFilteredCache.getUnchecked(contexts); + } + + /** + * Converts the output of {@link #getAllNodesFiltered(ExtractedContexts)}, and expands shorthand perms + * + * @param context the context for this request + * @return a map of permissions + */ + public Map exportNodes(Contexts context, boolean lowerCase) { + return exportNodesCache.getUnchecked(ExportNodesHolder.of(context, lowerCase)); + } + + public MetaHolder accumulateMeta(MetaHolder holder, List excludedGroups, ExtractedContexts contexts) { + if (holder == null) { + holder = new MetaHolder(); + } + + if (excludedGroups == null) { + excludedGroups = new ArrayList<>(); + } + + excludedGroups.add(getObjectName().toLowerCase()); + + Contexts context = contexts.getContexts(); + String server = contexts.getServer(); + String world = contexts.getWorld(); + + 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.getContextSet(), false)) continue; + + holder.accumulateNode(n); + } + + 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.getContextSet(), false) + ); + + TreeSet> sortedParents = new TreeSet<>(Util.META_COMPARATOR.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().getIfLoaded(parent.getGroupName()); + if (group == null) { + continue; + } + + if (excludedGroups.contains(group.getObjectName().toLowerCase())) { + continue; + } + + group.accumulateMeta(holder, excludedGroups, contexts); + } + + return holder; + } + /** * Removes temporary permissions that have expired * @@ -311,210 +572,6 @@ public abstract class PermissionHolder { return true; } - /** - * Resolves inherited nodes and returns them - * - * @param excludedGroups a list of groups to exclude - * @param contexts context to decide if groups should be applied - * @return a set of nodes - */ - public SortedSet getAllNodes(List excludedGroups, ExtractedContexts contexts) { - SortedSet all = new TreeSet<>((SortedSet) getPermissions(true)); - - if (excludedGroups == null) { - excludedGroups = new ArrayList<>(); - } - - excludedGroups.add(getObjectName().toLowerCase()); - - Set parents = all.stream() - .map(LocalizedNode::getNode) - .filter(Node::getValue) - .filter(Node::isGroupNode) - .collect(Collectors.toSet()); - - Contexts context = contexts.getContexts(); - String server = contexts.getServer(); - String world = contexts.getWorld(); - - parents.removeIf(node -> - !node.shouldApplyOnServer(server, context.isApplyGlobalGroups(), plugin.getConfiguration().isApplyingRegex()) || - !node.shouldApplyOnWorld(world, context.isApplyGlobalWorldGroups(), plugin.getConfiguration().isApplyingRegex()) || - !node.shouldApplyWithContext(contexts.getContextSet(), false) - ); - - TreeSet> sortedParents = new TreeSet<>(Util.META_COMPARATOR.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().getIfLoaded(parent.getGroupName()); - if (group == null) { - continue; - } - - if (excludedGroups.contains(group.getObjectName().toLowerCase())) { - continue; - } - - inherited: - for (LocalizedNode inherited : group.getAllNodes(excludedGroups, contexts)) { - for (LocalizedNode existing : all) { - if (existing.getNode().almostEquals(inherited.getNode())) { - continue inherited; - } - } - - all.add(inherited); - } - } - - return all; - } - - public MetaHolder accumulateMeta(MetaHolder holder, List excludedGroups, ExtractedContexts contexts) { - if (holder == null) { - holder = new MetaHolder(); - } - - if (excludedGroups == null) { - excludedGroups = new ArrayList<>(); - } - - excludedGroups.add(getObjectName().toLowerCase()); - - Contexts context = contexts.getContexts(); - String server = contexts.getServer(); - String world = contexts.getWorld(); - - 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.getContextSet(), false)) continue; - - holder.accumulateNode(n); - } - - 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.getContextSet(), false) - ); - - TreeSet> sortedParents = new TreeSet<>(Util.META_COMPARATOR.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().getIfLoaded(parent.getGroupName()); - if (group == null) { - continue; - } - - if (excludedGroups.contains(group.getObjectName().toLowerCase())) { - continue; - } - - group.accumulateMeta(holder, excludedGroups, contexts); - } - - return holder; - } - - /** - * Gets all of the nodes that this holder has (and inherits), given the context - * - * @param contexts the context for this request - * @return a map of permissions - */ - public Set getAllNodesFiltered(ExtractedContexts contexts) { - SortedSet allNodes; - - Contexts context = contexts.getContexts(); - String server = contexts.getServer(); - String world = contexts.getWorld(); - - if (context.isApplyGroups()) { - allNodes = getAllNodes(null, contexts); - } else { - allNodes = new TreeSet<>((SortedSet) getPermissions(true)); - } - - allNodes.removeIf(node -> - !node.shouldApplyOnServer(server, context.isIncludeGlobal(), plugin.getConfiguration().isApplyingRegex()) || - !node.shouldApplyOnWorld(world, context.isIncludeGlobalWorld(), plugin.getConfiguration().isApplyingRegex()) || - !node.shouldApplyWithContext(contexts.getContextSet(), false) - ); - - Set perms = ConcurrentHashMap.newKeySet(); - - all: - for (LocalizedNode ln : allNodes) { - // Force higher priority nodes to override - for (LocalizedNode alreadyIn : perms) { - if (ln.getNode().getPermission().equals(alreadyIn.getNode().getPermission())) { - continue all; - } - } - - perms.add(ln); - } - - return perms; - } - - /** - * Converts the output of {@link #getAllNodesFiltered(ExtractedContexts)}, and expands shorthand perms - * - * @param context the context for this request - * @return a map of permissions - */ - public Map exportNodes(Contexts context, boolean lowerCase) { - Map perms = new HashMap<>(); - - for (LocalizedNode ln : getAllNodesFiltered(ExtractedContexts.generate(context))) { - Node node = ln.getNode(); - - perms.put(lowerCase ? node.getPermission().toLowerCase() : node.getPermission(), node.getValue()); - - if (plugin.getConfiguration().isApplyingShorthand()) { - List sh = node.resolveShorthand(); - if (!sh.isEmpty()) { - sh.stream().map(s -> lowerCase ? s.toLowerCase() : s) - .filter(s -> !perms.containsKey(s)) - .forEach(s -> perms.put(s, node.getValue())); - } - } - } - - return ImmutableMap.copyOf(perms); - } - /** * Check if the holder has a permission node * @@ -1029,4 +1086,24 @@ public abstract class PermissionHolder { .map(Node::getGroupName) .collect(Collectors.toList()); } + + public static Map exportToLegacy(Set nodes) { + Map m = new HashMap<>(); + for (Node node : nodes) { + m.put(node.toSerializedNode(), node.getValue()); + } + return m; + } + + private static Node.Builder buildNode(String permission) { + return new NodeBuilder(permission); + } + + private static ImmutableLocalizedNode makeLocal(Node node, String location) { + return ImmutableLocalizedNode.of(node, location); + } + + private static Node makeNode(String s, Boolean b) { + return NodeFactory.fromSerialisedNode(s, b); + } } diff --git a/common/src/main/java/me/lucko/luckperms/common/core/model/User.java b/common/src/main/java/me/lucko/luckperms/common/core/model/User.java index f92b1cdd7..e9f0cf2b9 100644 --- a/common/src/main/java/me/lucko/luckperms/common/core/model/User.java +++ b/common/src/main/java/me/lucko/luckperms/common/core/model/User.java @@ -32,6 +32,8 @@ import me.lucko.luckperms.api.event.events.UserPermissionRefreshEvent; import me.lucko.luckperms.common.LuckPermsPlugin; import me.lucko.luckperms.common.api.internal.UserLink; import me.lucko.luckperms.common.caching.UserCache; +import me.lucko.luckperms.common.caching.handlers.HolderReference; +import me.lucko.luckperms.common.caching.handlers.UserReference; import me.lucko.luckperms.common.core.UserIdentifier; import me.lucko.luckperms.common.utils.BufferedRequest; import me.lucko.luckperms.common.utils.Identifiable; @@ -99,6 +101,11 @@ public class User extends PermissionHolder implements Identifiable toReference() { + return UserReference.of(getId()); + } + /** * Sets up the UserData cache * Blocking call. diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java index 0c1ea9e42..272790478 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java @@ -32,6 +32,7 @@ import me.lucko.luckperms.api.LuckPermsApi; import me.lucko.luckperms.api.PlatformType; import me.lucko.luckperms.common.LuckPermsPlugin; import me.lucko.luckperms.common.api.ApiProvider; +import me.lucko.luckperms.common.caching.handlers.CachedStateManager; import me.lucko.luckperms.common.calculators.CalculatorFactory; import me.lucko.luckperms.common.commands.BaseCommand; import me.lucko.luckperms.common.commands.ConsecutiveExecutor; @@ -141,6 +142,7 @@ public class LPSpongePlugin implements LuckPermsPlugin { private ConsecutiveExecutor consecutiveExecutor; private LuckPermsService service; private LocaleManager localeManager; + private CachedStateManager cachedStateManager; private ContextManager contextManager; private CalculatorFactory calculatorFactory; private BufferedRequest updateTaskBuffer; @@ -215,6 +217,7 @@ public class LPSpongePlugin implements LuckPermsPlugin { importer = new Importer(commandManager); consecutiveExecutor = new ConsecutiveExecutor(commandManager); calculatorFactory = new SpongeCalculatorFactory(this); + cachedStateManager = new CachedStateManager(this); contextManager = new ContextManager<>(); contextManager.registerCalculator(new ServerCalculator<>(getConfiguration().getServer()));