Cache SubjectReference instances, general cleanup

This commit is contained in:
Luck 2017-09-21 21:59:27 +01:00
parent f9efa15781
commit 777c972bdc
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
36 changed files with 732 additions and 780 deletions

View File

@ -106,6 +106,9 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
/**
* LuckPerms implementation for the Bukkit API.
*/
@Getter
public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {

View File

@ -37,7 +37,7 @@ import net.md_5.bungee.api.plugin.TabExecutor;
import java.util.Arrays;
class BungeeCommand extends Command implements TabExecutor {
public class BungeeCommand extends Command implements TabExecutor {
private final LPBungeePlugin plugin;
private final CommandManager manager;

View File

@ -91,6 +91,9 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* LuckPerms implementation for the BungeeCord API.
*/
@Getter
public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {

View File

@ -188,7 +188,7 @@ public final class GroupDelegate extends PermissionHolderDelegate implements Gro
if (!(o instanceof GroupDelegate)) return false;
GroupDelegate other = (GroupDelegate) o;
return this.getName() == null ? other.getName() == null : this.getName().equals(other.getName());
return this.getName().equals(other.getName());
}
public int hashCode() {

View File

@ -117,7 +117,7 @@ public final class TrackDelegate implements Track {
if (!(o instanceof TrackDelegate)) return false;
TrackDelegate other = (TrackDelegate) o;
return this.getName() == null ? other.getName() == null : this.getName().equals(other.getName());
return this.getName().equals(other.getName());
}
public int hashCode() {

View File

@ -228,7 +228,7 @@ public final class UserDelegate extends PermissionHolderDelegate implements User
if (!(o instanceof UserDelegate)) return false;
UserDelegate other = (UserDelegate) o;
return this.getUuid() == null ? other.getUuid() == null : this.getUuid().equals(other.getUuid());
return this.getUuid().equals(other.getUuid());
}
public int hashCode() {

View File

@ -66,8 +66,8 @@ public final class ImmutableNode implements Node {
/*
* NODE STATE
*
* This are the actual node parameters, and are
* basically what this class wraps.
* This are the actual node attributes, and are
* really just what this class wraps.
*/
@Getter

View File

@ -83,53 +83,6 @@ public class MongoDBBacking extends AbstractBacking {
}
}
/* MongoDB does not allow '.' or '$' in key names.
See: https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names
The following two methods convert the node maps so they can be stored. */
private static final Function<String, String> CONVERT_STRING = s -> s.replace(".", "[**DOT**]").replace("$", "[**DOLLAR**]");
private static final Function<String, String> REVERT_STRING = s -> s.replace("[**DOT**]", ".").replace("[**DOLLAR**]", "$");
private static <V> Map<String, V> convert(Map<String, V> map) {
return map.entrySet().stream()
.collect(Collectors.toMap(e -> CONVERT_STRING.apply(e.getKey()), Map.Entry::getValue));
}
private static <V> Map<String, V> revert(Map<String, V> map) {
return map.entrySet().stream()
.collect(Collectors.toMap(e -> REVERT_STRING.apply(e.getKey()), Map.Entry::getValue));
}
private static Document fromUser(User user) {
Document main = new Document("_id", user.getUuid())
.append("name", user.getName().orElse("null"))
.append("primaryGroup", user.getPrimaryGroup().getStoredValue());
Document perms = new Document();
for (Map.Entry<String, Boolean> e : convert(exportToLegacy(user.getEnduringNodes().values())).entrySet()) {
perms.append(e.getKey(), e.getValue());
}
main.append("perms", perms);
return main;
}
private static Document fromGroup(Group group) {
Document main = new Document("_id", group.getName());
Document perms = new Document();
for (Map.Entry<String, Boolean> e : convert(exportToLegacy(group.getEnduringNodes().values())).entrySet()) {
perms.append(e.getKey(), e.getValue());
}
main.append("perms", perms);
return main;
}
private static Document fromTrack(Track track) {
return new Document("_id", track.getName()).append("groups", track.getGroups());
}
private final DatastoreConfiguration configuration;
private MongoClient mongoClient;
private MongoDatabase database;
@ -752,6 +705,53 @@ public class MongoDBBacking extends AbstractBacking {
}, null);
}
/* MongoDB does not allow '.' or '$' in key names.
See: https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names
The following two methods convert the node maps so they can be stored. */
private static final Function<String, String> CONVERT_STRING = s -> s.replace(".", "[**DOT**]").replace("$", "[**DOLLAR**]");
private static final Function<String, String> REVERT_STRING = s -> s.replace("[**DOT**]", ".").replace("[**DOLLAR**]", "$");
private static <V> Map<String, V> convert(Map<String, V> map) {
return map.entrySet().stream()
.collect(Collectors.toMap(e -> CONVERT_STRING.apply(e.getKey()), Map.Entry::getValue));
}
private static <V> Map<String, V> revert(Map<String, V> map) {
return map.entrySet().stream()
.collect(Collectors.toMap(e -> REVERT_STRING.apply(e.getKey()), Map.Entry::getValue));
}
private static Document fromUser(User user) {
Document main = new Document("_id", user.getUuid())
.append("name", user.getName().orElse("null"))
.append("primaryGroup", user.getPrimaryGroup().getStoredValue());
Document perms = new Document();
for (Map.Entry<String, Boolean> e : convert(exportToLegacy(user.getEnduringNodes().values())).entrySet()) {
perms.append(e.getKey(), e.getValue());
}
main.append("perms", perms);
return main;
}
private static Document fromGroup(Group group) {
Document main = new Document("_id", group.getName());
Document perms = new Document();
for (Map.Entry<String, Boolean> e : convert(exportToLegacy(group.getEnduringNodes().values())).entrySet()) {
perms.append(e.getKey(), e.getValue());
}
main.append("perms", perms);
return main;
}
private static Document fromTrack(Track track) {
return new Document("_id", track.getName()).append("groups", track.getGroups());
}
public static Map<String, Boolean> exportToLegacy(Iterable<Node> nodes) {
Map<String, Boolean> m = new HashMap<>();
for (Node node : nodes) {

View File

@ -33,6 +33,11 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import java.util.concurrent.CompletableFuture;
/**
* System wide update task for LuckPerms.
*
* <p>Ensures that all local data is consistent with the storage.</p>
*/
@AllArgsConstructor
public class UpdateTask implements Runnable {
private final LuckPermsPlugin plugin;
@ -43,7 +48,9 @@ public class UpdateTask implements Runnable {
private final boolean initialUpdate;
/**
* Called ASYNC
* Runs the update task
*
* <p>Called <b>async</b>.</p>
*/
@Override
public void run() {

View File

@ -72,6 +72,7 @@ public class PermissionVault implements Runnable {
for (String e; (e = queue.poll()) != null; ) {
try {
String s = e.toLowerCase();
// only attempt an insert if we're not seen this permission before
if (knownPermissions.add(s)) {
insert(s);
}

View File

@ -32,6 +32,9 @@ import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* A simple hikari wrapper
*/
@RequiredArgsConstructor
public class HikariSupplier implements AutoCloseable {

View File

@ -39,12 +39,24 @@ import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
/**
* Uploads content to GitHub's GIST service.
*/
public class PasteUtils {
private static final String GIST_API = "https://api.github.com/gists";
private static final String SHORTEN_API = "https://git.io";
/**
* Uploads content to GIST, and returns a shortened URL.
*
* @param desc the description of the gist
* @param files the files to include in the gist (file name --> content)
* @return the url, or null
*/
public static String paste(String desc, List<Map.Entry<String, String>> files) {
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) new URL("https://api.github.com/gists").openConnection();
connection = (HttpURLConnection) new URL(GIST_API).openConnection();
connection.setRequestMethod("POST");
connection.setDoInput(true);
connection.setDoOutput(true);
@ -82,7 +94,7 @@ public class PasteUtils {
connection.disconnect();
try {
connection = (HttpURLConnection) new URL("https://git.io").openConnection();
connection = (HttpURLConnection) new URL(SHORTEN_API).openConnection();
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setDoOutput(true);
try (OutputStream os = connection.getOutputStream()) {

View File

@ -66,7 +66,7 @@ public class Predicates {
public static <T> Predicate<T> isOneOf(Set<T> ta) {
return t -> {
for (T i : ta) {
if (i == t) {
if (i.equals(t)) {
return true;
}
}
@ -79,7 +79,7 @@ public class Predicates {
}
public static <T> Predicate<T> is(T t) {
return t2 -> t == t2;
return t::equals;
}
public static <T> Predicate<T> inverse(Predicate<T> t) {

View File

@ -30,6 +30,9 @@ import lombok.experimental.UtilityClass;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
/**
* Nashorn provider utility
*/
@UtilityClass
public class Scripting {
private static ScriptEngine SCRIPT_ENGINE = null;

View File

@ -171,6 +171,8 @@ public class VerboseListener {
/**
* Uploads the captured data in this listener to a paste and returns the url
*
* @param showTraces if stack traces should be included in the output
* @param attachRaw if the rawdata should be attached to the gist
* @return the url
* @see PasteUtils#paste(String, List)
*/

View File

@ -33,6 +33,7 @@ import me.lucko.luckperms.sponge.service.model.LPPermissionService;
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.service.model.SubjectReferenceFactory;
import org.spongepowered.api.service.context.Context;
import org.spongepowered.api.service.permission.SubjectData;
@ -111,12 +112,12 @@ public class SubjectDataProxy implements SubjectData {
@Override
public CompletableFuture<Boolean> addParent(Set<Context> contexts, org.spongepowered.api.service.permission.SubjectReference ref) {
return getHandle().thenCompose(handle -> handle.addParent(CompatibilityUtil.convertContexts(contexts), SubjectReference.cast(service, ref)));
return getHandle().thenCompose(handle -> handle.addParent(CompatibilityUtil.convertContexts(contexts), SubjectReferenceFactory.obtain(service, ref)));
}
@Override
public CompletableFuture<Boolean> removeParent(Set<Context> contexts, org.spongepowered.api.service.permission.SubjectReference ref) {
return getHandle().thenCompose(handle -> handle.removeParent(CompatibilityUtil.convertContexts(contexts), SubjectReference.cast(service, ref)));
return getHandle().thenCompose(handle -> handle.removeParent(CompatibilityUtil.convertContexts(contexts), SubjectReferenceFactory.obtain(service, ref)));
}
@Override

View File

@ -32,6 +32,7 @@ import me.lucko.luckperms.sponge.service.model.CompatibilityUtil;
import me.lucko.luckperms.sponge.service.model.LPPermissionService;
import me.lucko.luckperms.sponge.service.model.LPSubject;
import me.lucko.luckperms.sponge.service.model.SubjectReference;
import me.lucko.luckperms.sponge.service.model.SubjectReferenceFactory;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.service.context.Context;
@ -109,14 +110,14 @@ public class SubjectProxy implements Subject {
@Override
public boolean isChildOf(org.spongepowered.api.service.permission.SubjectReference parent) {
return getHandle().thenApply(handle -> {
return handle.isChildOf(ImmutableContextSet.empty(), SubjectReference.cast(service, parent));
return handle.isChildOf(ImmutableContextSet.empty(), SubjectReferenceFactory.obtain(service, parent));
}).join();
}
@Override
public boolean isChildOf(Set<Context> contexts, org.spongepowered.api.service.permission.SubjectReference parent) {
return getHandle().thenApply(handle -> {
return handle.isChildOf(CompatibilityUtil.convertContexts(contexts), SubjectReference.cast(service, parent));
return handle.isChildOf(CompatibilityUtil.convertContexts(contexts), SubjectReferenceFactory.obtain(service, parent));
}).join();
}

View File

@ -40,6 +40,7 @@ import org.spongepowered.api.service.context.Context;
import org.spongepowered.api.util.Tristate;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Utility class for converting between Sponge and LuckPerms context and tristate classes
@ -47,9 +48,11 @@ import java.util.Set;
@UtilityClass
public class CompatibilityUtil {
private static final LoadingCache<Set<Context>, ImmutableContextSet> SPONGE_TO_LP_CACHE = Caffeine.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(ImmutableContextSet::fromEntries);
private static final LoadingCache<ImmutableContextSet, ImmutableSet<Context>> LP_TO_SPONGE_CACHE = Caffeine.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(set -> set.toSet().stream().map(e -> new Context(e.getKey(), e.getValue())).collect(ImmutableCollectors.toImmutableSet()));
public static ImmutableContextSet convertContexts(@NonNull Set<Context> contexts) {

View File

@ -31,10 +31,12 @@ import com.google.common.collect.ImmutableMap;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import org.spongepowered.api.service.permission.SubjectData;
import java.util.concurrent.CompletableFuture;
/**
* LuckPerms model for the Sponge {@link org.spongepowered.api.service.permission.SubjectData}
* LuckPerms model for the Sponge {@link SubjectData}
*/
public interface LPSubjectData {

View File

@ -25,103 +25,135 @@
package me.lucko.luckperms.sponge.service.model;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import com.google.common.base.Splitter;
import com.google.common.base.Preconditions;
import org.spongepowered.api.service.permission.Subject;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@ToString(of = {"collectionIdentifier", "subjectIdentifier"})
@EqualsAndHashCode(of = {"collectionIdentifier", "subjectIdentifier"})
@RequiredArgsConstructor(staticName = "of")
import javax.annotation.Nonnull;
/**
* Represents a reference to a given Subject.
*
* Use of this class (or interface) should have no negative impact on
* performance, as {@link #resolve()} calls are cached.
*/
public final class SubjectReference implements org.spongepowered.api.service.permission.SubjectReference {
@Deprecated
public static SubjectReference deserialize(LPPermissionService service, String s) {
List<String> parts = Splitter.on('/').limit(2).splitToList(s);
return of(service, parts.get(0), parts.get(1));
}
public static SubjectReference of(LPPermissionService service, Subject subject) {
return of(service, subject.getContainingCollection().getIdentifier(), subject.getIdentifier());
}
public static SubjectReference cast(LPPermissionService service, org.spongepowered.api.service.permission.SubjectReference reference) {
if (reference instanceof SubjectReference) {
return ((SubjectReference) reference);
} else {
return of(service, reference.getCollectionIdentifier(), reference.getSubjectIdentifier());
}
}
/**
* The time a subject instance should be cached in this reference
*/
private static final long CACHE_TIME = TimeUnit.SECONDS.toMillis(60);
/**
* Reference to the permission service
*/
private final LPPermissionService service;
/**
* The identifier of the collection which holds the subject
*/
@Getter
@Nonnull
private final String collectionIdentifier;
/**
* The identifier of the subject
*/
@Getter
@Nonnull
private final String subjectIdentifier;
// cache
private long lastLookup = 0L;
private WeakReference<LPSubject> cache = null;
private synchronized LPSubject resolveDirectly() {
long sinceLast = System.currentTimeMillis() - lastLookup;
SubjectReference(LPPermissionService service, String collectionIdentifier, String subjectIdentifier) {
this.service = Preconditions.checkNotNull(service);
this.collectionIdentifier = Preconditions.checkNotNull(collectionIdentifier);
this.subjectIdentifier = Preconditions.checkNotNull(subjectIdentifier);
}
// try the cache
if (sinceLast < TimeUnit.SECONDS.toMillis(10)) {
private LPSubject tryCache() {
if ((System.currentTimeMillis() - lastLookup) < CACHE_TIME) {
if (cache != null) {
LPSubject s = cache.get();
if (s != null) {
return s;
}
return cache.get();
}
}
LPSubject s = service.getCollection(collectionIdentifier).loadSubject(subjectIdentifier).join();
return null;
}
private synchronized LPSubject resolveDirectly() {
/* As this method is synchronized, it's possible that since this was invoked
the subject has been cached.
Therefore, we check the cache again, and return if there's a value present.
This effectively means all calls to this method block, but all return the same value
at the same time once the data is loaded :) */
LPSubject s = tryCache();
if (s != null) {
return s;
}
// subject isn't cached, so make a call to load it
s = service.getCollection(collectionIdentifier).loadSubject(subjectIdentifier).join();
// cache the result
lastLookup = System.currentTimeMillis();
cache = new WeakReference<>(s);
return s;
}
public CompletableFuture<LPSubject> resolveLp() {
long sinceLast = System.currentTimeMillis() - lastLookup;
// try the cache
if (sinceLast < TimeUnit.SECONDS.toMillis(10)) {
if (cache != null) {
LPSubject s = cache.get();
if (s != null) {
return CompletableFuture.completedFuture(s);
}
}
// check if there is a cached value before loading
LPSubject s = tryCache();
if (s != null) {
return CompletableFuture.completedFuture(s);
}
// load the subject
return CompletableFuture.supplyAsync(this::resolveDirectly);
}
@Override
public CompletableFuture<Subject> resolve() {
long sinceLast = System.currentTimeMillis() - lastLookup;
// try the cache
if (sinceLast < TimeUnit.SECONDS.toMillis(10)) {
if (cache != null) {
LPSubject s = cache.get();
if (s != null) {
return CompletableFuture.completedFuture(s.sponge());
}
}
// check if there is a cached value before loading
LPSubject s = tryCache();
if (s != null) {
return CompletableFuture.completedFuture(s.sponge());
}
// load the subject
return CompletableFuture.supplyAsync(() -> resolveDirectly().sponge());
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof SubjectReference)) return false;
final SubjectReference other = (SubjectReference) o;
return this.collectionIdentifier.equals(other.collectionIdentifier) && this.subjectIdentifier.equals(other.subjectIdentifier);
}
@Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
result = result * PRIME + this.collectionIdentifier.hashCode();
result = result * PRIME + this.subjectIdentifier.hashCode();
return result;
}
@Override
public String toString() {
return "SubjectReference(" +
"collectionIdentifier=" + this.collectionIdentifier + ", " +
"subjectIdentifier=" + this.subjectIdentifier + ")";
}
}

View File

@ -0,0 +1,110 @@
/*
* 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.service.model;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.experimental.UtilityClass;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.base.Splitter;
import org.spongepowered.api.service.permission.Subject;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Caches the creation of {@link SubjectReference}s.
*/
@UtilityClass
public final class SubjectReferenceFactory {
/**
* Cache based factory for SubjectReferences.
*
* Using a factory and caching here makes the Subject cache in SubjectReference
* more effective. This reduces the no. of times i/o is executed due to resolve calls
* within the SubjectReference.
*
* It's perfectly ok if two instances of the same SubjectReference exist. (hence the 1 hour expiry)
*/
private static final LoadingCache<SubjectReferenceAttributes, SubjectReference> REFERENCE_CACHE = Caffeine.newBuilder()
.expireAfterAccess(1, TimeUnit.HOURS)
.build(a -> new SubjectReference(a.permissionService, a.collectionId, a.id));
@Deprecated
public static SubjectReference deserialize(@NonNull LPPermissionService service, @NonNull String serialisedReference) {
List<String> parts = Splitter.on('/').limit(2).splitToList(serialisedReference);
return obtain(service, parts.get(0), parts.get(1));
}
public static SubjectReference obtain(@NonNull LPPermissionService service, @NonNull Subject subject) {
return obtain(service, subject.getContainingCollection().getIdentifier(), subject.getIdentifier());
}
public static SubjectReference obtain(@NonNull LPPermissionService service, @NonNull org.spongepowered.api.service.permission.SubjectReference reference) {
if (reference instanceof SubjectReference) {
return ((SubjectReference) reference);
} else {
return SubjectReferenceFactory.obtain(service, reference.getCollectionIdentifier(), reference.getSubjectIdentifier());
}
}
public static SubjectReference obtain(@NonNull LPPermissionService service, @NonNull String collectionIdentifier, @NonNull String subjectIdentifier) {
return REFERENCE_CACHE.get(new SubjectReferenceAttributes(service, collectionIdentifier, subjectIdentifier));
}
/**
* Used as a cache key.
*/
@AllArgsConstructor
private static final class SubjectReferenceAttributes {
private final LPPermissionService permissionService;
private final String collectionId;
private final String id;
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof SubjectReferenceAttributes)) return false;
final SubjectReferenceAttributes other = (SubjectReferenceAttributes) o;
return this.collectionId.equals(other.collectionId) && this.id.equals(other.id);
}
@Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
result = result * PRIME + this.collectionId.hashCode();
result = result * PRIME + this.id.hashCode();
return result;
}
}
}

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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());
}
}
}

View File

@ -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) {

View File

@ -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());
}
}

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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())
))
);
}

View File

@ -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();
}
}

View File

@ -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());

View File

@ -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;
}

View File

@ -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;
}
}