Compare commits

..
Author SHA1 Message Date
Luck b32e9040c1 Folia support 2025-07-21 22:33:47 +01:00
Luck 73d710a89e Update to Minecraft 1.21.8 2025-07-21 22:08:21 +01:00
Luck ed863b275d Update to Minecraft 1.21.7 2025-06-30 19:41:56 +01:00
Luck c20776d6aa Remove Forge capability attachment 2025-06-19 19:53:09 +01:00
Luck 3aca721f96 Rollback bytebuddy dependency version upgrade 2025-06-19 19:35:04 +01:00
Luck 8d9c5bf401 Add workaround for Forge eventbus limitation 2025-06-19 19:09:40 +01:00
Luck 0af1038d81 Update to Minecraft 1.21.6 2025-06-19 18:48:44 +01:00
Luck 51fe574a51 Move config load earlier in startup process (#4083) 2025-06-14 09:31:56 +01:00
Max Barrass bac0edd90e Fix missing closing brackets in .conf files (#4109) 2025-06-14 09:24:52 +01:00
Luck 8ab9910ef8 Downgrade jedis and h2 2025-06-01 19:32:26 +01:00
Luck 9538cb9dad Upgrade dependencies and require Java 11 2025-06-01 16:41:39 +01:00
Luck 0486bc885b Release API v5.5 2025-05-25 08:59:47 +01:00
Luck 27e7551e2b Fix adventure package relocations 2025-05-23 08:10:55 +01:00
Luck b77d5759d3 Upgrade adventure dependencies for 1.21.5 support 2025-05-22 21:15:24 +01:00
Luck 96abc0d4fe Update setup-gradle GitHub action 2025-05-10 15:04:42 +01:00
lucko 1a24a6a155 Add REST storage backend (#3939) 2025-05-10 15:01:08 +01:00
𝑩𝒊𝒒𝒖𝒂𝒕𝒆𝒓𝒏𝒊𝒐𝒏𝒔 95ea491787 Support plugins that 'provide' Vault (#4075) 2025-05-10 14:47:32 +01:00
Remigio 626d08431e Add missing IllegalArgumentExceptions to Javadocs (#4015) 2025-05-10 14:33:38 +01:00
lucko 94e7e11183 Add configurable read-only mode for commands (#4031) 2025-05-10 14:32:47 +01:00
Luck f64bf04ba4 Update mod loader dependencies 2025-05-08 19:45:14 +01:00
Luck 7744913df2 Update to Minecraft 1.21.5 2025-03-27 19:46:43 +00:00
lucko 6b7283ac83 Improve context manager caching (#4050) 2025-03-20 19:20:16 +00:00
Luck 9bc8a61e2d Add file secret config adapter (#3913) 2025-02-22 15:57:44 +00:00
Luck 5e24a7068c Mark plugin msg channel as optional on Forge (#3954) 2025-02-22 15:28:18 +00:00
Luck f351369a51 Update CI action versions 2025-02-13 09:59:59 +00:00
Luck 1bf0deb5a5 Add unit tests for messaging service messages 2025-01-20 22:46:52 +00:00
Luck 5b82837b80 Update Sponge module to API 12 2025-01-05 14:38:15 +00:00
Luck 0fef481d48 Add more unit tests 2024-12-22 09:51:11 +00:00
Luck f2040ab815 Update to Minecraft 1.21.4 2024-12-07 16:21:26 +00:00
Luck b18717edef Remove NeoForge capability attachment 2024-12-04 20:39:55 +00:00
Luck 29c93fac4f Add note about shading LuckPerms API into plugin jars 2024-12-04 11:27:14 +00:00
Luck 2e67011f45 Refactor some unit tests 2024-12-04 11:25:06 +00:00
Luck a92e04f605 Recreate individual sql tables if they are missing (#3995) 2024-11-09 12:09:23 +00:00
Luck 8d65693d9a Update to Minecraft 1.21.3 2024-10-26 10:07:03 +01:00
Luck 8973abd7f1 Support older signature alg if running on Java 8 2024-10-23 23:19:05 +01:00
Luck 9420efd3ac Switch to ECDSA keys for web editor socket 2024-10-23 22:52:20 +01:00
Alex c773126a0f Simplify ExpiringSet implementation (#3969) 2024-10-21 21:09:40 +01:00
Luck 9d80de5d5c Fix MongoStorage nodeFromDoc integer cast (#3846) 2024-08-24 09:55:32 +01:00
3TUSK 9bf30fc839 Fix login configuration phase on NeoForge (#3961) 2024-08-22 22:27:29 +01:00
Luck 136a4859f3 Update to Minecraft 1.21.1 2024-08-12 19:45:38 +01:00
Jason Penilla 642f1d3019 Port to NeoForge (#3950) 2024-08-12 19:11:27 +01:00
Luck b21753f5e9 Use builtin Fabric world change event 2024-07-07 21:31:34 +01:00
Luck 9cc129466b Init VerboseHandler earlier (#3932) 2024-07-07 21:22:40 +01:00
Drex 55440cac01 Fix memory leak in Fabric MixinUser (#3931) 2024-07-07 21:15:41 +01:00
lucko 9e7a3d26e4 Improve efficiency of action log queries (#3917) 2024-06-16 21:11:23 +01:00
259 changed files with 7878 additions and 2044 deletions

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
+14 -16
View File
@@ -19,26 +19,24 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: validate gradle wrapper
uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v3
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
uses: gradle/actions/setup-gradle@v4
- name: Run build and tests with Gradle wrapper
run: ./gradlew test build -PdockerTests
- name: Publish test report
uses: mikepenz/action-junit-report@v3
uses: mikepenz/action-junit-report@v5
if: success() || failure()
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
@@ -46,7 +44,7 @@ jobs:
detailed_summary: true
- name: Upload all artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: jars
path: |
@@ -61,7 +59,7 @@ jobs:
standalone/loader/build/libs/LuckPerms-Standalone-*.jar
- name: Upload standalone artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: standalone-binary
path: standalone/loader/build/libs/LuckPerms-Standalone-*.jar
@@ -76,12 +74,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Retrieve saved standalone jar artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: standalone-binary
path: standalone/docker/
@@ -90,13 +88,13 @@ jobs:
run: mv standalone/docker/LuckPerms-Standalone-*.jar standalone/docker/luckperms-standalone.jar
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
- name: Log in to the Container registry
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -108,7 +106,7 @@ jobs:
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v3
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
@@ -121,7 +119,7 @@ jobs:
type=raw,enable=${{ github.ref == 'refs/heads/master' }},value=${{ env.luckperms_version }}
- name: Build and push Docker image
uses: docker/build-push-action@v2
uses: docker/build-push-action@v6
with:
context: standalone/docker/
platforms: linux/amd64,linux/arm64
+9 -6
View File
@@ -1,5 +1,8 @@
group = 'net.luckperms'
project.version = '5.4'
project.version = '5.5'
sourceCompatibility = 1.8
targetCompatibility = 1.8
jar {
manifest {
@@ -8,8 +11,8 @@ jar {
}
dependencies {
compileOnly 'org.checkerframework:checker-qual:3.21.2'
compileOnly 'org.jetbrains:annotations:23.1.0'
compileOnly 'org.checkerframework:checker-qual:3.49.3'
compileOnly 'org.jetbrains:annotations:26.0.2'
}
// Only used occasionally for deployment - not needed for normal builds.
@@ -24,13 +27,13 @@ if (project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePass
options.charSet = 'UTF-8'
options.links(
'https://checkerframework.org/api/',
'https://javadoc.io/static/org.jetbrains/annotations/23.0.0/'
'https://javadoc.io/static/org.jetbrains/annotations/26.0.2/'
)
options.addStringOption('Xdoclint:none', '-quiet')
options.addStringOption('-since', '5.0,5.1,5.2,5.3,5.4,5.5')
if (JavaVersion.current() > JavaVersion.VERSION_1_8) {
options.links.add('https://docs.oracle.com/en/java/javase/11/docs/api/')
options.links.add('https://docs.oracle.com/en/java/javase/21/docs/api/')
} else {
options.links.add('https://docs.oracle.com/javase/8/docs/api/')
}
@@ -63,7 +66,7 @@ if (project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePass
pom {
name = 'LuckPerms API'
description = 'A permissions plugin for Minecraft servers.'
description = 'A permissions manager for Minecraft servers.'
url = 'https://luckperms.net'
licenses {
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<p>
LuckPerms is a permissions plugin for Minecraft servers. It allows server admins to control what
LuckPerms is a permissions manager for Minecraft servers. It allows server admins to control what
features players can use by creating groups and assigning permissions.
</p>
<h4>Useful Links</h4>
@@ -79,7 +79,8 @@ public final class LuckPermsProvider {
" a) the LuckPerms plugin is not installed or it failed to enable\n" +
" b) the plugin in the stacktrace does not declare a dependency on LuckPerms\n" +
" c) the plugin in the stacktrace is retrieving the API before the plugin 'enable' phase\n" +
" (call the #get method in onEnable, not the constructor!)\n";
" (call the #get method in onEnable, not the constructor!)\n" +
" d) the plugin in the stacktrace is incorrectly 'shading' the LuckPerms API into its jar\n";
NotLoadedException() {
super(MESSAGE);
@@ -63,6 +63,7 @@ public interface DisplayNameNode extends ScopedNode<DisplayNameNode, DisplayName
*
* @param displayName the display name to set
* @return the builder
* @throws IllegalArgumentException if {@code displayName} is empty
*/
static @NonNull Builder builder(@NonNull String displayName) {
return builder().displayName(displayName);
@@ -67,6 +67,7 @@ public interface InheritanceNode extends ScopedNode<InheritanceNode, Inheritance
*
* @param group the group to set
* @return the builder
* @throws IllegalArgumentException if {@code group} is not a valid group name
*/
static @NonNull Builder builder(@NonNull String group) {
return builder().group(group);
@@ -71,6 +71,7 @@ public interface MetaNode extends ScopedNode<MetaNode, MetaNode.Builder> {
* @param key the meta key to set
* @param value the meta value to set
* @return the builder
* @throws IllegalArgumentException if {@code key} is empty
*/
static @NonNull Builder builder(@NonNull String key, @NonNull String value) {
return builder().key(key).value(value);
@@ -88,6 +88,7 @@ public interface PermissionNode extends ScopedNode<PermissionNode, PermissionNod
*
* @param permission the permission to set
* @return the builder
* @throws IllegalArgumentException if {@code permission} is empty
*/
static @NonNull Builder builder(@NonNull String permission) {
return builder().permission(permission);
@@ -75,6 +75,7 @@ public interface RegexPermissionNode extends ScopedNode<RegexPermissionNode, Reg
*
* @param pattern the pattern to set
* @return the builder
* @throws IllegalArgumentException if {@code pattern} is empty
*/
static @NonNull Builder builder(@NonNull String pattern) {
return builder().pattern(pattern);
@@ -76,6 +76,7 @@ public interface Platform {
NUKKIT("Nukkit"),
VELOCITY("Velocity"),
FABRIC("Fabric"),
NEOFORGE("NeoForge"),
FORGE("Forge"),
STANDALONE("Standalone");
@@ -48,7 +48,7 @@ public interface DataTypeFilterFunction {
/**
* Creates a {@link DataTypeFilterFunction} that always returns the given
* {@code predicate}.
* {@code predicate} (commonly one of the values in {@link DataTypeFilter}).
*
* @param predicate the predicate
* @return the data type filter function
+4 -6
View File
@@ -13,10 +13,10 @@ subprojects {
apply plugin: 'org.cadixdev.licenser'
group = 'me.lucko.luckperms'
version = '5.4-SNAPSHOT'
version = '5.5-SNAPSHOT'
sourceCompatibility = 1.8
targetCompatibility = 1.8
sourceCompatibility = 11
targetCompatibility = 11
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8'
@@ -52,7 +52,7 @@ subprojects {
}
project.ext.majorVersion = '5'
project.ext.minorVersion = '4'
project.ext.minorVersion = '5'
project.ext.patchVersion = determinePatchVersion()
project.ext.apiVersion = project.ext.majorVersion + '.' + project.ext.minorVersion
project.ext.fullVersion = project.ext.apiVersion + '.' + project.ext.patchVersion
@@ -75,8 +75,6 @@ subprojects {
}
}
mavenCentral()
maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' }
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
maven { url 'https://repo.lucko.me/' }
maven { url 'https://libraries.minecraft.net/' }
}
+4 -2
View File
@@ -6,12 +6,14 @@ repositories {
maven { url 'https://repo.papermc.io/repository/maven-public/' }
}
disableAutoTargetJvm()
dependencies {
implementation project(':common')
compileOnly project(':common:loader-utils')
compileOnly 'com.destroystokyo.paper:paper-api:1.15.2-R0.1-SNAPSHOT'
compileOnly('net.kyori:adventure-platform-bukkit:4.3.3') {
compileOnly 'dev.folia:folia-api:1.21.6-R0.1-SNAPSHOT'
compileOnly('net.kyori:adventure-platform-bukkit:4.4.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
exclude(module: 'adventure-nbt')
@@ -10,6 +10,7 @@ load: STARTUP
# remapping when the plugin is loaded. Note that despite what this setting might otherwise imply,
# LP is still compatible with pre-1.13 releases.
api-version: 1.13
folia-supported: true
# Load LuckPerms before Vault. This means that all plugins that (soft-)depend
# on Vault depend on LuckPerms too.
@@ -25,22 +25,32 @@
package me.lucko.luckperms.bukkit;
import me.lucko.luckperms.common.plugin.scheduler.AbstractJavaScheduler;
import me.lucko.luckperms.common.plugin.scheduler.JavaSchedulerAdapter;
import me.lucko.luckperms.common.plugin.scheduler.SchedulerAdapter;
import me.lucko.luckperms.common.sender.Sender;
import org.bukkit.command.CommandSender;
import java.util.concurrent.Executor;
public class BukkitSchedulerAdapter extends AbstractJavaScheduler implements SchedulerAdapter {
private final Executor sync;
public class BukkitSchedulerAdapter extends JavaSchedulerAdapter implements SchedulerAdapter {
private final Executor syncExecutor;
public BukkitSchedulerAdapter(LPBukkitBootstrap bootstrap) {
super(bootstrap);
this.sync = r -> bootstrap.getServer().getScheduler().scheduleSyncDelayedTask(bootstrap.getLoader(), r);
this.syncExecutor = r -> bootstrap.getServer().getScheduler().scheduleSyncDelayedTask(bootstrap.getLoader(), r);
}
public void sync(Runnable task) {
this.syncExecutor.execute(task);
}
public void sync(CommandSender ctx, Runnable task) {
this.syncExecutor.execute(task);
}
@Override
public Executor sync() {
return this.sync;
public void sync(Sender ctx, Runnable task) {
this.syncExecutor.execute(task);
}
}
@@ -67,7 +67,7 @@ public class BukkitSenderFactory extends SenderFactory<LPBukkitPlugin, CommandSe
if (sender instanceof Player || sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender) {
this.audiences.sender(sender).sendMessage(message);
} else {
getPlugin().getBootstrap().getScheduler().executeSync(() -> this.audiences.sender(sender).sendMessage(message));
getPlugin().getBootstrap().getScheduler().sync(sender, () -> this.audiences.sender(sender).sendMessage(message));
}
}
@@ -0,0 +1,86 @@
/*
* 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.bukkit;
import io.papermc.paper.threadedregions.scheduler.RegionScheduler;
import me.lucko.luckperms.common.plugin.scheduler.SchedulerAdapter;
import me.lucko.luckperms.common.sender.AbstractSender;
import me.lucko.luckperms.common.sender.Sender;
import org.bukkit.Server;
import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.ProxiedCommandSender;
import org.bukkit.command.RemoteConsoleCommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.java.JavaPlugin;
public class FoliaSchedulerAdapter extends BukkitSchedulerAdapter implements SchedulerAdapter {
private final JavaPlugin loader;
private final Server server;
public FoliaSchedulerAdapter(LPBukkitBootstrap bootstrap) {
super(bootstrap);
this.loader = bootstrap.getLoader();
this.server = bootstrap.getServer();
}
@Override
public void sync(Runnable task) {
this.server.getGlobalRegionScheduler().execute(this.loader, task);
}
@Override
public void sync(Sender ctx, Runnable task) {
sync(unwrapSender(ctx), task);
}
@Override
public void sync(CommandSender ctx, Runnable task) {
if (ctx instanceof Entity) {
((Entity) ctx).getScheduler().execute(this.loader, task, null, 0);
} else if (ctx instanceof BlockCommandSender) {
RegionScheduler scheduler = this.server.getRegionScheduler();
scheduler.execute(this.loader, ((BlockCommandSender) ctx).getBlock().getLocation(), task);
} else if (ctx instanceof ConsoleCommandSender || ctx instanceof RemoteConsoleCommandSender) {
this.server.getGlobalRegionScheduler().execute(this.loader, task);
} else if (ctx instanceof ProxiedCommandSender) {
sync(((ProxiedCommandSender) ctx).getCallee(), task);
} else {
throw new IllegalArgumentException("Unknown command sender type: " + ctx.getClass().getName());
}
}
@SuppressWarnings("unchecked")
private static CommandSender unwrapSender(Sender sender) {
if (sender instanceof AbstractSender) {
return ((AbstractSender<CommandSender>) sender).getSender();
} else {
throw new IllegalArgumentException("unknown sender type: " + sender.getClass());
}
}
}
@@ -102,7 +102,9 @@ public class LPBukkitBootstrap implements LuckPermsBootstrap, LoaderBootstrap, B
this.loader = loader;
this.logger = new JavaPluginLogger(loader.getLogger());
this.schedulerAdapter = new BukkitSchedulerAdapter(this);
this.schedulerAdapter = isFolia()
? new FoliaSchedulerAdapter(this)
: new BukkitSchedulerAdapter(this);
this.classPathAppender = new JarInJarClassPathAppender(getClass().getClassLoader());
this.console = new NullSafeConsoleCommandSender(getServer());
this.plugin = new LPBukkitPlugin(this);
@@ -175,7 +177,7 @@ public class LPBukkitBootstrap implements LuckPermsBootstrap, LoaderBootstrap, B
this.plugin.enable();
// schedule a task to update the 'serverStarting' flag
getServer().getScheduler().runTask(this.loader, () -> this.serverStarting = false);
this.schedulerAdapter.sync(() -> this.serverStarting = false);
} finally {
this.enableLatch.countDown();
}
@@ -311,4 +313,13 @@ public class LPBukkitBootstrap implements LuckPermsBootstrap, LoaderBootstrap, B
return true;
}
}
private static boolean isFolia() {
try {
Class.forName("io.papermc.paper.threadedregions.scheduler.RegionScheduler");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
@@ -206,7 +206,7 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
// schedule another injection after all plugins have loaded
// the entire pluginmanager instance is replaced by some plugins :(
this.bootstrap.getServer().getScheduler().runTaskLaterAsynchronously(this.bootstrap.getLoader(), injector, 1);
this.bootstrap.getScheduler().sync(injector);
}
/*
@@ -268,7 +268,7 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
// remove all operators on startup if they're disabled
if (!getConfiguration().get(ConfigKeys.OPS_ENABLED)) {
this.bootstrap.getServer().getScheduler().runTaskAsynchronously(this.bootstrap.getLoader(), () -> {
this.bootstrap.getScheduler().sync(() -> {
for (OfflinePlayer player : this.bootstrap.getServer().getOperators()) {
player.setOp(false);
}
@@ -287,11 +287,11 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
// Load any online users (in the case of a reload)
for (Player player : this.bootstrap.getServer().getOnlinePlayers()) {
this.bootstrap.getScheduler().executeAsync(() -> {
this.bootstrap.getScheduler().async(() -> {
try {
User user = this.connectionListener.loadUser(player.getUniqueId(), player.getName());
if (user != null) {
this.bootstrap.getScheduler().executeSync(() -> {
this.bootstrap.getScheduler().sync(player, () -> {
try {
LuckPermsPermissible lpPermissible = new LuckPermsPermissible(player, user, this);
PermissibleInjector.inject(player, lpPermissible, getLogger());
@@ -25,85 +25,46 @@
package me.lucko.luckperms.bukkit.context;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.cache.LoadingMap;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.ContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.bukkit.inject.permissible.LuckPermsPermissible;
import me.lucko.luckperms.bukkit.inject.permissible.PermissibleInjector;
import me.lucko.luckperms.common.context.manager.DetachedContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import net.luckperms.api.query.OptionKey;
import net.luckperms.api.query.QueryOptions;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class BukkitContextManager extends ContextManager<Player, Player> {
public class BukkitContextManager extends DetachedContextManager<Player, Player> {
public static final OptionKey<Boolean> OP_OPTION = OptionKey.of("op", Boolean.class);
// cache the creation of ContextsCache instances for online players with no expiry
private final LoadingMap<Player, QueryOptionsCache<Player>> onlineSubjectCaches = LoadingMap.of(key -> new QueryOptionsCache<>(key, this));
// cache the creation of ContextsCache instances for offline players with a 1m expiry
private final LoadingCache<Player, QueryOptionsCache<Player>> offlineSubjectCaches = CaffeineFactory.newBuilder()
.expireAfterAccess(1, TimeUnit.MINUTES)
.build(key -> {
QueryOptionsCache<Player> cache = this.onlineSubjectCaches.getIfPresent(key);
if (cache != null) {
return cache;
}
return new QueryOptionsCache<>(key, this);
});
public BukkitContextManager(LPBukkitPlugin plugin) {
super(plugin, Player.class, Player.class);
}
public void onPlayerQuit(Player player) {
this.onlineSubjectCaches.remove(player);
}
@Override
public UUID getUniqueId(Player player) {
return player.getUniqueId();
}
@Override
public QueryOptionsCache<Player> getCacheFor(Player subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
if (subject.isOnline()) {
return this.onlineSubjectCaches.get(subject);
} else {
return this.offlineSubjectCaches.get(subject);
public @Nullable QueryOptionsSupplier getQueryOptionsSupplier(Player subject) {
Objects.requireNonNull(subject, "subject");
LuckPermsPermissible permissible = PermissibleInjector.get(subject);
if (permissible != null) {
return permissible.getQueryOptionsSupplier();
}
return null;
}
@Override
protected void invalidateCache(Player subject) {
QueryOptionsCache<Player> cache = this.onlineSubjectCaches.getIfPresent(subject);
if (cache != null) {
cache.invalidate();
}
cache = this.offlineSubjectCaches.getIfPresent(subject);
if (cache != null) {
cache.invalidate();
}
}
@Override
public QueryOptions formQueryOptions(Player subject, ImmutableContextSet contextSet) {
QueryOptions.Builder queryOptions = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder();
public void customizeQueryOptions(Player subject, QueryOptions.Builder builder) {
if (subject.isOp()) {
queryOptions.option(OP_OPTION, true);
builder.option(OP_OPTION, true);
}
return queryOptions.context(contextSet).build();
}
}
@@ -32,7 +32,7 @@ import me.lucko.luckperms.bukkit.calculator.OpProcessor;
import me.lucko.luckperms.bukkit.calculator.PermissionMapProcessor;
import me.lucko.luckperms.common.cacheddata.result.TristateResult;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.verbose.event.CheckOrigin;
import net.luckperms.api.query.QueryOptions;
@@ -68,7 +68,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* "Hot" method calls, (namely #hasPermission) are significantly faster than the base implementation.
*
* This class is **thread safe**. This means that when LuckPerms is installed on the server,
* is is safe to call Player#hasPermission asynchronously.
* it is safe to call Player#hasPermission asynchronously.
*/
public class LuckPermsPermissible extends PermissibleBase {
@@ -93,7 +93,7 @@ public class LuckPermsPermissible extends PermissibleBase {
private final LPBukkitPlugin plugin;
// caches context lookups for the player
private final QueryOptionsCache<Player> queryOptionsSupplier;
private final QueryOptionsSupplier queryOptionsSupplier;
// the players previous permissible. (the one they had before this one was injected)
private PermissibleBase oldPermissible = null;
@@ -110,7 +110,7 @@ public class LuckPermsPermissible extends PermissibleBase {
this.user = Objects.requireNonNull(user, "user");
this.player = Objects.requireNonNull(player, "player");
this.plugin = Objects.requireNonNull(plugin, "plugin");
this.queryOptionsSupplier = plugin.getContextManager().getCacheFor(player);
this.queryOptionsSupplier = plugin.getContextManager().createQueryOptionsSupplier(player);
injectFakeAttachmentsList();
}
@@ -293,7 +293,7 @@ public class LuckPermsPermissible extends PermissibleBase {
// the query options cache when op status changes.
// (#invalidate is a fast call)
if (this.queryOptionsSupplier != null) { // this method is called by the super class constructor, before this class has fully initialised
this.queryOptionsSupplier.invalidate();
this.queryOptionsSupplier.invalidateCache();
}
// but we don't need to do anything else in this method, unlike the CB impl.
@@ -316,6 +316,10 @@ public class LuckPermsPermissible extends PermissibleBase {
return this.plugin;
}
public QueryOptionsSupplier getQueryOptionsSupplier() {
return this.queryOptionsSupplier;
}
PermissibleBase getOldPermissible() {
return this.oldPermissible;
}
@@ -409,9 +413,7 @@ public class LuckPermsPermissible extends PermissibleBase {
@Override public PermissionAttachment remove(int index) { throw new UnsupportedOperationException(); }
@Override public int indexOf(Object o) { throw new UnsupportedOperationException(); }
@Override public int lastIndexOf(Object o) { throw new UnsupportedOperationException(); }
@Override
public @NonNull ListIterator<PermissionAttachment> listIterator(int index) { throw new UnsupportedOperationException(); }
@Override
public @NonNull List<PermissionAttachment> subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); }
@Override public @NonNull ListIterator<PermissionAttachment> listIterator(int index) { throw new UnsupportedOperationException(); }
@Override public @NonNull List<PermissionAttachment> subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); }
}
}
@@ -30,6 +30,7 @@ import me.lucko.luckperms.common.plugin.logging.PluginLogger;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.PermissionAttachment;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Field;
import java.util.List;
@@ -161,6 +162,19 @@ public final class PermissibleInjector {
}
}
public static @Nullable LuckPermsPermissible get(Player player) {
PermissibleBase permissibleBase;
try {
permissibleBase = (PermissibleBase) HUMAN_ENTITY_PERMISSIBLE_FIELD.get(player);
} catch (IllegalAccessException e) {
return null;
}
if (permissibleBase instanceof LuckPermsPermissible) {
return (LuckPermsPermissible) permissibleBase;
}
return null;
}
public static void checkInjected(Player player, PluginLogger logger) {
PermissibleBase permissibleBase;
try {
@@ -83,7 +83,7 @@ public class BukkitAutoOpListener implements LuckPermsEventListener {
if (callerIsSync) {
player.setOp(value);
} else {
this.plugin.getBootstrap().getScheduler().executeSync(() -> player.setOp(value));
this.plugin.getBootstrap().getScheduler().sync(player, () -> player.setOp(value));
}
}
@@ -26,6 +26,7 @@
package me.lucko.luckperms.bukkit.listeners;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.bukkit.LPBukkitBootstrap;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.cache.BufferedRequest;
import me.lucko.luckperms.common.event.LuckPermsEventListener;
@@ -90,12 +91,15 @@ public class BukkitCommandListUpdater implements LuckPermsEventListener {
// Called when the buffer times out.
private void sendUpdate(UUID uniqueId) {
if (this.plugin.getBootstrap().isServerStopping()) {
LPBukkitBootstrap bootstrap = this.plugin.getBootstrap();
if (bootstrap.isServerStopping()) {
return;
}
this.plugin.getBootstrap().getScheduler().sync()
.execute(() -> this.plugin.getBootstrap().getPlayer(uniqueId).ifPresent(Player::updateCommands));
Player player = bootstrap.getPlayer(uniqueId).orElse(null);
if (player != null) {
bootstrap.getScheduler().sync(player, player::updateCommands);
}
}
private final class SendBuffer extends BufferedRequest<Void> {
@@ -240,7 +240,7 @@ public class BukkitConnectionListener extends AbstractConnectionListener impleme
// perform unhooking from bukkit objects 1 tick later.
// this allows plugins listening after us on MONITOR to still have intact permissions data
this.plugin.getBootstrap().getServer().getScheduler().runTaskLater(this.plugin.getLoader(), () -> {
this.plugin.getBootstrap().getScheduler().sync(() -> {
// Remove the custom permissible
try {
PermissibleInjector.uninject(player, true);
@@ -253,10 +253,7 @@ public class BukkitConnectionListener extends AbstractConnectionListener impleme
if (this.plugin.getConfiguration().get(ConfigKeys.AUTO_OP)) {
player.setOp(false);
}
// remove their contexts cache
this.plugin.getContextManager().onPlayerQuit(player);
}, 1L);
});
}
}
@@ -36,12 +36,26 @@ import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.server.PluginEnableEvent;
import org.bukkit.event.server.RemoteServerCommandEvent;
import org.bukkit.event.server.ServerCommandEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import java.util.regex.Pattern;
public class BukkitPlatformListener implements Listener {
private static final Pattern OP_COMMAND_PATTERN = Pattern.compile("^/?(\\w+:)?(deop|op)( .*)?$", Pattern.CASE_INSENSITIVE);
private static final boolean PLUGIN_DESCRIPTION_FILE_PROVIDES_SUPPORTED;
static {
boolean supported = false;
try {
PluginDescriptionFile.class.getMethod("getProvides");
supported = true;
} catch (NoSuchMethodException ignored) {
}
PLUGIN_DESCRIPTION_FILE_PROVIDES_SUPPORTED = supported;
}
private final LPBukkitPlugin plugin;
public BukkitPlatformListener(LPBukkitPlugin plugin) {
@@ -80,7 +94,11 @@ public class BukkitPlatformListener implements Listener {
@EventHandler
public void onPluginEnable(PluginEnableEvent e) {
if (e.getPlugin().getName().equalsIgnoreCase("Vault")) {
Plugin p = e.getPlugin();
boolean shouldHook = p.getName().equalsIgnoreCase("Vault") ||
(PLUGIN_DESCRIPTION_FILE_PROVIDES_SUPPORTED && p.getDescription().getProvides().contains("Vault"));
if (shouldHook) {
this.plugin.tryVaultHook(true);
}
}
@@ -77,7 +77,7 @@ public class LilyPadMessenger implements Messenger {
@EventListener
public void onMessage(MessageEvent event) {
this.plugin.getBootstrap().getScheduler().executeAsync(() -> {
this.plugin.getBootstrap().getScheduler().async(() -> {
String channel = event.getChannel();
if (!channel.equals(CHANNEL)) {
return;
@@ -25,6 +25,7 @@
package me.lucko.luckperms.bukkit.util;
import net.kyori.adventure.text.Component;
import org.bukkit.ChatColor;
import org.bukkit.Server;
import org.bukkit.command.ConsoleCommandSender;
@@ -35,9 +36,12 @@ import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
/**
* The {@link Server#getConsoleSender()} method returns null during onEnable
@@ -109,12 +113,16 @@ public class NullSafeConsoleCommandSender implements ConsoleCommandSender {
// just throw UnsupportedOperationException - we never use any of these methods
@Override public @NonNull Spigot spigot() { throw new UnsupportedOperationException(); }
@Override public void sendMessage(@Nullable UUID uuid, @NotNull String s) { throw new UnsupportedOperationException(); }
@Override public void sendMessage(@Nullable UUID uuid, @NotNull String... strings) { throw new UnsupportedOperationException(); }
@Override public @NotNull Component name() { throw new UnsupportedOperationException(); }
@Override public boolean isConversing() { throw new UnsupportedOperationException(); }
@Override public void acceptConversationInput(@NonNull String s) { throw new UnsupportedOperationException(); }
@Override public boolean beginConversation(@NonNull Conversation conversation) { throw new UnsupportedOperationException(); }
@Override public void abandonConversation(@NonNull Conversation conversation) { throw new UnsupportedOperationException(); }
@Override public void abandonConversation(@NonNull Conversation conversation, @NonNull ConversationAbandonedEvent conversationAbandonedEvent) { throw new UnsupportedOperationException(); }
@Override public void sendRawMessage(@NonNull String s) { throw new UnsupportedOperationException(); }
@Override public void sendRawMessage(@Nullable UUID uuid, @NotNull String s) { throw new UnsupportedOperationException(); }
@Override public @NonNull PermissionAttachment addAttachment(@NonNull Plugin plugin, @NonNull String s, boolean b) { throw new UnsupportedOperationException(); }
@Override public @NonNull PermissionAttachment addAttachment(@NonNull Plugin plugin) { throw new UnsupportedOperationException(); }
@Override public PermissionAttachment addAttachment(@NonNull Plugin plugin, @NonNull String s, boolean b, int i) { throw new UnsupportedOperationException(); }
+30
View File
@@ -719,3 +719,33 @@ register-command-list-data: true
# If LuckPerms should attempt to resolve Vanilla command target selectors for LP commands.
# See here for more info: https://minecraft.wiki/w/Target_selectors
resolve-command-selectors: false
# If the plugin should run in "read-only" mode for commands.
#
# In this mode, players or the console will only be able to execute commands that read or view
# data, and will not be able to execute any LP commands that modify data.
#
# If enabling read-only mode for just players, be aware that some other plugins may allow players
# to execute commands as the console, allowing them to run LP commands indirectly.
#
# Note: This does not affect interactions with LuckPerms via the API.
commands-read-only-mode:
players: false
console: false
# If LuckPerms commands should be disabled. When true, this will prevent all LP commands from being
# executed. In a sense this is a more extreme version of read-only mode (see above).
#
# LuckPerms will still act as the permission manager, but server administrators will be unable to
# make changes via commands.
#
# If disabling commands just for players, be aware that some other plugins may allow players to
# execute commands as the console, allowing them to run commands indirectly.
#
# If commands are disabled for both players and the console, LuckPerms will not attempt to register
# a command with the server at all & in a sense will be invisible to both players and admins.
#
# Note: This does not affect interactions with LuckPerms via the API.
disable-luckperms-commands:
players: false
console: false
+4 -2
View File
@@ -6,8 +6,10 @@ dependencies {
implementation project(':common')
compileOnly project(':common:loader-utils')
compileOnly 'net.md-5:bungeecord-api:1.16-R0.4'
compileOnly('net.kyori:adventure-platform-bungeecord:4.3.3') {
compileOnly('net.md-5:bungeecord-api:1.19-R0.1-SNAPSHOT') {
exclude(module: 'bungeecord-protocol')
}
compileOnly('net.kyori:adventure-platform-bungeecord:4.4.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
exclude(module: 'adventure-nbt')
+3 -1
View File
@@ -3,7 +3,9 @@ plugins {
}
dependencies {
compileOnly 'net.md-5:bungeecord-api:1.15-SNAPSHOT'
compileOnly('net.md-5:bungeecord-api:1.19-R0.1-SNAPSHOT') {
exclude(module: 'bungeecord-protocol')
}
implementation project(':api')
implementation project(':common:loader-utils')
@@ -52,11 +52,6 @@ public class BungeeSchedulerAdapter implements SchedulerAdapter {
return this.executor;
}
@Override
public Executor sync() {
return this.executor;
}
@Override
public SchedulerTask asyncLater(Runnable task, long delay, TimeUnit unit) {
ScheduledTask t = this.bootstrap.getProxy().getScheduler().schedule(this.bootstrap.getLoader(), task, delay, unit);
@@ -25,25 +25,13 @@
package me.lucko.luckperms.bungee.context;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.bungee.LPBungeePlugin;
import me.lucko.luckperms.common.context.manager.ContextManager;
import me.lucko.luckperms.common.context.manager.InlineQueryOptionsSupplier;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.query.QueryOptions;
import me.lucko.luckperms.common.context.manager.SimpleContextManager;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class BungeeContextManager extends ContextManager<ProxiedPlayer, ProxiedPlayer> {
private final LoadingCache<ProxiedPlayer, QueryOptions> contextsCache = CaffeineFactory.newBuilder()
.expireAfterWrite(50, TimeUnit.MILLISECONDS)
.build(this::calculate);
public class BungeeContextManager extends SimpleContextManager<ProxiedPlayer, ProxiedPlayer> {
public BungeeContextManager(LPBungeePlugin plugin) {
super(plugin, ProxiedPlayer.class, ProxiedPlayer.class);
}
@@ -52,35 +40,4 @@ public class BungeeContextManager extends ContextManager<ProxiedPlayer, ProxiedP
public UUID getUniqueId(ProxiedPlayer player) {
return player.getUniqueId();
}
@Override
public QueryOptionsSupplier getCacheFor(ProxiedPlayer subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
return new InlineQueryOptionsSupplier<>(subject, this.contextsCache);
}
// override getContext, getQueryOptions and invalidateCache to skip the QueryOptionsSupplier
@Override
public ImmutableContextSet getContext(ProxiedPlayer subject) {
return getQueryOptions(subject).context();
}
@Override
public QueryOptions getQueryOptions(ProxiedPlayer subject) {
return this.contextsCache.get(subject);
}
@Override
protected void invalidateCache(ProxiedPlayer subject) {
this.contextsCache.invalidate(subject);
}
@Override
public QueryOptions formQueryOptions(ProxiedPlayer subject, ImmutableContextSet contextSet) {
return formQueryOptions(contextSet);
}
}
@@ -77,7 +77,7 @@ public class BungeeConnectionListener extends AbstractConnectionListener impleme
this will prevent the event from completing until we're finished handling. */
e.registerIntent(this.plugin.getLoader());
this.plugin.getBootstrap().getScheduler().executeAsync(() -> {
this.plugin.getBootstrap().getScheduler().async(() -> {
/* Actually process the login for the connection.
We do this here to delay the login until the data is ready.
If the login gets cancelled later on, then this will be cleaned up.
@@ -83,7 +83,7 @@ public class PluginMessageMessenger extends AbstractPluginMessageMessenger imple
if (handleIncomingMessage(buf)) {
// Forward to other servers
this.plugin.getBootstrap().getScheduler().executeAsync(() -> sendOutgoingMessage(buf));
this.plugin.getBootstrap().getScheduler().async(() -> sendOutgoingMessage(buf));
}
}
}
+30
View File
@@ -611,3 +611,33 @@ disable-bulkupdate: false
#
# - When this happens, the plugin will set their primary group back to default.
prevent-primary-group-removal: false
# If the plugin should run in "read-only" mode for commands.
#
# In this mode, players or the console will only be able to execute commands that read or view
# data, and will not be able to execute any LP commands that modify data.
#
# If enabling read-only mode for just players, be aware that some other plugins may allow players
# to execute commands as the console, allowing them to run LP commands indirectly.
#
# Note: This does not affect interactions with LuckPerms via the API.
commands-read-only-mode:
players: false
console: false
# If LuckPerms commands should be disabled. When true, this will prevent all LP commands from being
# executed. In a sense this is a more extreme version of read-only mode (see above).
#
# LuckPerms will still act as the permission manager, but server administrators will be unable to
# make changes via commands.
#
# If disabling commands just for players, be aware that some other plugins may allow players to
# execute commands as the console, allowing them to run commands indirectly.
#
# If commands are disabled for both players and the console, LuckPerms will not attempt to register
# a command with the server at all & in a sense will be invisible to both players and admins.
#
# Note: This does not affect interactions with LuckPerms via the API.
disable-luckperms-commands:
players: false
console: false
+33 -28
View File
@@ -16,17 +16,21 @@ jacocoTestReport {
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.1'
testImplementation "org.testcontainers:junit-jupiter:1.19.8"
testImplementation 'org.mockito:mockito-core:5.11.0'
testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'
testImplementation 'org.slf4j:slf4j-simple:1.7.36'
testImplementation 'org.junit.jupiter:junit-jupiter:5.13.0'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testImplementation 'org.testcontainers:junit-jupiter:1.21.1'
testImplementation 'org.mockito:mockito-core:5.18.0'
testImplementation 'org.mockito:mockito-junit-jupiter:5.18.0'
testImplementation 'com.h2database:h2:2.1.214'
testImplementation 'org.mongodb:mongodb-driver-legacy:4.5.0'
testImplementation 'org.spongepowered:configurate-yaml:3.7.2'
testImplementation 'org.spongepowered:configurate-hocon:3.7.2'
testImplementation 'org.mongodb:mongodb-driver-legacy:5.5.0'
testImplementation 'org.spongepowered:configurate-yaml:3.7.3'
testImplementation 'org.spongepowered:configurate-hocon:3.7.3'
testImplementation 'me.lucko.configurate:configurate-toml:3.7'
testImplementation 'net.luckperms:rest-api-java-client:0.1'
api project(':api')
api 'org.checkerframework:checker-qual:3.12.0'
@@ -37,29 +41,29 @@ dependencies {
compileOnly 'org.slf4j:slf4j-api:1.7.30'
compileOnly 'org.apache.logging.log4j:log4j-api:2.14.0'
api('net.kyori:adventure-api:4.11.0') {
api('net.kyori:adventure-api:4.21.0') {
exclude(module: 'adventure-bom')
exclude(module: 'checker-qual')
exclude(module: 'annotations')
}
api('net.kyori:adventure-text-serializer-gson:4.11.0') {
api('net.kyori:adventure-text-serializer-gson:4.21.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
exclude(module: 'gson')
}
api('net.kyori:adventure-text-serializer-legacy:4.11.0') {
api('net.kyori:adventure-text-serializer-legacy:4.21.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
}
api('net.kyori:adventure-text-serializer-plain:4.11.0') {
api('net.kyori:adventure-text-serializer-plain:4.21.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
}
api("net.kyori:adventure-text-minimessage:4.11.0") {
api("net.kyori:adventure-text-minimessage:4.21.0") {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
}
@@ -72,31 +76,32 @@ dependencies {
api 'com.google.code.gson:gson:2.7'
api 'com.google.guava:guava:19.0'
api 'com.github.ben-manes.caffeine:caffeine:2.9.0'
api 'com.github.ben-manes.caffeine:caffeine:3.2.0'
api 'com.squareup.okhttp3:okhttp:3.14.9'
api 'com.squareup.okio:okio:1.17.5'
api 'net.bytebuddy:byte-buddy:1.10.22'
api 'com.squareup.okio:okio:1.17.6'
api 'net.bytebuddy:byte-buddy:1.15.11'
api('org.spongepowered:configurate-core:3.7.2') {
api('org.spongepowered:configurate-core:3.7.3') {
transitive = false
}
api('org.spongepowered:configurate-yaml:3.7.2') {
api('org.spongepowered:configurate-yaml:3.7.3') {
transitive = false
}
api('org.spongepowered:configurate-gson:3.7.2') {
api('org.spongepowered:configurate-gson:3.7.3') {
transitive = false
}
api('org.spongepowered:configurate-hocon:3.7.2') {
api('org.spongepowered:configurate-hocon:3.7.3') {
transitive = false
}
api('me.lucko.configurate:configurate-toml:3.7') {
transitive = false
}
compileOnly 'com.zaxxer:HikariCP:4.0.3'
compileOnly 'redis.clients:jedis:4.4.3'
compileOnly 'io.nats:jnats:2.16.4'
compileOnly 'com.rabbitmq:amqp-client:5.12.0'
compileOnly 'org.mongodb:mongodb-driver-legacy:4.5.0'
compileOnly 'org.postgresql:postgresql:42.6.0'
compileOnly 'org.yaml:snakeyaml:1.28'
compileOnly 'com.zaxxer:HikariCP:6.3.0'
compileOnly 'redis.clients:jedis:5.2.0'
compileOnly 'io.nats:jnats:2.21.1'
compileOnly 'com.rabbitmq:amqp-client:5.25.0'
compileOnly 'org.mongodb:mongodb-driver-legacy:5.5.0'
compileOnly 'org.postgresql:postgresql:42.7.6'
compileOnly 'org.yaml:snakeyaml:1.33'
compileOnly 'net.luckperms:rest-api-java-client:0.1'
}
@@ -26,8 +26,8 @@
package me.lucko.luckperms.common.api.implementation;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.common.metastacking.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.metastacking.StandardStackElements;
import me.lucko.luckperms.common.cacheddata.metastack.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.cacheddata.metastack.StandardStackElements;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.luckperms.api.metastacking.DuplicateRemovalFunction;
import net.luckperms.api.metastacking.MetaStackDefinition;
@@ -193,7 +193,7 @@ public abstract class AbstractCachedDataManager implements CachedDataManager {
}
public void cleanup() {
this.cache.values().removeIf(value -> ((UsageTracked) value).usedSince(TimeUnit.MINUTES.toMillis(2)));
this.cache.values().removeIf(value -> !((UsageTracked) value).usedInTheLast(2, TimeUnit.MINUTES));
}
@Override
@@ -25,14 +25,20 @@
package me.lucko.luckperms.common.cacheddata;
import com.google.common.annotations.VisibleForTesting;
import java.util.concurrent.TimeUnit;
public abstract class UsageTracked {
private long lastUsed = System.currentTimeMillis();
@VisibleForTesting
protected long lastUsed = System.currentTimeMillis();
public void recordUsage() {
this.lastUsed = System.currentTimeMillis();
}
public boolean usedSince(long duration) {
return this.lastUsed > System.currentTimeMillis() - duration;
public boolean usedInTheLast(long duration, TimeUnit unit) {
return this.lastUsed > System.currentTimeMillis() - unit.toMillis(duration);
}
}
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.metastacking;
package me.lucko.luckperms.common.cacheddata.metastack;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.metastacking;
package me.lucko.luckperms.common.cacheddata.metastack;
import com.google.common.collect.ImmutableList;
import net.luckperms.api.metastacking.DuplicateRemovalFunction;
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.metastacking;
package me.lucko.luckperms.common.cacheddata.metastack;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
@@ -220,8 +220,11 @@ public final class StandardStackElements {
@Override
public boolean shouldAccumulate(@NonNull ChatMetaType type, @NonNull ChatMetaNode<?, ?> node, @Nullable ChatMetaNode<?, ?> current) {
Track track = this.plugin.getTrackManager().getIfLoaded(this.trackName);
if (track == null) {
return false;
}
PermissionHolder.Identifier origin = node.metadata(InheritanceOriginMetadata.KEY).getOrigin();
return track != null && origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && track.containsGroup(origin.getName());
return origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && track.containsGroup(origin.getName());
}
@Override
@@ -250,8 +253,11 @@ public final class StandardStackElements {
@Override
public boolean shouldAccumulate(@NonNull ChatMetaType type, @NonNull ChatMetaNode<?, ?> node, @Nullable ChatMetaNode<?, ?> current) {
Track track = this.plugin.getTrackManager().getIfLoaded(this.trackName);
if (track == null) {
return false;
}
PermissionHolder.Identifier origin = node.metadata(InheritanceOriginMetadata.KEY).getOrigin();
return track != null && !track.containsGroup(origin.getName());
return !(origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && track.containsGroup(origin.getName()));
}
@Override
@@ -305,7 +311,7 @@ public final class StandardStackElements {
@Override
public boolean shouldAccumulate(@NonNull ChatMetaType type, @NonNull ChatMetaNode<?, ?> node, @Nullable ChatMetaNode<?, ?> current) {
PermissionHolder.Identifier origin = node.metadata(InheritanceOriginMetadata.KEY).getOrigin();
return !this.groupName.equals(origin.getName());
return !(origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && this.groupName.equals(origin.getName()));
}
@Override
@@ -0,0 +1,57 @@
/*
* 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.common.cacheddata.result;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
import org.checkerframework.checker.nullness.qual.Nullable;
public abstract class AbstractResult<T, N extends Node, S extends AbstractResult<T, N, S>> implements Result<T, N> {
/** The node that caused the result */
protected final N node;
/** A reference to another result that this one overrides */
protected S overriddenResult;
public AbstractResult(N node, S overriddenResult) {
this.node = node;
this.overriddenResult = overriddenResult;
}
@Override
public final @Nullable N node() {
return this.node;
}
public final @Nullable S overriddenResult() {
return this.overriddenResult;
}
public final void setOverriddenResult(S overriddenResult) {
this.overriddenResult = overriddenResult;
}
}
@@ -25,30 +25,23 @@
package me.lucko.luckperms.common.cacheddata.result;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.types.WeightNode;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents the result of an integer meta lookup
*
* @param <N> the node type
*/
public final class IntegerResult<N extends Node> implements Result<Integer, N> {
public final class IntegerResult<N extends Node> extends AbstractResult<Integer, N, IntegerResult<N>> {
/** The result */
private final int result;
/** The node that caused the result */
private final N node;
/** A reference to another result that this one overrides */
private IntegerResult<N> overriddenResult;
public IntegerResult(int result, N node, IntegerResult<N> overriddenResult) {
super(node, overriddenResult);
this.result = result;
this.node = node;
this.overriddenResult = overriddenResult;
}
@Override
@@ -73,19 +66,6 @@ public final class IntegerResult<N extends Node> implements Result<Integer, N> {
}
}
@Override
public @Nullable N node() {
return this.node;
}
public @Nullable IntegerResult<N> overriddenResult() {
return this.overriddenResult;
}
public void setOverriddenResult(IntegerResult<N> overriddenResult) {
this.overriddenResult = overriddenResult;
}
public boolean isNull() {
return this == NULL_RESULT;
}
@@ -25,7 +25,6 @@
package me.lucko.luckperms.common.cacheddata.result;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.types.ChatMetaNode;
import net.luckperms.api.node.types.MetaNode;
@@ -36,19 +35,14 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*
* @param <N> the node type
*/
public final class StringResult<N extends Node> implements Result<String, N> {
public final class StringResult<N extends Node> extends AbstractResult<String, N, StringResult<N>> {
/** The result, nullable */
private final String result;
/** The node that caused the result */
private final N node;
/** A reference to another result that this one overrides */
private StringResult<N> overriddenResult;
public StringResult(String result, N node, StringResult<N> overriddenResult) {
super(node, overriddenResult);
this.result = result;
this.node = node;
this.overriddenResult = overriddenResult;
}
@Override
@@ -56,19 +50,6 @@ public final class StringResult<N extends Node> implements Result<String, N> {
return this.result;
}
@Override
public @Nullable N node() {
return this.node;
}
public @Nullable StringResult<N> overriddenResult() {
return this.overriddenResult;
}
public void setOverriddenResult(StringResult<N> overriddenResult) {
this.overriddenResult = overriddenResult;
}
public StringResult<N> copy() {
return new StringResult<>(this.result, this.node, this.overriddenResult);
}
@@ -27,7 +27,6 @@ package me.lucko.luckperms.common.cacheddata.result;
import me.lucko.luckperms.common.calculator.PermissionCalculator;
import me.lucko.luckperms.common.calculator.processor.PermissionProcessor;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
import net.luckperms.api.util.Tristate;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -36,20 +35,16 @@ import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents the result of a {@link PermissionCalculator} lookup.
*/
public final class TristateResult implements Result<Tristate, Node> {
public final class TristateResult extends AbstractResult<Tristate, Node, TristateResult> {
/** The result */
private final Tristate result;
/** The node that caused the result */
private final Node node;
/** The permission processor that provided the result */
private final Class<? extends PermissionProcessor> processorClass;
/** A reference to another result that this one overrides */
private TristateResult overriddenResult;
private TristateResult(Tristate result, Node node, Class<? extends PermissionProcessor> processorClass) {
super(node, null);
this.result = result;
this.node = node;
this.processorClass = processorClass;
}
@@ -58,11 +53,6 @@ public final class TristateResult implements Result<Tristate, Node> {
return this.result;
}
@Override
public @Nullable Node node() {
return this.node;
}
public @Nullable Class<? extends PermissionProcessor> processorClass() {
return this.processorClass;
}
@@ -79,14 +69,6 @@ public final class TristateResult implements Result<Tristate, Node> {
}
}
public @Nullable TristateResult overriddenResult() {
return this.overriddenResult;
}
public void setOverriddenResult(TristateResult overriddenResult) {
this.overriddenResult = overriddenResult;
}
@Override
public String toString() {
return "TristateResult(" +
@@ -79,6 +79,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@@ -101,7 +102,7 @@ public class CommandManager {
.build()
);
private final AtomicBoolean executingCommand = new AtomicBoolean(false);
private final ExpiringSet<UUID> playerRateLimit = new ExpiringSet<>(500, TimeUnit.MILLISECONDS);
private final Set<UUID> playerRateLimit = ExpiringSet.newExpiringSet(500, TimeUnit.MILLISECONDS);
private final TabCompletions tabCompletions;
private final Map<String, Command<?>> mainCommands;
@@ -159,6 +160,15 @@ public class CommandManager {
return CompletableFuture.completedFuture(null);
}
boolean commandsDisabled = sender.isConsole()
? this.plugin.getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_CONSOLE)
: this.plugin.getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_PLAYERS);
if (commandsDisabled) {
Message.COMMANDS_DISABLED.send(sender);
return CompletableFuture.completedFuture(null);
}
SchedulerAdapter scheduler = this.plugin.getBootstrap().getScheduler();
List<String> argsCopy = new ArrayList<>(args);
@@ -285,6 +295,14 @@ public class CommandManager {
}
public List<String> tabCompleteCommand(Sender sender, List<String> arguments) {
boolean commandsDisabled = sender.isConsole()
? this.plugin.getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_CONSOLE)
: this.plugin.getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_PLAYERS);
if (commandsDisabled) {
return Collections.emptyList();
}
applyConvenienceAliases(arguments, false);
final List<Command<?>> mains = this.mainCommands.values().stream()
@@ -31,6 +31,7 @@ import me.lucko.luckperms.common.command.spec.CommandSpec;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.sender.Sender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
@@ -66,7 +67,7 @@ public abstract class ChildCommand<T> extends Command<T> {
.collect(Collectors.toList());
builder.append(Component.text(" - ", NamedTextColor.DARK_AQUA))
.append(Component.join(Component.space(), argUsages))
.append(Component.join(JoinConfiguration.separator(Component.space()), argUsages))
.build();
}
@@ -35,6 +35,7 @@ import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
@@ -114,7 +115,7 @@ public abstract class GenericChildCommand {
.collect(Collectors.toList());
builder.append(Component.text(" - ", NamedTextColor.DARK_AQUA))
.append(Component.join(Component.space(), argUsages))
.append(Component.join(JoinConfiguration.separator(Component.space()), argUsages))
.build();
}
@@ -33,6 +33,7 @@ import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
@@ -70,7 +71,7 @@ public abstract class SingleCommand extends Command<Void> {
.collect(Collectors.toList());
builder.append(Component.text(" - ", NamedTextColor.DARK_AQUA))
.append(Component.join(Component.space(), argUsages))
.append(Component.join(JoinConfiguration.separator(Component.space()), argUsages))
.build();
}
@@ -32,143 +32,143 @@ import me.lucko.luckperms.common.sender.Sender;
*/
public enum CommandPermission {
SYNC("sync", Type.NONE),
INFO("info", Type.NONE),
EDITOR("editor", Type.NONE),
VERBOSE("verbose", Type.NONE),
VERBOSE_COMMAND_OTHERS("verbose.command.others", Type.NONE),
TREE("tree", Type.NONE),
SEARCH("search", Type.NONE),
IMPORT("import", Type.NONE),
EXPORT("export", Type.NONE),
RELOAD_CONFIG("reloadconfig", Type.NONE),
BULK_UPDATE("bulkupdate", Type.NONE),
APPLY_EDITS("applyedits", Type.NONE),
TRUST_EDITOR("trusteditor", Type.NONE),
TRANSLATIONS("translations", Type.NONE),
SYNC("sync", Type.NONE, true),
INFO("info", Type.NONE, true),
EDITOR("editor", Type.NONE, true),
VERBOSE("verbose", Type.NONE, true),
VERBOSE_COMMAND_OTHERS("verbose.command.others", Type.NONE, false),
TREE("tree", Type.NONE, true),
SEARCH("search", Type.NONE, true),
IMPORT("import", Type.NONE, false),
EXPORT("export", Type.NONE, true),
RELOAD_CONFIG("reloadconfig", Type.NONE, true),
BULK_UPDATE("bulkupdate", Type.NONE, false),
APPLY_EDITS("applyedits", Type.NONE, false),
TRUST_EDITOR("trusteditor", Type.NONE, false),
TRANSLATIONS("translations", Type.NONE, true),
CREATE_GROUP("creategroup", Type.NONE),
DELETE_GROUP("deletegroup", Type.NONE),
LIST_GROUPS("listgroups", Type.NONE),
CREATE_GROUP("creategroup", Type.NONE, false),
DELETE_GROUP("deletegroup", Type.NONE, false),
LIST_GROUPS("listgroups", Type.NONE, true),
CREATE_TRACK("createtrack", Type.NONE),
DELETE_TRACK("deletetrack", Type.NONE),
LIST_TRACKS("listtracks", Type.NONE),
CREATE_TRACK("createtrack", Type.NONE, false),
DELETE_TRACK("deletetrack", Type.NONE, false),
LIST_TRACKS("listtracks", Type.NONE, true),
USER_INFO("info", Type.USER),
USER_PERM_INFO("permission.info", Type.USER),
USER_PERM_SET("permission.set", Type.USER),
USER_PERM_UNSET("permission.unset", Type.USER),
USER_PERM_SET_TEMP("permission.settemp", Type.USER),
USER_PERM_UNSET_TEMP("permission.unsettemp", Type.USER),
USER_PERM_CHECK("permission.check", Type.USER),
USER_PERM_CLEAR("permission.clear", Type.USER),
USER_PARENT_INFO("parent.info", Type.USER),
USER_PARENT_SET("parent.set", Type.USER),
USER_PARENT_SET_TRACK("parent.settrack", Type.USER),
USER_PARENT_ADD("parent.add", Type.USER),
USER_PARENT_REMOVE("parent.remove", Type.USER),
USER_PARENT_ADD_TEMP("parent.addtemp", Type.USER),
USER_PARENT_REMOVE_TEMP("parent.removetemp", Type.USER),
USER_PARENT_CLEAR("parent.clear", Type.USER),
USER_PARENT_CLEAR_TRACK("parent.cleartrack", Type.USER),
USER_PARENT_SWITCHPRIMARYGROUP("parent.switchprimarygroup", Type.USER),
USER_META_INFO("meta.info", Type.USER),
USER_META_SET("meta.set", Type.USER),
USER_META_UNSET("meta.unset", Type.USER),
USER_META_SET_TEMP("meta.settemp", Type.USER),
USER_META_UNSET_TEMP("meta.unsettemp", Type.USER),
USER_META_ADD_PREFIX("meta.addprefix", Type.USER),
USER_META_ADD_SUFFIX("meta.addsuffix", Type.USER),
USER_META_SET_PREFIX("meta.setprefix", Type.USER),
USER_META_SET_SUFFIX("meta.setsuffix", Type.USER),
USER_META_REMOVE_PREFIX("meta.removeprefix", Type.USER),
USER_META_REMOVE_SUFFIX("meta.removesuffix", Type.USER),
USER_META_ADD_TEMP_PREFIX("meta.addtempprefix", Type.USER),
USER_META_ADD_TEMP_SUFFIX("meta.addtempsuffix", Type.USER),
USER_META_SET_TEMP_PREFIX("meta.settempprefix", Type.USER),
USER_META_SET_TEMP_SUFFIX("meta.settempsuffix", Type.USER),
USER_META_REMOVE_TEMP_PREFIX("meta.removetempprefix", Type.USER),
USER_META_REMOVE_TEMP_SUFFIX("meta.removetempsuffix", Type.USER),
USER_META_CLEAR("meta.clear", Type.USER),
USER_EDITOR("editor", Type.USER),
USER_SHOW_TRACKS("showtracks", Type.USER),
USER_PROMOTE("promote", Type.USER),
USER_DEMOTE("demote", Type.USER),
USER_CLEAR("clear", Type.USER),
USER_CLONE("clone", Type.USER),
USER_INFO("info", Type.USER, true),
USER_PERM_INFO("permission.info", Type.USER, true),
USER_PERM_SET("permission.set", Type.USER, false),
USER_PERM_UNSET("permission.unset", Type.USER, false),
USER_PERM_SET_TEMP("permission.settemp", Type.USER, false),
USER_PERM_UNSET_TEMP("permission.unsettemp", Type.USER, false),
USER_PERM_CHECK("permission.check", Type.USER, true),
USER_PERM_CLEAR("permission.clear", Type.USER, false),
USER_PARENT_INFO("parent.info", Type.USER, true),
USER_PARENT_SET("parent.set", Type.USER, false),
USER_PARENT_SET_TRACK("parent.settrack", Type.USER, false),
USER_PARENT_ADD("parent.add", Type.USER, false),
USER_PARENT_REMOVE("parent.remove", Type.USER, false),
USER_PARENT_ADD_TEMP("parent.addtemp", Type.USER, false),
USER_PARENT_REMOVE_TEMP("parent.removetemp", Type.USER, false),
USER_PARENT_CLEAR("parent.clear", Type.USER, false),
USER_PARENT_CLEAR_TRACK("parent.cleartrack", Type.USER, false),
USER_PARENT_SWITCHPRIMARYGROUP("parent.switchprimarygroup", Type.USER, false),
USER_META_INFO("meta.info", Type.USER, true),
USER_META_SET("meta.set", Type.USER, false),
USER_META_UNSET("meta.unset", Type.USER, false),
USER_META_SET_TEMP("meta.settemp", Type.USER, false),
USER_META_UNSET_TEMP("meta.unsettemp", Type.USER, false),
USER_META_ADD_PREFIX("meta.addprefix", Type.USER, false),
USER_META_ADD_SUFFIX("meta.addsuffix", Type.USER, false),
USER_META_SET_PREFIX("meta.setprefix", Type.USER, false),
USER_META_SET_SUFFIX("meta.setsuffix", Type.USER, false),
USER_META_REMOVE_PREFIX("meta.removeprefix", Type.USER, false),
USER_META_REMOVE_SUFFIX("meta.removesuffix", Type.USER, false),
USER_META_ADD_TEMP_PREFIX("meta.addtempprefix", Type.USER, false),
USER_META_ADD_TEMP_SUFFIX("meta.addtempsuffix", Type.USER, false),
USER_META_SET_TEMP_PREFIX("meta.settempprefix", Type.USER, false),
USER_META_SET_TEMP_SUFFIX("meta.settempsuffix", Type.USER, false),
USER_META_REMOVE_TEMP_PREFIX("meta.removetempprefix", Type.USER, false),
USER_META_REMOVE_TEMP_SUFFIX("meta.removetempsuffix", Type.USER, false),
USER_META_CLEAR("meta.clear", Type.USER, false),
USER_EDITOR("editor", Type.USER, true),
USER_SHOW_TRACKS("showtracks", Type.USER, true),
USER_PROMOTE("promote", Type.USER, false),
USER_DEMOTE("demote", Type.USER, false),
USER_CLEAR("clear", Type.USER, false),
USER_CLONE("clone", Type.USER, false),
GROUP_INFO("info", Type.GROUP),
GROUP_PERM_INFO("permission.info", Type.GROUP),
GROUP_PERM_SET("permission.set", Type.GROUP),
GROUP_PERM_UNSET("permission.unset", Type.GROUP),
GROUP_PERM_SET_TEMP("permission.settemp", Type.GROUP),
GROUP_PERM_UNSET_TEMP("permission.unsettemp", Type.GROUP),
GROUP_PERM_CHECK("permission.check", Type.GROUP),
GROUP_PERM_CLEAR("permission.clear", Type.GROUP),
GROUP_PARENT_INFO("parent.info", Type.GROUP),
GROUP_PARENT_SET("parent.set", Type.GROUP),
GROUP_PARENT_SET_TRACK("parent.settrack", Type.GROUP),
GROUP_PARENT_ADD("parent.add", Type.GROUP),
GROUP_PARENT_REMOVE("parent.remove", Type.GROUP),
GROUP_PARENT_ADD_TEMP("parent.addtemp", Type.GROUP),
GROUP_PARENT_REMOVE_TEMP("parent.removetemp", Type.GROUP),
GROUP_PARENT_CLEAR("parent.clear", Type.GROUP),
GROUP_PARENT_CLEAR_TRACK("parent.cleartrack", Type.GROUP),
GROUP_META_INFO("meta.info", Type.GROUP),
GROUP_META_SET("meta.set", Type.GROUP),
GROUP_META_UNSET("meta.unset", Type.GROUP),
GROUP_META_SET_TEMP("meta.settemp", Type.GROUP),
GROUP_META_UNSET_TEMP("meta.unsettemp", Type.GROUP),
GROUP_META_ADD_PREFIX("meta.addprefix", Type.GROUP),
GROUP_META_ADD_SUFFIX("meta.addsuffix", Type.GROUP),
GROUP_META_SET_PREFIX("meta.setprefix", Type.GROUP),
GROUP_META_SET_SUFFIX("meta.setsuffix", Type.GROUP),
GROUP_META_REMOVE_PREFIX("meta.removeprefix", Type.GROUP),
GROUP_META_REMOVE_SUFFIX("meta.removesuffix", Type.GROUP),
GROUP_META_ADD_TEMP_PREFIX("meta.addtempprefix", Type.GROUP),
GROUP_META_ADD_TEMP_SUFFIX("meta.addtempsuffix", Type.GROUP),
GROUP_META_SET_TEMP_PREFIX("meta.settempprefix", Type.GROUP),
GROUP_META_SET_TEMP_SUFFIX("meta.settempsuffix", Type.GROUP),
GROUP_META_REMOVE_TEMP_PREFIX("meta.removetempprefix", Type.GROUP),
GROUP_META_REMOVE_TEMP_SUFFIX("meta.removetempsuffix", Type.GROUP),
GROUP_META_CLEAR("meta.clear", Type.GROUP),
GROUP_EDITOR("editor", Type.GROUP),
GROUP_LIST_MEMBERS("listmembers", Type.GROUP),
GROUP_SHOW_TRACKS("showtracks", Type.GROUP),
GROUP_SET_WEIGHT("setweight", Type.GROUP),
GROUP_SET_DISPLAY_NAME("setdisplayname", Type.GROUP),
GROUP_CLEAR("clear", Type.GROUP),
GROUP_RENAME("rename", Type.GROUP),
GROUP_CLONE("clone", Type.GROUP),
GROUP_INFO("info", Type.GROUP, true),
GROUP_PERM_INFO("permission.info", Type.GROUP, true),
GROUP_PERM_SET("permission.set", Type.GROUP, false),
GROUP_PERM_UNSET("permission.unset", Type.GROUP, false),
GROUP_PERM_SET_TEMP("permission.settemp", Type.GROUP, false),
GROUP_PERM_UNSET_TEMP("permission.unsettemp", Type.GROUP, false),
GROUP_PERM_CHECK("permission.check", Type.GROUP, true),
GROUP_PERM_CLEAR("permission.clear", Type.GROUP, false),
GROUP_PARENT_INFO("parent.info", Type.GROUP, true),
GROUP_PARENT_SET("parent.set", Type.GROUP, false),
GROUP_PARENT_SET_TRACK("parent.settrack", Type.GROUP, false),
GROUP_PARENT_ADD("parent.add", Type.GROUP, false),
GROUP_PARENT_REMOVE("parent.remove", Type.GROUP, false),
GROUP_PARENT_ADD_TEMP("parent.addtemp", Type.GROUP, false),
GROUP_PARENT_REMOVE_TEMP("parent.removetemp", Type.GROUP, false),
GROUP_PARENT_CLEAR("parent.clear", Type.GROUP, false),
GROUP_PARENT_CLEAR_TRACK("parent.cleartrack", Type.GROUP, false),
GROUP_META_INFO("meta.info", Type.GROUP, true),
GROUP_META_SET("meta.set", Type.GROUP, false),
GROUP_META_UNSET("meta.unset", Type.GROUP, false),
GROUP_META_SET_TEMP("meta.settemp", Type.GROUP, false),
GROUP_META_UNSET_TEMP("meta.unsettemp", Type.GROUP, false),
GROUP_META_ADD_PREFIX("meta.addprefix", Type.GROUP, false),
GROUP_META_ADD_SUFFIX("meta.addsuffix", Type.GROUP, false),
GROUP_META_SET_PREFIX("meta.setprefix", Type.GROUP, false),
GROUP_META_SET_SUFFIX("meta.setsuffix", Type.GROUP, false),
GROUP_META_REMOVE_PREFIX("meta.removeprefix", Type.GROUP, false),
GROUP_META_REMOVE_SUFFIX("meta.removesuffix", Type.GROUP, false),
GROUP_META_ADD_TEMP_PREFIX("meta.addtempprefix", Type.GROUP, false),
GROUP_META_ADD_TEMP_SUFFIX("meta.addtempsuffix", Type.GROUP, false),
GROUP_META_SET_TEMP_PREFIX("meta.settempprefix", Type.GROUP, false),
GROUP_META_SET_TEMP_SUFFIX("meta.settempsuffix", Type.GROUP, false),
GROUP_META_REMOVE_TEMP_PREFIX("meta.removetempprefix", Type.GROUP, false),
GROUP_META_REMOVE_TEMP_SUFFIX("meta.removetempsuffix", Type.GROUP, false),
GROUP_META_CLEAR("meta.clear", Type.GROUP, false),
GROUP_EDITOR("editor", Type.GROUP, true),
GROUP_LIST_MEMBERS("listmembers", Type.GROUP, true),
GROUP_SHOW_TRACKS("showtracks", Type.GROUP, true),
GROUP_SET_WEIGHT("setweight", Type.GROUP, false),
GROUP_SET_DISPLAY_NAME("setdisplayname", Type.GROUP, false),
GROUP_CLEAR("clear", Type.GROUP, false),
GROUP_RENAME("rename", Type.GROUP, false),
GROUP_CLONE("clone", Type.GROUP, false),
TRACK_INFO("info", Type.TRACK),
TRACK_EDITOR("editor", Type.TRACK),
TRACK_APPEND("append", Type.TRACK),
TRACK_INSERT("insert", Type.TRACK),
TRACK_REMOVE("remove", Type.TRACK),
TRACK_CLEAR("clear", Type.TRACK),
TRACK_RENAME("rename", Type.TRACK),
TRACK_CLONE("clone", Type.TRACK),
TRACK_INFO("info", Type.TRACK, true),
TRACK_EDITOR("editor", Type.TRACK, true),
TRACK_APPEND("append", Type.TRACK, false),
TRACK_INSERT("insert", Type.TRACK, false),
TRACK_REMOVE("remove", Type.TRACK, false),
TRACK_CLEAR("clear", Type.TRACK, false),
TRACK_RENAME("rename", Type.TRACK, false),
TRACK_CLONE("clone", Type.TRACK, false),
LOG_RECENT("recent", Type.LOG),
LOG_USER_HISTORY("userhistory", Type.LOG),
LOG_GROUP_HISTORY("grouphistory", Type.LOG),
LOG_TRACK_HISTORY("trackhistory", Type.LOG),
LOG_SEARCH("search", Type.LOG),
LOG_NOTIFY("notify", Type.LOG),
LOG_RECENT("recent", Type.LOG, true),
LOG_USER_HISTORY("userhistory", Type.LOG, true),
LOG_GROUP_HISTORY("grouphistory", Type.LOG, true),
LOG_TRACK_HISTORY("trackhistory", Type.LOG, true),
LOG_SEARCH("search", Type.LOG, true),
LOG_NOTIFY("notify", Type.LOG, true),
SPONGE_PERMISSION_INFO("permission.info", Type.SPONGE),
SPONGE_PERMISSION_SET("permission.set", Type.SPONGE),
SPONGE_PERMISSION_CLEAR("permission.clear", Type.SPONGE),
SPONGE_PARENT_INFO("parent.info", Type.SPONGE),
SPONGE_PARENT_ADD("parent.add", Type.SPONGE),
SPONGE_PARENT_REMOVE("parent.remove", Type.SPONGE),
SPONGE_PARENT_CLEAR("parent.clear", Type.SPONGE),
SPONGE_OPTION_INFO("option.info", Type.SPONGE),
SPONGE_OPTION_SET("option.set", Type.SPONGE),
SPONGE_OPTION_UNSET("option.unset", Type.SPONGE),
SPONGE_OPTION_CLEAR("option.clear", Type.SPONGE);
SPONGE_PERMISSION_INFO("permission.info", Type.SPONGE, true),
SPONGE_PERMISSION_SET("permission.set", Type.SPONGE, false),
SPONGE_PERMISSION_CLEAR("permission.clear", Type.SPONGE, false),
SPONGE_PARENT_INFO("parent.info", Type.SPONGE, true),
SPONGE_PARENT_ADD("parent.add", Type.SPONGE, false),
SPONGE_PARENT_REMOVE("parent.remove", Type.SPONGE, false),
SPONGE_PARENT_CLEAR("parent.clear", Type.SPONGE, false),
SPONGE_OPTION_INFO("option.info", Type.SPONGE, true),
SPONGE_OPTION_SET("option.set", Type.SPONGE, false),
SPONGE_OPTION_UNSET("option.unset", Type.SPONGE, false),
SPONGE_OPTION_CLEAR("option.clear", Type.SPONGE, false);
public static final String ROOT = "luckperms.";
@@ -176,9 +176,11 @@ public enum CommandPermission {
private final String permission;
private final Type type;
private final boolean readOnly;
CommandPermission(String node, Type type) {
CommandPermission(String node, Type type, boolean readOnly) {
this.type = type;
this.readOnly = readOnly;
if (type == Type.NONE) {
this.node = node;
@@ -205,6 +207,10 @@ public enum CommandPermission {
return this.type;
}
public boolean isReadOnly() {
return this.readOnly;
}
public enum Type {
NONE(null),
@@ -113,7 +113,7 @@ public class ExportCommand extends SingleCommand {
}
// Run the exporter in its own thread.
plugin.getBootstrap().getScheduler().executeAsync(() -> {
plugin.getBootstrap().getScheduler().async(() -> {
try {
exporter.run();
} finally {
@@ -133,7 +133,7 @@ public class ImportCommand extends SingleCommand {
Importer importer = new Importer(plugin, sender, data, !args.contains("--replace"));
// Run the importer in its own thread.
plugin.getBootstrap().getScheduler().executeAsync(() -> {
plugin.getBootstrap().getScheduler().async(() -> {
try {
importer.run();
} finally {
@@ -94,7 +94,7 @@ public class VerboseCommand extends SingleCommand {
String commandWithSlash = String.join(" ", args.subList(2, args.size()));
String command = commandWithSlash.charAt(0) == '/' ? commandWithSlash.substring(1) : commandWithSlash;
plugin.getBootstrap().getScheduler().sync().execute(() -> {
plugin.getBootstrap().getScheduler().sync(executor, () -> {
Message.VERBOSE_ON_COMMAND.send(sender, executor.getName(), command);
verboseHandler.registerListener(sender, VerboseFilter.acceptAll(), true);
@@ -28,14 +28,14 @@ package me.lucko.luckperms.common.config;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import me.lucko.luckperms.common.cacheddata.metastack.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.cacheddata.metastack.StandardStackElements;
import me.lucko.luckperms.common.cacheddata.type.SimpleMetaValueSelector;
import me.lucko.luckperms.common.config.generic.KeyedConfiguration;
import me.lucko.luckperms.common.config.generic.key.ConfigKey;
import me.lucko.luckperms.common.config.generic.key.SimpleConfigKey;
import me.lucko.luckperms.common.context.calculator.WorldNameRewriter;
import me.lucko.luckperms.common.graph.TraversalAlgorithm;
import me.lucko.luckperms.common.metastacking.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.metastacking.StandardStackElements;
import me.lucko.luckperms.common.model.PrimaryGroupHolder;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.query.QueryOptionsBuilderImpl;
@@ -246,6 +246,26 @@ public final class ConfigKeys {
*/
public static final ConfigKey<Boolean> REQUIRE_SENDER_GROUP_MEMBERSHIP_TO_MODIFY = booleanKey("require-sender-group-membership-to-modify", false);
/**
* If the plugin is in read-only mode for players
*/
public static final ConfigKey<Boolean> READ_ONLY_MODE_PLAYERS = booleanKey("commands-read-only-mode.players", false);
/**
* If the plugin is in read-only mode for the server console
*/
public static final ConfigKey<Boolean> READ_ONLY_MODE_CONSOLE = booleanKey("commands-read-only-mode.console", false);
/**
* If LuckPerms commands are disabled for players
*/
public static final ConfigKey<Boolean> DISABLE_LUCKPERMS_COMMANDS_PLAYERS = booleanKey("disable-luckperms-commands.players", false);
/**
* If LuckPerms commands are disabled for the server console
*/
public static final ConfigKey<Boolean> DISABLE_LUCKPERMS_COMMANDS_CONSOLE = booleanKey("disable-luckperms-commands.console", false);
/**
* If wildcards are being applied
*/
@@ -370,16 +390,16 @@ public final class ConfigKeys {
/**
* Creates a new prefix MetaStack element based upon the configured values.
*/
public static final ConfigKey<MetaStackDefinition> PREFIX_FORMATTING_OPTIONS = key(l -> {
List<String> format = l.getStringList("meta-formatting.prefix.format", new ArrayList<>());
public static final ConfigKey<MetaStackDefinition> PREFIX_FORMATTING_OPTIONS = key(c -> {
List<String> format = c.getStringList("meta-formatting.prefix.format", new ArrayList<>());
if (format.isEmpty()) {
format.add("highest");
}
String startSpacer = l.getString("meta-formatting.prefix.start-spacer", "");
String middleSpacer = l.getString("meta-formatting.prefix.middle-spacer", " ");
String endSpacer = l.getString("meta-formatting.prefix.end-spacer", "");
String startSpacer = c.getString("meta-formatting.prefix.start-spacer", "");
String middleSpacer = c.getString("meta-formatting.prefix.middle-spacer", " ");
String endSpacer = c.getString("meta-formatting.prefix.end-spacer", "");
DuplicateRemovalFunction duplicateRemovalFunction;
switch (l.getString("meta-formatting.prefix.duplicates", "").toLowerCase(Locale.ROOT)) {
switch (c.getString("meta-formatting.prefix.duplicates", "").toLowerCase(Locale.ROOT)) {
case "first-only":
duplicateRemovalFunction = DuplicateRemovalFunction.FIRST_ONLY;
break;
@@ -391,22 +411,22 @@ public final class ConfigKeys {
break;
}
return new SimpleMetaStackDefinition(StandardStackElements.parseList(l.getPlugin(), format), duplicateRemovalFunction, startSpacer, middleSpacer, endSpacer);
return new SimpleMetaStackDefinition(StandardStackElements.parseList(c.getPlugin(), format), duplicateRemovalFunction, startSpacer, middleSpacer, endSpacer);
});
/**
* Creates a new suffix MetaStack element based upon the configured values.
*/
public static final ConfigKey<MetaStackDefinition> SUFFIX_FORMATTING_OPTIONS = key(l -> {
List<String> format = l.getStringList("meta-formatting.suffix.format", new ArrayList<>());
public static final ConfigKey<MetaStackDefinition> SUFFIX_FORMATTING_OPTIONS = key(c -> {
List<String> format = c.getStringList("meta-formatting.suffix.format", new ArrayList<>());
if (format.isEmpty()) {
format.add("highest");
}
String startSpacer = l.getString("meta-formatting.suffix.start-spacer", "");
String middleSpacer = l.getString("meta-formatting.suffix.middle-spacer", " ");
String endSpacer = l.getString("meta-formatting.suffix.end-spacer", "");
String startSpacer = c.getString("meta-formatting.suffix.start-spacer", "");
String middleSpacer = c.getString("meta-formatting.suffix.middle-spacer", " ");
String endSpacer = c.getString("meta-formatting.suffix.end-spacer", "");
DuplicateRemovalFunction duplicateRemovalFunction;
switch (l.getString("meta-formatting.suffix.duplicates", "").toLowerCase(Locale.ROOT)) {
switch (c.getString("meta-formatting.suffix.duplicates", "").toLowerCase(Locale.ROOT)) {
case "first-only":
duplicateRemovalFunction = DuplicateRemovalFunction.FIRST_ONLY;
break;
@@ -418,7 +438,7 @@ public final class ConfigKeys {
break;
}
return new SimpleMetaStackDefinition(StandardStackElements.parseList(l.getPlugin(), format), duplicateRemovalFunction, startSpacer, middleSpacer, endSpacer);
return new SimpleMetaStackDefinition(StandardStackElements.parseList(c.getPlugin(), format), duplicateRemovalFunction, startSpacer, middleSpacer, endSpacer);
});
/**
@@ -585,6 +605,16 @@ public final class ConfigKeys {
return c.getString("data.mongodb-connection-uri", c.getString("data.mongodb_connection_URI", ""));
}));
/**
* The REST storage URL
*/
public static final ConfigKey<String> REST_STORAGE_URL = notReloadable(stringKey("data.rest-url", "http://localhost:8080/"));
/**
* The REST storage auth key
*/
public static final ConfigKey<String> REST_STORAGE_AUTH_KEY = notReloadable(stringKey("data.rest-auth-key", ""));
/**
* The name of the storage method being used
*/
@@ -0,0 +1,90 @@
/*
* 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.common.config.generic.adapter;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Locale;
public class FileSecretConfigAdapter extends StringBasedConfigurationAdapter {
private static final String PREFIX = "luckperms_";
private final LuckPermsPlugin plugin;
private final Path directory;
public FileSecretConfigAdapter(LuckPermsPlugin plugin, String directory) {
this.plugin = plugin;
this.directory = directory == null ? null : Paths.get(directory);
}
public FileSecretConfigAdapter(LuckPermsPlugin plugin) {
this(plugin, System.getenv("LUCKPERMS_FILE_SECRET_DIRECTORY"));
}
@Override
protected @Nullable String resolveValue(String path) {
if (this.directory == null) {
return null; // not configured
}
// e.g.
// 'server' -> luckperms_server
// 'data.table_prefix' -> luckperms_data_table_prefix
String key = PREFIX + path.toLowerCase(Locale.ROOT)
.replace('-', '_')
.replace('.', '_');
Path resolvedFile = this.directory.resolve(key);
String value;
try {
value = new String(Files.readAllBytes(resolvedFile), StandardCharsets.UTF_8);
} catch (IOException e) {
return null;
}
String printableValue = ConfigKeys.shouldCensorValue(path) ? "*****" : value;
this.plugin.getLogger().info(String.format("Resolved configuration value from file secret: %s = %s", key, printableValue));
return value;
}
@Override
public LuckPermsPlugin getPlugin() {
return this.plugin;
}
@Override
public void reload() {
// no-op
}
}
@@ -46,9 +46,10 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
/**
* Base implementation of {@link ContextManager} which caches content lookups.
* Base implementation of {@link ContextManager}.
*
* @param <S> the calculator type
* @param <S> the subject type
* @param <P> the player type
*/
public abstract class ContextManager<S, P extends S> {
@@ -77,14 +78,10 @@ public abstract class ContextManager<S, P extends S> {
public abstract UUID getUniqueId(P player);
public abstract QueryOptionsSupplier getCacheFor(S subject);
public QueryOptions getQueryOptions(S subject) {
return getCacheFor(subject).getQueryOptions();
}
public abstract QueryOptions getQueryOptions(S subject);
public ImmutableContextSet getContext(S subject) {
return getCacheFor(subject).getContextSet();
return getQueryOptions(subject).context();
}
public QueryOptions getStaticQueryOptions() {
@@ -95,11 +92,13 @@ public abstract class ContextManager<S, P extends S> {
return getStaticQueryOptions().context();
}
public QueryOptions formQueryOptions(ImmutableContextSet contextSet) {
return this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder().context(contextSet).build();
public void customizeStaticQueryOptions(QueryOptions.Builder builder) {
// overridden
}
public abstract QueryOptions formQueryOptions(S subject, ImmutableContextSet contextSet);
public void customizeQueryOptions(S subject, QueryOptions.Builder builder) {
// overridden
}
public void signalContextUpdate(S subject) {
if (subject == null) {
@@ -113,7 +112,7 @@ public abstract class ContextManager<S, P extends S> {
this.plugin.getEventDispatcher().dispatchContextUpdate(subject);
}
protected abstract void invalidateCache(S subject);
public abstract void invalidateCache(S subject);
public void registerCalculator(ContextCalculator<? super S> calculator) {
String calculatorClass = calculator.getClass().getName();
@@ -157,7 +156,9 @@ public abstract class ContextManager<S, P extends S> {
callContextCalculator(calculator, subject, consumer);
}
return formQueryOptions(subject, accumulator.build());
QueryOptions.Builder builder = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder().context(accumulator.build());
customizeQueryOptions(subject, builder);
return builder.build();
}
private QueryOptions calculateStatic() {
@@ -168,7 +169,9 @@ public abstract class ContextManager<S, P extends S> {
callStaticContextCalculator(calculator, consumer);
}
return formQueryOptions(accumulator.build());
QueryOptions.Builder builder = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder().context(accumulator.build());
customizeStaticQueryOptions(builder);
return builder.build();
}
public ImmutableContextSet getPotentialContexts() {
@@ -0,0 +1,73 @@
/*
* 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.common.context.manager;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.query.QueryOptions;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.concurrent.TimeUnit;
/**
* Implementation of {@link ContextManager} which utilises 'detached' supplier caches stored alongside the subject instances.
*/
public abstract class DetachedContextManager<S, P extends S> extends ContextManager<S, P> {
private final LoadingCache<S, QueryOptions> fallbackContextsCache = CaffeineFactory.newBuilder()
.expireAfterWrite(50, TimeUnit.MILLISECONDS)
.build(this::calculate);
protected DetachedContextManager(LuckPermsPlugin plugin, Class<S> subjectClass, Class<P> playerClass) {
super(plugin, subjectClass, playerClass);
}
@Override
public QueryOptions getQueryOptions(S subject) {
QueryOptionsSupplier supplier = getQueryOptionsSupplier(subject);
if (supplier != null) {
return supplier.getQueryOptions();
}
return this.fallbackContextsCache.get(subject);
}
@Override
public void invalidateCache(S subject) {
QueryOptionsSupplier queryOptionsSupplier = getQueryOptionsSupplier(subject);
if (queryOptionsSupplier != null) {
queryOptionsSupplier.invalidateCache();
}
this.fallbackContextsCache.invalidate(subject);
}
public QueryOptionsSupplier createQueryOptionsSupplier(S subject) {
return new QueryOptionsCache<>(subject, this);
}
public abstract @Nullable QueryOptionsSupplier getQueryOptionsSupplier(S subject);
}
@@ -37,11 +37,11 @@ import java.util.concurrent.TimeUnit;
*
* @param <T> the player type
*/
public final class QueryOptionsCache<T> extends ExpiringCache<QueryOptions> implements QueryOptionsSupplier {
final class QueryOptionsCache<T> extends ExpiringCache<QueryOptions> implements QueryOptionsSupplier {
private final T subject;
private final ContextManager<T, ?> contextManager;
public QueryOptionsCache(T subject, ContextManager<T, ?> contextManager) {
QueryOptionsCache(T subject, ContextManager<T, ?> contextManager) {
super(50L, TimeUnit.MILLISECONDS); // expire roughly every tick
this.subject = subject;
this.contextManager = contextManager;
@@ -61,4 +61,9 @@ public final class QueryOptionsCache<T> extends ExpiringCache<QueryOptions> impl
public ImmutableContextSet getContextSet() {
return get().context();
}
@Override
public void invalidateCache() {
invalidate();
}
}
@@ -39,4 +39,6 @@ public interface QueryOptionsSupplier {
return getQueryOptions().context();
}
void invalidateCache();
}
@@ -0,0 +1,54 @@
/*
* 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.common.context.manager;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.query.QueryOptions;
import java.util.concurrent.TimeUnit;
public abstract class SimpleContextManager<S, P extends S> extends ContextManager<S, P> {
private final LoadingCache<S, QueryOptions> contextsCache = CaffeineFactory.newBuilder()
.expireAfterWrite(50, TimeUnit.MILLISECONDS)
.build(this::calculate);
protected SimpleContextManager(LuckPermsPlugin plugin, Class<S> subjectClass, Class<P> playerClass) {
super(plugin, subjectClass, playerClass);
}
@Override
public QueryOptions getQueryOptions(S subject) {
return this.contextsCache.get(subject);
}
@Override
public void invalidateCache(S subject) {
this.contextsCache.invalidate(subject);
}
}
@@ -44,14 +44,14 @@ public enum Dependency {
ASM(
"org.ow2.asm",
"asm",
"9.1",
"zaTeRV+rSP8Ly3xItGOUR9TehZp6/DCglKmG8JNr66I="
"9.8",
"h26raoPa7K1cpn65/KuwY8l7WuuM8fynqYns3hdSIFE="
),
ASM_COMMONS(
"org.ow2.asm",
"asm-commons",
"9.1",
"r8sm3B/BLAxKma2mcJCN2C4Y38SIyvXuklRplrRwwAw="
"9.8",
"MwGhwctMWfzFKSZI2sHXxa7UwPBn376IhzuM3+d0BPQ="
),
JAR_RELOCATOR(
"me.lucko",
@@ -62,28 +62,28 @@ public enum Dependency {
ADVENTURE(
"me{}lucko",
"adventure-api",
"4.13.0",
"gcmYlY1KDrzbnE9nB8rA44oBZKPSMvv9kNRefoEuud0=",
"4.21.1",
"kQJlZ0gUxdTRRkskT43qiy2kpt9s654LvB0nqoCP6YE=",
Relocation.of("adventure", "net{}kyori{}adventure")
),
ADVENTURE_PLATFORM(
"me{}lucko",
"adventure-platform-api",
"4.13.0",
"4.21.1",
"Kk8IkEMVa9ITBfC3yocpcXQiZ9CwN9VxeWjKUD8I0n0=",
Relocation.of("adventure", "net{}kyori{}adventure")
),
ADVENTURE_PLATFORM_BUKKIT(
"me{}lucko",
"adventure-platform-bukkit",
"4.13.3",
"ZXw7pf+a6tjGdB2WO1rB/KQ+ETLAH8kQFSu3El7jopY=",
"4.21.1",
"NffwBnfT/Mc6VpsmTcaXPvckv9T4vPJD83adt0C8vao=",
Relocation.of("adventure", "net{}kyori{}adventure")
),
ADVENTURE_PLATFORM_BUNGEECORD(
"me{}lucko",
"adventure-platform-bungeecord",
"4.13.3",
"4.21.1",
"7hnndD6dO6LZoRbtOBdn6OFK0y/T2PqNLHxCg6zaQlo=",
Relocation.of("adventure", "net{}kyori{}adventure")
),
@@ -97,15 +97,15 @@ public enum Dependency {
CAFFEINE(
"com{}github{}ben-manes{}caffeine",
"caffeine",
"2.9.0",
"VFMotEO3XLbTHfRKfL3m36GlN72E/dzRFH9B5BJiX2o=",
"3.2.0",
"7EEd/fDAPyUhhkjOiYYWMLcWgOWFippyeOusjlXKs9c=",
Relocation.of("caffeine", "com{}github{}benmanes{}caffeine")
),
OKIO(
"com{}squareup{}" + RelocationHelper.OKIO_STRING,
RelocationHelper.OKIO_STRING,
"1.17.5",
"Gaf/SNhtPPRJf38lD78pX0MME6Uo3Vt7ID+CGAK4hq0=",
"1.17.6",
"joiwVVI8yAYT37hE1Zh0DhCtpi9L2YMEzdFAxYVMw7Y=",
Relocation.of(RelocationHelper.OKIO_STRING, RelocationHelper.OKIO_STRING)
),
OKHTTP(
@@ -119,8 +119,8 @@ public enum Dependency {
BYTEBUDDY(
"net{}bytebuddy",
"byte-buddy",
"1.10.22",
"+TGtxDkxd6+lJExHJXqDlV4n/gR8QJN4xu2gkPsHSoQ=",
"1.15.11",
"+giZiq4ee9roO94HEsUOhETXHA4MGWuyJHrejUrQ65A=",
Relocation.of("bytebuddy", "net{}bytebuddy")
),
COMMODORE(
@@ -140,22 +140,22 @@ public enum Dependency {
MARIADB_DRIVER(
"org{}mariadb{}jdbc",
"mariadb-java-client",
"3.1.3",
"ESl+5lYkJsScgTh8hgFTy8ExxMPQQkktT20tl6s6HKU=",
"3.5.2",
"8vPDwaO9rKad0dThzYrtB1JC/HKuQUY924LjZ7OI9q0=",
Relocation.of("mariadb", "org{}mariadb{}jdbc")
),
MYSQL_DRIVER(
"mysql",
"mysql-connector-java",
"8.0.23",
"/31bQCr9OcEnh0cVBaM6MEEDsjjsG3pE6JNtMynadTU=",
"com{}mysql",
"mysql-connector-j",
"9.3.0",
"bI5mkrUhN22JvFYYwWzer4xhhUMp9PolZ37Qh3bFu3Y=",
Relocation.of("mysql", "com{}mysql")
),
POSTGRESQL_DRIVER(
"org{}postgresql",
"postgresql",
"42.6.0",
"uBfGekDJQkn9WdTmhuMyftDT0/rkJrINoPHnVlLPxGE=",
"42.7.6",
"8qHMA1LdXlxvZdut/ye+4Awy5DLGrQMNB0R/ilmDxCo=",
Relocation.of("postgresql", "org{}postgresql")
),
H2_DRIVER_LEGACY(
@@ -179,124 +179,124 @@ public enum Dependency {
SQLITE_DRIVER(
"org.xerial",
"sqlite-jdbc",
"3.28.0",
"k3hOVtv1RiXgbJks+D9w6cG93Vxq0dPwEwjIex2WG2A="
"3.49.1.0",
"XIYJ0so0HeuMb3F3iXS1ukmVx9MtfHyJ2TkqPnLDkpE="
// we don't apply relocations to sqlite - it gets loaded via
// an isolated classloader
),
HIKARI(
"com{}zaxxer",
"HikariCP",
"4.0.3",
"fAJK7/HBBjV210RTUT+d5kR9jmJNF/jifzCi6XaIxsk=",
"6.3.0",
"B8Y0QFmvMKE1FEIJx8i9ZmuIIxJEIuyFmGTSCdSrfKE=",
Relocation.of("hikari", "com{}zaxxer{}hikari")
),
SLF4J_SIMPLE(
"org.slf4j",
"slf4j-simple",
"1.7.30",
"i5J5y/9rn4hZTvrjzwIDm2mVAw7sAj7UOSh0jEFnD+4="
"1.7.36",
"Lzm+2UPWJN+o9BAtBXEoOhCHC2qjbxl6ilBvFHAQwQ8="
),
SLF4J_API(
"org.slf4j",
"slf4j-api",
"1.7.30",
"zboHlk0btAoHYUhcax6ML4/Z6x0ZxTkorA1/lRAQXFc="
"1.7.36",
"0+9XXj5JeWeNwBvx3M5RAhSTtNEft/G+itmCh3wWocA="
),
MONGODB_DRIVER_CORE(
"org.mongodb",
"mongodb-driver-core",
"4.5.0",
"awqoW0ImUcrCTA2d1rDCjDLEjLMCrOjKWIcC7E+zLGA=",
"5.5.0",
"69tQuKep52lbYvX2YM+J6GGlYkNySXkMBDuk6BqtsJE=",
Relocation.of("mongodb", "com{}mongodb"),
Relocation.of("bson", "org{}bson")
),
MONGODB_DRIVER_LEGACY(
"org.mongodb",
"mongodb-driver-legacy",
"4.5.0",
"77KZGIr3KZmzBpN69rGOLXmnlJIBCXRl/U4gEIdlFhY=",
"5.5.0",
"yo/0wEdLw0/Md1xqgEd/iqiKV+t0AqAcdnS1TNAaygM=",
Relocation.of("mongodb", "com{}mongodb"),
Relocation.of("bson", "org{}bson")
),
MONGODB_DRIVER_SYNC(
"org.mongodb",
"mongodb-driver-sync",
"4.5.0",
"q9XDSGJjlo/Ek6jHoCbqWnaK/dghB8y9aDM0hCLiSvk=",
"5.5.0",
"nFECiREXgMc5Ikamvmnzaxumhz75NKG+ajIhAW/ioPI=",
Relocation.of("mongodb", "com{}mongodb"),
Relocation.of("bson", "org{}bson")
),
MONGODB_DRIVER_BSON(
"org.mongodb",
"bson",
"4.5.0",
"6CFyEzxbdeiBEXdDBmcgqWs5dvicgFkBLU3MlQUIqRA=",
"5.5.0",
"hQx5w0v/DuQvASpnGXkLuWxkhXhewDTTAmrifPWbBJQ=",
Relocation.of("mongodb", "com{}mongodb"),
Relocation.of("bson", "org{}bson")
),
JEDIS(
"redis.clients",
"jedis",
"4.4.3",
"wwwoCDPCywcfoNwpvwP95kXYusXSTtXhuVrB31sxE0k=",
"5.2.0",
"3U+9osED8xmrSVrbK8GQYTmEB0bP1MZrJ3ENGvmDgtQ=",
Relocation.of("jedis", "redis{}clients{}jedis"),
Relocation.of("commonspool2", "org{}apache{}commons{}pool2")
),
NATS(
"io.nats",
"jnats",
"2.16.4",
"/WZgFi9iJToRGIiGoShlwE5aHwstOuNGZyr4UaBRilM=",
"2.21.1",
"QHUHUCnCCy/oRSwoqhy0245SrvD4lwCfc+ZVmemHXLg=",
Relocation.of("nats", "io{}nats{}client")
),
RABBITMQ(
"com{}rabbitmq",
"amqp-client",
"5.12.0",
"CxliwVWAnPKi5BwxCu1S1SGzx5fbhTk5JCKdBS27P2c=",
"5.25.0",
"WqlvAFCEE56xB32UtV3GQo7KfafizFPqtEp3M5H4qo8=",
Relocation.of("rabbitmq", "com{}rabbitmq")
),
COMMONS_POOL_2(
"org.apache.commons",
"commons-pool2",
"2.9.0",
"vJGbQmv6+zHsxF1mUqnxN0YkZdhJ+zhz142Qw/jTWwE=",
"2.12.1",
"UnPIvIwNyiIRF1wNJ++9cijvrplomqwAGo4e+Ohy6e8=",
Relocation.of("commonspool2", "org{}apache{}commons{}pool2")
),
CONFIGURATE_CORE(
"org{}spongepowered",
"configurate-core",
"3.7.2",
"XF2LzWLkSV0wyQRDt33I+gDlf3t2WzxH1h8JCZZgPp4=",
"3.7.3",
"06R3WDViB84WtSkHTudV8TSPxF1eQyCyfab8L7Pvo2M=",
Relocation.of("configurate", "ninja{}leaping{}configurate")
),
CONFIGURATE_GSON(
"org{}spongepowered",
"configurate-gson",
"3.7.2",
"9S/mp3Ig9De7NNd6+2kX+L4R90bHnAosSNVbFjrl7sM=",
"3.7.3",
"QM+bGrgrzfwT9nvIvTHtR2TUEpun+RwlXIO/a9BU0Mc=",
Relocation.of("configurate", "ninja{}leaping{}configurate")
),
CONFIGURATE_YAML(
"org{}spongepowered",
"configurate-yaml",
"3.7.2",
"OBfYn4nSMGZfVf2DoZhZq+G9TF1mODX/C5OOz/mkPmc=",
"3.7.3",
"a04vRkLhigIqiG/gdVvK7c1YiBQJ7k1q/kBNsS9OVDs=",
Relocation.of("configurate", "ninja{}leaping{}configurate")
),
SNAKEYAML(
"org.yaml",
"snakeyaml",
"1.28",
"NURqFCFDXUXkxqwN47U3hSfVzCRGwHGD4kRHcwzh//o=",
"1.33",
"Ef9Fl4jwoteB9WpKhtfmkgLOus0Cc9UmnErp8C8/2PA=",
Relocation.of("yaml", "org{}yaml{}snakeyaml")
),
CONFIGURATE_HOCON(
"org{}spongepowered",
"configurate-hocon",
"3.7.2",
"GOORZlK1FKLzdIm7dKyyXtBdvk7Z89HARAd2H6NiWSY=",
"3.7.3",
"e/UDpbIrWdJNB6yMFXtrOnnNn3nptmSv/J8n46uQPNs=",
Relocation.of("configurate", "ninja{}leaping{}configurate"),
Relocation.of("hocon", "com{}typesafe{}config")
),
@@ -138,7 +138,7 @@ public enum DependencyRepository {
}
/**
* Downloads the the {@code dependency} to the {@code file}, ensuring the
* Downloads the {@code dependency} to the {@code file}, ensuring the
* downloaded bytes match the checksum.
*
* @param dependency the dependency to download
@@ -140,7 +140,7 @@ public final class EventDispatcher {
}
// async: generate an event class and post it
this.eventBus.getPlugin().getBootstrap().getScheduler().executeAsync(() -> post(eventClass, params));
this.eventBus.getPlugin().getBootstrap().getScheduler().async(() -> post(eventClass, params));
}
private void postSync(Class<? extends LuckPermsEvent> eventClass, Object... params) {
@@ -27,7 +27,6 @@ package me.lucko.luckperms.common.extension;
import com.google.gson.JsonObject;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.plugin.classpath.URLClassLoaderAccess;
import me.lucko.luckperms.common.util.gson.GsonProvider;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.extension.Extension;
@@ -40,7 +39,6 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
@@ -115,7 +113,6 @@ public class SimpleExtensionManager implements ExtensionManager, AutoCloseable {
}
String className;
boolean useParentClassLoader = false;
try (JarFile jar = new JarFile(path.toFile())) {
JarEntry extensionJarEntry = jar.getJarEntry("extension.json");
if (extensionJarEntry == null) {
@@ -128,8 +125,14 @@ public class SimpleExtensionManager implements ExtensionManager, AutoCloseable {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
JsonObject parsed = GsonProvider.parser().parse(reader).getAsJsonObject();
className = parsed.get("class").getAsString();
// check for deprecated flags
if (parsed.has("useParentClassLoader")) {
useParentClassLoader = parsed.get("useParentClassLoader").getAsBoolean();
boolean useParentClassLoader = parsed.get("useParentClassLoader").getAsBoolean();
if (useParentClassLoader) {
this.plugin.getLogger().warn("Extension '" + className + "' specifies the 'useParentClassLoader' extension flag, which has been deprecated/removed. " +
"The extension will be loaded using the classloader of the LuckPerms plugin, and not the classloader of the platform, which may break functionality.");
}
}
}
}
@@ -139,15 +142,7 @@ public class SimpleExtensionManager implements ExtensionManager, AutoCloseable {
throw new IllegalArgumentException("class is null");
}
if (useParentClassLoader && isJarInJar()) {
try {
addJarToParentClasspath(path);
} catch (Throwable e) {
throw new RuntimeException("Exception whilst classloading extension", e);
}
} else {
this.plugin.getBootstrap().getClassPathAppender().addJarToClasspath(path);
}
this.plugin.getBootstrap().getClassPathAppender().addJarToClasspath(path);
Class<? extends Extension> extensionClass;
try {
@@ -191,21 +186,6 @@ public class SimpleExtensionManager implements ExtensionManager, AutoCloseable {
return this.extensions.stream().map(e -> e.instance).collect(Collectors.toSet());
}
private static boolean isJarInJar() {
String thisClassLoaderName = SimpleExtensionManager.class.getClassLoader().getClass().getName();
return thisClassLoaderName.equals("me.lucko.luckperms.common.loader.JarInJarClassLoader");
}
@Deprecated
private static void addJarToParentClasspath(Path path) throws Exception {
ClassLoader parentClassLoader = SimpleExtensionManager.class.getClassLoader().getParent();
if (!(parentClassLoader instanceof URLClassLoader)) {
throw new RuntimeException("useParentClassLoader is true but parent is not a URLClassLoader");
}
URLClassLoaderAccess.create(((URLClassLoader) parentClassLoader)).addURL(path.toUri().toURL());
}
private static final class LoadedExtension {
private final Extension instance;
private final Path path;
@@ -179,6 +179,13 @@ public interface Message {
.color(GRAY)
);
Args0 COMMANDS_DISABLED = () -> prefixed(translatable()
// "&3LuckPerms commands are disabled."
.key("luckperms.commandsystem.commands-disabled")
.color(DARK_AQUA)
.append(FULL_STOP)
);
Args2<String, String> FIRST_TIME_SETUP = (label, username) -> joinNewline(
// "&3It seems that no permissions have been setup yet!"
// "&3Before you can use any of the LuckPerms commands in-game, you need to use the console to give yourself access."
@@ -88,7 +88,7 @@ public class TranslationRepository {
return; // skip
}
this.plugin.getBootstrap().getScheduler().executeAsync(() -> {
this.plugin.getBootstrap().getScheduler().async(() -> {
// cleanup old translation files
clearDirectory(this.plugin.getTranslationManager().getTranslationsDirectory(), Files::isRegularFile);
@@ -54,13 +54,14 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class LuckPermsMessagingService extends AsyncInterface implements InternalMessagingService, IncomingMessageConsumer {
private final LuckPermsPlugin plugin;
private final ExpiringSet<UUID> receivedMessages;
private final Set<UUID> receivedMessages;
private final PushUpdateBuffer updateBuffer;
private final MessengerProvider messengerProvider;
@@ -74,7 +75,7 @@ public class LuckPermsMessagingService extends AsyncInterface implements Interna
this.messenger = messengerProvider.obtain(this);
Objects.requireNonNull(this.messenger, "messenger");
this.receivedMessages = new ExpiringSet<>(5, TimeUnit.MINUTES);
this.receivedMessages = ExpiringSet.newExpiringSet(5, TimeUnit.MINUTES);
this.updateBuffer = new PushUpdateBuffer(plugin);
}
@@ -113,7 +113,7 @@ public class PostgresMessenger implements Messenger {
try {
this.listener = new NotificationListener();
this.plugin.getBootstrap().getScheduler().executeAsync(() -> {
this.plugin.getBootstrap().getScheduler().async(() -> {
this.listener.listenAndBind();
if (!firstStartup) {
this.plugin.getLogger().info("Postgres listen/notify connection re-established");
@@ -73,7 +73,7 @@ public class RedisMessenger implements Messenger {
private void init(UnifiedJedis jedis) {
this.jedis = jedis;
this.sub = new Subscription(this);
this.plugin.getBootstrap().getScheduler().executeAsync(this.sub);
this.plugin.getBootstrap().getScheduler().async(this.sub);
}
private static JedisClientConfig jedisConfig(String username, String password, boolean ssl) {
@@ -86,6 +86,6 @@ public class SqlMessenger extends AbstractSqlMessenger {
@Override
protected String getTableName() {
return this.sqlStorage.getStatementProcessor().apply("{prefix}messenger");
return this.sqlStorage.getStatementProcessor().process("{prefix}messenger");
}
}
@@ -30,6 +30,7 @@ import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.util.ExpiringSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@@ -41,16 +42,16 @@ public class UserHousekeeper implements Runnable {
private final UserManager<?> userManager;
// contains the uuids of users who have recently logged in / out
private final ExpiringSet<UUID> recentlyUsed;
private final Set<UUID> recentlyUsed;
// contains the uuids of users who have recently been retrieved from the API
private final ExpiringSet<UUID> recentlyUsedApi;
private final Set<UUID> recentlyUsedApi;
public UserHousekeeper(LuckPermsPlugin plugin, UserManager<?> userManager, TimeoutSettings timeoutSettings) {
this.plugin = plugin;
this.userManager = userManager;
this.recentlyUsed = new ExpiringSet<>(timeoutSettings.duration, timeoutSettings.unit);
this.recentlyUsedApi = new ExpiringSet<>(5, TimeUnit.MINUTES);
this.recentlyUsed = ExpiringSet.newExpiringSet(timeoutSettings.duration, timeoutSettings.unit);
this.recentlyUsedApi = ExpiringSet.newExpiringSet(5, TimeUnit.MINUTES);
}
// called when a player attempts a connection or logs out
@@ -72,7 +72,7 @@ public final class StandardNodeMatchers {
return new TypeEquals<>(type);
}
private static class Generic extends ConstraintNodeMatcher<Node> {
public static class Generic extends ConstraintNodeMatcher<Node> {
Generic(Comparison comparison, String value) {
super(comparison, value);
}
@@ -83,7 +83,7 @@ public final class StandardNodeMatchers {
}
}
private static final class NodeEquals<T extends Node> extends ConstraintNodeMatcher<T> {
public static final class NodeEquals<T extends Node> extends ConstraintNodeMatcher<T> {
private final T node;
private final NodeEqualityPredicate equalityPredicate;
@@ -93,6 +93,14 @@ public final class StandardNodeMatchers {
this.equalityPredicate = equalityPredicate;
}
public T getNode() {
return this.node;
}
public NodeEqualityPredicate getEqualityPredicate() {
return this.equalityPredicate;
}
@SuppressWarnings("unchecked")
@Override
public @Nullable T filterConstraintMatch(@NonNull Node node) {
@@ -103,9 +111,16 @@ public final class StandardNodeMatchers {
}
}
private static final class MetaKeyEquals extends ConstraintNodeMatcher<MetaNode> {
public static final class MetaKeyEquals extends ConstraintNodeMatcher<MetaNode> {
private final String metaKey;
MetaKeyEquals(String metaKey) {
super(Comparison.SIMILAR, Meta.key(metaKey, Comparison.WILDCARD));
this.metaKey = metaKey;
}
public String getMetaKey() {
return this.metaKey;
}
@Override
@@ -114,7 +129,7 @@ public final class StandardNodeMatchers {
}
}
private static final class TypeEquals<T extends Node> extends ConstraintNodeMatcher<T> {
public static final class TypeEquals<T extends Node> extends ConstraintNodeMatcher<T> {
private final NodeType<? extends T> type;
TypeEquals(NodeType<? extends T> type) {
@@ -122,6 +137,10 @@ public final class StandardNodeMatchers {
this.type = type;
}
public NodeType<? extends T> getType() {
return this.type;
}
@Override
public @Nullable T filterConstraintMatch(@NonNull Node node) {
return this.type.tryCast(node).orElse(null);
@@ -210,12 +210,17 @@ public enum ShorthandParser {
* Implements an iterator over a given range of ints.
*/
private abstract static class RangeIterator implements Iterator<String> {
private static final int MAX_RANGE = 250;
private final int max;
private int next;
RangeIterator(int a, int b) {
this.max = Math.max(a, b);
this.next = Math.min(a, b);
if ((this.max - this.next) > MAX_RANGE) {
throw new IllegalArgumentException("Range too large");
}
}
protected abstract String toString(int i);
@@ -33,6 +33,7 @@ import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.config.LuckPermsConfiguration;
import me.lucko.luckperms.common.config.generic.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.config.generic.adapter.EnvironmentVariableConfigAdapter;
import me.lucko.luckperms.common.config.generic.adapter.FileSecretConfigAdapter;
import me.lucko.luckperms.common.config.generic.adapter.MultiConfigurationAdapter;
import me.lucko.luckperms.common.config.generic.adapter.SystemPropertyConfigAdapter;
import me.lucko.luckperms.common.context.calculator.ConfigurationContextCalculator;
@@ -61,6 +62,7 @@ import me.lucko.luckperms.common.storage.misc.DataConstraints;
import me.lucko.luckperms.common.tasks.CacheHousekeepingTask;
import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
import me.lucko.luckperms.common.tasks.SyncTask;
import me.lucko.luckperms.common.treeview.AsyncPermissionRegistry;
import me.lucko.luckperms.common.treeview.PermissionRegistry;
import me.lucko.luckperms.common.verbose.VerboseHandler;
import me.lucko.luckperms.common.webeditor.socket.WebEditorSocket;
@@ -92,10 +94,10 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
// init during load
private DependencyManager dependencyManager;
private TranslationManager translationManager;
private AsyncPermissionRegistry permissionRegistry;
private VerboseHandler verboseHandler;
// init during enable
private VerboseHandler verboseHandler;
private PermissionRegistry permissionRegistry;
private LogDispatcher logDispatcher;
private LuckPermsConfiguration configuration;
private OkHttpClient httpClient;
@@ -128,7 +130,18 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
this.translationManager.reload();
// load some utilities early
this.permissionRegistry = new PermissionRegistry(getBootstrap().getScheduler());
this.permissionRegistry = new AsyncPermissionRegistry(getBootstrap().getScheduler());
this.verboseHandler = new VerboseHandler(getBootstrap().getScheduler());
// load configuration
getLogger().info("Loading configuration...");
ConfigurationAdapter configFileAdapter = provideConfigurationAdapter();
this.configuration = new LuckPermsConfiguration(this, new MultiConfigurationAdapter(this,
new FileSecretConfigAdapter(this),
new SystemPropertyConfigAdapter(this),
new EnvironmentVariableConfigAdapter(this),
configFileAdapter
));
}
public final void enable() {
@@ -138,19 +151,9 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
// send the startup banner
Message.STARTUP_BANNER.send(getConsoleSender(), getBootstrap());
// load some utilities early
this.verboseHandler = new VerboseHandler(getBootstrap().getScheduler());
// setup log dispatcher instance early
this.logDispatcher = new LogDispatcher(this);
// load configuration
getLogger().info("Loading configuration...");
ConfigurationAdapter configFileAdapter = provideConfigurationAdapter();
this.configuration = new LuckPermsConfiguration(this, new MultiConfigurationAdapter(this,
new SystemPropertyConfigAdapter(this),
new EnvironmentVariableConfigAdapter(this),
configFileAdapter
));
// setup a bytebin instance
this.httpClient = new OkHttpClient.Builder()
.callTimeout(15, TimeUnit.SECONDS)
@@ -205,7 +208,11 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
this.syncTaskBuffer = new SyncTask.Buffer(this);
// register commands
registerCommands();
if (skipCommandRegistration()) {
getLogger().warn("LuckPerms commands are disabled in the configuration for both console and players. Skipping command registration.");
} else {
registerCommands();
}
// load internal managers
getLogger().info("Loading internal permission managers...");
@@ -228,7 +235,7 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
this.apiProvider = new LuckPermsApiProvider(this);
this.apiProvider.ensureApiWasLoadedByPlugin();
this.eventDispatcher = new EventDispatcher(provideEventBus(this.apiProvider));
getBootstrap().getScheduler().executeAsync(GeneratedEventClass::preGenerate);
getBootstrap().getScheduler().async(GeneratedEventClass::preGenerate);
ApiRegistrationUtil.registerProvider(this.apiProvider);
registerApiOnPlatform(this.apiProvider);
@@ -385,6 +392,11 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
return configFile;
}
protected boolean skipCommandRegistration() {
return getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_CONSOLE) &&
getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_PLAYERS);
}
@Override
public PluginLogger getLogger() {
return getBootstrap().getPluginLogger();
@@ -1,190 +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.common.plugin.classpath;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
/**
* Provides access to {@link URLClassLoader}#addURL.
*/
public abstract class URLClassLoaderAccess {
/**
* Creates a {@link URLClassLoaderAccess} for the given class loader.
*
* @param classLoader the class loader
* @return the access object
*/
public static URLClassLoaderAccess create(URLClassLoader classLoader) {
if (Reflection.isSupported()) {
return new Reflection(classLoader);
} else if (Unsafe.isSupported()) {
return new Unsafe(classLoader);
} else {
return Noop.INSTANCE;
}
}
private final URLClassLoader classLoader;
protected URLClassLoaderAccess(URLClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Adds the given URL to the class loader.
*
* @param url the URL to add
*/
public abstract void addURL(@NonNull URL url);
private static void throwError(Throwable cause) throws UnsupportedOperationException {
throw new UnsupportedOperationException("LuckPerms is unable to inject into the plugin URLClassLoader.\n" +
"You may be able to fix this problem by adding the following command-line argument " +
"directly after the 'java' command in your start script: \n'--add-opens java.base/java.lang=ALL-UNNAMED'", cause);
}
/**
* Accesses using reflection, not supported on Java 9+.
*/
private static class Reflection extends URLClassLoaderAccess {
private static final Method ADD_URL_METHOD;
static {
Method addUrlMethod;
try {
addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
addUrlMethod.setAccessible(true);
} catch (Exception e) {
addUrlMethod = null;
}
ADD_URL_METHOD = addUrlMethod;
}
private static boolean isSupported() {
return ADD_URL_METHOD != null;
}
Reflection(URLClassLoader classLoader) {
super(classLoader);
}
@Override
public void addURL(@NonNull URL url) {
try {
ADD_URL_METHOD.invoke(super.classLoader, url);
} catch (ReflectiveOperationException e) {
URLClassLoaderAccess.throwError(e);
}
}
}
/**
* Accesses using sun.misc.Unsafe, supported on Java 9+.
*
* @author Vaishnav Anil (https://github.com/slimjar/slimjar)
*/
private static class Unsafe extends URLClassLoaderAccess {
private static final sun.misc.Unsafe UNSAFE;
static {
sun.misc.Unsafe unsafe;
try {
Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
unsafe = (sun.misc.Unsafe) unsafeField.get(null);
} catch (Throwable t) {
unsafe = null;
}
UNSAFE = unsafe;
}
private static boolean isSupported() {
return UNSAFE != null;
}
private final Collection<URL> unopenedURLs;
private final Collection<URL> pathURLs;
@SuppressWarnings("unchecked")
Unsafe(URLClassLoader classLoader) {
super(classLoader);
Collection<URL> unopenedURLs;
Collection<URL> pathURLs;
try {
Object ucp = fetchField(URLClassLoader.class, classLoader, "ucp");
unopenedURLs = (Collection<URL>) fetchField(ucp.getClass(), ucp, "unopenedUrls");
pathURLs = (Collection<URL>) fetchField(ucp.getClass(), ucp, "path");
} catch (Throwable e) {
unopenedURLs = null;
pathURLs = null;
}
this.unopenedURLs = unopenedURLs;
this.pathURLs = pathURLs;
}
private static Object fetchField(final Class<?> clazz, final Object object, final String name) throws NoSuchFieldException {
Field field = clazz.getDeclaredField(name);
long offset = UNSAFE.objectFieldOffset(field);
return UNSAFE.getObject(object, offset);
}
@Override
public void addURL(@NonNull URL url) {
if (this.unopenedURLs == null || this.pathURLs == null) {
URLClassLoaderAccess.throwError(new NullPointerException("unopenedURLs or pathURLs"));
}
synchronized (this.unopenedURLs) {
this.unopenedURLs.add(url);
this.pathURLs.add(url);
}
}
}
private static class Noop extends URLClassLoaderAccess {
private static final Noop INSTANCE = new Noop();
private Noop() {
super(null);
}
@Override
public void addURL(@NonNull URL url) {
URLClassLoaderAccess.throwError(null);
}
}
}
@@ -44,7 +44,7 @@ import java.util.stream.Collectors;
/**
* Abstract implementation of {@link SchedulerAdapter} using a {@link ScheduledExecutorService}.
*/
public abstract class AbstractJavaScheduler implements SchedulerAdapter {
public class JavaSchedulerAdapter implements SchedulerAdapter {
private static final int PARALLELISM = 16;
private final LuckPermsBootstrap bootstrap;
@@ -52,7 +52,7 @@ public abstract class AbstractJavaScheduler implements SchedulerAdapter {
private final ScheduledThreadPoolExecutor scheduler;
private final ForkJoinPool worker;
public AbstractJavaScheduler(LuckPermsBootstrap bootstrap) {
public JavaSchedulerAdapter(LuckPermsBootstrap bootstrap) {
this.bootstrap = bootstrap;
this.scheduler = new ScheduledThreadPoolExecutor(1, r -> {
@@ -133,7 +133,7 @@ public abstract class AbstractJavaScheduler implements SchedulerAdapter {
private final class ExceptionHandler implements UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
AbstractJavaScheduler.this.bootstrap.getPluginLogger().warn("Thread " + t.getName() + " threw an uncaught exception", e);
JavaSchedulerAdapter.this.bootstrap.getPluginLogger().warn("Thread " + t.getName() + " threw an uncaught exception", e);
}
}
}
@@ -25,6 +25,8 @@
package me.lucko.luckperms.common.plugin.scheduler;
import me.lucko.luckperms.common.sender.Sender;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -33,6 +35,15 @@ import java.util.concurrent.TimeUnit;
*/
public interface SchedulerAdapter {
/**
* Executes a task sync
*
* @param task the task
*/
default void sync(Sender ctx, Runnable task) {
async(task);
}
/**
* Gets an async executor instance
*
@@ -40,31 +51,15 @@ public interface SchedulerAdapter {
*/
Executor async();
/**
* Gets a sync executor instance
*
* @return a sync executor instance
*/
Executor sync();
/**
* Executes a task async
*
* @param task the task
*/
default void executeAsync(Runnable task) {
default void async(Runnable task) {
async().execute(task);
}
/**
* Executes a task sync
*
* @param task the task
*/
default void executeSync(Runnable task) {
sync().execute(task);
}
/**
* Executes the given task with a delay.
*
@@ -95,7 +90,7 @@ public interface SchedulerAdapter {
/**
* Shuts down the executor instance.
*
* <p>{@link #async()} and {@link #executeAsync(Runnable)}.</p>
* <p>{@link #async()} and {@link #async(Runnable)}.</p>
*/
void shutdownExecutor();
@@ -117,7 +117,7 @@ public abstract class AbstractConnectionListener {
this.plugin.getUserManager().getHouseKeeper().registerUsage(uniqueId);
// force a clear of transient nodes
this.plugin.getBootstrap().getScheduler().executeAsync(() -> {
this.plugin.getBootstrap().getScheduler().async(() -> {
User user = this.plugin.getUserManager().getIfLoaded(uniqueId);
if (user != null) {
user.clearNodes(DataType.TRANSIENT, null, false);
@@ -54,7 +54,7 @@ final class FlagUtils {
private static byte toByte0(Set<Flag> settings) {
byte b = 0;
for (Flag setting : settings) {
b |= 1 << setting.ordinal();
b |= (byte) (1 << setting.ordinal());
}
return b;
}
@@ -56,7 +56,7 @@ public class QueryOptionsImpl implements QueryOptions {
// computed based on state above
private final int hashCode;
private Set<Flag> flagsSet = null;
private final ContextSatisfyMode contextSatisfyMode;
private final ContextSatisfyMode overrideContextSatisfyMode;
QueryOptionsImpl(QueryMode mode, @Nullable ImmutableContextSet context, byte flags, @Nullable Map<OptionKey<?>, Object> options) {
this.mode = mode;
@@ -65,7 +65,7 @@ public class QueryOptionsImpl implements QueryOptions {
this.options = options == null ? null : ImmutableMap.copyOf(options);
this.hashCode = calculateHashCode();
this.contextSatisfyMode = options == null ? null : (ContextSatisfyMode) options.get(ContextSatisfyMode.KEY);
this.overrideContextSatisfyMode = options == null ? null : (ContextSatisfyMode) options.get(ContextSatisfyMode.KEY);
}
@Override
@@ -119,7 +119,7 @@ public class QueryOptionsImpl implements QueryOptions {
public boolean satisfies(@NonNull ContextSet contextSet, @NonNull ContextSatisfyMode defaultContextSatisfyMode) {
switch (this.mode) {
case CONTEXTUAL:
return contextSet.isSatisfiedBy(this.context, this.contextSatisfyMode == null ? defaultContextSatisfyMode : this.contextSatisfyMode);
return contextSet.isSatisfiedBy(this.context, this.overrideContextSatisfyMode == null ? defaultContextSatisfyMode : this.overrideContextSatisfyMode);
case NON_CONTEXTUAL:
return true;
default:
@@ -26,8 +26,11 @@
package me.lucko.luckperms.common.sender;
import com.google.common.collect.Iterables;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.luckperms.api.util.Tristate;
@@ -66,6 +69,10 @@ public final class AbstractSender<T> implements Sender {
return this.plugin;
}
public T getSender() {
return this.sender;
}
@Override
public UUID getUniqueId() {
return this.uniqueId;
@@ -97,6 +104,19 @@ public final class AbstractSender<T> implements Sender {
return isConsole() || this.factory.hasPermission(this.sender, permission);
}
@Override
public boolean hasPermission(CommandPermission permission) {
boolean readOnlyMode = isConsole()
? this.plugin.getConfiguration().get(ConfigKeys.READ_ONLY_MODE_CONSOLE)
: this.plugin.getConfiguration().get(ConfigKeys.READ_ONLY_MODE_PLAYERS);
if (readOnlyMode && !permission.isReadOnly()) {
return false;
}
return hasPermission(permission.getPermission());
}
@Override
public void performCommand(String commandLine) {
this.factory.performCommand(this.sender, commandLine);
@@ -155,7 +175,7 @@ public final class AbstractSender<T> implements Sender {
case 1:
return input.get(0);
default:
return Component.join(Component.empty(), input);
return Component.join(JoinConfiguration.separator(Component.empty()), input);
}
});
}
@@ -25,6 +25,8 @@
package me.lucko.luckperms.common.sender;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.luckperms.api.util.Tristate;
@@ -47,6 +49,16 @@ public abstract class DummyConsoleSender implements Sender {
return true;
}
@Override
public boolean hasPermission(CommandPermission permission) {
boolean readOnlyMode = this.platform.getConfiguration().get(ConfigKeys.READ_ONLY_MODE_CONSOLE);
if (readOnlyMode && !permission.isReadOnly()) {
return false;
}
return true;
}
@Override
public void performCommand(String commandLine) {
@@ -132,9 +132,7 @@ public interface Sender {
* @param permission the permission to check for
* @return true if the sender has the permission
*/
default boolean hasPermission(CommandPermission permission) {
return hasPermission(permission.getPermission());
}
boolean hasPermission(CommandPermission permission);
/**
* Makes the sender perform a command.
@@ -37,6 +37,7 @@ import me.lucko.luckperms.common.storage.implementation.file.loader.JsonLoader;
import me.lucko.luckperms.common.storage.implementation.file.loader.TomlLoader;
import me.lucko.luckperms.common.storage.implementation.file.loader.YamlLoader;
import me.lucko.luckperms.common.storage.implementation.mongodb.MongoStorage;
import me.lucko.luckperms.common.storage.implementation.rest.RestStorage;
import me.lucko.luckperms.common.storage.implementation.split.SplitStorage;
import me.lucko.luckperms.common.storage.implementation.split.SplitStorageType;
import me.lucko.luckperms.common.storage.implementation.sql.SqlStorage;
@@ -129,6 +130,12 @@ public class StorageFactory {
this.plugin.getConfiguration().get(ConfigKeys.MONGODB_COLLECTION_PREFIX),
this.plugin.getConfiguration().get(ConfigKeys.MONGODB_CONNECTION_URI)
);
case REST:
return new RestStorage(
this.plugin,
this.plugin.getConfiguration().get(ConfigKeys.REST_STORAGE_URL),
this.plugin.getConfiguration().get(ConfigKeys.REST_STORAGE_AUTH_KEY)
);
case YAML:
return new SeparatedConfigurateStorage(this.plugin, "YAML", new YamlLoader(), ".yml", "yaml-storage");
case JSON:
@@ -51,6 +51,9 @@ public enum StorageType {
SQLITE("SQLite", "sqlite"),
H2("H2", "h2"),
// REST
REST("REST", "rest"),
// Custom
CUSTOM("Custom", "custom");
@@ -36,6 +36,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -57,7 +58,7 @@ public class FileWatcher extends AbstractFileWatcher {
this.basePath = basePath;
super.registerRecursively(basePath);
plugin.getBootstrap().getScheduler().executeAsync(super::runEventProcessingLoop);
plugin.getBootstrap().getScheduler().async(super::runEventProcessingLoop);
}
/**
@@ -97,7 +98,7 @@ public class FileWatcher extends AbstractFileWatcher {
private final Path path;
/** A set of files which have been modified recently */
private final ExpiringSet<String> recentlyModifiedFiles = new ExpiringSet<>(4, TimeUnit.SECONDS);
private final Set<String> recentlyModifiedFiles = ExpiringSet.newExpiringSet(4, TimeUnit.SECONDS);
/** The listener callback functions */
private final List<Consumer<Path>> callbacks = new CopyOnWriteArrayList<>();
@@ -25,6 +25,7 @@
package me.lucko.luckperms.common.storage.implementation.mongodb;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
@@ -586,7 +587,8 @@ public class MongoStorage implements StorageImplementation {
return new Document("_id", track.getName()).append("groups", track.getGroups());
}
private static Document nodeToDoc(Node node) {
@VisibleForTesting
static Document nodeToDoc(Node node) {
Document document = new Document()
.append("key", node.getKey())
.append("value", node.getValue());
@@ -603,7 +605,8 @@ public class MongoStorage implements StorageImplementation {
return document;
}
private static Node nodeFromDoc(Document document) {
@VisibleForTesting
static Node nodeFromDoc(Document document) {
String key = document.containsKey("permission") ? document.getString("permission") : document.getString("key");
if (key == null || key.isEmpty()) {
@@ -622,7 +625,7 @@ public class MongoStorage implements StorageImplementation {
}
if (document.containsKey("expiry")) {
builder.expiry((long) document.get("expiry"));
builder.expiry(((Number) document.get("expiry")).longValue());
}
if (document.containsKey("context") && document.get("context") instanceof List) {
@@ -0,0 +1,664 @@
/*
* 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.common.storage.implementation.rest;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.common.actionlog.LogPage;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.actionlog.filter.ActionFields;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.context.ImmutableContextSetImpl;
import me.lucko.luckperms.common.filter.Comparison;
import me.lucko.luckperms.common.filter.Constraint;
import me.lucko.luckperms.common.filter.Filter;
import me.lucko.luckperms.common.filter.FilterField;
import me.lucko.luckperms.common.filter.FilterList;
import me.lucko.luckperms.common.filter.PageParameters;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.factory.NodeBuilders;
import me.lucko.luckperms.common.node.matcher.ConstraintNodeMatcher;
import me.lucko.luckperms.common.node.matcher.StandardNodeMatchers;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.StorageMetadata;
import me.lucko.luckperms.common.storage.implementation.StorageImplementation;
import me.lucko.luckperms.common.storage.misc.NodeEntry;
import me.lucko.luckperms.common.storage.misc.PlayerSaveResultImpl;
import me.lucko.luckperms.common.util.Difference;
import me.lucko.luckperms.common.util.Iterators;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.model.PlayerSaveResult;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeBuilder;
import net.luckperms.api.node.NodeType;
import net.luckperms.rest.LuckPermsRestClient;
import net.luckperms.rest.model.ActionPage;
import net.luckperms.rest.model.Context;
import net.luckperms.rest.model.CreateGroupRequest;
import net.luckperms.rest.model.CreateTrackRequest;
import net.luckperms.rest.model.CreateUserRequest;
import net.luckperms.rest.model.GroupSearchResult;
import net.luckperms.rest.model.Health;
import net.luckperms.rest.model.UpdateTrackRequest;
import net.luckperms.rest.model.UpdateUserRequest;
import net.luckperms.rest.model.UserLookupResult;
import net.luckperms.rest.model.UserSearchResult;
import org.checkerframework.checker.nullness.qual.Nullable;
import retrofit2.Response;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
public class RestStorage implements StorageImplementation {
private final LuckPermsPlugin plugin;
private final LuckPermsRestClient client;
public RestStorage(LuckPermsPlugin plugin, String baseUrl, String apiKey) {
this.plugin = plugin;
this.client = LuckPermsRestClient.builder()
.baseUrl(baseUrl)
.apiKey(apiKey)
.build();
}
@Override
public LuckPermsPlugin getPlugin() {
return this.plugin;
}
@Override
public String getImplementationName() {
return "REST";
}
@Override
public void init() throws IOException {
Health health = this.client.misc().health().execute().body();
if (health == null || !health.healthy()) {
this.plugin.getLogger().warn("REST storage endpoint is unhealthy");
}
}
@Override
public void shutdown() {
this.client.close();
}
@Override
public StorageMetadata getMeta() {
StorageMetadata metadata = new StorageMetadata();
boolean success = true;
long start = System.currentTimeMillis();
try {
Health health = this.client.misc().health().execute().body();
if (health == null || !health.healthy()) {
success = false;
}
} catch (IOException e) {
success = false;
}
if (success) {
int duration = (int) (System.currentTimeMillis() - start);
metadata.ping(duration);
}
metadata.connected(success);
return metadata;
}
@Override
public void logAction(Action entry) throws IOException {
this.client.actions().submit(convertAction(entry)).execute();
}
@Override
public LogPage getLogPage(FilterList<Action> filters, @Nullable PageParameters page) throws Exception {
Response<ActionPage> resp = null;
if (filters.isEmpty()) {
// ActionFilters.all()
resp = page != null
? this.client.actions().query(page.pageSize(), page.pageNumber()).execute()
: this.client.actions().query().execute();
} else if (filters.size() == 1) {
// ActionFilters.source(uniqueId)
Filter<Action, ?> filter = filters.get(0);
if (filter.field() == ActionFields.SOURCE_UNIQUE_ID && filter.constraint().comparison() == Comparison.EQUAL) {
UUID uniqueId = (UUID) filter.constraint().value();
resp = page != null
? this.client.actions().querySource(uniqueId, page.pageSize(), page.pageNumber()).execute()
: this.client.actions().querySource(uniqueId).execute();
}
} else if (filters.operator() == FilterList.LogicalOperator.AND && filters.size() == 2) {
Filter<Action, ?> filterA = filters.get(0);
Filter<Action, ?> filterB = filters.get(1);
if (filterA.field() == ActionFields.TARGET_TYPE && filterA.constraint().comparison() == Comparison.EQUAL) {
Action.Target.Type type = (Action.Target.Type) filterA.constraint().value();
if (type == Action.Target.Type.USER) {
// ActionFilters.user(uniqueId)
if (filterB.field() == ActionFields.TARGET_UNIQUE_ID && filterB.constraint().comparison() == Comparison.EQUAL) {
UUID uniqueId = (UUID) filterB.constraint().value();
resp = page != null
? this.client.actions().queryTargetUser(uniqueId, page.pageSize(), page.pageNumber()).execute()
: this.client.actions().queryTargetUser(uniqueId).execute();
}
} else if (type == Action.Target.Type.GROUP) {
// ActionFilters.group(name)
if (filterB.field() == ActionFields.TARGET_NAME && filterB.constraint().comparison() == Comparison.EQUAL) {
String name = (String) filterB.constraint().value();
resp = page != null
? this.client.actions().queryTargetGroup(name, page.pageSize(), page.pageNumber()).execute()
: this.client.actions().queryTargetGroup(name).execute();
}
} else if (type == Action.Target.Type.TRACK) {
// ActionFilters.track(name)
if (filterB.field() == ActionFields.TARGET_NAME && filterB.constraint().comparison() == Comparison.EQUAL) {
String name = (String) filterB.constraint().value();
resp = page != null
? this.client.actions().queryTargetTrack(name, page.pageSize(), page.pageNumber()).execute()
: this.client.actions().queryTargetTrack(name).execute();
}
}
}
} else if (filters.operator() == FilterList.LogicalOperator.OR && filters.size() == 3) {
// ActionFilters.search(query)
ImmutableList<FilterField<Action, String>> searchFields = ImmutableList.of(ActionFields.SOURCE_NAME, ActionFields.TARGET_NAME, ActionFields.DESCRIPTION);
if (filters.stream().allMatch(filter -> filter.constraint().comparison() == Comparison.SIMILAR && searchFields.contains(filter.field()))) {
String query = (String) filters.get(0).constraint().value();
if (!query.startsWith("%") || !query.endsWith("%")) {
throw new IllegalArgumentException("Unsupported search query: " + query);
}
String searchTerm = query.substring(1, query.length() - 1);
resp = page != null
? this.client.actions().querySearch(searchTerm, page.pageSize(), page.pageNumber()).execute()
: this.client.actions().querySearch(searchTerm).execute();
}
}
if (resp == null) {
throw new UnsupportedOperationException("Unsupported filter: " + filters);
}
ActionPage body = Objects.requireNonNull(resp.body(), "resp.body()");
return LogPage.of(
body.entries().stream().map(RestStorage::convertAction).collect(Collectors.toList()),
page,
body.overallSize()
);
}
@Override
public void applyBulkUpdate(BulkUpdate bulkUpdate) {
throw new UnsupportedOperationException(); // TODO
}
@Override
public User loadUser(UUID uniqueId, String username) throws Exception {
net.luckperms.rest.model.User remoteUser = this.client.users().get(uniqueId).execute().body();
if (remoteUser == null) {
throw new IllegalStateException("Client did not return a user for " + uniqueId);
}
User user = this.plugin.getUserManager().getOrMake(uniqueId, username);
user.setUsername(remoteUser.username(), true);
user.loadNodesFromStorage(remoteUser.nodes().stream().map(RestStorage::convertNode).collect(Collectors.toList()));
return user;
}
@Override
public Map<UUID, User> loadUsers(Set<UUID> uniqueIds) throws Exception {
return uniqueIds.parallelStream()
.map(uniqueId -> {
try {
return loadUser(uniqueId, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toMap(User::getUniqueId, Function.identity()));
}
@Override
public void saveUser(User user) throws Exception {
Difference<Node> changes = user.normalData().exportChanges(results -> {
if (this.plugin.getUserManager().isNonDefaultUser(user)) {
return true;
}
// if the only change is adding the default node, we don't need to export
if (results.getChanges().size() == 1) {
Difference.Change<Node> onlyChange = results.getChanges().iterator().next();
return !(onlyChange.type() == Difference.ChangeType.ADD && this.plugin.getUserManager().isDefaultNode(onlyChange.value()));
}
return true;
});
if (changes == null) {
return;
}
String username = user.getUsername().orElse(null);
if (username != null) {
this.client.users().update(user.getUniqueId(), new UpdateUserRequest(username)).execute();
}
Set<Node> added = changes.getAdded();
Set<Node> removed = changes.getRemoved();
if (!removed.isEmpty()) {
this.client.users().nodesDelete(user.getUniqueId(), removed.stream().map(RestStorage::convertNode).collect(Collectors.toList())).execute();
}
if (!added.isEmpty()) {
this.client.users().nodesAdd(user.getUniqueId(), added.stream().map(RestStorage::convertNode).collect(Collectors.toList())).execute();
}
}
@Override
public Set<UUID> getUniqueUsers() throws Exception {
return this.client.users().list().execute().body();
}
@Override
public <N extends Node> List<NodeEntry<UUID, N>> searchUserNodes(ConstraintNodeMatcher<N> matcher) throws Exception {
List<UserSearchResult> results;
if (matcher instanceof StandardNodeMatchers.TypeEquals) {
NodeType<? extends N> type = ((StandardNodeMatchers.TypeEquals<N>) matcher).getType();
results = this.client.users().searchNodesByType(convertNodeType(type)).execute().body();
} else {
Constraint<String> constraint = matcher.getConstraint();
Comparison comparison = constraint.comparison();
String value = constraint.value();
if (comparison == Comparison.EQUAL) {
results = this.client.users().searchNodesByKey(value).execute().body();
} else if (comparison == Comparison.SIMILAR) {
long wildcards = value.chars().filter(ch -> ch == '%').count();
if (wildcards == 0) {
results = this.client.users().searchNodesByKey(value).execute().body();
} else if (wildcards == 1 && value.endsWith("%")) {
results = this.client.users().searchNodesByKeyStartsWith(value.substring(0, value.length() - 1)).execute().body();
} else {
throw new UnsupportedOperationException("Unsupported constraint: " + constraint);
}
} else {
throw new UnsupportedOperationException("Unsupported constraint: " + constraint);
}
}
if (results == null) {
throw new IllegalStateException("Client returned null results");
}
List<NodeEntry<UUID, N>> held = new ArrayList<>();
for (UserSearchResult result : results) {
UUID uniqueId = result.uniqueId();
for (net.luckperms.rest.model.Node node : result.results()) {
N match = matcher.filterConstraintMatch(convertNode(node));
if (match != null) {
held.add(NodeEntry.of(uniqueId, match));
}
}
}
return held;
}
@Override
public Group createAndLoadGroup(String name) throws Exception {
net.luckperms.rest.model.Group remoteGroup = this.client.groups().create(new CreateGroupRequest(name)).execute().body();
if (remoteGroup == null) {
remoteGroup = this.client.groups().get(name).execute().body();
if (remoteGroup == null) {
throw new IllegalStateException("Unable to create group: " + name);
}
}
Group group = this.plugin.getGroupManager().getOrMake(name);
group.loadNodesFromStorage(remoteGroup.nodes().stream().map(RestStorage::convertNode).collect(Collectors.toList()));
return group;
}
@Override
public Optional<Group> loadGroup(String name) throws Exception {
net.luckperms.rest.model.Group remoteGroup = this.client.groups().get(name).execute().body();
if (remoteGroup == null) {
return Optional.empty();
}
Group group = this.plugin.getGroupManager().getOrMake(name);
group.loadNodesFromStorage(remoteGroup.nodes().stream().map(RestStorage::convertNode).collect(Collectors.toList()));
return Optional.of(group);
}
@Override
public void loadAllGroups() throws Exception {
Set<String> groups = this.client.groups().list().execute().body();
if (groups == null) {
throw new IllegalStateException("Client returned a null list of groups");
}
if (!Iterators.tryIterate(groups, this::loadGroup)) {
throw new RuntimeException("Exception occurred whilst loading a group");
}
this.plugin.getGroupManager().retainAll(groups);
}
@Override
public void saveGroup(Group group) throws Exception {
Difference<Node> changes = group.normalData().exportChanges(c -> true);
Set<Node> added = changes.getAdded();
Set<Node> removed = changes.getRemoved();
if (!removed.isEmpty()) {
this.client.groups().nodesDelete(group.getName(), removed.stream().map(RestStorage::convertNode).collect(Collectors.toList())).execute();
}
if (!added.isEmpty()) {
this.client.groups().nodesAdd(group.getName(), added.stream().map(RestStorage::convertNode).collect(Collectors.toList())).execute();
}
}
@Override
public void deleteGroup(Group group) throws Exception {
this.client.groups().delete(group.getName()).execute();
}
@Override
public <N extends Node> List<NodeEntry<String, N>> searchGroupNodes(ConstraintNodeMatcher<N> matcher) throws Exception {
List<GroupSearchResult> results;
if (matcher instanceof StandardNodeMatchers.TypeEquals) {
NodeType<? extends N> type = ((StandardNodeMatchers.TypeEquals<N>) matcher).getType();
results = this.client.groups().searchNodesByType(convertNodeType(type)).execute().body();
} else {
Constraint<String> constraint = matcher.getConstraint();
Comparison comparison = constraint.comparison();
String value = constraint.value();
if (comparison == Comparison.EQUAL) {
results = this.client.groups().searchNodesByKey(value).execute().body();
} else if (comparison == Comparison.SIMILAR) {
long wildcards = value.chars().filter(ch -> ch == '%').count();
if (wildcards == 0) {
results = this.client.groups().searchNodesByKey(value).execute().body();
} else if (wildcards == 1 && value.endsWith("%")) {
results = this.client.groups().searchNodesByKeyStartsWith(value.substring(0, value.length() - 1)).execute().body();
} else {
throw new UnsupportedOperationException("Unsupported constraint: " + constraint);
}
} else {
throw new UnsupportedOperationException("Unsupported constraint: " + constraint);
}
}
if (results == null) {
throw new IllegalStateException("Client returned null results");
}
List<NodeEntry<String, N>> held = new ArrayList<>();
for (GroupSearchResult result : results) {
String name = result.name();
for (net.luckperms.rest.model.Node node : result.results()) {
N match = matcher.filterConstraintMatch(convertNode(node));
if (match != null) {
held.add(NodeEntry.of(name, match));
}
}
}
return held;
}
@Override
public Track createAndLoadTrack(String name) throws Exception {
net.luckperms.rest.model.Track remoteTrack = this.client.tracks().create(new CreateTrackRequest(name)).execute().body();
if (remoteTrack == null) {
remoteTrack = this.client.tracks().get(name).execute().body();
if (remoteTrack == null) {
throw new IllegalStateException("Unable to create track: " + name);
}
}
Track track = this.plugin.getTrackManager().getOrMake(name);
track.setGroups(remoteTrack.groups());
return track;
}
@Override
public Optional<Track> loadTrack(String name) throws Exception {
net.luckperms.rest.model.Track remoteTrack = this.client.tracks().get(name).execute().body();
if (remoteTrack == null) {
return Optional.empty();
}
Track track = this.plugin.getTrackManager().getOrMake(name);
track.setGroups(remoteTrack.groups());
return Optional.of(track);
}
@Override
public void loadAllTracks() throws Exception {
Set<String> tracks = this.client.tracks().list().execute().body();
if (tracks == null) {
throw new IllegalStateException("Client returned a null list of tracks");
}
if (!Iterators.tryIterate(tracks, this::loadTrack)) {
throw new RuntimeException("Exception occurred whilst loading a track");
}
this.plugin.getTrackManager().retainAll(tracks);
}
@Override
public void saveTrack(Track track) throws Exception {
this.client.tracks().update(track.getName(), new UpdateTrackRequest(track.getGroups())).execute();
}
@Override
public void deleteTrack(Track track) throws Exception {
this.client.tracks().delete(track.getName()).execute();
}
@Override
public PlayerSaveResult savePlayerData(UUID uniqueId, String username) throws Exception {
net.luckperms.rest.model.PlayerSaveResult remoteResult = this.client.users().create(new CreateUserRequest(uniqueId, username)).execute().body();
Set<PlayerSaveResult.Outcome> outcomes = remoteResult.outcomes().stream().map(outcome -> {
switch (outcome) {
case CLEAN_INSERT:
return PlayerSaveResult.Outcome.CLEAN_INSERT;
case NO_CHANGE:
return PlayerSaveResult.Outcome.NO_CHANGE;
case USERNAME_UPDATED:
return PlayerSaveResult.Outcome.USERNAME_UPDATED;
case OTHER_UNIQUE_IDS_PRESENT_FOR_USERNAME:
return PlayerSaveResult.Outcome.OTHER_UNIQUE_IDS_PRESENT_FOR_USERNAME;
default:
throw new AssertionError(outcome);
}
}).collect(Collectors.toSet());
if (outcomes.isEmpty()) {
throw new IllegalStateException("No outcomes returned");
}
PlayerSaveResultImpl result;
if (outcomes.contains(PlayerSaveResult.Outcome.CLEAN_INSERT)) {
result = PlayerSaveResultImpl.cleanInsert();
} else if (outcomes.contains(PlayerSaveResult.Outcome.NO_CHANGE)) {
result = PlayerSaveResultImpl.noChange();
} else if (outcomes.contains(PlayerSaveResult.Outcome.USERNAME_UPDATED)) {
result = PlayerSaveResultImpl.usernameUpdated(remoteResult.previousUsername());
} else {
throw new IllegalStateException("No base outcome returned");
}
if (outcomes.contains(PlayerSaveResult.Outcome.OTHER_UNIQUE_IDS_PRESENT_FOR_USERNAME)) {
result = result.withOtherUuidsPresent(remoteResult.otherUniqueIds());
}
return result;
}
@Override
public void deletePlayerData(UUID uniqueId) throws Exception {
this.client.users().delete(uniqueId, true).execute();
}
@Override
public @Nullable UUID getPlayerUniqueId(String username) throws Exception {
UserLookupResult result = this.client.users().lookup(username).execute().body();
return result == null ? null : result.uniqueId();
}
@Override
public @Nullable String getPlayerName(UUID uniqueId) throws Exception {
UserLookupResult result = this.client.users().lookup(uniqueId).execute().body();
return result == null ? null : result.username();
}
private static net.luckperms.rest.model.Action convertAction(Action action) {
return new net.luckperms.rest.model.Action(
action.getTimestamp().getEpochSecond(),
new net.luckperms.rest.model.Action.Source(
action.getSource().getUniqueId(),
action.getSource().getName()
),
new net.luckperms.rest.model.Action.Target(
action.getTarget().getUniqueId().orElse(null),
action.getTarget().getName(),
convertActionTargetType(action.getTarget().getType())
),
action.getDescription()
);
}
private static net.luckperms.rest.model.Action.Target.Type convertActionTargetType(Action.Target.Type type) {
switch (type) {
case USER:
return net.luckperms.rest.model.Action.Target.Type.USER;
case GROUP:
return net.luckperms.rest.model.Action.Target.Type.GROUP;
case TRACK:
return net.luckperms.rest.model.Action.Target.Type.TRACK;
default:
throw new AssertionError(type);
}
}
private static LoggedAction convertAction(net.luckperms.rest.model.Action action) {
return LoggedAction.build()
.timestamp(Instant.ofEpochSecond(action.timestamp()))
.source(action.source().uniqueId())
.sourceName(action.source().name())
.target(action.target().uniqueId())
.targetName(action.target().name())
.targetType(convertActionTargetType(action.target().type()))
.description(action.description())
.build();
}
private static Action.Target.Type convertActionTargetType(net.luckperms.rest.model.Action.Target.Type type) {
switch (type) {
case USER:
return Action.Target.Type.USER;
case GROUP:
return Action.Target.Type.GROUP;
case TRACK:
return Action.Target.Type.TRACK;
default:
throw new AssertionError(type);
}
}
private static Node convertNode(net.luckperms.rest.model.Node node) {
NodeBuilder<?, ?> builder = NodeBuilders.determineMostApplicable(node.key());
if (node.value() != null) {
builder.value(node.value());
}
if (node.context() != null) {
builder.context(convertContexts(node.context()));
}
if (node.expiry() != null) {
builder.expiry(node.expiry());
}
return builder.build();
}
private static net.luckperms.rest.model.Node convertNode(Node node) {
return new net.luckperms.rest.model.Node(
node.getKey(),
node.getValue(),
convertContexts(node.getContexts()),
node.getExpiry() == null ? null : node.getExpiry().getEpochSecond()
);
}
public static ContextSet convertContexts(Set<Context> contexts) {
ImmutableContextSetImpl.BuilderImpl builder = new ImmutableContextSetImpl.BuilderImpl();
for (Context context : contexts) {
builder.add(context.key(), context.value());
}
return builder.build();
}
public static Set<Context> convertContexts(ContextSet contexts) {
return StreamSupport.stream(contexts.spliterator(), false)
.map(c -> new Context(c.getKey(), c.getValue()))
.collect(Collectors.toSet());
}
public static net.luckperms.rest.model.NodeType convertNodeType(NodeType<?> type) {
if (type == NodeType.REGEX_PERMISSION) return net.luckperms.rest.model.NodeType.REGEX_PERMISSION;
if (type == NodeType.INHERITANCE) return net.luckperms.rest.model.NodeType.INHERITANCE;
if (type == NodeType.PREFIX) return net.luckperms.rest.model.NodeType.PREFIX;
if (type == NodeType.SUFFIX) return net.luckperms.rest.model.NodeType.SUFFIX;
if (type == NodeType.META) return net.luckperms.rest.model.NodeType.META;
if (type == NodeType.WEIGHT) return net.luckperms.rest.model.NodeType.WEIGHT;
if (type == NodeType.DISPLAY_NAME) return net.luckperms.rest.model.NodeType.DISPLAY_NAME;
throw new IllegalArgumentException("Invalid type: " + type.name());
}
}
@@ -32,10 +32,24 @@ import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public final class SchemaReader {
private SchemaReader() {}
private static final Pattern CREATE_TABLE_PATTERN = Pattern.compile("^CREATE TABLE [`\"']([^`\"']+)[`\"'].*");
private static final Pattern CREATE_INDEX_PATTERN = Pattern.compile("^CREATE INDEX.* ON [`\"']([^`\"']+)[`\"'].*");
/**
* Parses a schema file to a list of SQL statements
*
* @param is the input stream to read from
* @return a list of statements
* @throws IOException if an error occurs whilst reading the file
*/
public static List<String> getStatements(InputStream is) throws IOException {
List<String> queries = new LinkedList<>();
@@ -53,7 +67,7 @@ public final class SchemaReader {
if (line.endsWith(";")) {
sb.deleteCharAt(sb.length() - 1);
String result = sb.toString().trim();
String result = sb.toString().trim().replaceAll(" +", " ");
if (!result.isEmpty()) {
queries.add(result);
}
@@ -66,4 +80,29 @@ public final class SchemaReader {
return queries;
}
public static String tableFromStatement(String statement) {
Matcher table = CREATE_TABLE_PATTERN.matcher(statement);
if (table.matches()) {
return table.group(1).toLowerCase(Locale.ROOT);
}
Matcher index = CREATE_INDEX_PATTERN.matcher(statement);
if (index.matches()) {
return index.group(1).toLowerCase(Locale.ROOT);
}
throw new IllegalArgumentException("Unknown statement type: " + statement);
}
/**
* Filters which statements should be executed based on the current list of tables in the database
*
* @param statements the statements to filter
* @param currentTables the current tables in the database
* @return the filtered list of statements
*/
public static List<String> filterStatements(List<String> statements, List<String> currentTables) {
return statements.stream()
.filter(statement -> !currentTables.contains(tableFromStatement(statement)))
.collect(Collectors.toList());
}
}
@@ -80,7 +80,6 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
public class SqlStorage implements StorageImplementation {
@@ -137,7 +136,7 @@ public class SqlStorage implements StorageImplementation {
private final LuckPermsPlugin plugin;
private final ConnectionFactory connectionFactory;
private final Function<String, String> statementProcessor;
private final StatementProcessor statementProcessor;
public SqlStorage(LuckPermsPlugin plugin, ConnectionFactory connectionFactory, String tablePrefix) {
this.plugin = plugin;
@@ -159,7 +158,7 @@ public class SqlStorage implements StorageImplementation {
return this.connectionFactory;
}
public Function<String, String> getStatementProcessor() {
public StatementProcessor getStatementProcessor() {
return this.statementProcessor;
}
@@ -167,30 +166,32 @@ public class SqlStorage implements StorageImplementation {
public void init() throws Exception {
this.connectionFactory.init(this.plugin);
boolean tableExists;
List<String> tables;
try (Connection c = this.connectionFactory.getConnection()) {
tableExists = tableExists(c, this.statementProcessor.apply("{prefix}user_permissions"));
}
if (!tableExists) {
applySchema();
tables = listTables(c);
}
applySchema(tables);
}
private void applySchema() throws IOException, SQLException {
List<String> statements;
private void applySchema(List<String> existingTables) throws IOException, SQLException {
String schemaFileName = "me/lucko/luckperms/schema/" + this.connectionFactory.getImplementationName().toLowerCase(Locale.ROOT) + ".sql";
List<String> statements;
try (InputStream is = this.plugin.getBootstrap().getResourceStream(schemaFileName)) {
if (is == null) {
throw new IOException("Couldn't locate schema file for " + this.connectionFactory.getImplementationName());
}
statements = SchemaReader.getStatements(is).stream()
.map(this.statementProcessor)
.map(this.statementProcessor::process)
.collect(Collectors.toList());
}
statements = SchemaReader.filterStatements(statements, existingTables);
if (statements.isEmpty()) {
return;
}
try (Connection connection = this.connectionFactory.getConnection()) {
boolean utf8mb4Unsupported = false;
@@ -240,7 +241,7 @@ public class SqlStorage implements StorageImplementation {
@Override
public void logAction(Action entry) throws SQLException {
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(ACTION_INSERT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(ACTION_INSERT))) {
writeAction(entry, ps);
ps.execute();
}
@@ -288,7 +289,7 @@ public class SqlStorage implements StorageImplementation {
try (Connection c = this.connectionFactory.getConnection()) {
if (bulkUpdate.getDataType().isIncludingUsers()) {
Function<String, String> tableReplacement = s -> s.replace("{table}", "{prefix}user_permissions");
StatementProcessor tableReplacement = s -> s.replace("{table}", "{prefix}user_permissions");
BulkUpdateSqlBuilder sqlBuilder = new BulkUpdateSqlBuilder();
sqlBuilder.visit(bulkUpdate);
@@ -319,7 +320,7 @@ public class SqlStorage implements StorageImplementation {
}
if (bulkUpdate.getDataType().isIncludingGroups()) {
Function<String, String> tableReplacement = s -> s.replace("{table}", "{prefix}group_permissions");
StatementProcessor tableReplacement = s -> s.replace("{table}", "{prefix}group_permissions");
BulkUpdateSqlBuilder sqlBuilder = new BulkUpdateSqlBuilder();
sqlBuilder.visit(bulkUpdate);
@@ -448,7 +449,7 @@ public class SqlStorage implements StorageImplementation {
public Set<UUID> getUniqueUsers() throws SQLException {
Set<UUID> uuids = new HashSet<>();
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(USER_PERMISSIONS_SELECT_DISTINCT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(USER_PERMISSIONS_SELECT_DISTINCT))) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
UUID uuid = Uuids.fromString(rs.getString("uuid"));
@@ -496,7 +497,7 @@ public class SqlStorage implements StorageImplementation {
public Group createAndLoadGroup(String name) throws SQLException {
String query = GROUP_INSERT.getOrDefault(this.connectionFactory.getImplementationName(), GROUP_INSERT_DEFAULT);
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(query))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(query))) {
ps.setString(1, name);
ps.execute();
}
@@ -559,7 +560,7 @@ public class SqlStorage implements StorageImplementation {
try (Connection c = this.connectionFactory.getConnection()) {
deleteGroupPermissions(c, group.getName());
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_DELETE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(GROUP_DELETE))) {
ps.setString(1, group.getName());
ps.execute();
}
@@ -662,7 +663,7 @@ public class SqlStorage implements StorageImplementation {
@Override
public void deleteTrack(Track track) throws SQLException {
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_DELETE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(TRACK_DELETE))) {
ps.setString(1, track.getName());
ps.execute();
}
@@ -679,7 +680,7 @@ public class SqlStorage implements StorageImplementation {
try (Connection c = this.connectionFactory.getConnection()) {
SqlPlayerData existingPlayerData = selectPlayerData(c, uniqueId);
if (existingPlayerData == null) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_INSERT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_INSERT))) {
ps.setString(1, uniqueId.toString());
ps.setString(2, username);
ps.setString(3, GroupManager.DEFAULT_GROUP_NAME);
@@ -688,7 +689,7 @@ public class SqlStorage implements StorageImplementation {
} else {
oldUsername = existingPlayerData.username;
if (!username.equals(oldUsername)) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_UPDATE_USERNAME_FOR_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_UPDATE_USERNAME_FOR_UUID))) {
ps.setString(1, username);
ps.setString(2, uniqueId.toString());
ps.execute();
@@ -701,7 +702,7 @@ public class SqlStorage implements StorageImplementation {
Set<UUID> conflicting = new HashSet<>();
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_ALL_UUIDS_BY_USERNAME))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_SELECT_ALL_UUIDS_BY_USERNAME))) {
ps.setString(1, username);
ps.setString(2, uniqueId.toString());
try (ResultSet rs = ps.executeQuery()) {
@@ -715,7 +716,7 @@ public class SqlStorage implements StorageImplementation {
if (!conflicting.isEmpty()) {
// remove the mappings for conflicting uuids
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_DELETE_ALL_UUIDS_BY_USERNAME))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_DELETE_ALL_UUIDS_BY_USERNAME))) {
ps.setString(1, username);
ps.setString(2, uniqueId.toString());
ps.execute();
@@ -730,7 +731,7 @@ public class SqlStorage implements StorageImplementation {
@Override
public void deletePlayerData(UUID uniqueId) throws SQLException {
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_DELETE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_DELETE))) {
ps.setString(1, uniqueId.toString());
ps.execute();
}
@@ -741,7 +742,7 @@ public class SqlStorage implements StorageImplementation {
public UUID getPlayerUniqueId(String username) throws SQLException {
username = username.toLowerCase(Locale.ROOT);
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_UUID_BY_USERNAME))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_SELECT_UUID_BY_USERNAME))) {
ps.setString(1, username);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
@@ -756,7 +757,7 @@ public class SqlStorage implements StorageImplementation {
@Override
public String getPlayerName(UUID uniqueId) throws SQLException {
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_USERNAME_BY_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_SELECT_USERNAME_BY_UUID))) {
ps.setString(1, uniqueId.toString());
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
@@ -872,7 +873,7 @@ public class SqlStorage implements StorageImplementation {
}
if (!deleteRows.isEmpty()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(deleteSpecificQuery))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(deleteSpecificQuery))) {
for (Long id : deleteRows) {
ps.setLong(1, id);
ps.addBatch();
@@ -881,7 +882,7 @@ public class SqlStorage implements StorageImplementation {
}
}
if (!deleteNodes.isEmpty()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(deleteQuery))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(deleteQuery))) {
for (Node node : deleteNodes) {
ps.setString(1, holder);
writeNode(node, ps);
@@ -893,7 +894,7 @@ public class SqlStorage implements StorageImplementation {
}
if (!add.isEmpty()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(insertQuery))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(insertQuery))) {
for (Node node : add) {
ps.setString(1, holder);
writeNode(node, ps);
@@ -906,7 +907,7 @@ public class SqlStorage implements StorageImplementation {
private List<Node> selectUserPermissions(Connection c, UUID user) throws SQLException {
List<Node> nodes = new ArrayList<>();
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(USER_PERMISSIONS_SELECT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(USER_PERMISSIONS_SELECT))) {
ps.setString(1, user.toString());
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
@@ -921,7 +922,7 @@ public class SqlStorage implements StorageImplementation {
}
private SqlPlayerData selectPlayerData(Connection c, UUID user) throws SQLException {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_BY_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_SELECT_BY_UUID))) {
ps.setString(1, user.toString());
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
@@ -982,15 +983,15 @@ public class SqlStorage implements StorageImplementation {
// we don't want to use preparedstatements because the parameter length is variable
// safe to do string concat/replacement because the UUID.toString value isn't injectable
return this.statementProcessor.apply(baseQuery) + param;
return this.statementProcessor.process(baseQuery) + param;
}
private void deleteUser(Connection c, UUID user) throws SQLException {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(USER_PERMISSIONS_DELETE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(USER_PERMISSIONS_DELETE))) {
ps.setString(1, user.toString());
ps.execute();
}
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_UPDATE_PRIMARY_GROUP_BY_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_UPDATE_PRIMARY_GROUP_BY_UUID))) {
ps.setString(1, GroupManager.DEFAULT_GROUP_NAME);
ps.setString(2, user.toString());
ps.execute();
@@ -999,7 +1000,7 @@ public class SqlStorage implements StorageImplementation {
private void insertPlayerData(Connection c, UUID user, SqlPlayerData data) throws SQLException {
boolean hasPrimaryGroupSaved;
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_PRIMARY_GROUP_BY_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_SELECT_PRIMARY_GROUP_BY_UUID))) {
ps.setString(1, user.toString());
try (ResultSet rs = ps.executeQuery()) {
hasPrimaryGroupSaved = rs.next();
@@ -1008,14 +1009,14 @@ public class SqlStorage implements StorageImplementation {
if (hasPrimaryGroupSaved) {
// update
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_UPDATE_PRIMARY_GROUP_BY_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_UPDATE_PRIMARY_GROUP_BY_UUID))) {
ps.setString(1, data.primaryGroup);
ps.setString(2, user.toString());
ps.execute();
}
} else {
// insert
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_INSERT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_INSERT))) {
ps.setString(1, user.toString());
ps.setString(2, data.username);
ps.setString(3, data.primaryGroup);
@@ -1026,7 +1027,7 @@ public class SqlStorage implements StorageImplementation {
private Set<String> selectGroups(Connection c) throws SQLException {
Set<String> groups = new HashSet<>();
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_SELECT_ALL))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(GROUP_SELECT_ALL))) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
groups.add(rs.getString("name").toLowerCase(Locale.ROOT));
@@ -1038,7 +1039,7 @@ public class SqlStorage implements StorageImplementation {
private List<Node> selectGroupPermissions(Connection c, String group) throws SQLException {
List<Node> nodes = new ArrayList<>();
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_PERMISSIONS_SELECT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(GROUP_PERMISSIONS_SELECT))) {
ps.setString(1, group);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
@@ -1053,7 +1054,7 @@ public class SqlStorage implements StorageImplementation {
}
private void selectAllGroupPermissions(Map<String, Collection<Node>> nodes, Connection c) throws SQLException {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_PERMISSIONS_SELECT_ALL))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(GROUP_PERMISSIONS_SELECT_ALL))) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
String holder = rs.getString("name");
@@ -1070,7 +1071,7 @@ public class SqlStorage implements StorageImplementation {
}
private void deleteGroupPermissions(Connection c, String group) throws SQLException {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_PERMISSIONS_DELETE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(GROUP_PERMISSIONS_DELETE))) {
ps.setString(1, group);
ps.execute();
}
@@ -1078,7 +1079,7 @@ public class SqlStorage implements StorageImplementation {
private List<String> selectTrack(Connection c, String name) throws SQLException {
String groups;
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_SELECT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(TRACK_SELECT))) {
ps.setString(1, name);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
@@ -1093,7 +1094,7 @@ public class SqlStorage implements StorageImplementation {
private void insertTrack(Connection c, String name, List<String> groups) throws SQLException {
String json = GsonProvider.normal().toJson(groups);
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_INSERT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(TRACK_INSERT))) {
ps.setString(1, name);
ps.setString(2, json);
ps.execute();
@@ -1102,7 +1103,7 @@ public class SqlStorage implements StorageImplementation {
private void updateTrack(Connection c, String name, List<String> groups) throws SQLException {
String json = GsonProvider.normal().toJson(groups);
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_UPDATE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(TRACK_UPDATE))) {
ps.setString(1, json);
ps.setString(2, name);
ps.execute();
@@ -1111,7 +1112,7 @@ public class SqlStorage implements StorageImplementation {
private Set<String> selectTracks(Connection c) throws SQLException {
Set<String> tracks = new HashSet<>();
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_SELECT_ALL))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(TRACK_SELECT_ALL))) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
tracks.add(rs.getString("name").toLowerCase(Locale.ROOT));
@@ -1121,15 +1122,14 @@ public class SqlStorage implements StorageImplementation {
return tracks;
}
private static boolean tableExists(Connection connection, String table) throws SQLException {
private static List<String> listTables(Connection connection) throws SQLException {
List<String> tables = new ArrayList<>();
try (ResultSet rs = connection.getMetaData().getTables(connection.getCatalog(), null, "%", null)) {
while (rs.next()) {
if (rs.getString(3).equalsIgnoreCase(table)) {
return true;
}
tables.add(rs.getString(3).toLowerCase(Locale.ROOT));
}
return false;
}
return tables;
}
private static final class SqlPlayerData {
@@ -23,22 +23,21 @@
* SOFTWARE.
*/
package me.lucko.luckperms.standalone;
package me.lucko.luckperms.common.storage.implementation.sql;
import me.lucko.luckperms.common.plugin.scheduler.AbstractJavaScheduler;
import me.lucko.luckperms.common.plugin.scheduler.SchedulerAdapter;
import java.util.Objects;
import java.util.concurrent.Executor;
public interface StatementProcessor {
public class StandaloneSchedulerAdapter extends AbstractJavaScheduler implements SchedulerAdapter {
StatementProcessor USE_BACKTICKS = s -> s.replace('\'', '`');
public StandaloneSchedulerAdapter(LPStandaloneBootstrap bootstrap) {
super(bootstrap);
}
StatementProcessor USE_DOUBLE_QUOTES = s -> s.replace('\'', '"');
@Override
public Executor sync() {
return this.async();
String process(String statement);
default StatementProcessor compose(StatementProcessor before) {
Objects.requireNonNull(before);
return s -> process(before.process(s));
}
}
@@ -25,12 +25,13 @@
package me.lucko.luckperms.common.storage.implementation.sql.builder;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class PreparedStatementBuilder {
private final StringBuilder sb = new StringBuilder();
@@ -56,10 +57,10 @@ public class PreparedStatementBuilder {
return this;
}
public PreparedStatement build(Connection connection, Function<String, String> mapping) throws SQLException {
public PreparedStatement build(Connection connection, StatementProcessor processor) throws SQLException {
PreparedStatement statement = null;
try {
statement = connection.prepareStatement(mapping.apply(this.sb.toString()));
statement = connection.prepareStatement(processor.process(this.sb.toString()));
for (int i = 0; i < this.variables.size(); i++) {
String var = this.variables.get(i);
statement.setString(i + 1, var);
@@ -27,10 +27,10 @@ package me.lucko.luckperms.common.storage.implementation.sql.connection;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.StorageMetadata;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.function.Function;
public interface ConnectionFactory {
@@ -42,7 +42,7 @@ public interface ConnectionFactory {
StorageMetadata getMeta();
Function<String, String> getStatementProcessor();
StatementProcessor getStatementProcessor();
Connection getConnection() throws SQLException;
@@ -27,6 +27,7 @@ package me.lucko.luckperms.common.storage.implementation.sql.connection.file;
import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import java.io.IOException;
import java.lang.reflect.Constructor;
@@ -38,9 +39,14 @@ import java.sql.Statement;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Properties;
import java.util.function.Function;
public class H2ConnectionFactory extends FlatfileConnectionFactory {
public static final StatementProcessor STATEMENT_PROCESSOR = s -> s
.replace('\'', '`')
.replace("LIKE", "ILIKE")
.replace("value", "`value`")
.replace("``value``", "`value`");
private Constructor<?> connectionConstructor;
public H2ConnectionFactory(Path file) {
@@ -91,11 +97,8 @@ public class H2ConnectionFactory extends FlatfileConnectionFactory {
}
@Override
public Function<String, String> getStatementProcessor() {
return s -> s.replace('\'', '`')
.replace("LIKE", "ILIKE")
.replace("value", "`value`")
.replace("``value``", "`value`");
public StatementProcessor getStatementProcessor() {
return STATEMENT_PROCESSOR;
}
/**
@@ -27,6 +27,7 @@ package me.lucko.luckperms.common.storage.implementation.sql.connection.file;
import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import java.lang.reflect.Constructor;
import java.nio.file.Path;
@@ -34,7 +35,6 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.util.EnumSet;
import java.util.Properties;
import java.util.function.Function;
public class SqliteConnectionFactory extends FlatfileConnectionFactory {
private Constructor<?> connectionConstructor;
@@ -74,7 +74,7 @@ public class SqliteConnectionFactory extends FlatfileConnectionFactory {
}
@Override
public Function<String, String> getStatementProcessor() {
return s -> s.replace('\'', '`');
public StatementProcessor getStatementProcessor() {
return StatementProcessor.USE_BACKTICKS;
}
}
@@ -25,10 +25,9 @@
package me.lucko.luckperms.common.storage.implementation.sql.connection.hikari;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import me.lucko.luckperms.common.storage.misc.StorageCredentials;
import java.util.function.Function;
public class MariaDbConnectionFactory extends DriverBasedHikariConnectionFactory {
public MariaDbConnectionFactory(StorageCredentials configuration) {
super(configuration);
@@ -55,7 +54,7 @@ public class MariaDbConnectionFactory extends DriverBasedHikariConnectionFactory
}
@Override
public Function<String, String> getStatementProcessor() {
return s -> s.replace('\'', '`'); // use backticks for quotes
public StatementProcessor getStatementProcessor() {
return StatementProcessor.USE_BACKTICKS;
}
}
@@ -25,10 +25,10 @@
package me.lucko.luckperms.common.storage.implementation.sql.connection.hikari;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import me.lucko.luckperms.common.storage.misc.StorageCredentials;
import java.util.Map;
import java.util.function.Function;
public class MySqlConnectionFactory extends DriverBasedHikariConnectionFactory {
public MySqlConnectionFactory(StorageCredentials configuration) {
@@ -80,7 +80,7 @@ public class MySqlConnectionFactory extends DriverBasedHikariConnectionFactory {
}
@Override
public Function<String, String> getStatementProcessor() {
return s -> s.replace('\'', '`'); // use backticks for quotes
public StatementProcessor getStatementProcessor() {
return StatementProcessor.USE_BACKTICKS;
}
}
Loaded 100 of 259 files, more files were not shown because too many files have changed in this diff. Show more