Refactor caching system (WIP) - still working towards #23

This commit is contained in:
Luck
2016-10-12 21:57:57 +01:00
Unverified
parent e079b0f3ed
commit 83d9229295
40 changed files with 1166 additions and 1451 deletions
@@ -34,7 +34,6 @@ import me.lucko.luckperms.config.LPConfiguration;
import me.lucko.luckperms.constants.Message;
import me.lucko.luckperms.constants.Permission;
import me.lucko.luckperms.contexts.ContextManager;
import me.lucko.luckperms.contexts.ContextUpdateTask;
import me.lucko.luckperms.contexts.ServerCalculator;
import me.lucko.luckperms.contexts.WorldCalculator;
import me.lucko.luckperms.core.UuidCache;
@@ -46,6 +45,7 @@ import me.lucko.luckperms.storage.Datastore;
import me.lucko.luckperms.storage.StorageFactory;
import me.lucko.luckperms.tracks.TrackManager;
import me.lucko.luckperms.users.SpongeUserManager;
import me.lucko.luckperms.users.UserManager;
import me.lucko.luckperms.utils.LocaleManager;
import me.lucko.luckperms.utils.LogFactory;
import org.slf4j.Logger;
@@ -89,7 +89,7 @@ public class LPSpongePlugin implements LuckPermsPlugin {
private final Set<UUID> ignoringLogs = ConcurrentHashMap.newKeySet();
private LPConfiguration configuration;
private SpongeUserManager userManager;
private UserManager userManager;
private GroupManager groupManager;
private TrackManager trackManager;
private Datastore datastore;
@@ -133,7 +133,7 @@ public class LPSpongePlugin implements LuckPermsPlugin {
getLog().info("Loading internal permission managers...");
uuidCache = new UuidCache(getConfiguration().isOnlineMode());
userManager = new SpongeUserManager(this);
userManager = new UserManager(this);
groupManager = new GroupManager(this);
trackManager = new TrackManager();
importer = new Importer(commandManager);
@@ -142,7 +142,6 @@ public class LPSpongePlugin implements LuckPermsPlugin {
contextManager = new ContextManager<>();
contextManager.registerCalculator(new ServerCalculator<>(getConfiguration().getServer()));
contextManager.registerCalculator(new WorldCalculator());
contextManager.registerListener(userManager);
getLog().info("Registering PermissionService...");
Sponge.getServiceManager().setProvider(this, PermissionService.class, (service = new LuckPermsService(this)));
@@ -164,7 +163,6 @@ public class LPSpongePlugin implements LuckPermsPlugin {
scheduler.createTaskBuilder().intervalTicks(1L).execute(SpongeSenderFactory.get(this)).submit(this);
scheduler.createTaskBuilder().async().intervalTicks(60L).execute(new ExpireTemporaryTask(this)).submit(this);
scheduler.createTaskBuilder().async().intervalTicks(20L).execute(consecutiveExecutor).submit(this);
scheduler.createTaskBuilder().async().intervalTicks(600L).execute(new ContextUpdateTask(service.getUserSubjects())).submit(this);
getLog().info("Successfully loaded.");
}
@@ -252,15 +250,6 @@ public class LPSpongePlugin implements LuckPermsPlugin {
return SpongeSenderFactory.get(this).wrap(game.getServer().getConsole());
}
@Override
public List<String> getPossiblePermissions() {
Optional<PermissionService> p = game.getServiceManager().provide(PermissionService.class);
if (!p.isPresent()) {
return Collections.emptyList();
}
return p.get().getDescriptions().stream().map(PermissionDescription::getId).collect(Collectors.toList());
}
@Override
public Object getPlugin(String name) {
return game.getPluginManager().getPlugin(name).get().getInstance().get();
@@ -1,78 +0,0 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* 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.api.sponge;
import lombok.Getter;
import lombok.NonNull;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.calculators.*;
import org.spongepowered.api.service.context.Context;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class ContextData {
@Getter
private final Map<String, String> context;
@Getter
private final Map<String, Boolean> permissionCache = new ConcurrentHashMap<>();
private final PermissionCalculator calculator;
public ContextData(LuckPermsUserSubject parent, Map<String, String> context, LuckPermsService service) {
this.context = context;
Set<Context> contexts = context.entrySet().stream().map(e -> new Context(e.getKey(), e.getValue())).collect(Collectors.toSet());
List<PermissionProcessor> processors = new ArrayList<>(5);
processors.add(new MapProcessor(permissionCache));
if (service.getPlugin().getConfiguration().isApplyingWildcards()) {
processors.add(new SpongeWildcardProcessor(permissionCache));
processors.add(new WildcardProcessor(permissionCache));
}
if (service.getPlugin().getConfiguration().isApplyingRegex()) {
processors.add(new RegexProcessor(permissionCache));
}
processors.add(new DefaultsProcessor(service, contexts));
calculator = new PermissionCalculator(service.getPlugin(), parent.getUser().getName(), service.getPlugin().getConfiguration().isDebugPermissionChecks(), processors);
}
public void invalidateCache() {
calculator.invalidateCache();
}
public Tristate getPermissionValue(@NonNull String permission) {
me.lucko.luckperms.api.Tristate t = calculator.getPermissionValue(permission);
if (t != me.lucko.luckperms.api.Tristate.UNDEFINED) {
return Tristate.fromBoolean(t.asBoolean());
} else {
return Tristate.UNDEFINED;
}
}
}
@@ -30,7 +30,6 @@ import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.core.PermissionHolder;
import me.lucko.luckperms.groups.Group;
import me.lucko.luckperms.users.User;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.service.context.Context;
import org.spongepowered.api.service.permission.Subject;
@@ -43,48 +42,33 @@ import java.util.stream.Collectors;
import static me.lucko.luckperms.utils.ArgumentChecker.unescapeCharacters;
@EqualsAndHashCode(of = {"holder"})
public class LuckPermsSubject implements Subject {
public static LuckPermsSubject wrapHolder(PermissionHolder holder, LuckPermsService service) {
return new LuckPermsSubject(holder, service);
@EqualsAndHashCode(of = "group")
public class LuckPermsGroupSubject implements Subject {
public static LuckPermsGroupSubject wrapGroup(Group group, LuckPermsService service) {
return new LuckPermsGroupSubject(group, service);
}
@Getter
private PermissionHolder holder;
private LuckPermsSubjectData enduringData;
private LuckPermsSubjectData transientData;
protected LuckPermsService service;
private PermissionHolder group;
LuckPermsSubject(PermissionHolder holder, LuckPermsService service) {
this.holder = holder;
this.enduringData = new LuckPermsSubjectData(true, this, service, holder);
this.transientData = new LuckPermsSubjectData(true, this, service, holder);
private LuckPermsService service;
@Getter
private LuckPermsSubjectData subjectData;
@Getter
private LuckPermsSubjectData transientSubjectData;
private LuckPermsGroupSubject(PermissionHolder group, LuckPermsService service) {
this.group = group;
this.subjectData = new LuckPermsSubjectData(true, service, group);
this.transientSubjectData = new LuckPermsSubjectData(false, service, group);
this.service = service;
}
public void deprovision() {
holder = null;
enduringData = null;
transientData = null;
service = null;
}
void objectSave(PermissionHolder t) {
service.getPlugin().doAsync(() -> {
if (t instanceof User) {
((User) t).refreshPermissions();
service.getPlugin().getDatastore().saveUser(((User) t));
}
if (t instanceof Group) {
service.getPlugin().getDatastore().saveGroup(((Group) t));
service.getPlugin().runUpdateTask();
}
});
}
@Override
public String getIdentifier() {
return holder.getObjectName();
return group.getObjectName();
}
@Override
@@ -94,25 +78,7 @@ public class LuckPermsSubject implements Subject {
@Override
public SubjectCollection getContainingCollection() {
if (holder instanceof Group) {
return service.getGroupSubjects();
} else {
return service.getUserSubjects();
}
}
@Override
public SubjectData getSubjectData() {
return enduringData;
}
@Override
public SubjectData getTransientSubjectData() {
return transientData;
}
public boolean isPermissionSet(@NonNull Set<Context> contexts, @NonNull String node) {
return getPermissionValue(contexts, node) != Tristate.UNDEFINED;
return service.getGroupSubjects();
}
@Override
@@ -120,11 +86,6 @@ public class LuckPermsSubject implements Subject {
return getPermissionValue(contexts, node).asBoolean();
}
@Override
public boolean hasPermission(String permission) {
return getPermissionValue(getActiveContexts(), permission).asBoolean();
}
@Override
public Tristate getPermissionValue(@NonNull Set<Context> contexts, @NonNull String node) {
Map<String, String> context = new HashMap<>();
@@ -132,7 +93,7 @@ public class LuckPermsSubject implements Subject {
context.put(c.getKey(), c.getValue());
}
switch (holder.inheritsPermission(new me.lucko.luckperms.core.Node.Builder(node).withExtraContext(context).build())) {
switch (group.inheritsPermission(new me.lucko.luckperms.core.Node.Builder(node).withExtraContext(context).build())) {
case UNDEFINED:
return Tristate.UNDEFINED;
case TRUE:
@@ -142,53 +103,45 @@ public class LuckPermsSubject implements Subject {
default:
return null;
}
}
@Override
public boolean isChildOf(@NonNull Subject parent) {
return isChildOf(getActiveContexts(), parent);
// TODO
}
@Override
public boolean isChildOf(@NonNull Set<Context> contexts, @NonNull Subject parent) {
return parent instanceof PermissionHolder && getPermissionValue(contexts, "group." + parent.getIdentifier()).asBoolean();
}
@Override
public List<Subject> getParents() {
return getParents(getActiveContexts());
return parent instanceof LuckPermsGroupSubject && getPermissionValue(contexts, "group." + parent.getIdentifier()).asBoolean();
}
@Override
public List<Subject> getParents(@NonNull Set<Context> contexts) {
List<Subject> parents = new ArrayList<>();
parents.addAll(enduringData.getParents(contexts));
parents.addAll(transientData.getParents(contexts));
parents.addAll(subjectData.getParents(contexts));
parents.addAll(transientSubjectData.getParents(contexts));
return ImmutableList.copyOf(parents);
}
@Override
public Optional<String> getOption(Set<Context> set, String s) {
if (s.equalsIgnoreCase("prefix")) {
String prefix = getChatMeta(set, true, holder);
String prefix = getChatMeta(set, true, group);
if (!prefix.equals("")) {
return Optional.of(prefix);
}
}
if (s.equalsIgnoreCase("suffix")) {
String suffix = getChatMeta(set, false, holder);
String suffix = getChatMeta(set, false, group);
if (!suffix.equals("")) {
return Optional.of(suffix);
}
}
Map<String, String> transientOptions = enduringData.getOptions(set);
Map<String, String> transientOptions = subjectData.getOptions(set);
if (transientOptions.containsKey(s)) {
return Optional.of(transientOptions.get(s));
}
Map<String, String> enduringOptions = enduringData.getOptions(set);
Map<String, String> enduringOptions = subjectData.getOptions(set);
if (enduringOptions.containsKey(s)) {
return Optional.of(enduringOptions.get(s));
}
@@ -196,11 +149,6 @@ public class LuckPermsSubject implements Subject {
return Optional.empty();
}
@Override
public Optional<String> getOption(String key) {
return getOption(getActiveContexts(), key);
}
@Override
public Set<Context> getActiveContexts() {
return SubjectData.GLOBAL_CONTEXT;
@@ -33,6 +33,7 @@ import me.lucko.luckperms.api.sponge.simple.persisted.SimplePersistedCollection;
import me.lucko.luckperms.api.sponge.simple.persisted.SubjectStorage;
import me.lucko.luckperms.contexts.SpongeCalculatorLink;
import org.spongepowered.api.plugin.PluginContainer;
import org.spongepowered.api.service.context.Context;
import org.spongepowered.api.service.context.ContextCalculator;
import org.spongepowered.api.service.permission.*;
import org.spongepowered.api.text.Text;
@@ -41,6 +42,7 @@ import org.spongepowered.api.util.Tristate;
import java.io.File;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* The LuckPerms implementation of the Sponge Permission Service
@@ -140,6 +142,36 @@ public class LuckPermsService implements PermissionService {
plugin.getContextManager().registerCalculator(new SpongeCalculatorLink(contextCalculator));
}
public static Map<String, String> convertContexts(Set<Context> contexts) {
return contexts.stream().collect(Collectors.toMap(Context::getKey, Context::getValue));
}
public static Set<Context> convertContexts(Map<String, String> contexts) {
return contexts.entrySet().stream().map(e -> new Context(e.getKey(), e.getValue())).collect(Collectors.toSet());
}
public static Tristate convertTristate(me.lucko.luckperms.api.Tristate tristate) {
switch (tristate) {
case TRUE:
return Tristate.TRUE;
case FALSE:
return Tristate.FALSE;
default:
return Tristate.UNDEFINED;
}
}
public static me.lucko.luckperms.api.Tristate convertTristate(Tristate tristate) {
switch (tristate) {
case TRUE:
return me.lucko.luckperms.api.Tristate.TRUE;
case FALSE:
return me.lucko.luckperms.api.Tristate.FALSE;
default:
return me.lucko.luckperms.api.Tristate.UNDEFINED;
}
}
@RequiredArgsConstructor
@EqualsAndHashCode
@ToString
@@ -31,6 +31,7 @@ import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.core.PermissionHolder;
import me.lucko.luckperms.exceptions.ObjectAlreadyHasException;
import me.lucko.luckperms.exceptions.ObjectLacksException;
import me.lucko.luckperms.groups.Group;
import me.lucko.luckperms.users.User;
import org.spongepowered.api.service.context.Context;
import org.spongepowered.api.service.permission.Subject;
@@ -41,25 +42,34 @@ import java.util.*;
import java.util.stream.Collectors;
import static me.lucko.luckperms.utils.ArgumentChecker.escapeCharacters;
import static me.lucko.luckperms.utils.ArgumentChecker.unescapeCharacters;
@AllArgsConstructor
public class LuckPermsSubjectData implements SubjectData {
private final boolean enduring;
private final LuckPermsSubject superClass;
private final LuckPermsService service;
@Getter
private final PermissionHolder holder;
private void objectSave(PermissionHolder t) {
service.getPlugin().doAsync(() -> {
if (t instanceof User) {
((User) t).refreshPermissions();
service.getPlugin().getDatastore().saveUser(((User) t));
}
if (t instanceof Group) {
service.getPlugin().getDatastore().saveGroup(((Group) t));
service.getPlugin().runUpdateTask();
}
});
}
@Override
public Map<Set<Context>, Map<String, Boolean>> getAllPermissions() {
Map<Set<Context>, Map<String, Boolean>> perms = new HashMap<>();
for (Node n : enduring ? holder.getNodes() : holder.getTransientNodes()) {
Set<Context> contexts = n.getExtraContexts().entrySet().stream()
.map(entry -> new Context(entry.getKey(), entry.getValue()))
.collect(Collectors.toSet());
Set<Context> contexts = LuckPermsService.convertContexts(n.getExtraContexts());
if (n.isServerSpecific()) {
contexts.add(new Context(LuckPermsService.SERVER_CONTEXT, n.getServer().get()));
@@ -84,8 +94,14 @@ public class LuckPermsSubjectData implements SubjectData {
}
@Override
public Map<String, Boolean> getPermissions(Set<Context> set) {
return getAllPermissions().getOrDefault(set, ImmutableMap.of());
public Map<String, Boolean> getPermissions(Set<Context> contexts) {
ImmutableMap.Builder<String, Boolean> permissions = ImmutableMap.builder();
(enduring ? holder.getNodes() : holder.getTransientNodes()).stream()
.filter(n -> n.shouldApplyWithContext(LuckPermsService.convertContexts(contexts), true))
.forEach(n -> permissions.put(n.getKey(), n.getValue()));
return permissions.build();
}
@Override
@@ -105,7 +121,8 @@ public class LuckPermsSubjectData implements SubjectData {
holder.unsetTransientPermission(builder.build());
}
} catch (ObjectLacksException ignored) {}
superClass.objectSave(holder);
objectSave(holder);
return true;
}
@@ -123,7 +140,8 @@ public class LuckPermsSubjectData implements SubjectData {
holder.setTransientPermission(builder.build());
}
} catch (ObjectAlreadyHasException ignored) {}
superClass.objectSave(holder);
objectSave(holder);
return true;
}
@@ -134,15 +152,14 @@ public class LuckPermsSubjectData implements SubjectData {
} else {
holder.clearTransientNodes();
}
superClass.objectSave(holder);
objectSave(holder);
return true;
}
@Override
public boolean clearPermissions(Set<Context> set) {
Map<String, String> context = set.stream().collect(Collectors.toMap(Context::getKey, Context::getValue));
public boolean clearPermissions(Set<Context> contexts) {
List<Node> toRemove = (enduring ? holder.getNodes() : holder.getTransientNodes()).stream()
.filter(node -> node.shouldApplyWithContext(context))
.filter(node -> node.shouldApplyWithContext(LuckPermsService.convertContexts(contexts)))
.collect(Collectors.toList());
toRemove.forEach(n -> {
@@ -159,7 +176,7 @@ public class LuckPermsSubjectData implements SubjectData {
service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false);
}
superClass.objectSave(holder);
objectSave(holder);
return !toRemove.isEmpty();
}
@@ -172,9 +189,7 @@ public class LuckPermsSubjectData implements SubjectData {
continue;
}
Set<Context> contexts = n.getExtraContexts().entrySet().stream()
.map(entry -> new Context(entry.getKey(), entry.getValue()))
.collect(Collectors.toSet());
Set<Context> contexts = LuckPermsService.convertContexts(n.getExtraContexts());
if (n.isServerSpecific()) {
contexts.add(new Context(LuckPermsService.SERVER_CONTEXT, n.getServer().get()));
@@ -200,14 +215,21 @@ public class LuckPermsSubjectData implements SubjectData {
@Override
public List<Subject> getParents(Set<Context> contexts) {
return getAllParents().getOrDefault(contexts, ImmutableList.of());
ImmutableList.Builder<Subject> parents = ImmutableList.builder();
(enduring ? holder.getNodes() : holder.getTransientNodes()).stream()
.filter(Node::isGroupNode)
.filter(n -> n.shouldApplyWithContext(LuckPermsService.convertContexts(contexts), true))
.forEach(n -> parents.add(service.getGroupSubjects().get(n.getGroupName())));
return parents.build();
}
@Override
public boolean addParent(Set<Context> set, Subject subject) {
if (subject instanceof LuckPermsSubject) {
LuckPermsSubject permsSubject = ((LuckPermsSubject) subject);
Map<String, String> contexts = set.stream().collect(Collectors.toMap(Context::getKey, Context::getValue));
if (subject instanceof LuckPermsGroupSubject) {
LuckPermsGroupSubject permsSubject = ((LuckPermsGroupSubject) subject);
Map<String, String> contexts = LuckPermsService.convertContexts(set);
try {
if (enduring) {
@@ -220,18 +242,18 @@ public class LuckPermsSubjectData implements SubjectData {
.build());
}
} catch (ObjectAlreadyHasException ignored) {}
superClass.objectSave(holder);
} else {
return false;
objectSave(holder);
return true;
}
return false;
}
@Override
public boolean removeParent(Set<Context> set, Subject subject) {
if (subject instanceof LuckPermsSubject) {
LuckPermsSubject permsSubject = ((LuckPermsSubject) subject);
Map<String, String> contexts = set.stream().collect(Collectors.toMap(Context::getKey, Context::getValue));
if (subject instanceof LuckPermsGroupSubject) {
LuckPermsGroupSubject permsSubject = ((LuckPermsGroupSubject) subject);
Map<String, String> contexts = LuckPermsService.convertContexts(set);
try {
if (enduring) {
@@ -244,9 +266,9 @@ public class LuckPermsSubjectData implements SubjectData {
.build());
}
} catch (ObjectLacksException ignored) {}
superClass.objectSave(holder);
} else {
return false;
objectSave(holder);
return true;
}
return false;
}
@@ -271,13 +293,14 @@ public class LuckPermsSubjectData implements SubjectData {
service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false);
}
superClass.objectSave(holder);
objectSave(holder);
return !toRemove.isEmpty();
}
@Override
public boolean clearParents(Set<Context> set) {
Map<String, String> context = set.stream().collect(Collectors.toMap(Context::getKey, Context::getValue));
Map<String, String> context = LuckPermsService.convertContexts(set);
List<Node> toRemove = (enduring ? holder.getNodes() : holder.getTransientNodes()).stream()
.filter(Node::isGroupNode)
.filter(node -> node.shouldApplyWithContext(context))
@@ -297,7 +320,7 @@ public class LuckPermsSubjectData implements SubjectData {
service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false);
}
superClass.objectSave(holder);
objectSave(holder);
return !toRemove.isEmpty();
}
@@ -305,14 +328,19 @@ public class LuckPermsSubjectData implements SubjectData {
public Map<Set<Context>, Map<String, String>> getAllOptions() {
Map<Set<Context>, Map<String, String>> options = new HashMap<>();
int prefixPriority = Integer.MIN_VALUE;
int suffixPriority = Integer.MIN_VALUE;
for (Node n : enduring ? holder.getNodes() : holder.getTransientNodes()) {
if (!n.isMeta() && !n.isPrefix() && !n.isSuffix()) {
if (!n.getValue()) {
continue;
}
Set<Context> contexts = n.getExtraContexts().entrySet().stream()
.map(entry -> new Context(entry.getKey(), entry.getValue()))
.collect(Collectors.toSet());
if (!n.isMeta() || !n.isPrefix() || n.isSuffix()) {
continue;
}
Set<Context> contexts = LuckPermsService.convertContexts(n.getExtraContexts());
if (n.isServerSpecific()) {
contexts.add(new Context(LuckPermsService.SERVER_CONTEXT, n.getServer().get()));
@@ -326,7 +354,28 @@ public class LuckPermsSubjectData implements SubjectData {
options.put(contexts, new HashMap<>());
}
options.get(contexts).put(unescapeCharacters(n.getMeta().getKey()), unescapeCharacters(n.getMeta().getValue()));
if (n.isPrefix()) {
Map.Entry<Integer, String> value = n.getPrefix();
if (value.getKey() > prefixPriority) {
options.get(contexts).put("prefix", value.getValue());
prefixPriority = value.getKey();
}
continue;
}
if (n.isSuffix()) {
Map.Entry<Integer, String> value = n.getSuffix();
if (value.getKey() > suffixPriority) {
options.get(contexts).put("suffix", value.getValue());
suffixPriority = value.getKey();
}
continue;
}
if (n.isMeta()) {
Map.Entry<String, String> meta = n.getMeta();
options.get(contexts).put(meta.getKey(), meta.getValue());
}
}
ImmutableMap.Builder<Set<Context>, Map<String, String>> map = ImmutableMap.builder();
@@ -338,15 +387,55 @@ public class LuckPermsSubjectData implements SubjectData {
@Override
public Map<String, String> getOptions(Set<Context> set) {
return getAllOptions().getOrDefault(set, Collections.emptyMap());
ImmutableMap.Builder<String, String> options = ImmutableMap.builder();
Map<String, String> contexts = LuckPermsService.convertContexts(set);
int prefixPriority = Integer.MIN_VALUE;
int suffixPriority = Integer.MIN_VALUE;
for (Node n : enduring ? holder.getNodes() : holder.getTransientNodes()) {
if (!n.getValue()) {
continue;
}
if (!n.isMeta() || !n.isPrefix() || n.isSuffix()) {
continue;
}
if (!n.shouldApplyWithContext(contexts, true)) {
continue;
}
if (n.isPrefix()) {
Map.Entry<Integer, String> value = n.getPrefix();
if (value.getKey() > prefixPriority) {
options.put("prefix", value.getValue());
prefixPriority = value.getKey();
}
continue;
}
if (n.isSuffix()) {
Map.Entry<Integer, String> value = n.getSuffix();
if (value.getKey() > suffixPriority) {
options.put("suffix", value.getValue());
suffixPriority = value.getKey();
}
continue;
}
if (n.isMeta()) {
Map.Entry<String, String> meta = n.getMeta();
options.put(meta.getKey(), meta.getValue());
}
}
return options.build();
}
@Override
public boolean setOption(Set<Context> set, String key, String value) {
Map<String, String> context = new HashMap<>();
for (Context c : set) {
context.put(c.getKey(), c.getValue());
}
Map<String, String> context = LuckPermsService.convertContexts(set);
key = escapeCharacters(key);
value = escapeCharacters(value);
@@ -364,13 +453,14 @@ public class LuckPermsSubjectData implements SubjectData {
);
}
} catch (ObjectAlreadyHasException ignored) {}
superClass.objectSave(holder);
objectSave(holder);
return true;
}
@Override
public boolean clearOptions(Set<Context> set) {
Map<String, String> context = set.stream().collect(Collectors.toMap(Context::getKey, Context::getValue));
Map<String, String> context = LuckPermsService.convertContexts(set);
List<Node> toRemove = (enduring ? holder.getNodes() : holder.getTransientNodes()).stream()
.filter(n -> n.isMeta() || n.isPrefix() || n.isSuffix())
.filter(node -> node.shouldApplyWithContext(context))
@@ -386,7 +476,7 @@ public class LuckPermsSubjectData implements SubjectData {
} catch (ObjectLacksException ignored) {}
});
superClass.objectSave(holder);
objectSave(holder);
return !toRemove.isEmpty();
}
@@ -406,7 +496,7 @@ public class LuckPermsSubjectData implements SubjectData {
} catch (ObjectLacksException ignored) {}
});
superClass.objectSave(holder);
objectSave(holder);
return !toRemove.isEmpty();
}
}
@@ -22,24 +22,26 @@
package me.lucko.luckperms.api.sponge;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.event.events.UserPermissionRefreshEvent;
import me.lucko.luckperms.api.implementation.internal.UserLink;
import me.lucko.luckperms.caching.MetaData;
import me.lucko.luckperms.users.User;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.service.context.Context;
import org.spongepowered.api.service.permission.Subject;
import org.spongepowered.api.service.permission.SubjectCollection;
import org.spongepowered.api.service.permission.SubjectData;
import org.spongepowered.api.util.Tristate;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class LuckPermsUserSubject extends LuckPermsSubject {
@EqualsAndHashCode(of = "user")
public class LuckPermsUserSubject implements Subject {
public static LuckPermsUserSubject wrapUser(User user, LuckPermsService service) {
return new LuckPermsUserSubject(user, service);
}
@@ -47,92 +49,45 @@ public class LuckPermsUserSubject extends LuckPermsSubject {
@Getter
private User user;
private LuckPermsService service;
@Getter
private Map<Map<String, String>, ContextData> contextData;
private LuckPermsSubjectData subjectData;
@Getter
private LuckPermsSubjectData transientSubjectData;
private LuckPermsUserSubject(User user, LuckPermsService service) {
super(user, service);
this.user = user;
contextData = new ConcurrentHashMap<>();
this.service = service;
this.subjectData = new LuckPermsSubjectData(true, service, user);
this.transientSubjectData = new LuckPermsSubjectData(false, service, user);
}
@Override
public void deprovision() {
/* For some reason, Sponge holds onto User instances in a cache, which in turn, prevents LuckPerms data from being GCed.
As well as unloading, we also remove all references to the User instances. */
super.deprovision();
user = null;
contextData = null;
service = null;
subjectData = null;
transientSubjectData = null;
}
@Override
public Tristate getPermissionValue(@NonNull Set<Context> contexts, @NonNull String permission) {
Map<String, String> context = contexts.stream().collect(Collectors.toMap(Context::getKey, Context::getValue));
ContextData cd = contextData.computeIfAbsent(context, map -> calculatePermissions(map, false));
me.lucko.luckperms.api.Tristate t = cd.getPermissionValue(permission);
if (t != me.lucko.luckperms.api.Tristate.UNDEFINED) {
return Tristate.fromBoolean(t.asBoolean());
} else {
return Tristate.UNDEFINED;
}
}
public ContextData calculatePermissions(Map<String, String> context, boolean apply) {
Map<String, Boolean> toApply = user.exportNodes(
new Contexts(
context,
service.getPlugin().getConfiguration().isIncludingGlobalPerms(),
service.getPlugin().getConfiguration().isIncludingGlobalWorldPerms(),
true,
service.getPlugin().getConfiguration().isApplyingGlobalGroups(),
service.getPlugin().getConfiguration().isApplyingGlobalWorldGroups()
),
Collections.emptyList(),
true
private Contexts calculateContexts(Set<Context> contexts) {
return new Contexts(
LuckPermsService.convertContexts(contexts),
service.getPlugin().getConfiguration().isIncludingGlobalPerms(),
service.getPlugin().getConfiguration().isIncludingGlobalWorldPerms(),
true,
service.getPlugin().getConfiguration().isApplyingGlobalGroups(),
service.getPlugin().getConfiguration().isApplyingGlobalWorldGroups()
);
ContextData existing = contextData.get(context);
if (existing == null) {
existing = new ContextData(this, context, service);
if (apply) {
contextData.put(context, existing);
}
}
boolean different = false;
if (toApply.size() != existing.getPermissionCache().size()) {
different = true;
} else {
for (Map.Entry<String, Boolean> e : existing.getPermissionCache().entrySet()) {
if (toApply.containsKey(e.getKey()) && toApply.get(e.getKey()) == e.getValue()) {
continue;
}
different = true;
break;
}
}
if (!different) return existing;
existing.getPermissionCache().clear();
existing.invalidateCache();
existing.getPermissionCache().putAll(toApply);
service.getPlugin().getApiProvider().fireEventAsync(new UserPermissionRefreshEvent(new UserLink(user)));
return existing;
}
public void calculatePermissions(Set<Context> contexts, boolean apply) {
Map<String, String> context = contexts.stream().collect(Collectors.toMap(Context::getKey, Context::getValue));
calculatePermissions(context, apply);
private boolean hasData() {
return user.getUserData() != null;
}
public void calculateActivePermissions(boolean apply) {
calculatePermissions(getActiveContexts(), apply);
}
@Override
public String getIdentifier() {
return service.getPlugin().getUuidCache().getExternalUUID(user.getUuid()).toString();
@@ -150,6 +105,82 @@ public class LuckPermsUserSubject extends LuckPermsSubject {
return Optional.empty();
}
@Override
public SubjectCollection getContainingCollection() {
return service.getUserSubjects();
}
@Override
public boolean hasPermission(Set<Context> contexts, String permission) {
return getPermissionValue(contexts, permission).asBoolean();
}
@Override
public Tristate getPermissionValue(@NonNull Set<Context> contexts, @NonNull String permission) {
if (hasData()) {
return LuckPermsService.convertTristate(user.getUserData().getPermissionData(calculateContexts(contexts)).getPermissionValue(permission));
}
return Tristate.UNDEFINED;
}
@Override
public boolean isChildOf(@NonNull Set<Context> contexts, @NonNull Subject parent) {
return parent instanceof LuckPermsGroupSubject && getPermissionValue(contexts, "group." + parent.getIdentifier()).asBoolean();
}
@Override
public List<Subject> getParents(Set<Context> contexts) {
List<Subject> subjects = new ArrayList<>();
if (hasData()) {
for (String perm : user.getUserData().getPermissionData(calculateContexts(contexts)).getImmutableBacking().keySet()) {
if (!perm.startsWith("group.")) {
continue;
}
String groupName = perm.substring("group.".length());
if (service.getPlugin().getGroupManager().isLoaded(groupName)) {
subjects.add(service.getGroupSubjects().get(groupName));
}
}
}
subjects.addAll(service.getUserSubjects().getDefaults().getParents(contexts));
subjects.addAll(service.getDefaults().getParents(contexts));
return subjects;
}
@Override
public Optional<String> getOption(Set<Context> contexts, String s) {
if (hasData()) {
MetaData data = user.getUserData().getMetaData(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 = service.getUserSubjects().getDefaults().getOption(contexts, s);
if (v.isPresent()) {
return v;
}
return service.getDefaults().getOption(contexts, s);
}
@Override
public Set<Context> getActiveContexts() {
final UUID uuid = service.getPlugin().getUuidCache().getExternalUUID(user.getUuid());
@@ -24,7 +24,7 @@ package me.lucko.luckperms.api.sponge.collections;
import lombok.NonNull;
import me.lucko.luckperms.api.sponge.LuckPermsService;
import me.lucko.luckperms.api.sponge.LuckPermsSubject;
import me.lucko.luckperms.api.sponge.LuckPermsGroupSubject;
import me.lucko.luckperms.api.sponge.simple.SimpleCollection;
import me.lucko.luckperms.groups.GroupManager;
import org.spongepowered.api.service.context.Context;
@@ -56,7 +56,7 @@ public class GroupCollection implements SubjectCollection {
@Override
public Subject get(@NonNull String id) {
if (manager.isLoaded(id)) {
return LuckPermsSubject.wrapHolder(manager.get(id), service);
return LuckPermsGroupSubject.wrapGroup(manager.get(id), service);
}
return fallback.get(id);
@@ -70,7 +70,7 @@ public class GroupCollection implements SubjectCollection {
@Override
public Iterable<Subject> getAllSubjects() {
return manager.getAll().values().stream()
.map(u -> LuckPermsSubject.wrapHolder(u, service))
.map(u -> LuckPermsGroupSubject.wrapGroup(u, service))
.collect(Collectors.toList());
}
@@ -82,7 +82,7 @@ public class GroupCollection implements SubjectCollection {
@Override
public Map<Subject, Boolean> getAllWithPermission(@NonNull Set<Context> contexts, @NonNull String node) {
return manager.getAll().values().stream()
.map(u -> LuckPermsSubject.wrapHolder(u, service))
.map(u -> LuckPermsGroupSubject.wrapGroup(u, service))
.filter(sub -> sub.isPermissionSet(contexts, node))
.collect(Collectors.toMap(sub -> sub, sub -> sub.getPermissionValue(contexts, node).asBoolean()));
}
@@ -30,6 +30,8 @@ import org.spongepowered.api.util.Tristate;
import java.util.*;
import java.util.stream.Collectors;
import static me.lucko.luckperms.api.sponge.LuckPermsService.convertContexts;
/**
* Holds SubjectData in a "gson friendly" format for serialization
*/
@@ -92,12 +94,4 @@ public class SimpleSubjectDataHolder {
}
}
}
public static Map<String, String> convertContexts(Set<Context> contexts) {
return contexts.stream().collect(Collectors.toMap(Context::getKey, Context::getValue));
}
public static Set<Context> convertContexts(Map<String, String> contexts) {
return contexts.entrySet().stream().map(e -> new Context(e.getKey(), e.getValue())).collect(Collectors.toSet());
}
}
@@ -25,9 +25,12 @@ package me.lucko.luckperms.calculators;
import lombok.AllArgsConstructor;
import me.lucko.luckperms.api.sponge.LuckPermsService;
import org.spongepowered.api.service.context.Context;
import org.spongepowered.api.util.Tristate;
import java.util.Set;
import static me.lucko.luckperms.api.sponge.LuckPermsService.convertTristate;
@AllArgsConstructor
public class DefaultsProcessor implements PermissionProcessor {
private final LuckPermsService service;
@@ -35,11 +38,16 @@ public class DefaultsProcessor implements PermissionProcessor {
@Override
public me.lucko.luckperms.api.Tristate hasPermission(String permission) {
org.spongepowered.api.util.Tristate t = service.getDefaults().getPermissionValue(contexts, permission);
if (t != org.spongepowered.api.util.Tristate.UNDEFINED) {
return me.lucko.luckperms.api.Tristate.fromBoolean(t.asBoolean());
} else {
return me.lucko.luckperms.api.Tristate.UNDEFINED;
Tristate t = service.getUserSubjects().getDefaults().getPermissionValue(contexts, permission);
if (t != Tristate.UNDEFINED) {
return convertTristate(Tristate.fromBoolean(t.asBoolean()));
}
Tristate t2 = service.getDefaults().getPermissionValue(contexts, permission);
if (t2 != Tristate.UNDEFINED) {
return convertTristate(Tristate.fromBoolean(t.asBoolean()));
}
return me.lucko.luckperms.api.Tristate.UNDEFINED;
}
}
@@ -1,45 +0,0 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* 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.contexts;
import lombok.AllArgsConstructor;
import me.lucko.luckperms.api.sponge.LuckPermsUserSubject;
import me.lucko.luckperms.api.sponge.collections.UserCollection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@AllArgsConstructor
public class ContextUpdateTask implements Runnable {
private final UserCollection userCollection;
@Override
public void run() {
for (LuckPermsUserSubject subject : userCollection.getUsers().values()) {
Set<Map<String, String>> contexts = new HashSet<>(subject.getContextData().keySet());
contexts.forEach(map -> subject.calculatePermissions(map, true));
}
}
}
@@ -1,54 +0,0 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* 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.users;
import me.lucko.luckperms.LPSpongePlugin;
import me.lucko.luckperms.api.sponge.LuckPermsUserSubject;
import me.lucko.luckperms.api.sponge.collections.UserCollection;
import java.util.UUID;
class SpongeUser extends User {
private final LPSpongePlugin plugin;
SpongeUser(UUID uuid, LPSpongePlugin plugin) {
super(uuid, plugin);
this.plugin = plugin;
}
SpongeUser(UUID uuid, String username, LPSpongePlugin plugin) {
super(uuid, username, plugin);
this.plugin = plugin;
}
@Override
public synchronized void refreshPermissions() {
UserCollection uc = plugin.getService().getUserSubjects();
if (!uc.getUsers().containsKey(getUuid())) {
return;
}
LuckPermsUserSubject us = uc.getUsers().get(getUuid());
us.calculateActivePermissions(true);
}
}
@@ -1,70 +0,0 @@
/*
* Copyright (c) 2016 Lucko (Luck) <luck@lucko.me>
*
* 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.users;
import me.lucko.luckperms.LPSpongePlugin;
import me.lucko.luckperms.api.context.ContextListener;
import org.spongepowered.api.entity.living.player.Player;
import java.util.Map;
public class SpongeUserManager extends UserManager implements ContextListener<Player> {
private final LPSpongePlugin plugin;
public SpongeUserManager(LPSpongePlugin plugin) {
super(plugin);
this.plugin = plugin;
}
@Override
public void cleanup(User user) {
if (!plugin.getGame().getServer().getPlayer(plugin.getUuidCache().getExternalUUID(user.getUuid())).isPresent()) {
unload(user);
}
}
@Override
public void preUnload(User user) {
plugin.getService().getUserSubjects().unload(user.getUuid());
}
@Override
public User apply(UserIdentifier id) {
SpongeUser user = id.getUsername() == null ?
new SpongeUser(id.getUuid(), plugin) :
new SpongeUser(id.getUuid(), id.getUsername(), plugin);
return user;
}
@Override
public void updateAllUsers() {
plugin.getGame().getServer().getOnlinePlayers().stream()
.map(p -> plugin.getUuidCache().getUUID(p.getUniqueId()))
.forEach(u -> plugin.getDatastore().loadUser(u, "null"));
}
@Override
public void onContextChange(Player subject, Map.Entry<String, String> before, Map.Entry<String, String> current) throws Exception {
// Not needed on Sponge. The context is accumulated on each permission check.
}
}