Files
PandaLib/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermissionsCachedBackendReader.java

364 lines
11 KiB
Java

package fr.pandacube.lib.permissions;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import fr.pandacube.lib.db.DB;
import fr.pandacube.lib.db.DBException;
import fr.pandacube.lib.db.SQLElementList;
import fr.pandacube.lib.permissions.SQLPermissions.EntityType;
import fr.pandacube.lib.util.log.Log;
/* package */ class PermissionsCachedBackendReader
{
/* package */ PermissionsCachedBackendReader() throws DBException {
clearAndResetCache();
}
/* package */ static final CachedPlayer DEFAULT_PLAYER = new CachedPlayer(DefaultPlayer.ID, null, null, Map.of());
static {
DEFAULT_PLAYER.usingDefaultGroups = true;
}
private final Cache<UUID, CachedPlayer> usersCache = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
private Set<String> fullPermissionsList = new TreeSet<>();
/* package */ synchronized List<String> getFullPermissionsList() {
return new ArrayList<>(fullPermissionsList);
}
/* package */ synchronized void clearPlayerCache(UUID playerId) {
usersCache.invalidate(playerId);
}
/* package */ synchronized CachedPlayer getCachedPlayer(UUID playerId) {
try {
return usersCache.get(playerId, () -> {
try {
return initPlayer(playerId);
} catch (DBException e) {
throw new RuntimeException(e);
}
});
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
/* package */ synchronized List<CachedPlayer> getAllCachedPlayers() {
return new ArrayList<>(usersCache.asMap().values());
}
/* package */ synchronized void precacheAllPlayers() {
try {
DB.getAll(SQLPermissions.class, SQLPermissions.type.eq(EntityType.User.getCode()))
.stream()
.collect(Collectors.groupingBy(el -> el.get(SQLPermissions.name))
)
.forEach((idStr, pData) -> {
try {
UUID pId = UUID.fromString(idStr);
usersCache.put(pId, initPlayer(pId, pData));
} catch (Exception e) {
Log.severe("Error caching player permission data (name=\"" + idStr + "\")", e);
}
});
} catch (DBException e) {
throw new RuntimeException(e);
}
}
private CachedPlayer initPlayer(UUID playerId) throws DBException {
if (playerId.equals(DEFAULT_PLAYER.playerId))
return DEFAULT_PLAYER;
SQLElementList<SQLPermissions> playerData = DB.getAll(SQLPermissions.class,
SQLPermissions.type.eq(EntityType.User.getCode())
.and(SQLPermissions.name.like(playerId.toString()))
);
return initPlayer(playerId, playerData);
}
private CachedPlayer initPlayer(UUID playerId, List<SQLPermissions> playerData) {
Map<String, List<SQLPermissions>> playerRawData = playerData.stream()
.collect(
Collectors.groupingBy(e -> e.get(SQLPermissions.key),
LinkedHashMap::new,
Collectors.toList())
);
String playerSelfPrefix = null;
if (playerRawData.containsKey("prefix")) {
playerSelfPrefix = playerRawData.get("prefix").stream()
.map(e -> e.get(SQLPermissions.value))
.collect(Collectors.joining());
}
String playerSelfSuffix = null;
if (playerRawData.containsKey("suffix")) {
playerSelfSuffix = playerRawData.get("suffix").stream()
.map(e -> e.get(SQLPermissions.value))
.collect(Collectors.joining());
}
Map<ServerWorldKey, List<String>> playerSelfPerms = new LinkedHashMap<>();
if (playerRawData.containsKey("permissions")) {
playerSelfPerms = playerRawData.get("permissions").stream()
.peek(e -> {
String value = e.get(SQLPermissions.value);
fullPermissionsList.add(value.substring(value.startsWith("-") ? 1 : 0).toLowerCase());
})
.collect(Collectors.groupingBy(e -> new ServerWorldKey(e.get(SQLPermissions.server), e.get(SQLPermissions.world)),
LinkedHashMap::new,
Collectors.mapping(e -> e.get(SQLPermissions.value),
Collectors.toList()
)
)
);
}
CachedPlayer player = new CachedPlayer(playerId, playerSelfPrefix, playerSelfSuffix, playerSelfPerms);
if (playerRawData.containsKey("groups")) {
playerRawData.get("groups").stream()
.map(e -> e.get(SQLPermissions.value))
.forEach(g -> player.groups.add(getCachedGroup(g)));
}
if (player.groups.isEmpty()) {
player.usingDefaultGroups = true;
player.groups.addAll(getDefaultGroups());
}
return player;
}
private final Map<String, CachedGroup> groupsCache = new LinkedHashMap<>();
private boolean cacheIsUpdating = false;
/* package */ synchronized CachedGroup getCachedGroup(String group) {
return groupsCache.getOrDefault(group, new CachedGroup(group, null, null, false, new LinkedHashMap<>()));
}
/* package */ synchronized List<CachedGroup> getDefaultGroups() {
return groupsCache.values().stream()
.filter(g -> g.deflt)
.collect(Collectors.toList());
}
public List<CachedGroup> getGroups() {
return new ArrayList<>(groupsCache.values());
}
/* package */ void clearAndResetCacheAsync(Runnable then) {
synchronized (this) {
if (cacheIsUpdating)
return;
}
Thread t = new Thread(() -> {
try {
clearAndResetCache();
} catch (Throwable e) {
Log.severe(e);
}
if (then != null)
then.run();
}, "Permissions Backend Group Cache Updater");
t.setDaemon(true);
t.start();
}
private void clearAndResetCache() throws DBException {
synchronized (this) {
if (cacheIsUpdating)
return;
cacheIsUpdating = true;
}
try {
Map<String, CachedGroup> newData = new LinkedHashMap<>();
Set<String> newFullPermissionsList = new TreeSet<>();
SQLElementList<SQLPermissions> groupData = DB.getAll(SQLPermissions.class, SQLPermissions.type.eq(EntityType.Group.getCode()));
Map<String, Map<String, List<SQLPermissions>>> groupsRawData = groupData.stream()
.collect(
Collectors.groupingBy(e -> e.get(SQLPermissions.name),
LinkedHashMap::new,
Collectors.groupingBy(e -> e.get(SQLPermissions.key),
LinkedHashMap::new,
Collectors.toList())
)
);
for (String groupName : groupsRawData.keySet()) {
initGroup(groupName, groupsRawData, newData, newFullPermissionsList);
}
synchronized (this) {
groupsCache.clear();
groupsCache.putAll(newData);
cacheIsUpdating = false;
usersCache.invalidateAll();
fullPermissionsList = newFullPermissionsList;
DEFAULT_PLAYER.groups.clear();
DEFAULT_PLAYER.groups.addAll(getDefaultGroups());
}
} finally {
synchronized (this) {
cacheIsUpdating = false;
}
}
}
private void initGroup(String groupName, Map<String, Map<String, List<SQLPermissions>>> groupsRawData, Map<String, CachedGroup> newData, Set<String> newFullPermissionsList) {
if (newData.containsKey(groupName))
return;
Map<String, List<SQLPermissions>> groupRawData = groupsRawData.getOrDefault(groupName, new LinkedHashMap<>());
boolean groupDefault = groupRawData.containsKey("default")
&& "true".equals(groupRawData.get("default").get(0).get(SQLPermissions.value));
String groupSelfPrefix = null;
if (groupRawData.containsKey("prefix")) {
groupSelfPrefix = groupRawData.get("prefix").stream()
.map(e -> e.get(SQLPermissions.value))
.collect(Collectors.joining());
}
String groupSelfSuffix = null;
if (groupRawData.containsKey("suffix")) {
groupSelfSuffix = groupRawData.get("suffix").stream()
.map(e -> e.get(SQLPermissions.value))
.collect(Collectors.joining());
}
Map<ServerWorldKey, List<String>> groupSelfPerms = new LinkedHashMap<>();
if (groupRawData.containsKey("permissions")) {
groupSelfPerms = groupRawData.get("permissions").stream()
.peek(e -> {
String value = e.get(SQLPermissions.value);
newFullPermissionsList.add(value.substring(value.startsWith("-") ? 1 : 0));
})
.collect(Collectors.groupingBy(e -> new ServerWorldKey(e.get(SQLPermissions.server), e.get(SQLPermissions.world)),
LinkedHashMap::new,
Collectors.mapping(e -> e.get(SQLPermissions.value),
Collectors.toList()
)
)
);
}
CachedGroup group = new CachedGroup(groupName, groupSelfPrefix, groupSelfSuffix, groupDefault, groupSelfPerms);
newData.put(groupName, group);
if (groupRawData.containsKey("inheritances")) {
groupRawData.get("inheritances").stream()
.map(e -> e.get(SQLPermissions.value))
.forEach(g -> {
initGroup(g, groupsRawData, newData, newFullPermissionsList);
group.inheritances.add(newData.get(g));
});
}
}
/* package */ static abstract class CachedEntity {
public final String name;
private final String selfPrefix, selfSuffix;
private final Map<ServerWorldKey, List<String>> selfPermissions;
private CachedEntity(String n, String p, String s,
Map<ServerWorldKey, List<String>> perms) {
name = n; selfPrefix = p; selfSuffix = s; selfPermissions = perms;
}
/* package */ List<String> getSelfPermissions(String server, String world) {
return selfPermissions.getOrDefault(new ServerWorldKey(server, world), new ArrayList<>());
}
/* package */ Set<ServerWorldKey> getSelfPermissionsServerWorldKeys() {
return new TreeSet<>(selfPermissions.keySet());
}
/* package */ String getSelfPrefix() {
return selfPrefix;
}
/* package */ String getSelfSuffix() {
return selfSuffix;
}
}
/* package */ static class CachedPlayer extends CachedEntity {
public final UUID playerId;
public final List<CachedGroup> groups = new ArrayList<>();
public boolean usingDefaultGroups = false;
private CachedPlayer(UUID pl, String p, String s,
Map<ServerWorldKey, List<String>> perms) {
super(pl.toString(), p, s, perms);
playerId = pl;
}
}
/* package */ static class CachedGroup extends CachedEntity {
public final boolean deflt;
public final List<CachedGroup> inheritances = new ArrayList<>();
private CachedGroup(String n, String p, String s,
boolean deflt, Map<ServerWorldKey, List<String>> perms) {
super(n, p, s, perms);
this.deflt = deflt;
}
/* package */ Set<UUID> getPlayersInGroup() throws DBException {
Set<UUID> ids = new HashSet<>();
DB.forEach(SQLPermissions.class,
SQLPermissions.type.eq(EntityType.User.getCode())
.and(SQLPermissions.key.eq("groups"))
.and(SQLPermissions.value.eq(name)),
e -> ids.add(UUID.fromString(e.get(SQLPermissions.name)))
);
return ids;
}
}
}