Skip to content

Instantly share code, notes, and snippets.

@robsonkades
Created August 30, 2021 12:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robsonkades/265e5481ef2e9d1c6abff2ffca0c0334 to your computer and use it in GitHub Desktop.
Save robsonkades/265e5481ef2e9d1c6abff2ffca0c0334 to your computer and use it in GitHub Desktop.
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