This is an automated email from the ASF dual-hosted git repository.
roryqi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new f4482cd27e [#9770] improvement(authz): Convert Jcasbin internal map to
the cache (#9812)
f4482cd27e is described below
commit f4482cd27eefa043f1bb00216def952cbe0ae79a
Author: roryqi <[email protected]>
AuthorDate: Fri Jan 30 14:34:07 2026 +0800
[#9770] improvement(authz): Convert Jcasbin internal map to the cache
(#9812)
### What changes were proposed in this pull request?
Convert Jcasbin internal map to the cache
### Why are the changes needed?
Fix: #9770
### Does this PR introduce _any_ user-facing change?
Add documents.
### How was this patch tested?
Add UTs.
---
.../main/java/org/apache/gravitino/Configs.java | 27 ++++
docs/security/access-control.md | 30 ++++-
server-common/build.gradle.kts | 1 +
.../authorization/jcasbin/JcasbinAuthorizer.java | 79 ++++++++----
.../jcasbin/TestJcasbinAuthorizer.java | 142 +++++++++++++++++++--
5 files changed, 244 insertions(+), 35 deletions(-)
diff --git a/core/src/main/java/org/apache/gravitino/Configs.java
b/core/src/main/java/org/apache/gravitino/Configs.java
index 9f04aa9318..bdf604770b 100644
--- a/core/src/main/java/org/apache/gravitino/Configs.java
+++ b/core/src/main/java/org/apache/gravitino/Configs.java
@@ -302,6 +302,33 @@ public class Configs {
.intConf()
.createWithDefault(DEFAULT_GRAVITINO_AUTHORIZATION_THREAD_POOL_SIZE);
+ public static final long
DEFAULT_GRAVITINO_AUTHORIZATION_CACHE_EXPIRATION_SECS = 3600L;
+
+ public static final ConfigEntry<Long>
GRAVITINO_AUTHORIZATION_CACHE_EXPIRATION_SECS =
+ new ConfigBuilder("gravitino.authorization.jcasbin.cacheExpirationSecs")
+ .doc("The expiration time in seconds for authorization cache
entries")
+ .version(ConfigConstants.VERSION_1_2_0)
+ .longConf()
+
.createWithDefault(DEFAULT_GRAVITINO_AUTHORIZATION_CACHE_EXPIRATION_SECS);
+
+ public static final long DEFAULT_GRAVITINO_AUTHORIZATION_ROLE_CACHE_SIZE =
10000L;
+
+ public static final ConfigEntry<Long>
GRAVITINO_AUTHORIZATION_ROLE_CACHE_SIZE =
+ new ConfigBuilder("gravitino.authorization.jcasbin.roleCacheSize")
+ .doc("The maximum size of the role cache for authorization")
+ .version(ConfigConstants.VERSION_1_2_0)
+ .longConf()
+ .createWithDefault(DEFAULT_GRAVITINO_AUTHORIZATION_ROLE_CACHE_SIZE);
+
+ public static final long DEFAULT_GRAVITINO_AUTHORIZATION_OWNER_CACHE_SIZE =
100000L;
+
+ public static final ConfigEntry<Long>
GRAVITINO_AUTHORIZATION_OWNER_CACHE_SIZE =
+ new ConfigBuilder("gravitino.authorization.jcasbin.ownerCacheSize")
+ .doc("The maximum size of the owner cache for authorization")
+ .version(ConfigConstants.VERSION_1_2_0)
+ .longConf()
+ .createWithDefault(DEFAULT_GRAVITINO_AUTHORIZATION_OWNER_CACHE_SIZE);
+
public static final ConfigEntry<List<String>> SERVICE_ADMINS =
new ConfigBuilder("gravitino.authorization.serviceAdmins")
.doc("The admins of Gravitino service")
diff --git a/docs/security/access-control.md b/docs/security/access-control.md
index af5d26e6ce..55d81177ba 100644
--- a/docs/security/access-control.md
+++ b/docs/security/access-control.md
@@ -440,10 +440,27 @@ This model ensures that denials cannot be circumvented by
grants at lower levels
To enable access control in Gravitino, configure the following settings in
your server configuration file:
-| Configuration Item | Description
| Default Value | Required
| Since Version |
-|-----------------------------------------|---------------------------------------------------------------------------|---------------|---------------------------------------------|---------------|
-| `gravitino.authorization.enable` | Enable or disable authorization in
Gravitino | `false` | No
| 0.5.0 |
-| `gravitino.authorization.serviceAdmins` | Comma-separated list of service
administrator usernames | (none) | Yes (when
authorization is enabled) | 0.5.0 |
+| Configuration Item | Description
| Default Value |
Required | Since Version |
+|---------------------------------------------------------|---------------------------------------------------------------------------|---------------|---------------------------------------------|---------------|
+| `gravitino.authorization.enable` | Enable or disable
authorization in Gravitino | `false` | No
| 0.5.0 |
+| `gravitino.authorization.serviceAdmins` | Comma-separated
list of service administrator usernames | (none) | Yes
(when authorization is enabled) | 0.5.0 |
+| `gravitino.authorization.jcasbin.cacheExpirationSecs` | The expiration
time in seconds for authorization cache entries | `3600` | No
| 1.2.0 |
+| `gravitino.authorization.jcasbin.roleCacheSize` | The maximum size
of the role cache for authorization | `10000` | No
| 1.2.0 |
+| `gravitino.authorization.jcasbin.ownerCacheSize` | The maximum size
of the owner cache for authorization | `100000` | No
| 1.2.0 |
+
+### Authorization Cache
+
+Gravitino uses Caffeine caches to improve authorization performance by caching
role and owner information. The cache configuration options allow you to tune
the cache behavior:
+
+- **`cacheExpirationSecs`**: Controls how long cache entries remain valid.
After this time, entries are automatically evicted and reloaded from the
backend on the next access. Lower values provide more up-to-date authorization
decisions but may increase load on the backend.
+
+- **`roleCacheSize`**: Controls the maximum number of role entries that can be
cached. When the cache reaches this size, the least recently used entries are
evicted.
+
+- **`ownerCacheSize`**: Controls the maximum number of owner relationship
entries that can be cached. This cache maps metadata object IDs to their owner
IDs.
+
+:::info
+When role privileges or ownership are changed through the Gravitino API, the
corresponding cache entries are automatically invalidated to ensure
authorization decisions reflect the latest state.
+:::
### Important Notes
@@ -462,6 +479,11 @@ gravitino.authorization.enable = true
# Define service administrators
gravitino.authorization.serviceAdmins = admin1,admin2
+
+# Optional: Configure authorization cache (default values shown)
+gravitino.authorization.jcasbin.cacheExpirationSecs = 3600
+gravitino.authorization.jcasbin.roleCacheSize = 10000
+gravitino.authorization.jcasbin.ownerCacheSize = 100000
```
## Migration Guide
diff --git a/server-common/build.gradle.kts b/server-common/build.gradle.kts
index 6bd6a31f45..fdcdf2f931 100644
--- a/server-common/build.gradle.kts
+++ b/server-common/build.gradle.kts
@@ -40,6 +40,7 @@ dependencies {
implementation(libs.bundles.kerby)
implementation(libs.bundles.log4j)
implementation(libs.bundles.metrics)
+ implementation(libs.caffeine)
implementation(libs.commons.lang3)
implementation(libs.guava)
implementation(libs.jackson.datatype.jdk8)
diff --git
a/server-common/src/main/java/org/apache/gravitino/server/authorization/jcasbin/JcasbinAuthorizer.java
b/server-common/src/main/java/org/apache/gravitino/server/authorization/jcasbin/JcasbinAuthorizer.java
index 2b569933da..f957bd8b96 100644
---
a/server-common/src/main/java/org/apache/gravitino/server/authorization/jcasbin/JcasbinAuthorizer.java
+++
b/server-common/src/main/java/org/apache/gravitino/server/authorization/jcasbin/JcasbinAuthorizer.java
@@ -17,6 +17,8 @@
package org.apache.gravitino.server.authorization.jcasbin;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
@@ -26,14 +28,13 @@ import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
-import java.util.Set;
+import java.util.Optional;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.Configs;
@@ -84,14 +85,47 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
* loadedRoles is used to cache roles that have loaded permissions. When the
permissions of a role
* are updated, they should be removed from it.
*/
- private Set<Long> loadedRoles = ConcurrentHashMap.newKeySet();
+ private Cache<Long, Boolean> loadedRoles;
- private Map<Long, Long> ownerRel = new ConcurrentHashMap<>();
+ private Cache<Long, Optional<Long>> ownerRel;
private Executor executor = null;
@Override
public void initialize() {
+ long cacheExpirationSecs =
+ GravitinoEnv.getInstance()
+ .config()
+ .get(Configs.GRAVITINO_AUTHORIZATION_CACHE_EXPIRATION_SECS);
+ long roleCacheSize =
+
GravitinoEnv.getInstance().config().get(Configs.GRAVITINO_AUTHORIZATION_ROLE_CACHE_SIZE);
+ long ownerCacheSize =
+
GravitinoEnv.getInstance().config().get(Configs.GRAVITINO_AUTHORIZATION_OWNER_CACHE_SIZE);
+
+ // Initialize enforcers before the caches that reference them in removal
listeners
+ allowEnforcer = new SyncedEnforcer(getModel("/jcasbin_model.conf"), new
GravitinoAdapter());
+ allowInternalAuthorizer = new InternalAuthorizer(allowEnforcer);
+ denyEnforcer = new SyncedEnforcer(getModel("/jcasbin_model.conf"), new
GravitinoAdapter());
+ denyInternalAuthorizer = new InternalAuthorizer(denyEnforcer);
+
+ loadedRoles =
+ Caffeine.newBuilder()
+ .expireAfterAccess(cacheExpirationSecs, TimeUnit.SECONDS)
+ .maximumSize(roleCacheSize)
+ .executor(Runnable::run)
+ .removalListener(
+ (roleId, value, cause) -> {
+ if (roleId != null) {
+ allowEnforcer.deleteRole(String.valueOf(roleId));
+ denyEnforcer.deleteRole(String.valueOf(roleId));
+ }
+ })
+ .build();
+ ownerRel =
+ Caffeine.newBuilder()
+ .expireAfterAccess(cacheExpirationSecs, TimeUnit.SECONDS)
+ .maximumSize(ownerCacheSize)
+ .build();
executor =
Executors.newFixedThreadPool(
GravitinoEnv.getInstance()
@@ -102,10 +136,6 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
thread.setName("GravitinoAuthorizer-ThreadPool-" +
thread.getId());
return thread;
});
- allowEnforcer = new SyncedEnforcer(getModel("/jcasbin_model.conf"), new
GravitinoAdapter());
- allowInternalAuthorizer = new InternalAuthorizer(allowEnforcer);
- denyEnforcer = new SyncedEnforcer(getModel("/jcasbin_model.conf"), new
GravitinoAdapter());
- denyInternalAuthorizer = new InternalAuthorizer(denyEnforcer);
}
private Model getModel(String modelFilePath) {
@@ -196,7 +226,7 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
UserEntity userEntity = getUserEntity(principal.getName(), metalake);
userId = userEntity.id();
metadataId = MetadataIdConverter.getID(metadataObject, metalake);
- result = Objects.equals(userId, ownerRel.get(metadataId));
+ result = Objects.equals(Optional.of(userId),
ownerRel.getIfPresent(metadataId));
} catch (Exception e) {
LOG.debug("Can not get entity id", e);
result = false;
@@ -353,9 +383,7 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
@Override
public void handleRolePrivilegeChange(Long roleId) {
- loadedRoles.remove(roleId);
- allowEnforcer.deleteRole(String.valueOf(roleId));
- denyEnforcer.deleteRole(String.valueOf(roleId));
+ loadedRoles.invalidate(roleId);
}
@Override
@@ -363,7 +391,7 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
String metalake, Long oldOwnerId, NameIdentifier nameIdentifier,
Entity.EntityType type) {
MetadataObject metadataObject =
NameIdentifierUtil.toMetadataObject(nameIdentifier, type);
Long metadataId = MetadataIdConverter.getID(metadataObject, metalake);
- ownerRel.remove(metadataId);
+ ownerRel.invalidate(metadataId);
}
@Override
@@ -417,7 +445,8 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
private boolean authorizeByJcasbin(
Long userId, MetadataObject metadataObject, Long metadataId, String
privilege) {
if (AuthConstants.OWNER.equals(privilege)) {
- return Objects.equals(userId, ownerRel.get(metadataId));
+ Optional<Long> owner = ownerRel.getIfPresent(metadataId);
+ return Objects.equals(Optional.of(userId), owner);
}
return enforcer.enforce(
String.valueOf(userId),
@@ -457,7 +486,7 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
Long roleId = role.id();
allowEnforcer.addRoleForUser(String.valueOf(userId),
String.valueOf(roleId));
denyEnforcer.addRoleForUser(String.valueOf(userId),
String.valueOf(roleId));
- if (loadedRoles.contains(roleId)) {
+ if (loadedRoles.getIfPresent(roleId) != null) {
continue;
}
CompletableFuture<Void> loadRoleFuture =
@@ -476,7 +505,7 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
.thenAcceptAsync(
roleEntity -> {
loadPolicyByRoleEntity(roleEntity);
- loadedRoles.add(roleId);
+ loadedRoles.put(roleId, true);
},
executor);
loadRoleFutures.add(loadRoleFuture);
@@ -489,8 +518,8 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
}
private void loadOwnerPolicy(String metalake, MetadataObject metadataObject,
Long metadataId) {
- if (ownerRel.containsKey(metadataId)) {
- LOG.debug("Metadata {} OWNER has bean loaded.", metadataId);
+ if (ownerRel.getIfPresent(metadataId) != null) {
+ LOG.debug("Metadata {} OWNER has been loaded.", metadataId);
return;
}
try {
@@ -503,10 +532,14 @@ public class JcasbinAuthorizer implements
GravitinoAuthorizer {
SupportsRelationOperations.Type.OWNER_REL,
entityIdent,
Entity.EntityType.valueOf(metadataObject.type().name()));
- for (Entity ownerEntity : owners) {
- if (ownerEntity instanceof UserEntity) {
- UserEntity user = (UserEntity) ownerEntity;
- ownerRel.put(metadataId, user.id());
+ if (owners.isEmpty()) {
+ ownerRel.put(metadataId, Optional.empty());
+ } else {
+ for (Entity ownerEntity : owners) {
+ if (ownerEntity instanceof UserEntity) {
+ UserEntity user = (UserEntity) ownerEntity;
+ ownerRel.put(metadataId, Optional.of(user.id()));
+ }
}
}
} catch (IOException e) {
diff --git
a/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
b/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
index 3533dfab53..4388ef0952 100644
---
a/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
+++
b/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
@@ -19,21 +19,26 @@ package org.apache.gravitino.server.authorization.jcasbin;
import static org.apache.gravitino.authorization.Privilege.Name.USE_CATALOG;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.benmanes.caffeine.cache.Cache;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.lang.reflect.Field;
import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import org.apache.commons.lang3.reflect.FieldUtils;
@@ -62,6 +67,7 @@ import
org.apache.gravitino.storage.relational.utils.POConverters;
import org.apache.gravitino.utils.NameIdentifierUtil;
import org.apache.gravitino.utils.NamespaceUtil;
import org.apache.gravitino.utils.PrincipalUtils;
+import org.casbin.jcasbin.main.Enforcer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -160,7 +166,7 @@ public class TestJcasbinAuthorizer {
}
@Test
- public void testAuthorize() throws IOException {
+ public void testAuthorize() throws Exception {
makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);
Principal currentPrincipal = PrincipalUtils.getCurrentPrincipal();
assertFalse(doAuthorize(currentPrincipal));
@@ -228,21 +234,25 @@ public class TestJcasbinAuthorizer {
}
@Test
- public void testAuthorizeByOwner() throws IOException {
+ public void testAuthorizeByOwner() throws Exception {
Principal currentPrincipal = PrincipalUtils.getCurrentPrincipal();
assertFalse(doAuthorizeOwner(currentPrincipal));
NameIdentifier catalogIdent = NameIdentifierUtil.ofCatalog(METALAKE,
"testCatalog");
- when(supportsRelationOperations.listEntitiesByRelation(
+ List<UserEntity> owners = ImmutableList.of(getUserEntity());
+ doReturn(owners)
+ .when(supportsRelationOperations)
+ .listEntitiesByRelation(
eq(SupportsRelationOperations.Type.OWNER_REL),
eq(catalogIdent),
- eq(Entity.EntityType.CATALOG)))
- .thenReturn(ImmutableList.of(getUserEntity()));
+ eq(Entity.EntityType.CATALOG));
+ getOwnerRelCache(jcasbinAuthorizer).invalidateAll();
assertTrue(doAuthorizeOwner(currentPrincipal));
- when(supportsRelationOperations.listEntitiesByRelation(
+ doReturn(new ArrayList<>())
+ .when(supportsRelationOperations)
+ .listEntitiesByRelation(
eq(SupportsRelationOperations.Type.OWNER_REL),
eq(catalogIdent),
- eq(Entity.EntityType.CATALOG)))
- .thenReturn(new ArrayList<>());
+ eq(Entity.EntityType.CATALOG));
jcasbinAuthorizer.handleMetadataOwnerChange(
METALAKE, USER_ID, catalogIdent, Entity.EntityType.CATALOG);
assertFalse(doAuthorizeOwner(currentPrincipal));
@@ -347,4 +357,120 @@ public class TestJcasbinAuthorizer {
throw new RuntimeException(e);
}
}
+
+ @Test
+ public void testRoleCacheInvalidation() throws Exception {
+ makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);
+
+ // Get the loadedRoles cache via reflection
+ Cache<Long, Boolean> loadedRoles = getLoadedRolesCache(jcasbinAuthorizer);
+
+ // Manually add a role to the cache
+ Long testRoleId = 100L;
+ loadedRoles.put(testRoleId, true);
+
+ // Verify it's in the cache
+ assertNotNull(loadedRoles.getIfPresent(testRoleId));
+
+ // Call handleRolePrivilegeChange which should invalidate the cache entry
+ jcasbinAuthorizer.handleRolePrivilegeChange(testRoleId);
+
+ // Verify it's removed from the cache
+ assertNull(loadedRoles.getIfPresent(testRoleId));
+ }
+
+ @Test
+ public void testOwnerCacheInvalidation() throws Exception {
+ // Get the ownerRel cache via reflection
+ Cache<Long, Optional<Long>> ownerRel = getOwnerRelCache(jcasbinAuthorizer);
+
+ // Manually add an owner relation to the cache
+ ownerRel.put(CATALOG_ID, Optional.of(USER_ID));
+
+ // Verify it's in the cache
+ assertNotNull(ownerRel.getIfPresent(CATALOG_ID));
+
+ // Create a mock NameIdentifier for the metadata object
+ NameIdentifier catalogIdent = NameIdentifierUtil.ofCatalog(METALAKE,
"testCatalog");
+
+ // Call handleMetadataOwnerChange which should invalidate the cache entry
+ jcasbinAuthorizer.handleMetadataOwnerChange(
+ METALAKE, USER_ID, catalogIdent, Entity.EntityType.CATALOG);
+
+ // Verify it's removed from the cache
+ assertNull(ownerRel.getIfPresent(CATALOG_ID));
+ }
+
+ @Test
+ public void testRoleCacheSynchronousRemovalListenerDeletesPolicy() throws
Exception {
+ makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);
+
+ // Get the enforcers via reflection
+ Enforcer allowEnforcer = getAllowEnforcer(jcasbinAuthorizer);
+ Enforcer denyEnforcer = getDenyEnforcer(jcasbinAuthorizer);
+
+ // Get the loadedRoles cache
+ Cache<Long, Boolean> loadedRoles = getLoadedRolesCache(jcasbinAuthorizer);
+
+ // Add a role and its policy to the enforcer
+ Long testRoleId = 300L;
+ String roleIdStr = String.valueOf(testRoleId);
+
+ // Add a policy for this role
+ allowEnforcer.addPolicy(roleIdStr, "CATALOG", "999", "USE_CATALOG",
"allow");
+ denyEnforcer.addPolicy(roleIdStr, "CATALOG", "999", "USE_CATALOG",
"allow");
+
+ // Add role to cache
+ loadedRoles.put(testRoleId, true);
+
+ // Verify role exists in enforcer (has policy)
+ assertTrue(allowEnforcer.hasPolicy(roleIdStr, "CATALOG", "999",
"USE_CATALOG", "allow"));
+ assertTrue(denyEnforcer.hasPolicy(roleIdStr, "CATALOG", "999",
"USE_CATALOG", "allow"));
+
+ // Invalidate the cache entry - this triggers the synchronous removal
listener
+ // (using executor(Runnable::run) to ensure synchronous execution)
+ loadedRoles.invalidate(testRoleId);
+
+ // Verify the role's policies have been deleted from enforcers
(synchronous, no need to wait)
+ assertFalse(allowEnforcer.hasPolicy(roleIdStr, "CATALOG", "999",
"USE_CATALOG", "allow"));
+ assertFalse(denyEnforcer.hasPolicy(roleIdStr, "CATALOG", "999",
"USE_CATALOG", "allow"));
+ }
+
+ @Test
+ public void testCacheInitialization() throws Exception {
+ // Verify that caches are initialized
+ Cache<Long, Boolean> loadedRoles = getLoadedRolesCache(jcasbinAuthorizer);
+ Cache<Long, Optional<Long>> ownerRel = getOwnerRelCache(jcasbinAuthorizer);
+
+ assertNotNull(loadedRoles, "loadedRoles cache should be initialized");
+ assertNotNull(ownerRel, "ownerRel cache should be initialized");
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Cache<Long, Boolean> getLoadedRolesCache(JcasbinAuthorizer
authorizer)
+ throws Exception {
+ Field field = JcasbinAuthorizer.class.getDeclaredField("loadedRoles");
+ field.setAccessible(true);
+ return (Cache<Long, Boolean>) field.get(authorizer);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Cache<Long, Optional<Long>>
getOwnerRelCache(JcasbinAuthorizer authorizer)
+ throws Exception {
+ Field field = JcasbinAuthorizer.class.getDeclaredField("ownerRel");
+ field.setAccessible(true);
+ return (Cache<Long, Optional<Long>>) field.get(authorizer);
+ }
+
+ private static Enforcer getAllowEnforcer(JcasbinAuthorizer authorizer)
throws Exception {
+ Field field = JcasbinAuthorizer.class.getDeclaredField("allowEnforcer");
+ field.setAccessible(true);
+ return (Enforcer) field.get(authorizer);
+ }
+
+ private static Enforcer getDenyEnforcer(JcasbinAuthorizer authorizer) throws
Exception {
+ Field field = JcasbinAuthorizer.class.getDeclaredField("denyEnforcer");
+ field.setAccessible(true);
+ return (Enforcer) field.get(authorizer);
+ }
}