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 usersCache = CacheBuilder.newBuilder() .expireAfterAccess(10, TimeUnit.MINUTES) .build(); private Set fullPermissionsList = new TreeSet<>(); /* package */ synchronized List 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 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 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 playerData) { Map> 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> 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 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 getDefaultGroups() { return groupsCache.values().stream() .filter(g -> g.deflt) .collect(Collectors.toList()); } public List 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 newData = new LinkedHashMap<>(); Set newFullPermissionsList = new TreeSet<>(); SQLElementList groupData = DB.getAll(SQLPermissions.class, SQLPermissions.type.eq(EntityType.Group.getCode())); Map>> 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>> groupsRawData, Map newData, Set newFullPermissionsList) { if (newData.containsKey(groupName)) return; Map> 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> 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> selfPermissions; private CachedEntity(String n, String p, String s, Map> perms) { name = n; selfPrefix = p; selfSuffix = s; selfPermissions = perms; } /* package */ List getSelfPermissions(String server, String world) { return selfPermissions.getOrDefault(new ServerWorldKey(server, world), new ArrayList<>()); } /* package */ Set 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 groups = new ArrayList<>(); public boolean usingDefaultGroups = false; private CachedPlayer(UUID pl, String p, String s, Map> perms) { super(pl.toString(), p, s, perms); playerId = pl; } } /* package */ static class CachedGroup extends CachedEntity { public final boolean deflt; public final List inheritances = new ArrayList<>(); private CachedGroup(String n, String p, String s, boolean deflt, Map> perms) { super(n, p, s, perms); this.deflt = deflt; } /* package */ Set getPlayersInGroup() throws DBException { Set 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; } } }