From 1d5bd2567e122750e21a283179f5650543d42f25 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Tue, 23 May 2017 16:13:20 -0300 Subject: [PATCH] [KEYCLOAK-4913] - Caching more query methods --- .../demo-dist/src/main/xslt/standalone.xsl | 4 +- .../authorization/PolicyAdapter.java | 12 +- .../authorization/ResourceAdapter.java | 4 +- .../StoreFactoryCacheManager.java | 16 +- .../StoreFactoryCacheSession.java | 261 +++++++++--------- .../authorization/entities/InResource.java | 24 ++ .../authorization/entities/InScope.java | 5 +- .../entities/PolicyListQuery.java | 7 +- .../authorization/entities/PolicyQuery.java | 30 ++ .../entities/PolicyResourceListQuery.java | 42 +++ .../entities/PolicyScopeListQuery.java | 42 +++ .../entities/ResourceListQuery.java | 7 +- .../authorization/entities/ResourceQuery.java | 30 ++ .../entities/ResourceScopeListQuery.java | 11 +- .../events/PolicyRemovedEvent.java | 10 +- .../events/PolicyUpdatedEvent.java | 6 +- .../stream/InResourcePredicate.java | 50 ++++ .../keycloak-infinispan2.xml | 2 +- 18 files changed, 402 insertions(+), 161 deletions(-) create mode 100644 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InResource.java create mode 100644 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyQuery.java create mode 100755 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyResourceListQuery.java create mode 100755 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyScopeListQuery.java create mode 100644 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceQuery.java create mode 100755 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InResourcePredicate.java diff --git a/distribution/demo-dist/src/main/xslt/standalone.xsl b/distribution/demo-dist/src/main/xslt/standalone.xsl index d78ff753e7..5ea7e93ce0 100755 --- a/distribution/demo-dist/src/main/xslt/standalone.xsl +++ b/distribution/demo-dist/src/main/xslt/standalone.xsl @@ -92,7 +92,9 @@ - + + + diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java index a8cd37994c..970d1b9e34 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java @@ -27,8 +27,6 @@ import org.keycloak.representations.idm.authorization.Logic; import java.util.Collections; import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; import java.util.Map; import java.util.Set; @@ -49,7 +47,7 @@ public class PolicyAdapter implements Policy, CachedModel { @Override public Policy getDelegateForUpdate() { if (updated == null) { - cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourceServerId()); + cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), cached.getResourceServerId()); updated = cacheSession.getPolicyStoreDelegate().findById(cached.getId(), cached.getResourceServerId()); if (updated == null) throw new IllegalStateException("Not found in database"); } @@ -98,6 +96,7 @@ public class PolicyAdapter implements Policy, CachedModel { @Override public void setName(String name) { getDelegateForUpdate(); + cacheSession.registerPolicyInvalidation(cached.getId(), name, cached.getResourcesIds(), cached.getResourceServerId()); updated.setName(name); } @@ -208,7 +207,6 @@ public class PolicyAdapter implements Policy, CachedModel { public void addScope(Scope scope) { getDelegateForUpdate(); updated.addScope(scope); - } @Override @@ -235,6 +233,9 @@ public class PolicyAdapter implements Policy, CachedModel { @Override public void addResource(Resource resource) { getDelegateForUpdate(); + HashSet resources = new HashSet<>(); + resources.add(resource.getId()); + cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getResourceServerId()); updated.addResource(resource); } @@ -242,6 +243,9 @@ public class PolicyAdapter implements Policy, CachedModel { @Override public void removeResource(Resource resource) { getDelegateForUpdate(); + HashSet resources = new HashSet<>(); + resources.add(resource.getId()); + cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getResourceServerId()); updated.removeResource(resource); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java index 2706681884..dc721b7112 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java @@ -95,6 +95,7 @@ public class ResourceAdapter implements Resource, CachedModel { @Override public void setName(String name) { getDelegateForUpdate(); + cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUri(), cached.getScopesIds(), cached.getResourceServerId()); updated.setName(name); } @@ -126,8 +127,8 @@ public class ResourceAdapter implements Resource, CachedModel { @Override public void setUri(String uri) { getDelegateForUpdate(); + cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), uri, cached.getScopesIds(), cached.getResourceServerId()); updated.setUri(uri); - } @Override @@ -139,6 +140,7 @@ public class ResourceAdapter implements Resource, CachedModel { @Override public void setType(String type) { getDelegateForUpdate(); + cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), type, cached.getUri(), cached.getScopesIds(), cached.getResourceServerId()); updated.setType(type); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java index c64db03ee9..794358926f 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java @@ -21,6 +21,7 @@ import org.jboss.logging.Logger; import org.keycloak.models.cache.infinispan.CacheManager; import org.keycloak.models.cache.infinispan.RealmCacheManager; import org.keycloak.models.cache.infinispan.authorization.events.AuthorizationCacheInvalidationEvent; +import org.keycloak.models.cache.infinispan.authorization.stream.InResourcePredicate; import org.keycloak.models.cache.infinispan.authorization.stream.InResourceServerPredicate; import org.keycloak.models.cache.infinispan.authorization.stream.InScopePredicate; import org.keycloak.models.cache.infinispan.entities.Revisioned; @@ -80,6 +81,7 @@ public class StoreFactoryCacheManager extends CacheManager { if (type != null) { invalidations.add(StoreFactoryCacheSession.getResourceByTypeCacheKey(type, serverId)); + addInvalidations(InResourcePredicate.create().resource(type), invalidations); } if (uri != null) { @@ -89,6 +91,7 @@ public class StoreFactoryCacheManager extends CacheManager { if (scopes != null) { for (String scope : scopes) { invalidations.add(StoreFactoryCacheSession.getResourceByScopeCacheKey(scope, serverId)); + addInvalidations(InScopePredicate.create().scope(scope), invalidations); } } } @@ -96,15 +99,22 @@ public class StoreFactoryCacheManager extends CacheManager { public void resourceRemoval(String id, String name, String type, String uri, String owner, Set scopes, String serverId, Set invalidations) { resourceUpdated(id, name, type, uri, scopes, serverId, invalidations); invalidations.add(StoreFactoryCacheSession.getResourceByOwnerCacheKey(owner, serverId)); + addInvalidations(InResourcePredicate.create().resource(id), invalidations); } - public void policyUpdated(String id, String name, String serverId, Set invalidations) { + public void policyUpdated(String id, String name, Set resources, String serverId, Set invalidations) { invalidations.add(id); invalidations.add(StoreFactoryCacheSession.getPolicyByNameCacheKey(name, serverId)); + + if (resources != null) { + for (String resource : resources) { + invalidations.add(StoreFactoryCacheSession.getPolicyByResource(resource, serverId)); + } + } } - public void policyRemoval(String id, String name, String serverId, Set invalidations) { - policyUpdated(id, name, serverId, invalidations); + public void policyRemoval(String id, String name, Set resources, String serverId, Set invalidations) { + policyUpdated(id, name, resources, serverId, invalidations); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java index db39562cc8..3e4c2052b2 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java @@ -16,6 +16,18 @@ */ package org.keycloak.models.cache.infinispan.authorization; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Supplier; +import java.util.stream.Collectors; + import org.jboss.logging.Logger; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; @@ -34,7 +46,11 @@ import org.keycloak.models.cache.infinispan.authorization.entities.CachedResourc import org.keycloak.models.cache.infinispan.authorization.entities.CachedResourceServer; import org.keycloak.models.cache.infinispan.authorization.entities.CachedScope; import org.keycloak.models.cache.infinispan.authorization.entities.PolicyListQuery; +import org.keycloak.models.cache.infinispan.authorization.entities.PolicyQuery; +import org.keycloak.models.cache.infinispan.authorization.entities.PolicyResourceListQuery; +import org.keycloak.models.cache.infinispan.authorization.entities.PolicyScopeListQuery; import org.keycloak.models.cache.infinispan.authorization.entities.ResourceListQuery; +import org.keycloak.models.cache.infinispan.authorization.entities.ResourceQuery; import org.keycloak.models.cache.infinispan.authorization.entities.ResourceScopeListQuery; import org.keycloak.models.cache.infinispan.authorization.entities.ResourceServerListQuery; import org.keycloak.models.cache.infinispan.authorization.entities.ScopeListQuery; @@ -49,17 +65,6 @@ import org.keycloak.models.cache.infinispan.authorization.events.ScopeUpdatedEve import org.keycloak.models.cache.infinispan.events.InvalidationEvent; import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - /** * @author Bill Burke * @version $Revision: 1 $ @@ -247,12 +252,12 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { invalidationEvents.add(ResourceUpdatedEvent.create(id, name, type, uri, scopes, serverId)); } - public void registerPolicyInvalidation(String id, String name, String serverId) { - cache.policyUpdated(id, name, serverId, invalidations); + public void registerPolicyInvalidation(String id, String name, Set resources, String serverId) { + cache.policyUpdated(id, name, resources, serverId, invalidations); PolicyAdapter adapter = managedPolicies.get(id); if (adapter != null) adapter.invalidateFlag(); - invalidationEvents.add(PolicyUpdatedEvent.create(id, name, serverId)); + invalidationEvents.add(PolicyUpdatedEvent.create(id, name, resources, serverId)); } public ResourceServerStore getResourceServerStoreDelegate() { @@ -303,6 +308,18 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { return "policy.name." + name + "." + serverId; } + public static String getPolicyByResource(String resourceId, String serverId) { + return "policy.resource." + resourceId + "." + serverId; + } + + public static String getPolicyByResourceType(String type, String serverId) { + return "policy.resource.type." + type + "." + serverId; + } + + public static String getPolicyByScope(String scope, String serverId) { + return "policy.scope." + scope + "." + serverId; + } + public StoreFactory getDelegate() { if (delegate != null) return delegate; delegate = session.getProvider(StoreFactory.class); @@ -520,73 +537,37 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { public Resource findByName(String name, String resourceServerId) { if (name == null) return null; String cacheKey = getResourceByNameCacheKey(name, resourceServerId); - ResourceListQuery query = cache.get(cacheKey, ResourceListQuery.class); - if (query != null) { - logger.tracev("resource by name cache hit: {0}", name); - } - if (query == null) { - Long loaded = cache.getCurrentRevision(cacheKey); - Resource model = getResourceStoreDelegate().findByName(name, resourceServerId); - if (model == null) return null; - if (invalidations.contains(model.getId())) return model; - query = new ResourceListQuery(loaded, cacheKey, model.getId(), resourceServerId); - cache.addRevisioned(query, startupRevision); - return model; - } else if (invalidations.contains(cacheKey)) { - return getResourceStoreDelegate().findByName(name, resourceServerId); - } else { - String id = query.getResources().iterator().next(); - if (invalidations.contains(id)) { - return getResourceStoreDelegate().findByName(name, resourceServerId); + List result = cacheQuery(cacheKey, ResourceListQuery.class, () -> { + Resource resource = getResourceStoreDelegate().findByName(name, resourceServerId); + + if (resource == null) { + return Collections.emptyList(); } - return findById(id, query.getResourceServerId()); + + return Arrays.asList(resource); + }, + (revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId); + + if (result.isEmpty()) { + return null; } + + return result.get(0); } @Override public List findByOwner(String ownerId, String resourceServerId) { - if (ownerId == null) return null; String cacheKey = getResourceByOwnerCacheKey(ownerId, resourceServerId); - ResourceListQuery query = cache.get(cacheKey, ResourceListQuery.class); - if (query != null) { - logger.tracev("resource by owner cache hit: {0}", ownerId); - } - if (query == null) { - Long loaded = cache.getCurrentRevision(cacheKey); - List model = getResourceStoreDelegate().findByOwner(ownerId, resourceServerId); - if (model == null) return null; - if (invalidations.contains(cacheKey)) return model; - query = new ResourceListQuery(loaded, cacheKey, model.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId); - cache.addRevisioned(query, startupRevision); - return model; - } else if (invalidations.contains(cacheKey)) { - return getResourceStoreDelegate().findByOwner(ownerId, resourceServerId); - } else { - return query.getResources().stream().map(resourceId -> findById(resourceId, resourceServerId)).collect(Collectors.toList()); - } + return cacheQuery(cacheKey, ResourceListQuery.class, () -> getResourceStoreDelegate().findByOwner(ownerId, resourceServerId), + (revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId); } @Override public List findByUri(String uri, String resourceServerId) { if (uri == null) return null; String cacheKey = getResourceByUriCacheKey(uri, resourceServerId); - ResourceListQuery query = cache.get(cacheKey, ResourceListQuery.class); - if (query != null) { - logger.tracev("resource by uri cache hit: {0}", uri); - } - if (query == null) { - Long loaded = cache.getCurrentRevision(cacheKey); - List model = getResourceStoreDelegate().findByUri(uri, resourceServerId); - if (model == null) return null; - if (invalidations.contains(cacheKey)) return model; - query = new ResourceListQuery(loaded, cacheKey, model.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId); - cache.addRevisioned(query, startupRevision); - return model; - } else if (invalidations.contains(cacheKey)) { - return getResourceStoreDelegate().findByUri(uri, resourceServerId); - } else { - return query.getResources().stream().map(resourceId -> findById(resourceId, resourceServerId)).collect(Collectors.toList()); - } + return cacheQuery(cacheKey, ResourceListQuery.class, () -> getResourceStoreDelegate().findByUri(uri, resourceServerId), + (revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId); } @Override @@ -603,30 +584,10 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { public List findByScope(List ids, String resourceServerId) { if (ids == null) return null; List result = new ArrayList<>(); - Iterator iterator = ids.iterator(); - while (iterator.hasNext()) { - String id = iterator.next(); + for (String id : ids) { String cacheKey = getResourceByScopeCacheKey(id, resourceServerId); - ResourceScopeListQuery query = cache.get(cacheKey, ResourceScopeListQuery.class); - if (query != null) { - logger.tracev("resource by scope cache hit: {0}", id); - if (invalidations.contains(cacheKey)) { - result.addAll(getResourceStoreDelegate().findByScope(ids, resourceServerId)); - } else { - result.addAll(query.getResources().stream().map(resourceId -> findById(resourceId, resourceServerId)).collect(Collectors.toList())); - } - } else if (invalidations.contains(id)) { - result.addAll(getResourceStoreDelegate().findByScope(ids, resourceServerId)); - } else { - Long loaded = cache.getCurrentRevision(cacheKey); - List model = getResourceStoreDelegate().findByScope(Arrays.asList(id), resourceServerId); - if (model == null) return null; - if (invalidations.contains(cacheKey)) return model; - query = new ResourceScopeListQuery(loaded, cacheKey, id, model.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId); - cache.addRevisioned(query, startupRevision); - result.addAll(query.getResources().stream().map(resourceId -> findById(resourceId, resourceServerId)).collect(Collectors.toList())); - } + result.addAll(cacheQuery(cacheKey, ResourceScopeListQuery.class, () -> getResourceStoreDelegate().findByScope(Arrays.asList(id), resourceServerId), (revision, resources) -> new ResourceScopeListQuery(revision, cacheKey, id, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId)); } return result; @@ -636,32 +597,37 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { public List findByType(String type, String resourceServerId) { if (type == null) return null; String cacheKey = getResourceByTypeCacheKey(type, resourceServerId); - ResourceListQuery query = cache.get(cacheKey, ResourceListQuery.class); - if (query != null) { - logger.tracev("resource by type cache hit: {0}", type); - } - if (query == null) { - Long loaded = cache.getCurrentRevision(cacheKey); - List model = getResourceStoreDelegate().findByType(type, resourceServerId); - if (model == null) return null; - if (invalidations.contains(cacheKey)) return model; - query = new ResourceListQuery(loaded, cacheKey, model.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId); - cache.addRevisioned(query, startupRevision); - return model; - } else if (invalidations.contains(cacheKey)) { - return getResourceStoreDelegate().findByType(type, resourceServerId); - } else { - return query.getResources().stream().map(resourceId -> findById(resourceId, resourceServerId)).collect(Collectors.toList()); - } + return cacheQuery(cacheKey, ResourceListQuery.class, () -> getResourceStoreDelegate().findByType(type, resourceServerId), + (revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId); + } + + private List cacheQuery(String cacheKey, Class queryType, Supplier> resultSupplier, BiFunction, Q> querySupplier, String resourceServerId) { + Q query = cache.get(cacheKey, queryType); + if (query != null) { + logger.tracev("cache hit for key: {0}", cacheKey); + } + if (query == null) { + Long loaded = cache.getCurrentRevision(cacheKey); + List model = resultSupplier.get(); + if (model == null) return null; + if (invalidations.contains(cacheKey)) return model; + query = querySupplier.apply(loaded, model); + cache.addRevisioned(query, startupRevision); + return model; + } else if (query.isInvalid(invalidations)) { + return resultSupplier.get(); + } else { + return query.getResources().stream().map(resourceId -> (R) findById(resourceId, resourceServerId)).collect(Collectors.toList()); + } } } protected class PolicyCache implements PolicyStore { @Override public Policy create(AbstractPolicyRepresentation representation, ResourceServer resourceServer) { - Policy resource = getPolicyStoreDelegate().create(representation, resourceServer); - registerPolicyInvalidation(resource.getId(), resource.getName(), resourceServer.getId()); - return resource; + Policy policy = getPolicyStoreDelegate().create(representation, resourceServer); + registerPolicyInvalidation(policy.getId(), policy.getName(), policy.getResources().stream().map(resource1 -> resource1.getId()).collect(Collectors.toSet()), resourceServer.getId()); + return policy; } @Override @@ -671,8 +637,8 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { if (policy == null) return; cache.invalidateObject(id); - invalidationEvents.add(PolicyRemovedEvent.create(id, policy.getName(), policy.getResourceServer().getId())); - cache.policyRemoval(id, policy.getName(), policy.getResourceServer().getId(), invalidations); + invalidationEvents.add(PolicyRemovedEvent.create(id, policy.getName(), policy.getResources().stream().map(resource1 -> resource1.getId()).collect(Collectors.toSet()), policy.getResourceServer().getId())); + cache.policyRemoval(id, policy.getName(), policy.getResources().stream().map(resource1 -> resource1.getId()).collect(Collectors.toSet()), policy.getResourceServer().getId(), invalidations); getPolicyStoreDelegate().delete(id); } @@ -708,27 +674,22 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { public Policy findByName(String name, String resourceServerId) { if (name == null) return null; String cacheKey = getPolicyByNameCacheKey(name, resourceServerId); - PolicyListQuery query = cache.get(cacheKey, PolicyListQuery.class); - if (query != null) { - logger.tracev("policy by name cache hit: {0}", name); - } - if (query == null) { - Long loaded = cache.getCurrentRevision(cacheKey); - Policy model = getPolicyStoreDelegate().findByName(name, resourceServerId); - if (model == null) return null; - if (invalidations.contains(model.getId())) return model; - query = new PolicyListQuery(loaded, cacheKey, model.getId(), resourceServerId); - cache.addRevisioned(query, startupRevision); - return model; - } else if (invalidations.contains(cacheKey)) { - return getPolicyStoreDelegate().findByName(name, resourceServerId); - } else { - String id = query.getPolicies().iterator().next(); - if (invalidations.contains(id)) { - return getPolicyStoreDelegate().findByName(name, resourceServerId); + List result = cacheQuery(cacheKey, PolicyListQuery.class, () -> { + Policy policy = getPolicyStoreDelegate().findByName(name, resourceServerId); + + if (policy == null) { + return Collections.emptyList(); } - return findById(id, query.getResourceServerId()); + + return Arrays.asList(policy); + }, + (revision, policies) -> new PolicyListQuery(revision, cacheKey, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId); + + if (result.isEmpty()) { + return null; } + + return result.get(0); } @Override @@ -743,17 +704,29 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { @Override public List findByResource(String resourceId, String resourceServerId) { - return getPolicyStoreDelegate().findByResource(resourceId, resourceServerId); + String cacheKey = getPolicyByResource(resourceId, resourceServerId); + return cacheQuery(cacheKey, PolicyResourceListQuery.class, () -> getPolicyStoreDelegate().findByResource(resourceId, resourceServerId), + (revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceId, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId); } @Override public List findByResourceType(String resourceType, String resourceServerId) { - return getPolicyStoreDelegate().findByResourceType(resourceType, resourceServerId); + String cacheKey = getPolicyByResourceType(resourceType, resourceServerId); + return cacheQuery(cacheKey, PolicyResourceListQuery.class, () -> getPolicyStoreDelegate().findByResourceType(resourceType, resourceServerId), + (revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceType, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId); } @Override public List findByScopeIds(List scopeIds, String resourceServerId) { - return getPolicyStoreDelegate().findByScopeIds(scopeIds, resourceServerId); + if (scopeIds == null) return null; + List result = new ArrayList<>(); + + for (String id : scopeIds) { + String cacheKey = getPolicyByScope(id, resourceServerId); + result.addAll(cacheQuery(cacheKey, PolicyScopeListQuery.class, () -> getPolicyStoreDelegate().findByScopeIds(Arrays.asList(id), resourceServerId), (revision, resources) -> new PolicyScopeListQuery(revision, cacheKey, id, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId)); + } + + return result; } @Override @@ -765,6 +738,26 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { public List findDependentPolicies(String id, String resourceServerId) { return getPolicyStoreDelegate().findDependentPolicies(id, resourceServerId); } + + private List cacheQuery(String cacheKey, Class queryType, Supplier> resultSupplier, BiFunction, Q> querySupplier, String resourceServerId) { + Q query = cache.get(cacheKey, queryType); + if (query != null) { + logger.tracev("cache hit for key: {0}", cacheKey); + } + if (query == null) { + Long loaded = cache.getCurrentRevision(cacheKey); + List model = resultSupplier.get(); + if (model == null) return null; + if (invalidations.contains(cacheKey)) return model; + query = querySupplier.apply(loaded, model); + cache.addRevisioned(query, startupRevision); + return model; + } else if (query.isInvalid(invalidations)) { + return resultSupplier.get(); + } else { + return query.getPolicies().stream().map(resourceId -> (R) findById(resourceId, resourceServerId)).collect(Collectors.toList()); + } + } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InResource.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InResource.java new file mode 100644 index 0000000000..e316b1c78a --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InResource.java @@ -0,0 +1,24 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.models.cache.infinispan.authorization.entities; + +/** + * @author Pedro Igor + */ +public interface InResource { + String getResourceId(); +} diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InScope.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InScope.java index aab2643413..c345677c50 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InScope.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/InScope.java @@ -16,11 +16,8 @@ */ package org.keycloak.models.cache.infinispan.authorization.entities; -import java.util.Set; - /** - * @author Bill Burke - * @version $Revision: 1 $ + * @author Pedro Igor */ public interface InScope { String getScopeId(); diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyListQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyListQuery.java index 1cbf044d01..0b2227ab92 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyListQuery.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyListQuery.java @@ -9,7 +9,7 @@ import java.util.Set; * @author Bill Burke * @version $Revision: 1 $ */ -public class PolicyListQuery extends AbstractRevisioned implements InResourceServer { +public class PolicyListQuery extends AbstractRevisioned implements PolicyQuery { private final Set policies; private final String serverId; @@ -33,4 +33,9 @@ public class PolicyListQuery extends AbstractRevisioned implements InResourceSer public Set getPolicies() { return policies; } + + @Override + public boolean isInvalid(Set invalidations) { + return invalidations.contains(getId()) || invalidations.contains(getResourceServerId()); + } } \ No newline at end of file diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyQuery.java new file mode 100644 index 0000000000..a844b25213 --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyQuery.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.models.cache.infinispan.authorization.entities; + +import java.util.Set; + +import org.keycloak.models.cache.infinispan.entities.Revisioned; + +/** + * @author Pedro Igor + */ +public interface PolicyQuery extends InResourceServer, Revisioned { + + Set getPolicies(); + boolean isInvalid(Set invalidations); +} diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyResourceListQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyResourceListQuery.java new file mode 100755 index 0000000000..d0af77b8f1 --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyResourceListQuery.java @@ -0,0 +1,42 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.models.cache.infinispan.authorization.entities; + +import java.util.Set; + +/** + * @author Pedro Igor + */ +public class PolicyResourceListQuery extends PolicyListQuery implements InResource { + + private final String resourceId; + + public PolicyResourceListQuery(Long revision, String id, String resourceId, Set policies, String serverId) { + super(revision, id, policies, serverId); + this.resourceId = resourceId; + } + + @Override + public boolean isInvalid(Set invalidations) { + return super.isInvalid(invalidations) || invalidations.contains(getResourceId()); + } + + @Override + public String getResourceId() { + return resourceId; + } +} \ No newline at end of file diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyScopeListQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyScopeListQuery.java new file mode 100755 index 0000000000..7db8a0198a --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/PolicyScopeListQuery.java @@ -0,0 +1,42 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.models.cache.infinispan.authorization.entities; + +import java.util.Set; + +/** + * @author Pedro Igor + */ +public class PolicyScopeListQuery extends PolicyListQuery implements InScope { + + private final String scopeId; + + public PolicyScopeListQuery(Long revision, String id, String scopeId, Set resources, String serverId) { + super(revision, id, resources, serverId); + this.scopeId = scopeId; + } + + @Override + public String getScopeId() { + return scopeId; + } + + @Override + public boolean isInvalid(Set invalidations) { + return super.isInvalid(invalidations) || invalidations.contains(getScopeId()); + } +} \ No newline at end of file diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceListQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceListQuery.java index d322b62c02..d8db81bd5c 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceListQuery.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceListQuery.java @@ -9,7 +9,7 @@ import java.util.Set; * @author Bill Burke * @version $Revision: 1 $ */ -public class ResourceListQuery extends AbstractRevisioned implements InResourceServer { +public class ResourceListQuery extends AbstractRevisioned implements ResourceQuery, InResourceServer { private final Set resources; private final String serverId; @@ -33,4 +33,9 @@ public class ResourceListQuery extends AbstractRevisioned implements InResourceS public Set getResources() { return resources; } + + @Override + public boolean isInvalid(Set invalidations) { + return invalidations.contains(getId()) || invalidations.contains(getResourceServerId()); + } } \ No newline at end of file diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceQuery.java new file mode 100644 index 0000000000..db07bc8ce5 --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceQuery.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.models.cache.infinispan.authorization.entities; + +import java.util.Set; + +import org.keycloak.models.cache.infinispan.entities.Revisioned; + +/** + * @author Pedro Igor + */ +public interface ResourceQuery extends Revisioned { + + Set getResources(); + boolean isInvalid(Set invalidations); +} diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceScopeListQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceScopeListQuery.java index 116e1aaa3b..ee733446f5 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceScopeListQuery.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/ResourceScopeListQuery.java @@ -1,13 +1,9 @@ package org.keycloak.models.cache.infinispan.authorization.entities; -import java.util.HashSet; import java.util.Set; -import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned; - /** - * @author Bill Burke - * @version $Revision: 1 $ + * @author Pedro Igor */ public class ResourceScopeListQuery extends ResourceListQuery implements InScope { @@ -22,4 +18,9 @@ public class ResourceScopeListQuery extends ResourceListQuery implements InScope public String getScopeId() { return scopeId; } + + @Override + public boolean isInvalid(Set invalidations) { + return super.isInvalid(invalidations) || invalidations.contains(getScopeId()); + } } \ No newline at end of file diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java index 3a721baa89..759c284b93 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyRemovedEvent.java @@ -17,11 +17,11 @@ package org.keycloak.models.cache.infinispan.authorization.events; +import java.util.Set; + import org.keycloak.models.cache.infinispan.authorization.StoreFactoryCacheManager; import org.keycloak.models.cache.infinispan.events.InvalidationEvent; -import java.util.Set; - /** * @author Marek Posolda */ @@ -29,12 +29,14 @@ public class PolicyRemovedEvent extends InvalidationEvent implements Authorizati private String id; private String name; + private Set resources; private String serverId; - public static PolicyRemovedEvent create(String id, String name, String serverId) { + public static PolicyRemovedEvent create(String id, String name, Set resources, String serverId) { PolicyRemovedEvent event = new PolicyRemovedEvent(); event.id = id; event.name = name; + event.resources = resources; event.serverId = serverId; return event; } @@ -51,6 +53,6 @@ public class PolicyRemovedEvent extends InvalidationEvent implements Authorizati @Override public void addInvalidations(StoreFactoryCacheManager cache, Set invalidations) { - cache.policyRemoval(id, name, serverId, invalidations); + cache.policyRemoval(id, name, resources, serverId, invalidations); } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java index 1e1d9925bf..b576bda165 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/events/PolicyUpdatedEvent.java @@ -29,12 +29,14 @@ public class PolicyUpdatedEvent extends InvalidationEvent implements Authorizati private String id; private String name; + private static Set resources; private String serverId; - public static PolicyUpdatedEvent create(String id, String name, String serverId) { + public static PolicyUpdatedEvent create(String id, String name, Set resources, String serverId) { PolicyUpdatedEvent event = new PolicyUpdatedEvent(); event.id = id; event.name = name; + event.resources = resources; event.serverId = serverId; return event; } @@ -51,6 +53,6 @@ public class PolicyUpdatedEvent extends InvalidationEvent implements Authorizati @Override public void addInvalidations(StoreFactoryCacheManager cache, Set invalidations) { - cache.policyUpdated(id, name, serverId, invalidations); + cache.policyUpdated(id, name, resources, serverId, invalidations); } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InResourcePredicate.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InResourcePredicate.java new file mode 100755 index 0000000000..f72de49c85 --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/stream/InResourcePredicate.java @@ -0,0 +1,50 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.models.cache.infinispan.authorization.stream; + +import java.io.Serializable; +import java.util.Map; +import java.util.function.Predicate; + +import org.keycloak.models.cache.infinispan.authorization.entities.InResource; +import org.keycloak.models.cache.infinispan.entities.Revisioned; + +/** + * @author Pedro Igor + */ +public class InResourcePredicate implements Predicate>, Serializable { + + private String resourceId; + + public static InResourcePredicate create() { + return new InResourcePredicate(); + } + + public InResourcePredicate resource(String id) { + resourceId = id; + return this; + } + + @Override + public boolean test(Map.Entry entry) { + Object value = entry.getValue(); + if (value == null) return false; + if (!(value instanceof InResource)) return false; + + return resourceId.equals(((InResource)value).getResourceId()); + } +} diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan2.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan2.xml index a76162b83a..839ecdf2df 100755 --- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan2.xml +++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan2.xml @@ -37,7 +37,7 @@ - +