Cache SubjectReference instances, general cleanup

This commit is contained in:
Luck
2017-09-21 21:59:27 +01:00
Unverified
parent f9efa15781
commit 777c972bdc
36 changed files with 732 additions and 780 deletions
@@ -81,7 +81,6 @@ import me.lucko.luckperms.sponge.service.model.LPPermissionService;
import me.lucko.luckperms.sponge.service.model.LPSubjectCollection;
import me.lucko.luckperms.sponge.service.persisted.PersistedCollection;
import me.lucko.luckperms.sponge.tasks.ServiceCacheHousekeepingTask;
import me.lucko.luckperms.sponge.timings.LPTimings;
import me.lucko.luckperms.sponge.utils.VersionData;
import org.slf4j.Logger;
@@ -122,8 +121,18 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
/**
* LuckPerms implementation for the Sponge API.
*/
@Getter
@Plugin(id = "luckperms", name = "LuckPerms", version = VersionData.VERSION, authors = {"Luck"}, description = "A permissions plugin")
@Plugin(
id = "luckperms",
name = "LuckPerms",
version = VersionData.VERSION,
authors = {"Luck"},
description = "A permissions plugin",
url = "https://github.com/lucko/LuckPerms"
)
public class LPSpongePlugin implements LuckPermsPlugin {
@Inject
@@ -146,7 +155,6 @@ public class LPSpongePlugin implements LuckPermsPlugin {
@AsynchronousExecutor
private SpongeExecutorService asyncExecutorService;
private LPTimings timings;
private boolean lateLoad = false;
private long startTime;
@@ -186,7 +194,6 @@ public class LPSpongePlugin implements LuckPermsPlugin {
verboseHandler = new VerboseHandler(scheduler.async(), getVersion());
permissionVault = new PermissionVault(scheduler.async());
logDispatcher = new LogDispatcher(this);
timings = new LPTimings(this);
getLog().info("Loading configuration...");
configuration = new SpongeConfig(this);
@@ -29,7 +29,6 @@ import com.google.common.base.Splitter;
import me.lucko.luckperms.common.commands.CommandManager;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.sponge.timings.LPTiming;
import org.spongepowered.api.command.CommandCallable;
import org.spongepowered.api.command.CommandException;
@@ -41,16 +40,13 @@ import org.spongepowered.api.text.selector.Selector;
import org.spongepowered.api.world.Location;
import org.spongepowered.api.world.World;
import co.aikar.timings.Timing;
import java.util.List;
import java.util.ListIterator;
import java.util.Optional;
import javax.annotation.Nullable;
@SuppressWarnings("NullableProblems")
class SpongeCommand extends CommandManager implements CommandCallable {
public class SpongeCommand extends CommandManager implements CommandCallable {
private final LPSpongePlugin plugin;
SpongeCommand(LPSpongePlugin plugin) {
@@ -58,56 +54,56 @@ class SpongeCommand extends CommandManager implements CommandCallable {
this.plugin = plugin;
}
@Override
public CommandResult process(CommandSource source, String s) throws CommandException {
try (Timing ignored = plugin.getTimings().time(LPTiming.ON_COMMAND)) {
List<String> args = Util.stripQuotes(Splitter.on(COMMAND_SEPARATOR_PATTERN).omitEmptyStrings().splitToList(s));
private List<String> processArgs(CommandSource source, String s) {
List<String> args = Util.stripQuotes(Splitter.on(COMMAND_SEPARATOR_PATTERN).omitEmptyStrings().splitToList(s));
// resolve selectors
ListIterator<String> it = args.listIterator();
while (it.hasNext()) {
String element = it.next();
if (element.startsWith("@")) {
try {
Player ret = Selector.parse(element).resolve(source).stream()
.filter(e -> e instanceof Player)
.map(e -> ((Player) e))
.findFirst().orElse(null);
// resolve selectors
ListIterator<String> it = args.listIterator();
while (it.hasNext()) {
String element = it.next();
if (element.startsWith("@")) {
try {
Player ret = Selector.parse(element).resolve(source).stream()
.filter(e -> e instanceof Player)
.map(e -> ((Player) e))
.findFirst().orElse(null);
if (ret != null) {
it.set(ret.getUniqueId().toString());
}
} catch (IllegalArgumentException e) {
// ignored
if (ret != null) {
it.set(ret.getUniqueId().toString());
}
} catch (IllegalArgumentException e) {
// ignored
}
}
onCommand(plugin.getSenderFactory().wrap(source), "lp", args);
return CommandResult.success();
}
return args;
}
@Override
public CommandResult process(CommandSource source, String s) throws CommandException {
onCommand(plugin.getSenderFactory().wrap(source), "lp", processArgs(source, s));
return CommandResult.success();
}
@Override
public List<String> getSuggestions(CommandSource source, String s, @Nullable Location<World> location) throws CommandException {
try (Timing ignored = plugin.getTimings().time(LPTiming.COMMAND_TAB_COMPLETE)) {
return onTabComplete(plugin.getSenderFactory().wrap(source), Splitter.on(' ').splitToList(s));
}
return onTabComplete(plugin.getSenderFactory().wrap(source), processArgs(source, s));
}
@Override
public boolean testPermission(CommandSource source) {
return true;
return true; // we run permission checks internally
}
@Override
public Optional<Text> getShortDescription(CommandSource source) {
return Optional.of(Text.of("LuckPerms main command."));
return Optional.of(Text.of("Manage permissions"));
}
@Override
public Optional<Text> getHelp(CommandSource source) {
return Optional.of(Text.of("Type /luckperms for help."));
return Optional.of(Text.of("Run /luckperms to view usage."));
}
@Override
@@ -34,7 +34,6 @@ import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.utils.LoginHelper;
import me.lucko.luckperms.common.utils.UuidCache;
import me.lucko.luckperms.sponge.timings.LPTiming;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.entity.living.player.Player;
@@ -48,8 +47,6 @@ import org.spongepowered.api.text.serializer.TextSerializers;
import org.spongepowered.api.util.Tristate;
import org.spongepowered.api.world.World;
import co.aikar.timings.Timing;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -142,52 +139,50 @@ public class SpongeListener {
@Listener(order = Order.FIRST)
@IsCancelled(Tristate.UNDEFINED)
public void onClientLogin(ClientConnectionEvent.Login e) {
try (Timing ignored = plugin.getTimings().time(LPTiming.ON_CLIENT_LOGIN)) {
/* Called when the player starts logging into the server.
/* Called when the player starts logging into the server.
At this point, the users data should be present and loaded.
Listening on LOW priority to allow plugins to further modify data here. (auth plugins, etc.) */
final GameProfile player = e.getProfile();
final GameProfile player = e.getProfile();
if (plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
plugin.getLog().info("Processing login event for " + player.getUniqueId() + " - " + player.getName());
}
if (plugin.getConfiguration().get(ConfigKeys.DEBUG_LOGINS)) {
plugin.getLog().info("Processing login event for " + player.getUniqueId() + " - " + player.getName());
}
final User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(player.getUniqueId()));
final User user = plugin.getUserManager().getIfLoaded(plugin.getUuidCache().getUUID(player.getUniqueId()));
/* User instance is null for whatever reason. Could be that it was unloaded between asyncpre and now. */
if (user == null) {
deniedLogin.add(player.getUniqueId());
if (user == null) {
deniedLogin.add(player.getUniqueId());
plugin.getLog().warn("User " + player.getUniqueId() + " - " + player.getName() + " doesn't have data pre-loaded. - denying login.");
e.setCancelled(true);
e.setMessageCancelled(false);
//noinspection deprecation
e.setMessage(TextSerializers.LEGACY_FORMATTING_CODE.deserialize(Message.LOADING_ERROR.asString(plugin.getLocaleManager())));
return;
}
plugin.getLog().warn("User " + player.getUniqueId() + " - " + player.getName() + " doesn't have data pre-loaded. - denying login.");
e.setCancelled(true);
e.setMessageCancelled(false);
//noinspection deprecation
e.setMessage(TextSerializers.LEGACY_FORMATTING_CODE.deserialize(Message.LOADING_ERROR.asString(plugin.getLocaleManager())));
return;
}
// Attempt to pre-process some permissions for the user to save time later. Might not work, but it's better than nothing.
Optional<Player> p = e.getCause().first(Player.class);
if (p.isPresent()) {
MutableContextSet context = MutableContextSet.fromSet(plugin.getContextManager().getApplicableContext(p.get()));
// Attempt to pre-process some permissions for the user to save time later. Might not work, but it's better than nothing.
Optional<Player> p = e.getCause().first(Player.class);
if (p.isPresent()) {
MutableContextSet context = MutableContextSet.fromSet(plugin.getContextManager().getApplicableContext(p.get()));
List<String> worlds = plugin.getGame().isServerAvailable() ? plugin.getGame().getServer().getWorlds().stream()
.map(World::getName)
.collect(Collectors.toList()) : Collections.emptyList();
List<String> worlds = plugin.getGame().isServerAvailable() ? plugin.getGame().getServer().getWorlds().stream()
.map(World::getName)
.collect(Collectors.toList()) : Collections.emptyList();
plugin.doAsync(() -> {
UserData data = user.getUserData();
data.preCalculate(plugin.getService().calculateContexts(context.makeImmutable()));
plugin.doAsync(() -> {
UserData data = user.getUserData();
data.preCalculate(plugin.getService().calculateContexts(context.makeImmutable()));
for (String world : worlds) {
MutableContextSet modified = MutableContextSet.fromSet(context);
modified.removeAll("world");
modified.add("world", world);
data.preCalculate(plugin.getService().calculateContexts(modified.makeImmutable()));
}
});
}
for (String world : worlds) {
MutableContextSet modified = MutableContextSet.fromSet(context);
modified.removeAll("world");
modified.add("world", world);
data.preCalculate(plugin.getService().calculateContexts(modified.makeImmutable()));
}
});
}
}
@@ -218,12 +213,11 @@ public class SpongeListener {
/* We don't actually remove the user instance here, as Sponge likes to keep performing checks
on players when they disconnect. The instance gets cleared up on a housekeeping task
after a period of inactivity. */
try (Timing ignored = plugin.getTimings().time(LPTiming.ON_CLIENT_LEAVE)) {
final UuidCache cache = plugin.getUuidCache();
// Unload the user from memory when they disconnect
cache.clearCache(e.getTargetEntity().getUniqueId());
}
final UuidCache cache = plugin.getUuidCache();
// Unload the user from memory when they disconnect
cache.clearCache(e.getTargetEntity().getUniqueId());
}
@Listener
@@ -60,7 +60,7 @@ public class SpongeCalculatorLink implements ContextCalculator<Subject> {
delegate.accumulateContexts(subject, contexts);
accumulator.addAll(CompatibilityUtil.convertContexts(contexts));
} catch (Exception e) {
new RuntimeException("Exception thrown by delegate Sponge calculator: " + delegate.getClass().getName(), e).printStackTrace();
throw new RuntimeException("Exception thrown by delegate Sponge calculator: " + delegate.getClass().getName(), e);
}
return accumulator;
@@ -47,13 +47,13 @@ import java.util.Set;
@UtilityClass
public class SpongeMigrationUtils {
public static void migrateSubject(Subject subject, PermissionHolder holder, int priority) {
if (holder instanceof Group) {
MigrationUtils.setGroupWeight((Group) holder, priority);
public static void migrateSubject(Subject from, PermissionHolder to, int priority) {
if (to instanceof Group) {
MigrationUtils.setGroupWeight((Group) to, priority);
}
// Migrate permissions
Map<Set<Context>, Map<String, Boolean>> perms = subject.getSubjectData().getAllPermissions();
Map<Set<Context>, Map<String, Boolean>> perms = from.getSubjectData().getAllPermissions();
for (Map.Entry<Set<Context>, Map<String, Boolean>> e : perms.entrySet()) {
ContextSet context = CompatibilityUtil.convertContexts(e.getKey());
@@ -62,12 +62,12 @@ public class SpongeMigrationUtils {
continue;
}
holder.setPermission(NodeFactory.newBuilder(perm.getKey()).withExtraContext(context).setValue(perm.getValue()).build());
to.setPermission(NodeFactory.newBuilder(perm.getKey()).withExtraContext(context).setValue(perm.getValue()).build());
}
}
// Migrate options
Map<Set<Context>, Map<String, String>> opts = subject.getSubjectData().getAllOptions();
Map<Set<Context>, Map<String, String>> opts = from.getSubjectData().getAllOptions();
for (Map.Entry<Set<Context>, Map<String, String>> e : opts.entrySet()) {
ContextSet context = CompatibilityUtil.convertContexts(e.getKey());
@@ -77,17 +77,17 @@ public class SpongeMigrationUtils {
}
if (opt.getKey().equalsIgnoreCase("prefix")) {
holder.setPermission(NodeFactory.makePrefixNode(priority, opt.getValue()).withExtraContext(context).setValue(true).build());
to.setPermission(NodeFactory.makePrefixNode(priority, opt.getValue()).withExtraContext(context).setValue(true).build());
} else if (opt.getKey().equalsIgnoreCase("suffix")) {
holder.setPermission(NodeFactory.makeSuffixNode(priority, opt.getValue()).withExtraContext(context).setValue(true).build());
to.setPermission(NodeFactory.makeSuffixNode(priority, opt.getValue()).withExtraContext(context).setValue(true).build());
} else {
holder.setPermission(NodeFactory.makeMetaNode(opt.getKey(), opt.getValue()).withExtraContext(context).setValue(true).build());
to.setPermission(NodeFactory.makeMetaNode(opt.getKey(), opt.getValue()).withExtraContext(context).setValue(true).build());
}
}
}
// Migrate parents
Map<Set<Context>, List<Subject>> parents = subject.getSubjectData().getAllParents();
Map<Set<Context>, List<Subject>> parents = from.getSubjectData().getAllParents();
for (Map.Entry<Set<Context>, List<Subject>> e : parents.entrySet()) {
ContextSet context = CompatibilityUtil.convertContexts(e.getKey());
@@ -96,7 +96,7 @@ public class SpongeMigrationUtils {
continue; // LuckPerms does not support persisting other subject types.
}
holder.setPermission(NodeFactory.newBuilder("group." + MigrationUtils.standardizeName(s.getIdentifier())).withExtraContext(context).setValue(true).build());
to.setPermission(NodeFactory.newBuilder("group." + MigrationUtils.standardizeName(s.getIdentifier())).withExtraContext(context).setValue(true).build());
}
}
}
@@ -48,15 +48,12 @@ import me.lucko.luckperms.sponge.service.model.CompatibilityUtil;
import me.lucko.luckperms.sponge.service.model.LPSubject;
import me.lucko.luckperms.sponge.service.model.LPSubjectCollection;
import me.lucko.luckperms.sponge.service.model.SubjectReference;
import me.lucko.luckperms.sponge.timings.LPTiming;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.service.permission.NodeTree;
import org.spongepowered.api.service.permission.PermissionService;
import org.spongepowered.api.service.permission.Subject;
import co.aikar.timings.Timing;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -176,69 +173,59 @@ public class SpongeGroup extends Group {
@Override
public Tristate getPermissionValue(ImmutableContextSet contexts, String permission) {
try (Timing ignored = plugin.getTimings().time(LPTiming.GROUP_GET_PERMISSION_VALUE)) {
NodeTree nt = permissionCache.get(contexts);
Tristate t = CompatibilityUtil.convertTristate(nt.get(permission));
if (t != Tristate.UNDEFINED) {
return t;
}
t = plugin.getService().getGroupSubjects().getDefaults().getPermissionValue(contexts, permission);
if (t != Tristate.UNDEFINED) {
return t;
}
t = plugin.getService().getDefaults().getPermissionValue(contexts, permission);
NodeTree nt = permissionCache.get(contexts);
Tristate t = CompatibilityUtil.convertTristate(nt.get(permission));
if (t != Tristate.UNDEFINED) {
return t;
}
t = plugin.getService().getGroupSubjects().getDefaults().getPermissionValue(contexts, permission);
if (t != Tristate.UNDEFINED) {
return t;
}
t = plugin.getService().getDefaults().getPermissionValue(contexts, permission);
return t;
}
@Override
public boolean isChildOf(ImmutableContextSet contexts, SubjectReference parent) {
try (Timing ignored = plugin.getTimings().time(LPTiming.GROUP_IS_CHILD_OF)) {
return parent.getCollectionIdentifier().equals(PermissionService.SUBJECTS_GROUP) && getPermissionValue(contexts, "group." + parent.getSubjectIdentifier()).asBoolean();
}
return parent.getCollectionIdentifier().equals(PermissionService.SUBJECTS_GROUP) && getPermissionValue(contexts, "group." + parent.getSubjectIdentifier()).asBoolean();
}
@Override
public ImmutableList<SubjectReference> getParents(ImmutableContextSet contexts) {
try (Timing ignored = plugin.getTimings().time(LPTiming.GROUP_GET_PARENTS)) {
return parentCache.get(contexts);
}
return parentCache.get(contexts);
}
@Override
public Optional<String> getOption(ImmutableContextSet contexts, String s) {
try (Timing ignored = plugin.getService().getPlugin().getTimings().time(LPTiming.GROUP_GET_OPTION)) {
Optional<String> option;
if (s.equalsIgnoreCase("prefix")) {
option = getChatMeta(contexts, ChatMetaType.PREFIX);
Optional<String> option;
if (s.equalsIgnoreCase("prefix")) {
option = getChatMeta(contexts, ChatMetaType.PREFIX);
} else if (s.equalsIgnoreCase("suffix")) {
option = getChatMeta(contexts, ChatMetaType.SUFFIX);
} else if (s.equalsIgnoreCase("suffix")) {
option = getChatMeta(contexts, ChatMetaType.SUFFIX);
} else {
option = getMeta(contexts, s);
}
if (option.isPresent()) {
return option;
}
option = plugin.getService().getGroupSubjects().getDefaults().getOption(contexts, s);
if (option.isPresent()) {
return option;
}
return plugin.getService().getDefaults().getOption(contexts, s);
} else {
option = getMeta(contexts, s);
}
if (option.isPresent()) {
return option;
}
option = plugin.getService().getGroupSubjects().getDefaults().getOption(contexts, s);
if (option.isPresent()) {
return option;
}
return plugin.getService().getDefaults().getOption(contexts, s);
}
@Override
public ImmutableContextSet getActiveContextSet() {
try (Timing ignored = plugin.getTimings().time(LPTiming.GROUP_GET_ACTIVE_CONTEXTS)) {
return plugin.getContextManager().getApplicableContext(this.sponge()).makeImmutable();
}
return plugin.getContextManager().getApplicableContext(this.sponge()).makeImmutable();
}
private Optional<String> getChatMeta(ImmutableContextSet contexts, ChatMetaType type) {
@@ -42,7 +42,6 @@ import me.lucko.luckperms.sponge.service.ProxyFactory;
import me.lucko.luckperms.sponge.service.model.LPSubject;
import me.lucko.luckperms.sponge.service.model.LPSubjectCollection;
import me.lucko.luckperms.sponge.service.model.SubjectReference;
import me.lucko.luckperms.sponge.timings.LPTiming;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandSource;
@@ -50,8 +49,6 @@ import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.service.permission.PermissionService;
import org.spongepowered.api.service.permission.Subject;
import co.aikar.timings.Timing;
import java.util.Optional;
import java.util.UUID;
@@ -129,75 +126,66 @@ public class SpongeUser extends User {
@Override
public Tristate getPermissionValue(ImmutableContextSet contexts, String permission) {
try (Timing ignored = plugin.getTimings().time(LPTiming.USER_GET_PERMISSION_VALUE)) {
return parent.getUserData().getPermissionData(plugin.getService().calculateContexts(contexts)).getPermissionValue(permission, CheckOrigin.PLATFORM_LOOKUP_CHECK);
}
return parent.getUserData().getPermissionData(plugin.getService().calculateContexts(contexts)).getPermissionValue(permission, CheckOrigin.PLATFORM_LOOKUP_CHECK);
}
@Override
public boolean isChildOf(ImmutableContextSet contexts, SubjectReference parent) {
try (Timing ignored = plugin.getTimings().time(LPTiming.USER_IS_CHILD_OF)) {
return parent.getCollectionIdentifier().equals(PermissionService.SUBJECTS_GROUP) && getPermissionValue(contexts, "group." + parent.getSubjectIdentifier()).asBoolean();
}
return parent.getCollectionIdentifier().equals(PermissionService.SUBJECTS_GROUP) && getPermissionValue(contexts, "group." + parent.getSubjectIdentifier()).asBoolean();
}
@Override
public ImmutableList<SubjectReference> getParents(ImmutableContextSet contexts) {
try (Timing ignored = plugin.getTimings().time(LPTiming.USER_GET_PARENTS)) {
ImmutableSet.Builder<SubjectReference> subjects = ImmutableSet.builder();
ImmutableSet.Builder<SubjectReference> subjects = ImmutableSet.builder();
for (String perm : parent.getUserData().getPermissionData(plugin.getService().calculateContexts(contexts)).getImmutableBacking().keySet()) {
if (!perm.startsWith("group.")) {
continue;
}
String groupName = perm.substring("group.".length());
if (plugin.getGroupManager().isLoaded(groupName)) {
subjects.add(plugin.getService().getGroupSubjects().loadSubject(groupName).join().toReference());
}
for (String perm : parent.getUserData().getPermissionData(plugin.getService().calculateContexts(contexts)).getImmutableBacking().keySet()) {
if (!perm.startsWith("group.")) {
continue;
}
subjects.addAll(plugin.getService().getUserSubjects().getDefaults().getParents(contexts));
subjects.addAll(plugin.getService().getDefaults().getParents(contexts));
return getService().sortSubjects(subjects.build());
String groupName = perm.substring("group.".length());
if (plugin.getGroupManager().isLoaded(groupName)) {
subjects.add(plugin.getService().getGroupSubjects().loadSubject(groupName).join().toReference());
}
}
subjects.addAll(plugin.getService().getUserSubjects().getDefaults().getParents(contexts));
subjects.addAll(plugin.getService().getDefaults().getParents(contexts));
return getService().sortSubjects(subjects.build());
}
@Override
public Optional<String> getOption(ImmutableContextSet contexts, String s) {
try (Timing ignored = plugin.getTimings().time(LPTiming.USER_GET_OPTION)) {
MetaData data = parent.getUserData().getMetaData(plugin.getService().calculateContexts(contexts));
if (s.equalsIgnoreCase("prefix")) {
if (data.getPrefix() != null) {
return Optional.of(data.getPrefix());
}
MetaData data = parent.getUserData().getMetaData(plugin.getService().calculateContexts(contexts));
if (s.equalsIgnoreCase("prefix")) {
if (data.getPrefix() != null) {
return Optional.of(data.getPrefix());
}
if (s.equalsIgnoreCase("suffix")) {
if (data.getSuffix() != null) {
return Optional.of(data.getSuffix());
}
}
if (data.getMeta().containsKey(s)) {
return Optional.of(data.getMeta().get(s));
}
Optional<String> v = plugin.getService().getUserSubjects().getDefaults().getOption(contexts, s);
if (v.isPresent()) {
return v;
}
return plugin.getService().getDefaults().getOption(contexts, s);
}
if (s.equalsIgnoreCase("suffix")) {
if (data.getSuffix() != null) {
return Optional.of(data.getSuffix());
}
}
String val = data.getMeta().get(s);
if (val != null) {
return Optional.of(val);
}
Optional<String> v = plugin.getService().getUserSubjects().getDefaults().getOption(contexts, s);
if (v.isPresent()) {
return v;
}
return plugin.getService().getDefaults().getOption(contexts, s);
}
@Override
public ImmutableContextSet getActiveContextSet() {
try (Timing ignored = plugin.getTimings().time(LPTiming.USER_GET_ACTIVE_CONTEXTS)) {
return plugin.getContextManager().getApplicableContext(this.sponge());
}
return plugin.getContextManager().getApplicableContext(this.sponge());
}
}
@@ -29,7 +29,6 @@ import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
@@ -58,17 +57,15 @@ import me.lucko.luckperms.sponge.service.model.LPPermissionService;
import me.lucko.luckperms.sponge.service.model.LPSubject;
import me.lucko.luckperms.sponge.service.model.LPSubjectCollection;
import me.lucko.luckperms.sponge.service.model.SubjectReference;
import me.lucko.luckperms.sponge.service.model.SubjectReferenceFactory;
import me.lucko.luckperms.sponge.service.persisted.PersistedCollection;
import me.lucko.luckperms.sponge.service.storage.SubjectStorage;
import me.lucko.luckperms.sponge.timings.LPTiming;
import org.spongepowered.api.plugin.PluginContainer;
import org.spongepowered.api.service.permission.PermissionService;
import org.spongepowered.api.service.permission.Subject;
import org.spongepowered.api.text.Text;
import co.aikar.timings.Timing;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
@@ -81,7 +78,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
/**
* The LuckPerms implementation of the Sponge Permission Service
* LuckPerms implementation of the Sponge Permission Service
*/
@Getter
public class LuckPermsService implements LPPermissionService {
@@ -105,17 +102,7 @@ public class LuckPermsService implements LPPermissionService {
@Getter(value = AccessLevel.NONE)
private final LoadingCache<String, LPSubjectCollection> collections = Caffeine.newBuilder()
.build(new CacheLoader<String, LPSubjectCollection>() {
@Override
public LPSubjectCollection load(String s) {
return new PersistedCollection(LuckPermsService.this, s);
}
@Override
public LPSubjectCollection reload(String s, LPSubjectCollection collection) {
return collection; // Never needs to be refreshed.
}
});
.build(s -> new PersistedCollection(this, s));
public LuckPermsService(LPSpongePlugin plugin) {
this.plugin = plugin;
@@ -134,8 +121,8 @@ public class LuckPermsService implements LPPermissionService {
defaultSubjects = new PersistedCollection(this, "defaults");
defaultSubjects.loadAll();
collections.put(PermissionService.SUBJECTS_USER, userSubjects);
collections.put(PermissionService.SUBJECTS_GROUP, groupSubjects);
collections.put("user", userSubjects);
collections.put("group", groupSubjects);
collections.put("defaults", defaultSubjects);
for (String collection : storage.getSavedCollections()) {
@@ -168,9 +155,7 @@ public class LuckPermsService implements LPPermissionService {
@Override
public LPSubjectCollection getCollection(String s) {
try (Timing ignored = plugin.getTimings().time(LPTiming.GET_SUBJECTS)) {
return collections.get(s.toLowerCase());
}
return collections.get(s.toLowerCase());
}
@Override
@@ -180,7 +165,7 @@ public class LuckPermsService implements LPPermissionService {
@Override
public SubjectReference newSubjectReference(String collectionIdentifier, String subjectIdentifier) {
return SubjectReference.of(this, collectionIdentifier, subjectIdentifier);
return SubjectReferenceFactory.obtain(this, collectionIdentifier, subjectIdentifier);
}
@Override
@@ -46,12 +46,9 @@ import me.lucko.luckperms.common.node.NodeFactory;
import me.lucko.luckperms.sponge.service.model.LPSubject;
import me.lucko.luckperms.sponge.service.model.LPSubjectData;
import me.lucko.luckperms.sponge.service.model.SubjectReference;
import me.lucko.luckperms.sponge.timings.LPTiming;
import org.spongepowered.api.service.permission.PermissionService;
import co.aikar.timings.Timing;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -75,388 +72,358 @@ public class LuckPermsSubjectData implements LPSubjectData {
@Override
public ImmutableMap<ImmutableContextSet, ImmutableMap<String, Boolean>> getAllPermissions() {
try (Timing ignored = service.getPlugin().getTimings().time(LPTiming.LP_SUBJECT_GET_PERMISSIONS)) {
Map<ImmutableContextSet, ImmutableMap.Builder<String, Boolean>> perms = new HashMap<>();
Map<ImmutableContextSet, ImmutableMap.Builder<String, Boolean>> perms = new HashMap<>();
for (Map.Entry<ImmutableContextSet, Collection<Node>> e : (enduring ? holder.getEnduringNodes() : holder.getTransientNodes()).asMap().entrySet()) {
ImmutableMap.Builder<String, Boolean> results = ImmutableMap.builder();
for (Node n : e.getValue()) {
results.put(n.getPermission(), n.getValuePrimitive());
}
perms.put(e.getKey(), results);
for (Map.Entry<ImmutableContextSet, Collection<Node>> e : (enduring ? holder.getEnduringNodes() : holder.getTransientNodes()).asMap().entrySet()) {
ImmutableMap.Builder<String, Boolean> results = ImmutableMap.builder();
for (Node n : e.getValue()) {
results.put(n.getPermission(), n.getValuePrimitive());
}
ImmutableMap.Builder<ImmutableContextSet, ImmutableMap<String, Boolean>> map = ImmutableMap.builder();
for (Map.Entry<ImmutableContextSet, ImmutableMap.Builder<String, Boolean>> e : perms.entrySet()) {
map.put(e.getKey(), e.getValue().build());
}
return map.build();
perms.put(e.getKey(), results);
}
ImmutableMap.Builder<ImmutableContextSet, ImmutableMap<String, Boolean>> map = ImmutableMap.builder();
for (Map.Entry<ImmutableContextSet, ImmutableMap.Builder<String, Boolean>> e : perms.entrySet()) {
map.put(e.getKey(), e.getValue().build());
}
return map.build();
}
@Override
public CompletableFuture<Boolean> setPermission(@NonNull ImmutableContextSet contexts, @NonNull String permission, @NonNull Tristate tristate) {
try (Timing i = service.getPlugin().getTimings().time(LPTiming.LP_SUBJECT_SET_PERMISSION)) {
if (tristate == Tristate.UNDEFINED) {
// Unset
Node node = NodeFactory.newBuilder(permission).withExtraContext(contexts).build();
if (tristate == Tristate.UNDEFINED) {
// Unset
Node node = NodeFactory.newBuilder(permission).withExtraContext(contexts).build();
if (enduring) {
holder.unsetPermission(node);
} else {
holder.unsetTransientPermission(node);
}
return objectSave(holder).thenApply(v -> true);
}
Node node = NodeFactory.newBuilder(permission).setValue(tristate.asBoolean()).withExtraContext(contexts).build();
// Workaround: unset the inverse, to allow false -> true, true -> false overrides.
if (enduring) {
holder.unsetPermission(node);
} else {
holder.unsetTransientPermission(node);
}
if (enduring) {
holder.setPermission(node);
} else {
holder.setTransientPermission(node);
}
return objectSave(holder).thenApply(v -> true);
}
Node node = NodeFactory.newBuilder(permission).setValue(tristate.asBoolean()).withExtraContext(contexts).build();
// Workaround: unset the inverse, to allow false -> true, true -> false overrides.
if (enduring) {
holder.unsetPermission(node);
} else {
holder.unsetTransientPermission(node);
}
if (enduring) {
holder.setPermission(node);
} else {
holder.setTransientPermission(node);
}
return objectSave(holder).thenApply(v -> true);
}
@Override
public CompletableFuture<Boolean> clearPermissions() {
try (Timing ignored = service.getPlugin().getTimings().time(LPTiming.LP_SUBJECT_CLEAR_PERMISSIONS)) {
boolean ret;
if (enduring) {
ret = holder.clearNodes();
} else {
ret = holder.clearTransientNodes();
}
if (!ret) {
return CompletableFuture.completedFuture(false);
}
if (holder instanceof User) {
service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false);
}
return objectSave(holder).thenApply(v -> true);
boolean ret;
if (enduring) {
ret = holder.clearNodes();
} else {
ret = holder.clearTransientNodes();
}
if (!ret) {
return CompletableFuture.completedFuture(false);
}
if (holder instanceof User) {
service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false);
}
return objectSave(holder).thenApply(v -> true);
}
@Override
public CompletableFuture<Boolean> clearPermissions(@NonNull ImmutableContextSet set) {
try (Timing i = service.getPlugin().getTimings().time(LPTiming.LP_SUBJECT_CLEAR_PERMISSIONS)) {
boolean ret;
boolean ret;
if (enduring) {
ret = holder.clearNodes(set);
} else {
List<Node> toRemove = streamNodes(false)
.filter(n -> n.getFullContexts().equals(set))
.collect(Collectors.toList());
if (enduring) {
ret = holder.clearNodes(set);
} else {
List<Node> toRemove = streamNodes(false)
.filter(n -> n.getFullContexts().equals(set))
.collect(Collectors.toList());
toRemove.forEach(makeUnsetConsumer(false));
ret = !toRemove.isEmpty();
}
if (!ret) {
return CompletableFuture.completedFuture(false);
}
if (holder instanceof User) {
service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false);
}
return objectSave(holder).thenApply(v -> true);
toRemove.forEach(makeUnsetConsumer(false));
ret = !toRemove.isEmpty();
}
if (!ret) {
return CompletableFuture.completedFuture(false);
}
if (holder instanceof User) {
service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false);
}
return objectSave(holder).thenApply(v -> true);
}
@Override
public ImmutableMap<ImmutableContextSet, ImmutableList<SubjectReference>> getAllParents() {
try (Timing ignored = service.getPlugin().getTimings().time(LPTiming.LP_SUBJECT_GET_PARENTS)) {
Map<ImmutableContextSet, ImmutableList.Builder<SubjectReference>> parents = new HashMap<>();
Map<ImmutableContextSet, ImmutableList.Builder<SubjectReference>> parents = new HashMap<>();
for (Map.Entry<ImmutableContextSet, Collection<Node>> e : (enduring ? holder.getEnduringNodes() : holder.getTransientNodes()).asMap().entrySet()) {
ImmutableList.Builder<SubjectReference> results = ImmutableList.builder();
for (Node n : e.getValue()) {
if (n.isGroupNode()) {
results.add(service.getGroupSubjects().loadSubject(n.getGroupName()).join().toReference());
}
for (Map.Entry<ImmutableContextSet, Collection<Node>> e : (enduring ? holder.getEnduringNodes() : holder.getTransientNodes()).asMap().entrySet()) {
ImmutableList.Builder<SubjectReference> results = ImmutableList.builder();
for (Node n : e.getValue()) {
if (n.isGroupNode()) {
results.add(service.getGroupSubjects().loadSubject(n.getGroupName()).join().toReference());
}
parents.put(e.getKey(), results);
}
ImmutableMap.Builder<ImmutableContextSet, ImmutableList<SubjectReference>> map = ImmutableMap.builder();
for (Map.Entry<ImmutableContextSet, ImmutableList.Builder<SubjectReference>> e : parents.entrySet()) {
map.put(e.getKey(), e.getValue().build());
}
return map.build();
parents.put(e.getKey(), results);
}
ImmutableMap.Builder<ImmutableContextSet, ImmutableList<SubjectReference>> map = ImmutableMap.builder();
for (Map.Entry<ImmutableContextSet, ImmutableList.Builder<SubjectReference>> e : parents.entrySet()) {
map.put(e.getKey(), e.getValue().build());
}
return map.build();
}
@Override
public CompletableFuture<Boolean> addParent(@NonNull ImmutableContextSet contexts, @NonNull SubjectReference subject) {
try (Timing i = service.getPlugin().getTimings().time(LPTiming.LP_SUBJECT_ADD_PARENT)) {
if (subject.getCollectionIdentifier().equals(PermissionService.SUBJECTS_GROUP)) {
return subject.resolveLp().thenCompose(sub -> {
DataMutateResult result;
if (subject.getCollectionIdentifier().equals(PermissionService.SUBJECTS_GROUP)) {
return subject.resolveLp().thenCompose(sub -> {
DataMutateResult result;
if (enduring) {
result = holder.setPermission(NodeFactory.newBuilder("group." + sub.getIdentifier())
.withExtraContext(contexts)
.build());
} else {
result = holder.setTransientPermission(NodeFactory.newBuilder("group." + sub.getIdentifier())
.withExtraContext(contexts)
.build());
}
if (enduring) {
result = holder.setPermission(NodeFactory.newBuilder("group." + sub.getIdentifier())
.withExtraContext(contexts)
.build());
} else {
result = holder.setTransientPermission(NodeFactory.newBuilder("group." + sub.getIdentifier())
.withExtraContext(contexts)
.build());
}
if (!result.asBoolean()) {
return CompletableFuture.completedFuture(false);
}
if (!result.asBoolean()) {
return CompletableFuture.completedFuture(false);
}
return objectSave(holder).thenApply(v -> true);
});
}
return CompletableFuture.completedFuture(false);
return objectSave(holder).thenApply(v -> true);
});
}
return CompletableFuture.completedFuture(false);
}
@Override
public CompletableFuture<Boolean> removeParent(@NonNull ImmutableContextSet contexts, @NonNull SubjectReference subject) {
try (Timing i = service.getPlugin().getTimings().time(LPTiming.LP_SUBJECT_REMOVE_PARENT)) {
if (subject.getCollectionIdentifier().equals(PermissionService.SUBJECTS_GROUP)) {
subject.resolveLp().thenCompose(sub -> {
DataMutateResult result;
if (subject.getCollectionIdentifier().equals(PermissionService.SUBJECTS_GROUP)) {
subject.resolveLp().thenCompose(sub -> {
DataMutateResult result;
if (enduring) {
result = holder.unsetPermission(NodeFactory.newBuilder("group." + sub.getIdentifier())
.withExtraContext(contexts)
.build());
} else {
result = holder.unsetTransientPermission(NodeFactory.newBuilder("group." + sub.getIdentifier())
.withExtraContext(contexts)
.build());
}
if (enduring) {
result = holder.unsetPermission(NodeFactory.newBuilder("group." + sub.getIdentifier())
.withExtraContext(contexts)
.build());
} else {
result = holder.unsetTransientPermission(NodeFactory.newBuilder("group." + sub.getIdentifier())
.withExtraContext(contexts)
.build());
}
if (!result.asBoolean()) {
return CompletableFuture.completedFuture(false);
}
if (!result.asBoolean()) {
return CompletableFuture.completedFuture(false);
}
return objectSave(holder).thenApply(v -> true);
});
}
return CompletableFuture.completedFuture(false);
return objectSave(holder).thenApply(v -> true);
});
}
return CompletableFuture.completedFuture(false);
}
@Override
public CompletableFuture<Boolean> clearParents() {
try (Timing i = service.getPlugin().getTimings().time(LPTiming.LP_SUBJECT_CLEAR_PARENTS)) {
boolean ret;
boolean ret;
if (enduring) {
ret = holder.clearParents(true);
} else {
List<Node> toRemove = streamNodes(false)
.filter(Node::isGroupNode)
.collect(Collectors.toList());
if (enduring) {
ret = holder.clearParents(true);
} else {
List<Node> toRemove = streamNodes(false)
.filter(Node::isGroupNode)
.collect(Collectors.toList());
toRemove.forEach(makeUnsetConsumer(false));
ret = !toRemove.isEmpty();
toRemove.forEach(makeUnsetConsumer(false));
ret = !toRemove.isEmpty();
if (ret && holder instanceof User) {
service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false);
}
if (ret && holder instanceof User) {
service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false);
}
if (!ret) {
return CompletableFuture.completedFuture(false);
}
return objectSave(holder).thenApply(v -> true);
}
if (!ret) {
return CompletableFuture.completedFuture(false);
}
return objectSave(holder).thenApply(v -> true);
}
@Override
public CompletableFuture<Boolean> clearParents(@NonNull ImmutableContextSet set) {
try (Timing i = service.getPlugin().getTimings().time(LPTiming.LP_SUBJECT_CLEAR_PARENTS)) {
boolean ret;
if (enduring) {
ret = holder.clearParents(set, true);
} else {
List<Node> toRemove = streamNodes(false)
.filter(Node::isGroupNode)
.filter(n -> n.getFullContexts().equals(set))
.collect(Collectors.toList());
boolean ret;
if (enduring) {
ret = holder.clearParents(set, true);
} else {
List<Node> toRemove = streamNodes(false)
.filter(Node::isGroupNode)
.filter(n -> n.getFullContexts().equals(set))
.collect(Collectors.toList());
toRemove.forEach(makeUnsetConsumer(false));
ret = !toRemove.isEmpty();
toRemove.forEach(makeUnsetConsumer(false));
ret = !toRemove.isEmpty();
if (ret && holder instanceof User) {
service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false);
}
if (ret && holder instanceof User) {
service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false);
}
if (!ret) {
return CompletableFuture.completedFuture(false);
}
return objectSave(holder).thenApply(v -> true);
}
if (!ret) {
return CompletableFuture.completedFuture(false);
}
return objectSave(holder).thenApply(v -> true);
}
@Override
public ImmutableMap<ImmutableContextSet, ImmutableMap<String, String>> getAllOptions() {
try (Timing ignored = service.getPlugin().getTimings().time(LPTiming.LP_SUBJECT_GET_OPTIONS)) {
Map<ImmutableContextSet, Map<String, String>> options = new HashMap<>();
Map<ImmutableContextSet, Integer> minPrefixPriority = new HashMap<>();
Map<ImmutableContextSet, Integer> minSuffixPriority = new HashMap<>();
Map<ImmutableContextSet, Map<String, String>> options = new HashMap<>();
Map<ImmutableContextSet, Integer> minPrefixPriority = new HashMap<>();
Map<ImmutableContextSet, Integer> minSuffixPriority = new HashMap<>();
for (Node n : enduring ? holder.getEnduringNodes().values() : holder.getTransientNodes().values()) {
if (!n.getValuePrimitive()) continue;
if (!n.isMeta() && !n.isPrefix() && !n.isSuffix()) continue;
for (Node n : enduring ? holder.getEnduringNodes().values() : holder.getTransientNodes().values()) {
if (!n.getValuePrimitive()) continue;
if (!n.isMeta() && !n.isPrefix() && !n.isSuffix()) continue;
ImmutableContextSet immutableContexts = n.getFullContexts().makeImmutable();
ImmutableContextSet immutableContexts = n.getFullContexts().makeImmutable();
if (!options.containsKey(immutableContexts)) {
options.put(immutableContexts, new HashMap<>());
minPrefixPriority.put(immutableContexts, Integer.MIN_VALUE);
minSuffixPriority.put(immutableContexts, Integer.MIN_VALUE);
}
if (n.isPrefix()) {
Map.Entry<Integer, String> value = n.getPrefix();
if (value.getKey() > minPrefixPriority.get(immutableContexts)) {
options.get(immutableContexts).put("prefix", value.getValue());
minPrefixPriority.put(immutableContexts, value.getKey());
}
continue;
}
if (n.isSuffix()) {
Map.Entry<Integer, String> value = n.getSuffix();
if (value.getKey() > minSuffixPriority.get(immutableContexts)) {
options.get(immutableContexts).put("suffix", value.getValue());
minSuffixPriority.put(immutableContexts, value.getKey());
}
continue;
}
if (n.isMeta()) {
Map.Entry<String, String> meta = n.getMeta();
options.get(immutableContexts).put(meta.getKey(), meta.getValue());
}
if (!options.containsKey(immutableContexts)) {
options.put(immutableContexts, new HashMap<>());
minPrefixPriority.put(immutableContexts, Integer.MIN_VALUE);
minSuffixPriority.put(immutableContexts, Integer.MIN_VALUE);
}
ImmutableMap.Builder<ImmutableContextSet, ImmutableMap<String, String>> map = ImmutableMap.builder();
for (Map.Entry<ImmutableContextSet, Map<String, String>> e : options.entrySet()) {
map.put(e.getKey(), ImmutableMap.copyOf(e.getValue()));
if (n.isPrefix()) {
Map.Entry<Integer, String> value = n.getPrefix();
if (value.getKey() > minPrefixPriority.get(immutableContexts)) {
options.get(immutableContexts).put("prefix", value.getValue());
minPrefixPriority.put(immutableContexts, value.getKey());
}
continue;
}
if (n.isSuffix()) {
Map.Entry<Integer, String> value = n.getSuffix();
if (value.getKey() > minSuffixPriority.get(immutableContexts)) {
options.get(immutableContexts).put("suffix", value.getValue());
minSuffixPriority.put(immutableContexts, value.getKey());
}
continue;
}
if (n.isMeta()) {
Map.Entry<String, String> meta = n.getMeta();
options.get(immutableContexts).put(meta.getKey(), meta.getValue());
}
return map.build();
}
ImmutableMap.Builder<ImmutableContextSet, ImmutableMap<String, String>> map = ImmutableMap.builder();
for (Map.Entry<ImmutableContextSet, Map<String, String>> e : options.entrySet()) {
map.put(e.getKey(), ImmutableMap.copyOf(e.getValue()));
}
return map.build();
}
@Override
public CompletableFuture<Boolean> setOption(@NonNull ImmutableContextSet context, @NonNull String key, @NonNull String value) {
try (Timing i = service.getPlugin().getTimings().time(LPTiming.LP_SUBJECT_SET_OPTION)) {
if (key.equalsIgnoreCase("prefix") || key.equalsIgnoreCase("suffix")) {
// special handling.
ChatMetaType type = ChatMetaType.valueOf(key.toUpperCase());
if (key.equalsIgnoreCase("prefix") || key.equalsIgnoreCase("suffix")) {
// special handling.
ChatMetaType type = ChatMetaType.valueOf(key.toUpperCase());
// remove all prefixes/suffixes from the user
List<Node> toRemove = streamNodes(enduring)
.filter(type::matches)
.filter(n -> n.getFullContexts().equals(context))
.collect(Collectors.toList());
// remove all prefixes/suffixes from the user
List<Node> toRemove = streamNodes(enduring)
.filter(type::matches)
.filter(n -> n.getFullContexts().equals(context))
.collect(Collectors.toList());
toRemove.forEach(makeUnsetConsumer(enduring));
toRemove.forEach(makeUnsetConsumer(enduring));
MetaAccumulator metaAccumulator = holder.accumulateMeta(null, null, ExtractedContexts.generate(service.calculateContexts(context)));
int priority = metaAccumulator.getChatMeta(type).keySet().stream().mapToInt(e -> e).max().orElse(0);
priority += 10;
if (enduring) {
holder.setPermission(NodeFactory.makeChatMetaNode(type, priority, value).withExtraContext(context).build());
} else {
holder.setTransientPermission(NodeFactory.makeChatMetaNode(type, priority, value).withExtraContext(context).build());
}
MetaAccumulator metaAccumulator = holder.accumulateMeta(null, null, ExtractedContexts.generate(service.calculateContexts(context)));
int priority = metaAccumulator.getChatMeta(type).keySet().stream().mapToInt(e -> e).max().orElse(0);
priority += 10;
if (enduring) {
holder.setPermission(NodeFactory.makeChatMetaNode(type, priority, value).withExtraContext(context).build());
} else {
// standard remove
List<Node> toRemove = streamNodes(enduring)
.filter(n -> n.isMeta() && n.getMeta().getKey().equals(key))
.filter(n -> n.getFullContexts().equals(context))
.collect(Collectors.toList());
toRemove.forEach(makeUnsetConsumer(enduring));
if (enduring) {
holder.setPermission(NodeFactory.makeMetaNode(key, value).withExtraContext(context).build());
} else {
holder.setTransientPermission(NodeFactory.makeMetaNode(key, value).withExtraContext(context).build());
}
holder.setTransientPermission(NodeFactory.makeChatMetaNode(type, priority, value).withExtraContext(context).build());
}
return objectSave(holder).thenApply(v -> true);
} else {
// standard remove
List<Node> toRemove = streamNodes(enduring)
.filter(n -> n.isMeta() && n.getMeta().getKey().equals(key))
.filter(n -> n.getFullContexts().equals(context))
.collect(Collectors.toList());
toRemove.forEach(makeUnsetConsumer(enduring));
if (enduring) {
holder.setPermission(NodeFactory.makeMetaNode(key, value).withExtraContext(context).build());
} else {
holder.setTransientPermission(NodeFactory.makeMetaNode(key, value).withExtraContext(context).build());
}
}
return objectSave(holder).thenApply(v -> true);
}
@Override
public CompletableFuture<Boolean> unsetOption(ImmutableContextSet set, String key) {
try (Timing i = service.getPlugin().getTimings().time(LPTiming.LP_SUBJECT_SET_OPTION)) {
List<Node> toRemove = streamNodes(enduring)
.filter(n -> {
if (key.equalsIgnoreCase("prefix")) {
return n.isPrefix();
} else if (key.equalsIgnoreCase("suffix")) {
return n.isSuffix();
} else {
return n.isMeta() && n.getMeta().getKey().equals(key);
}
})
.filter(n -> n.getFullContexts().equals(set))
.collect(Collectors.toList());
List<Node> toRemove = streamNodes(enduring)
.filter(n -> {
if (key.equalsIgnoreCase("prefix")) {
return n.isPrefix();
} else if (key.equalsIgnoreCase("suffix")) {
return n.isSuffix();
} else {
return n.isMeta() && n.getMeta().getKey().equals(key);
}
})
.filter(n -> n.getFullContexts().equals(set))
.collect(Collectors.toList());
toRemove.forEach(makeUnsetConsumer(enduring));
toRemove.forEach(makeUnsetConsumer(enduring));
return objectSave(holder).thenApply(v -> true);
}
return objectSave(holder).thenApply(v -> true);
}
@Override
public CompletableFuture<Boolean> clearOptions(@NonNull ImmutableContextSet set) {
try (Timing i = service.getPlugin().getTimings().time(LPTiming.LP_SUBJECT_CLEAR_OPTIONS)) {
List<Node> toRemove = streamNodes(enduring)
.filter(n -> n.isMeta() || n.isPrefix() || n.isSuffix())
.filter(n -> n.getFullContexts().equals(set))
.collect(Collectors.toList());
List<Node> toRemove = streamNodes(enduring)
.filter(n -> n.isMeta() || n.isPrefix() || n.isSuffix())
.filter(n -> n.getFullContexts().equals(set))
.collect(Collectors.toList());
toRemove.forEach(makeUnsetConsumer(enduring));
toRemove.forEach(makeUnsetConsumer(enduring));
return objectSave(holder).thenApply(v -> !toRemove.isEmpty());
}
return objectSave(holder).thenApply(v -> !toRemove.isEmpty());
}
@Override
public CompletableFuture<Boolean> clearOptions() {
try (Timing i = service.getPlugin().getTimings().time(LPTiming.LP_SUBJECT_CLEAR_OPTIONS)) {
List<Node> toRemove = streamNodes(enduring)
.filter(n -> n.isMeta() || n.isPrefix() || n.isSuffix())
.collect(Collectors.toList());
List<Node> toRemove = streamNodes(enduring)
.filter(n -> n.isMeta() || n.isPrefix() || n.isSuffix())
.collect(Collectors.toList());
toRemove.forEach(makeUnsetConsumer(enduring));
toRemove.forEach(makeUnsetConsumer(enduring));
return objectSave(holder).thenApply(v -> !toRemove.isEmpty());
}
return objectSave(holder).thenApply(v -> !toRemove.isEmpty());
}
private Stream<Node> streamNodes(boolean enduring) {
@@ -40,6 +40,7 @@ import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.common.calculators.PermissionCalculator;
import me.lucko.luckperms.common.calculators.PermissionCalculatorMetadata;
import me.lucko.luckperms.common.contexts.ContextSetComparator;
import me.lucko.luckperms.common.processors.MapProcessor;
import me.lucko.luckperms.common.processors.PermissionProcessor;
import me.lucko.luckperms.common.verbose.CheckOrigin;
@@ -49,7 +50,6 @@ import me.lucko.luckperms.sponge.service.model.LPSubject;
import me.lucko.luckperms.sponge.service.model.LPSubjectData;
import me.lucko.luckperms.sponge.service.model.SubjectReference;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -65,7 +65,6 @@ import java.util.concurrent.TimeUnit;
*/
@RequiredArgsConstructor
public class CalculatedSubjectData implements LPSubjectData {
private static final ContextComparator CONTEXT_COMPARATOR = new ContextComparator();
@Getter
private final LPSubject parentSubject;
@@ -87,7 +86,7 @@ public class CalculatedSubjectData implements LPSubjectData {
processors.add(new SpongeWildcardProcessor());
CalculatorHolder holder = new CalculatorHolder(new PermissionCalculator(service.getPlugin(), PermissionCalculatorMetadata.of(calculatorDisplayName, contexts), processors.build()));
holder.setPermissions(flattenMap(contexts, permissions));
holder.setPermissions(flattenMap(getRelevantEntries(contexts, permissions)));
return holder;
}
@@ -290,11 +289,10 @@ public class CalculatedSubjectData implements LPSubjectData {
return CompletableFuture.completedFuture(!map.isEmpty());
}
private static <V> Map<String, V> flattenMap(ContextSet contexts, Map<ImmutableContextSet, Map<String, V>> source) {
private static <V> Map<String, V> flattenMap(SortedMap<ImmutableContextSet, Map<String, V>> data) {
Map<String, V> map = new HashMap<>();
SortedMap<ImmutableContextSet, Map<String, V>> ret = getRelevantEntries(contexts, source);
for (Map<String, V> m : ret.values()) {
for (Map<String, V> m : data.values()) {
for (Map.Entry<String, V> e : m.entrySet()) {
map.putIfAbsent(e.getKey(), e.getValue());
}
@@ -304,7 +302,7 @@ public class CalculatedSubjectData implements LPSubjectData {
}
private static <K, V> SortedMap<ImmutableContextSet, Map<K, V>> getRelevantEntries(ContextSet set, Map<ImmutableContextSet, Map<K, V>> map) {
ImmutableSortedMap.Builder<ImmutableContextSet, Map<K, V>> perms = ImmutableSortedMap.orderedBy(CONTEXT_COMPARATOR);
ImmutableSortedMap.Builder<ImmutableContextSet, Map<K, V>> perms = ImmutableSortedMap.orderedBy(ContextSetComparator.reverse());
for (Map.Entry<ImmutableContextSet, Map<K, V>> e : map.entrySet()) {
if (!e.getKey().isSatisfiedBy(set)) {
@@ -321,15 +319,6 @@ public class CalculatedSubjectData implements LPSubjectData {
return a == null && b == null || a != null && b != null && a.equalsIgnoreCase(b);
}
private static class ContextComparator implements Comparator<ImmutableContextSet> {
@Override
public int compare(ImmutableContextSet o1, ImmutableContextSet o2) {
int i = Integer.compare(o1.size(), o2.size());
return i == 0 ? 1 : i;
}
}
private static class CalculatorHolder {
@Getter
@@ -29,7 +29,7 @@ import lombok.ToString;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.sponge.service.model.LPPermissionService;
import me.lucko.luckperms.sponge.service.model.SubjectReference;
import me.lucko.luckperms.sponge.service.model.SubjectReferenceFactory;
import me.lucko.luckperms.sponge.service.storage.SubjectStorageModel;
import java.util.List;
@@ -66,7 +66,7 @@ public class SubjectDataHolder {
parents.entrySet().stream()
.collect(Collectors.toMap(
k -> ImmutableContextSet.fromMap(k.getKey()),
v -> v.getValue().stream().map(s -> SubjectReference.deserialize(service, s)).collect(Collectors.toList())
v -> v.getValue().stream().map(s -> SubjectReferenceFactory.deserialize(service, s)).collect(Collectors.toList())
))
);
}
@@ -44,13 +44,10 @@ import me.lucko.luckperms.sponge.service.calculated.PermissionLookup;
import me.lucko.luckperms.sponge.service.model.LPSubject;
import me.lucko.luckperms.sponge.service.model.SubjectReference;
import me.lucko.luckperms.sponge.service.storage.SubjectStorageModel;
import me.lucko.luckperms.sponge.timings.LPTiming;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.service.permission.Subject;
import co.aikar.timings.Timing;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -246,46 +243,36 @@ public class PersistedSubject implements LPSubject {
@Override
public Tristate getPermissionValue(@NonNull ImmutableContextSet contexts, @NonNull String node) {
try (Timing ignored = service.getPlugin().getTimings().time(LPTiming.INTERNAL_SUBJECT_GET_PERMISSION_VALUE)) {
Tristate t = permissionLookupCache.get(PermissionLookup.of(node, contexts));
service.getPlugin().getVerboseHandler().offerCheckData(CheckOrigin.INTERNAL, "local:" + getParentCollection().getIdentifier() + "/" + identifier, contexts, node, t);
return t;
}
Tristate t = permissionLookupCache.get(PermissionLookup.of(node, contexts));
service.getPlugin().getVerboseHandler().offerCheckData(CheckOrigin.INTERNAL, "local:" + getParentCollection().getIdentifier() + "/" + identifier, contexts, node, t);
return t;
}
@Override
public boolean isChildOf(@NonNull ImmutableContextSet contexts, @NonNull SubjectReference subject) {
try (Timing ignored = service.getPlugin().getTimings().time(LPTiming.INTERNAL_SUBJECT_IS_CHILD_OF)) {
if (getParentCollection().getIdentifier().equalsIgnoreCase("defaults")) {
return subjectData.getParents(contexts).contains(subject) ||
transientSubjectData.getParents(contexts).contains(subject);
} else {
return subjectData.getParents(contexts).contains(subject) ||
transientSubjectData.getParents(contexts).contains(subject) ||
getParentCollection().getDefaults().getParents(contexts).contains(subject) ||
service.getDefaults().getParents(contexts).contains(subject);
}
if (getParentCollection().getIdentifier().equalsIgnoreCase("defaults")) {
return subjectData.getParents(contexts).contains(subject) ||
transientSubjectData.getParents(contexts).contains(subject);
} else {
return subjectData.getParents(contexts).contains(subject) ||
transientSubjectData.getParents(contexts).contains(subject) ||
getParentCollection().getDefaults().getParents(contexts).contains(subject) ||
service.getDefaults().getParents(contexts).contains(subject);
}
}
@Override
public ImmutableList<SubjectReference> getParents(@NonNull ImmutableContextSet contexts) {
try (Timing ignored = service.getPlugin().getTimings().time(LPTiming.INTERNAL_SUBJECT_GET_PARENTS)) {
return parentLookupCache.get(contexts);
}
return parentLookupCache.get(contexts);
}
@Override
public Optional<String> getOption(ImmutableContextSet contexts, String key) {
try (Timing ignored = service.getPlugin().getTimings().time(LPTiming.INTERNAL_SUBJECT_GET_OPTION)) {
return optionLookupCache.get(OptionLookup.of(key, contexts));
}
return optionLookupCache.get(OptionLookup.of(key, contexts));
}
@Override
public ImmutableContextSet getActiveContextSet() {
try (Timing ignored = service.getPlugin().getTimings().time(LPTiming.INTERNAL_SUBJECT_GET_ACTIVE_CONTEXTS)) {
return service.getPlugin().getContextManager().getApplicableContext(sponge()).makeImmutable();
}
return service.getPlugin().getContextManager().getApplicableContext(sponge()).makeImmutable();
}
}
@@ -41,6 +41,7 @@ import me.lucko.luckperms.common.node.NodeWithContextComparator;
import me.lucko.luckperms.sponge.service.calculated.CalculatedSubjectData;
import me.lucko.luckperms.sponge.service.model.LPPermissionService;
import me.lucko.luckperms.sponge.service.model.SubjectReference;
import me.lucko.luckperms.sponge.service.model.SubjectReferenceFactory;
import java.util.ArrayList;
import java.util.List;
@@ -164,7 +165,7 @@ public class SubjectStorageModel {
String collection = parent.get("collection").getAsString();
String subject = parent.get("subject").getAsString();
pars.add(SubjectReference.of(service, collection, subject));
pars.add(SubjectReferenceFactory.obtain(service, collection, subject));
}
parentsBuilder.put(contextSet, pars.build());
@@ -1,76 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* 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.sponge.timings;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum LPTiming {
GET_SUBJECTS("getSubjects"),
USER_COLLECTION_GET("userCollectionGet"),
GROUP_COLLECTION_GET("groupCollectionGet"),
USER_GET_PERMISSION_VALUE("userGetPermissionValue"),
USER_GET_PARENTS("userGetParents"),
USER_IS_CHILD_OF("userIsChildOf"),
USER_GET_OPTION("userGetOption"),
USER_GET_ACTIVE_CONTEXTS("userGetActiveContexts"),
GROUP_GET_PERMISSION_VALUE("groupGetPermissionValue"),
GROUP_GET_PARENTS("groupGetParents"),
GROUP_IS_CHILD_OF("groupIsChildOf"),
GROUP_GET_OPTION("groupGetOption"),
GROUP_GET_ACTIVE_CONTEXTS("groupGetActiveContexts"),
LP_SUBJECT_GET_PERMISSIONS("lpSubjectGetPermissions"),
LP_SUBJECT_SET_PERMISSION("lpSubjectSetPermission"),
LP_SUBJECT_CLEAR_PERMISSIONS("lpSubjectClearPermissions"),
LP_SUBJECT_GET_PARENTS("lpSubjectGetParents"),
LP_SUBJECT_ADD_PARENT("lpSubjectAddParent"),
LP_SUBJECT_REMOVE_PARENT("lpSubjectRemoveParent"),
LP_SUBJECT_CLEAR_PARENTS("lpSubjectClearParents"),
LP_SUBJECT_GET_OPTIONS("lpSubjectGetOptions"),
LP_SUBJECT_SET_OPTION("lpSubjectSetOption"),
LP_SUBJECT_CLEAR_OPTIONS("lpSubjectClearOptions"),
INTERNAL_SUBJECT_GET_PERMISSION_VALUE("internalSubjectGetPermissionValue"),
INTERNAL_SUBJECT_IS_CHILD_OF("internalSubjectIsChildOf"),
INTERNAL_SUBJECT_GET_PARENTS("internalSubjectGetParents"),
INTERNAL_SUBJECT_GET_OPTION("internalSubjectGetOption"),
INTERNAL_SUBJECT_GET_ACTIVE_CONTEXTS("internalSubjectGetActiveContexts"),
ON_COMMAND("onCommand"),
COMMAND_TAB_COMPLETE("commandTabComplete"),
ON_CLIENT_LOGIN("onClientLogin"),
ON_CLIENT_LEAVE("onClientLeave");
private final String id;
}
@@ -1,56 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* 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.sponge.timings;
import lombok.NonNull;
import com.google.common.collect.ImmutableMap;
import me.lucko.luckperms.sponge.LPSpongePlugin;
import co.aikar.timings.Timing;
import co.aikar.timings.Timings;
import java.util.Map;
public class LPTimings {
private final Map<LPTiming, Timing> timings;
public LPTimings(LPSpongePlugin plugin) {
ImmutableMap.Builder<LPTiming, Timing> map = ImmutableMap.builder();
for (LPTiming t : LPTiming.values()) {
map.put(t, Timings.of(plugin, t.getId()));
}
timings = map.build();
}
public Timing time(@NonNull LPTiming timing) {
Timing t = timings.get(timing);
t.startTimingIfSync();
return t;
}
}