Created
August 30, 2021 12:02
-
-
Save robsonkades/265e5481ef2e9d1c6abff2ffca0c0334 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.robsonkades.platform.authorization.cache; | |
import java.nio.charset.StandardCharsets; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Optional; | |
import java.util.Set; | |
import java.util.concurrent.ConcurrentHashMap; | |
import java.util.concurrent.ConcurrentSkipListSet; | |
import java.util.concurrent.ExecutorService; | |
import java.util.concurrent.Executors; | |
import java.util.concurrent.TimeUnit; | |
import java.util.function.Function; | |
import java.util.stream.Collectors; | |
import javax.annotation.PostConstruct; | |
import javax.annotation.PreDestroy; | |
import javax.inject.Inject; | |
import org.apache.commons.lang3.StringUtils; | |
import org.apache.commons.lang3.tuple.Pair; | |
import org.springframework.context.annotation.Lazy; | |
import com.google.common.cache.Cache; | |
import com.google.common.cache.CacheBuilder; | |
import com.robsonkades.platform.authorization.CheckedPermission; | |
import com.robsonkades.platform.authorization.PermissionToCheck; | |
import com.robsonkades.platform.authorization.utils.HashFunctions; | |
import lombok.AccessLevel; | |
import lombok.RequiredArgsConstructor; | |
import lombok.experimental.FieldDefaults; | |
import lombok.experimental.NonFinal; | |
import lombok.extern.slf4j.Slf4j; | |
@Lazy | |
@Slf4j | |
@RequiredArgsConstructor(onConstructor = @__(@Inject)) | |
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) | |
public class LocalCachePermission implements CachePermission { | |
ExecutorService executor = Executors.newFixedThreadPool(10); | |
CacheConfiguration cacheConfiguration; | |
CacheStatistics cacheStatistics; | |
Map<String, Set<String>> cacheByTenant = new ConcurrentHashMap<>(); | |
@NonFinal | |
Cache<String, Pair<Boolean, Boolean>> cache; | |
@PostConstruct | |
public void initialize() { | |
cache = CacheBuilder.newBuilder() | |
.expireAfterWrite(cacheConfiguration.getTimeToLive(), TimeUnit.SECONDS) | |
.maximumSize(cacheConfiguration.getMaxEntries()) | |
.removalListener(notification -> { | |
String tenant = StringUtils.substringBefore(notification.getKey().toString(), "|"); | |
if (cacheByTenant.containsKey(tenant)) { | |
cacheByTenant.get(tenant).remove(notification.getKey().toString()); | |
} | |
}) | |
.build(); | |
} | |
@PreDestroy | |
public void destroy() { | |
executor.shutdownNow(); | |
} | |
@Override | |
public Map<PermissionToCheck, Optional<CheckedPermission>> get(String tenant, String user, Set<PermissionToCheck> resources) { | |
return resources.stream() | |
.collect(Collectors.toMap(Function.identity(), resource -> get(tenant, user, resource.resource, resource.action))); | |
} | |
private Optional<CheckedPermission> get(String tenant, String user, String resource, String action) { | |
Optional<CheckedPermission> value = Optional.ofNullable(cache.getIfPresent(getKey(tenant, user, resource, action))) | |
.map(permission -> { | |
CheckedPermission checkedPermission = new CheckedPermission(); | |
checkedPermission.resource = resource; | |
checkedPermission.action = action; | |
checkedPermission.authorized = permission.getLeft(); | |
checkedPermission.owner = permission.getRight(); | |
return checkedPermission; | |
}); | |
value.ifPresentOrElse(permission -> cacheStatistics.incrementHit(), () -> cacheStatistics.incrementMiss()); | |
return value; | |
} | |
private void put(String tenant, String user, String resource, String action, boolean authorized, boolean owner) { | |
String key = getKey(tenant, user, resource, action); | |
cache.put(key, Pair.of(authorized, owner)); | |
getTenantCache(tenant).add(key); | |
} | |
@Override | |
public void put(String tenant, String user, List<CheckedPermission> checkedPermissions) { | |
executor.execute(() -> | |
checkedPermissions.forEach(permission -> put(tenant, user, permission.resource, permission.action, permission.authorized, permission.owner)) | |
); | |
} | |
@Override | |
public void invalidate(String tenant) { | |
LOGGER.info("Invalidating {} cache permission", tenant); | |
Set<String> tenantCache = getTenantCache(tenant); | |
cache.invalidateAll(tenantCache); | |
tenantCache.clear(); | |
cacheStatistics.incrementEviction(); | |
} | |
private String getKey(String tenant, String user, String resource, String action) { | |
return tenant + "|" + HashFunctions.hash128((user + resource + action).getBytes(StandardCharsets.UTF_8)); | |
} | |
private Set<String> getTenantCache(String tenant) { | |
return cacheByTenant.computeIfAbsent(tenant, key -> new ConcurrentSkipListSet<>()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment